@ -0,0 +1,140 @@
|
||||
// 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 cmd |
||||
|
||||
import ( |
||||
"bufio" |
||||
"bytes" |
||||
"os" |
||||
"os/exec" |
||||
"path/filepath" |
||||
|
||||
"github.com/Unknwon/com" |
||||
"github.com/urfave/cli" |
||||
|
||||
"github.com/gogits/gogs/models" |
||||
) |
||||
|
||||
var ( |
||||
CmdHook = cli.Command{ |
||||
Name: "hook", |
||||
Usage: "Delegate commands to corresponding Git hooks", |
||||
Description: "All sub-commands should only be called by Git", |
||||
Flags: []cli.Flag{ |
||||
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"), |
||||
}, |
||||
Subcommands: []cli.Command{ |
||||
subcmdHookPreReceive, |
||||
subcmdHookUpadte, |
||||
subcmdHookPostReceive, |
||||
}, |
||||
} |
||||
|
||||
subcmdHookPreReceive = cli.Command{ |
||||
Name: "pre-receive", |
||||
Usage: "Delegate pre-receive Git hook", |
||||
Description: "This command should only be called by Git", |
||||
Action: runHookPreReceive, |
||||
} |
||||
subcmdHookUpadte = cli.Command{ |
||||
Name: "update", |
||||
Usage: "Delegate update Git hook", |
||||
Description: "This command should only be called by Git", |
||||
Action: runHookUpdate, |
||||
} |
||||
subcmdHookPostReceive = cli.Command{ |
||||
Name: "post-receive", |
||||
Usage: "Delegate post-receive Git hook", |
||||
Description: "This command should only be called by Git", |
||||
Action: runHookPostReceive, |
||||
} |
||||
) |
||||
|
||||
func runHookPreReceive(c *cli.Context) error { |
||||
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { |
||||
return nil |
||||
} |
||||
setup(c, "hooks/pre-receive.log") |
||||
|
||||
buf := bytes.NewBuffer(nil) |
||||
scanner := bufio.NewScanner(os.Stdin) |
||||
for scanner.Scan() { |
||||
buf.Write(scanner.Bytes()) |
||||
buf.WriteByte('\n') |
||||
} |
||||
|
||||
customHooksPath := os.Getenv(_ENV_REPO_CUSTOM_HOOKS_PATH) |
||||
if !com.IsFile(customHooksPath) { |
||||
return nil |
||||
} |
||||
|
||||
hookCmd := exec.Command(filepath.Join(customHooksPath, "pre-receive")) |
||||
hookCmd.Stdout = os.Stdout |
||||
hookCmd.Stdin = buf |
||||
hookCmd.Stderr = os.Stderr |
||||
if err := hookCmd.Run(); err != nil { |
||||
fail("Internal error", "Fail to execute custom pre-receive hook: %v", err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func runHookUpdate(c *cli.Context) error { |
||||
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { |
||||
return nil |
||||
} |
||||
setup(c, "hooks/update.log") |
||||
|
||||
args := c.Args() |
||||
if len(args) != 3 { |
||||
fail("Arguments received are not equal to three", "Arguments received are not equal to three") |
||||
} else if len(args[0]) == 0 { |
||||
fail("First argument 'refName' is empty", "First argument 'refName' is empty") |
||||
} |
||||
|
||||
uuid := os.Getenv(_ENV_UPDATE_TASK_UUID) |
||||
if err := models.AddUpdateTask(&models.UpdateTask{ |
||||
UUID: uuid, |
||||
RefName: args[0], |
||||
OldCommitID: args[1], |
||||
NewCommitID: args[2], |
||||
}); err != nil { |
||||
fail("Internal error", "Fail to add update task '%s': %v", uuid, err) |
||||
} |
||||
|
||||
customHooksPath := os.Getenv(_ENV_REPO_CUSTOM_HOOKS_PATH) |
||||
if !com.IsFile(customHooksPath) { |
||||
return nil |
||||
} |
||||
|
||||
hookCmd := exec.Command(filepath.Join(customHooksPath, "update"), args...) |
||||
hookCmd.Stdout = os.Stdout |
||||
hookCmd.Stdin = os.Stdin |
||||
hookCmd.Stderr = os.Stderr |
||||
if err := hookCmd.Run(); err != nil { |
||||
fail("Internal error", "Fail to execute custom pre-receive hook: %v", err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func runHookPostReceive(c *cli.Context) error { |
||||
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { |
||||
return nil |
||||
} |
||||
setup(c, "hooks/post-receive.log") |
||||
|
||||
customHooksPath := os.Getenv(_ENV_REPO_CUSTOM_HOOKS_PATH) |
||||
if !com.IsFile(customHooksPath) { |
||||
return nil |
||||
} |
||||
|
||||
hookCmd := exec.Command(filepath.Join(customHooksPath, "post-receive")) |
||||
hookCmd.Stdout = os.Stdout |
||||
hookCmd.Stdin = os.Stdin |
||||
hookCmd.Stderr = os.Stderr |
||||
if err := hookCmd.Run(); err != nil { |
||||
fail("Internal error", "Fail to execute custom post-receive hook: %v", err) |
||||
} |
||||
return nil |
||||
} |
@ -1,58 +0,0 @@
|
||||
// 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 cmd |
||||
|
||||
import ( |
||||
"os" |
||||
|
||||
"github.com/urfave/cli" |
||||
|
||||
"github.com/gogits/gogs/models" |
||||
"github.com/gogits/gogs/modules/log" |
||||
"github.com/gogits/gogs/modules/setting" |
||||
) |
||||
|
||||
var CmdUpdate = cli.Command{ |
||||
Name: "update", |
||||
Usage: "This command should only be called by Git hook", |
||||
Description: `Update get pushed info and insert into database`, |
||||
Action: runUpdate, |
||||
Flags: []cli.Flag{ |
||||
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"), |
||||
}, |
||||
} |
||||
|
||||
func runUpdate(c *cli.Context) error { |
||||
if c.IsSet("config") { |
||||
setting.CustomConf = c.String("config") |
||||
} |
||||
|
||||
setup("update.log") |
||||
|
||||
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 { |
||||
log.GitLogger.Trace("SSH_ORIGINAL_COMMAND is empty") |
||||
return nil |
||||
} |
||||
|
||||
args := c.Args() |
||||
if len(args) != 3 { |
||||
log.GitLogger.Fatal(2, "Arguments received are not equal to three") |
||||
} else if len(args[0]) == 0 { |
||||
log.GitLogger.Fatal(2, "First argument 'refName' is empty, shouldn't use") |
||||
} |
||||
|
||||
task := models.UpdateTask{ |
||||
UUID: os.Getenv("uuid"), |
||||
RefName: args[0], |
||||
OldCommitID: args[1], |
||||
NewCommitID: args[2], |
||||
} |
||||
|
||||
if err := models.AddUpdateTask(&task); err != nil { |
||||
log.GitLogger.Fatal(2, "AddUpdateTask: %v", err) |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -1,154 +0,0 @@
|
||||
hash: 1d5fcf2a90f7621ecbc0b1abed548e11d13bda3fea49b4326c829a523268e5cf |
||||
updated: 2016-06-12T17:35:14.27036884+08:00 |
||||
imports: |
||||
- name: github.com/bradfitz/gomemcache |
||||
version: 2fafb84a66c4911e11a8f50955b01e74fe3ab9c5 |
||||
subpackages: |
||||
- memcache |
||||
- name: github.com/urfave/cli |
||||
version: 347a9884a87374d000eec7e6445a34487c1f4a2b |
||||
- name: github.com/go-macaron/binding |
||||
version: 48920167fa152d02f228cfbece7e0f1e452d200a |
||||
- name: github.com/go-macaron/cache |
||||
version: 56173531277692bc2925924d51fda1cd0a6b8178 |
||||
subpackages: |
||||
- memcache |
||||
- redis |
||||
- name: github.com/go-macaron/captcha |
||||
version: 8aa5919789ab301e865595eb4b1114d6b9847deb |
||||
- name: github.com/go-macaron/csrf |
||||
version: 6a9a7df172cc1fcd81e4585f44b09200b6087cc0 |
||||
- name: github.com/go-macaron/gzip |
||||
version: cad1c6580a07c56f5f6bc52d66002a05985c5854 |
||||
- name: github.com/go-macaron/i18n |
||||
version: ef57533c3b0fc2d8581deda14937e52f11a203ab |
||||
- name: github.com/go-macaron/inject |
||||
version: d8a0b8677191f4380287cfebd08e462217bac7ad |
||||
- name: github.com/go-macaron/session |
||||
version: 66031fcb37a0fff002a1f028eb0b3a815c78306b |
||||
subpackages: |
||||
- redis |
||||
- name: github.com/go-macaron/toolbox |
||||
version: 82b511550b0aefc36b3a28062ad3a52e812bee38 |
||||
- name: github.com/go-sql-driver/mysql |
||||
version: 2e00b5cd70399450106cec6431c2e2ce3cae5034 |
||||
- name: github.com/go-xorm/builder |
||||
version: 867edcc549127f15667ece8876c3a60093c9634b |
||||
- name: github.com/go-xorm/core |
||||
version: 2fbe2c76c6781d9e1c0398fc25386426e611f975 |
||||
- name: github.com/go-xorm/xorm |
||||
version: 445a934d32ed0934aedf0ef99b4b7c69b22f8e58 |
||||
- name: github.com/gogits/chardet |
||||
version: 2404f777256163ea3eadb273dada5dcb037993c0 |
||||
- name: github.com/gogits/cron |
||||
version: 2fc07a4c4f1e3c4d2301c5ed578d5e2c31c70421 |
||||
- name: github.com/gogits/git-module |
||||
version: 172cbc21accbf0085a58fd0832f46a9f694130e8 |
||||
- name: github.com/gogits/go-gogs-client |
||||
version: 98046bb98061fc6baa5bb86359af0b7c300d384a |
||||
- name: github.com/gogits/go-libravatar |
||||
version: cd1abbd55d09b793672732a7a1dfdaa12a40dfd0 |
||||
- name: github.com/issue9/identicon |
||||
version: d36b54562f4cf70c83653e13dc95c220c79ef521 |
||||
- name: github.com/jaytaylor/html2text |
||||
version: d16d4129aab4e6be4497f4db898965f09b93b565 |
||||
- name: github.com/kardianos/minwinsvc |
||||
version: cad6b2b879b0970e4245a20ebf1a81a756e2bb70 |
||||
- name: github.com/klauspost/compress |
||||
version: 461e8fd8397ae84a23f56e385801e4feda2048ce |
||||
subpackages: |
||||
- gzip |
||||
- flate |
||||
- name: github.com/klauspost/cpuid |
||||
version: 09cded8978dc9e80714c4d85b0322337b0a1e5e0 |
||||
- name: github.com/klauspost/crc32 |
||||
version: cb6bfca970f6908083f26f39a79009d608efd5cd |
||||
- name: github.com/lib/pq |
||||
version: 67c3f2a8884c9b1aac5503c8d42ae4f73a93511c |
||||
subpackages: |
||||
- oid |
||||
- name: github.com/mattn/go-sqlite3 |
||||
version: ce9149a3c941c30de51a01dbc5bc414ddaa52927 |
||||
- name: github.com/mcuadros/go-version |
||||
version: 257f7b9a7d87427c8d7f89469a5958d57f8abd7c |
||||
- name: github.com/microcosm-cc/bluemonday |
||||
version: e79763773ab6222ca1d5a7cbd9d62d83c1f77081 |
||||
- name: github.com/msteinert/pam |
||||
version: 02ccfbfaf0cc627aa3aec8ef7ed5cfeec5b43f63 |
||||
- name: github.com/nfnt/resize |
||||
version: 891127d8d1b52734debe1b3c3d7e747502b6c366 |
||||
- name: github.com/russross/blackfriday |
||||
version: 5f33e7b7878355cd2b7e6b8eefc48a5472c69f70 |
||||
- name: github.com/satori/go.uuid |
||||
version: b061729afc07e77a8aa4fad0a2fd840958f1942a |
||||
- name: github.com/sergi/go-diff |
||||
version: 24e2351369ec4949b2ed0dc5c477afdd4c4034e8 |
||||
subpackages: |
||||
- diffmatchpatch |
||||
- name: github.com/shurcooL/sanitized_anchor_name |
||||
version: 1dba4b3954bc059efc3991ec364f9f9a35f597d2 |
||||
- name: github.com/Unknwon/cae |
||||
version: c6aac99ea2cae2ebaf23f26f76b04fe3fcfc9f8c |
||||
subpackages: |
||||
- zip |
||||
- name: github.com/Unknwon/com |
||||
version: 28b053d5a2923b87ce8c5a08f3af779894a72758 |
||||
- name: github.com/Unknwon/i18n |
||||
version: 39d6f2727e0698b1021ceb6a77c1801aa92e7d5d |
||||
- name: github.com/Unknwon/paginater |
||||
version: 701c23f468663c34d1b1768c3ae1bcc57e11c5b3 |
||||
- name: golang.org/x/crypto |
||||
version: dc137beb6cce2043eb6b5f223ab8bf51c32459f4 |
||||
subpackages: |
||||
- ssh |
||||
- curve25519 |
||||
- ed25519 |
||||
- ed25519/internal/edwards25519 |
||||
- name: golang.org/x/net |
||||
version: f2499483f923065a842d38eb4c7f1927e6fc6e6d |
||||
subpackages: |
||||
- html |
||||
- html/charset |
||||
- html/atom |
||||
- name: golang.org/x/sys |
||||
version: d75a52659825e75fff6158388dddc6a5b04f9ba5 |
||||
subpackages: |
||||
- windows/svc |
||||
- windows |
||||
- name: golang.org/x/text |
||||
version: ece019dcfd29abcf65d0d1dfe145e8faad097640 |
||||
subpackages: |
||||
- transform |
||||
- language |
||||
- encoding |
||||
- encoding/charmap |
||||
- encoding/htmlindex |
||||
- internal/tag |
||||
- encoding/internal/identifier |
||||
- encoding/internal |
||||
- encoding/japanese |
||||
- encoding/korean |
||||
- encoding/simplifiedchinese |
||||
- encoding/traditionalchinese |
||||
- encoding/unicode |
||||
- internal/utf8internal |
||||
- runes |
||||
- name: gopkg.in/alexcesaro/quotedprintable.v3 |
||||
version: 2caba252f4dc53eaf6b553000885530023f54623 |
||||
- name: gopkg.in/asn1-ber.v1 |
||||
version: 4e86f4367175e39f69d9358a5f17b4dda270378d |
||||
- name: gopkg.in/bufio.v1 |
||||
version: 567b2bfa514e796916c4747494d6ff5132a1dfce |
||||
- name: gopkg.in/editorconfig/editorconfig-core-go.v1 |
||||
version: a872f05c2e34b37b567401384d202aff11ba06d4 |
||||
- name: gopkg.in/gomail.v2 |
||||
version: 81ebce5c23dfd25c6c67194b37d3dd3f338c98b1 |
||||
- name: gopkg.in/ini.v1 |
||||
version: e3c2d47c61e5333f9aa2974695dd94396eb69c75 |
||||
- name: gopkg.in/ldap.v2 |
||||
version: 8168ee085ee43257585e50c6441aadf54ecb2c9f |
||||
- name: gopkg.in/macaron.v1 |
||||
version: 78521e4647ad5dcbabd5734d94582ca2373cbad8 |
||||
- name: gopkg.in/redis.v2 |
||||
version: e6179049628164864e6e84e973cfb56335748dea |
||||
devImports: [] |
@ -1,59 +0,0 @@
|
||||
package: github.com/gogits/gogs |
||||
import: |
||||
- package: github.com/Unknwon/cae |
||||
subpackages: |
||||
- zip |
||||
- package: github.com/Unknwon/com |
||||
- package: github.com/Unknwon/i18n |
||||
- package: github.com/Unknwon/paginater |
||||
- package: github.com/urfave/cli |
||||
- package: github.com/go-macaron/binding |
||||
- package: github.com/go-macaron/cache |
||||
subpackages: |
||||
- memcache |
||||
- redis |
||||
- package: github.com/go-macaron/captcha |
||||
- package: github.com/go-macaron/csrf |
||||
- package: github.com/go-macaron/gzip |
||||
- package: github.com/go-macaron/i18n |
||||
- package: github.com/go-macaron/session |
||||
subpackages: |
||||
- redis |
||||
- package: github.com/go-macaron/toolbox |
||||
- package: github.com/go-sql-driver/mysql |
||||
- package: github.com/go-xorm/core |
||||
- package: github.com/go-xorm/xorm |
||||
- package: github.com/gogits/chardet |
||||
- package: github.com/gogits/cron |
||||
- package: github.com/gogits/git-module |
||||
- package: github.com/gogits/go-gogs-client |
||||
- package: github.com/issue9/identicon |
||||
- package: github.com/kardianos/minwinsvc |
||||
- package: github.com/lib/pq |
||||
- package: github.com/mattn/go-sqlite3 |
||||
- package: github.com/mcuadros/go-version |
||||
- package: github.com/microcosm-cc/bluemonday |
||||
- package: github.com/msteinert/pam |
||||
- package: github.com/nfnt/resize |
||||
- package: github.com/russross/blackfriday |
||||
- package: github.com/satori/go.uuid |
||||
- package: github.com/sergi/go-diff |
||||
subpackages: |
||||
- diffmatchpatch |
||||
- package: github.com/strk/go-libravatar |
||||
- package: golang.org/x/crypto |
||||
subpackages: |
||||
- ssh |
||||
- package: golang.org/x/net |
||||
subpackages: |
||||
- html |
||||
- html/charset |
||||
- package: golang.org/x/text |
||||
subpackages: |
||||
- transform |
||||
- language |
||||
- package: gopkg.in/editorconfig/editorconfig-core-go.v1 |
||||
- package: gopkg.in/gomail.v2 |
||||
- package: gopkg.in/ini.v1 |
||||
- package: gopkg.in/ldap.v2 |
||||
- package: gopkg.in/macaron.v1 |
@ -0,0 +1,175 @@
|
||||
// Copyright 2017 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 models |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io" |
||||
"mime/multipart" |
||||
"os" |
||||
"path" |
||||
"time" |
||||
|
||||
"github.com/go-xorm/xorm" |
||||
gouuid "github.com/satori/go.uuid" |
||||
|
||||
"github.com/gogits/gogs/modules/setting" |
||||
) |
||||
|
||||
// Attachment represent a attachment of issue/comment/release.
|
||||
type Attachment struct { |
||||
ID int64 `xorm:"pk autoincr"` |
||||
UUID string `xorm:"uuid UNIQUE"` |
||||
IssueID int64 `xorm:"INDEX"` |
||||
CommentID int64 |
||||
ReleaseID int64 `xorm:"INDEX"` |
||||
Name string |
||||
|
||||
Created time.Time `xorm:"-"` |
||||
CreatedUnix int64 |
||||
} |
||||
|
||||
func (a *Attachment) BeforeInsert() { |
||||
a.CreatedUnix = time.Now().Unix() |
||||
} |
||||
|
||||
func (a *Attachment) AfterSet(colName string, _ xorm.Cell) { |
||||
switch colName { |
||||
case "created_unix": |
||||
a.Created = time.Unix(a.CreatedUnix, 0).Local() |
||||
} |
||||
} |
||||
|
||||
// AttachmentLocalPath returns where attachment is stored in local file system based on given UUID.
|
||||
func AttachmentLocalPath(uuid string) string { |
||||
return path.Join(setting.AttachmentPath, uuid[0:1], uuid[1:2], uuid) |
||||
} |
||||
|
||||
// LocalPath returns where attachment is stored in local file system.
|
||||
func (attach *Attachment) LocalPath() string { |
||||
return AttachmentLocalPath(attach.UUID) |
||||
} |
||||
|
||||
// NewAttachment creates a new attachment object.
|
||||
func NewAttachment(name string, buf []byte, file multipart.File) (_ *Attachment, err error) { |
||||
attach := &Attachment{ |
||||
UUID: gouuid.NewV4().String(), |
||||
Name: name, |
||||
} |
||||
|
||||
localPath := attach.LocalPath() |
||||
if err = os.MkdirAll(path.Dir(localPath), os.ModePerm); err != nil { |
||||
return nil, fmt.Errorf("MkdirAll: %v", err) |
||||
} |
||||
|
||||
fw, err := os.Create(localPath) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("Create: %v", err) |
||||
} |
||||
defer fw.Close() |
||||
|
||||
if _, err = fw.Write(buf); err != nil { |
||||
return nil, fmt.Errorf("Write: %v", err) |
||||
} else if _, err = io.Copy(fw, file); err != nil { |
||||
return nil, fmt.Errorf("Copy: %v", err) |
||||
} |
||||
|
||||
if _, err := x.Insert(attach); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return attach, nil |
||||
} |
||||
|
||||
func getAttachmentByUUID(e Engine, uuid string) (*Attachment, error) { |
||||
attach := &Attachment{UUID: uuid} |
||||
has, err := x.Get(attach) |
||||
if err != nil { |
||||
return nil, err |
||||
} else if !has { |
||||
return nil, ErrAttachmentNotExist{0, uuid} |
||||
} |
||||
return attach, nil |
||||
} |
||||
|
||||
func getAttachmentsByUUIDs(e Engine, uuids []string) ([]*Attachment, error) { |
||||
if len(uuids) == 0 { |
||||
return []*Attachment{}, nil |
||||
} |
||||
|
||||
// Silently drop invalid uuids.
|
||||
attachments := make([]*Attachment, 0, len(uuids)) |
||||
return attachments, e.In("uuid", uuids).Find(&attachments) |
||||
} |
||||
|
||||
// GetAttachmentByUUID returns attachment by given UUID.
|
||||
func GetAttachmentByUUID(uuid string) (*Attachment, error) { |
||||
return getAttachmentByUUID(x, uuid) |
||||
} |
||||
|
||||
func getAttachmentsByIssueID(e Engine, issueID int64) ([]*Attachment, error) { |
||||
attachments := make([]*Attachment, 0, 10) |
||||
return attachments, e.Where("issue_id = ? AND comment_id = 0", issueID).Find(&attachments) |
||||
} |
||||
|
||||
// GetAttachmentsByIssueID returns all attachments of an issue.
|
||||
func GetAttachmentsByIssueID(issueID int64) ([]*Attachment, error) { |
||||
return getAttachmentsByIssueID(x, issueID) |
||||
} |
||||
|
||||
func getAttachmentsByCommentID(e Engine, commentID int64) ([]*Attachment, error) { |
||||
attachments := make([]*Attachment, 0, 10) |
||||
return attachments, e.Where("comment_id=?", commentID).Find(&attachments) |
||||
} |
||||
|
||||
// GetAttachmentsByCommentID returns all attachments if comment by given ID.
|
||||
func GetAttachmentsByCommentID(commentID int64) ([]*Attachment, error) { |
||||
return getAttachmentsByCommentID(x, commentID) |
||||
} |
||||
|
||||
// DeleteAttachment deletes the given attachment and optionally the associated file.
|
||||
func DeleteAttachment(a *Attachment, remove bool) error { |
||||
_, err := DeleteAttachments([]*Attachment{a}, remove) |
||||
return err |
||||
} |
||||
|
||||
// DeleteAttachments deletes the given attachments and optionally the associated files.
|
||||
func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) { |
||||
for i, a := range attachments { |
||||
if remove { |
||||
if err := os.Remove(a.LocalPath()); err != nil { |
||||
return i, err |
||||
} |
||||
} |
||||
|
||||
if _, err := x.Delete(a); err != nil { |
||||
return i, err |
||||
} |
||||
} |
||||
|
||||
return len(attachments), nil |
||||
} |
||||
|
||||
// DeleteAttachmentsByIssue deletes all attachments associated with the given issue.
|
||||
func DeleteAttachmentsByIssue(issueId int64, remove bool) (int, error) { |
||||
attachments, err := GetAttachmentsByIssueID(issueId) |
||||
|
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
|
||||
return DeleteAttachments(attachments, remove) |
||||
} |
||||
|
||||
// DeleteAttachmentsByComment deletes all attachments associated with the given comment.
|
||||
func DeleteAttachmentsByComment(commentId int64, remove bool) (int, error) { |
||||
attachments, err := GetAttachmentsByCommentID(commentId) |
||||
|
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
|
||||
return DeleteAttachments(attachments, remove) |
||||
} |
@ -0,0 +1,91 @@
|
||||
// Copyright 2017 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 migrations |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io/ioutil" |
||||
"os" |
||||
"path/filepath" |
||||
"strings" |
||||
|
||||
"github.com/Unknwon/com" |
||||
"github.com/go-xorm/xorm" |
||||
log "gopkg.in/clog.v1" |
||||
|
||||
"github.com/gogits/gogs/modules/setting" |
||||
) |
||||
|
||||
func generateAndMigrateGitHooks(x *xorm.Engine) (err error) { |
||||
type Repository struct { |
||||
ID int64 |
||||
OwnerID int64 |
||||
Name string |
||||
} |
||||
type User struct { |
||||
ID int64 |
||||
Name string |
||||
} |
||||
var ( |
||||
hookNames = []string{"pre-receive", "update", "post-receive"} |
||||
hookTpls = []string{ |
||||
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf), |
||||
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n", setting.ScriptType, setting.AppPath, setting.CustomConf), |
||||
fmt.Sprintf("#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n", setting.ScriptType, setting.AppPath, setting.CustomConf), |
||||
} |
||||
) |
||||
|
||||
// Cleanup old update.log and http.log files.
|
||||
filepath.Walk(setting.LogRootPath, func(path string, info os.FileInfo, err error) error { |
||||
if !info.IsDir() && |
||||
(strings.HasPrefix(filepath.Base(path), "update.log") || |
||||
strings.HasPrefix(filepath.Base(path), "http.log")) { |
||||
os.Remove(path) |
||||
} |
||||
return nil |
||||
}) |
||||
|
||||
return x.Where("id > 0").Iterate(new(Repository), |
||||
func(idx int, bean interface{}) error { |
||||
repo := bean.(*Repository) |
||||
if repo.Name == "." || repo.Name == ".." { |
||||
return nil |
||||
} |
||||
|
||||
user := new(User) |
||||
has, err := x.Where("id = ?", repo.OwnerID).Get(user) |
||||
if err != nil { |
||||
return fmt.Errorf("query owner of repository [repo_id: %d, owner_id: %d]: %v", repo.ID, repo.OwnerID, err) |
||||
} else if !has { |
||||
return nil |
||||
} |
||||
|
||||
repoPath := filepath.Join(setting.RepoRootPath, strings.ToLower(user.Name), strings.ToLower(repo.Name)) + ".git" |
||||
log.Trace("[%04d]: %s", idx, repoPath) |
||||
|
||||
hookDir := filepath.Join(repoPath, "hooks") |
||||
customHookDir := filepath.Join(repoPath, "custom_hooks") |
||||
|
||||
for i, hookName := range hookNames { |
||||
oldHookPath := filepath.Join(hookDir, hookName) |
||||
newHookPath := filepath.Join(customHookDir, hookName) |
||||
|
||||
// Gogs didn't allow user to set custom update hook thus no migration for it.
|
||||
// In case user runs this migration multiple times, and custom hook exists,
|
||||
// we assume it's been migrated already.
|
||||
if hookName != "update" && com.IsFile(oldHookPath) && !com.IsExist(customHookDir) { |
||||
os.MkdirAll(customHookDir, os.ModePerm) |
||||
if err = os.Rename(oldHookPath, newHookPath); err != nil { |
||||
return fmt.Errorf("move hook file to custom directory '%s' -> '%s': %v", oldHookPath, newHookPath, err) |
||||
} |
||||
} |
||||
|
||||
if err = ioutil.WriteFile(oldHookPath, []byte(hookTpls[i]), 0777); err != nil { |
||||
return fmt.Errorf("write hook file '%s': %v", oldHookPath, err) |
||||
} |
||||
} |
||||
return nil |
||||
}) |
||||
} |
@ -0,0 +1,349 @@
|
||||
// Copyright 2017 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 models |
||||
|
||||
import ( |
||||
"time" |
||||
|
||||
"github.com/go-xorm/xorm" |
||||
|
||||
api "github.com/gogits/go-gogs-client" |
||||
|
||||
"github.com/gogits/gogs/modules/setting" |
||||
) |
||||
|
||||
// Milestone represents a milestone of repository.
|
||||
type Milestone struct { |
||||
ID int64 `xorm:"pk autoincr"` |
||||
RepoID int64 `xorm:"INDEX"` |
||||
Name string |
||||
Content string `xorm:"TEXT"` |
||||
RenderedContent string `xorm:"-"` |
||||
IsClosed bool |
||||
NumIssues int |
||||
NumClosedIssues int |
||||
NumOpenIssues int `xorm:"-"` |
||||
Completeness int // Percentage(1-100).
|
||||
IsOverDue bool `xorm:"-"` |
||||
|
||||
DeadlineString string `xorm:"-"` |
||||
Deadline time.Time `xorm:"-"` |
||||
DeadlineUnix int64 |
||||
ClosedDate time.Time `xorm:"-"` |
||||
ClosedDateUnix int64 |
||||
} |
||||
|
||||
func (m *Milestone) BeforeInsert() { |
||||
m.DeadlineUnix = m.Deadline.Unix() |
||||
} |
||||
|
||||
func (m *Milestone) BeforeUpdate() { |
||||
if m.NumIssues > 0 { |
||||
m.Completeness = m.NumClosedIssues * 100 / m.NumIssues |
||||
} else { |
||||
m.Completeness = 0 |
||||
} |
||||
|
||||
m.DeadlineUnix = m.Deadline.Unix() |
||||
m.ClosedDateUnix = m.ClosedDate.Unix() |
||||
} |
||||
|
||||
func (m *Milestone) AfterSet(colName string, _ xorm.Cell) { |
||||
switch colName { |
||||
case "num_closed_issues": |
||||
m.NumOpenIssues = m.NumIssues - m.NumClosedIssues |
||||
|
||||
case "deadline_unix": |
||||
m.Deadline = time.Unix(m.DeadlineUnix, 0).Local() |
||||
if m.Deadline.Year() == 9999 { |
||||
return |
||||
} |
||||
|
||||
m.DeadlineString = m.Deadline.Format("2006-01-02") |
||||
if time.Now().Local().After(m.Deadline) { |
||||
m.IsOverDue = true |
||||
} |
||||
|
||||
case "closed_date_unix": |
||||
m.ClosedDate = time.Unix(m.ClosedDateUnix, 0).Local() |
||||
} |
||||
} |
||||
|
||||
// State returns string representation of milestone status.
|
||||
func (m *Milestone) State() api.StateType { |
||||
if m.IsClosed { |
||||
return api.STATE_CLOSED |
||||
} |
||||
return api.STATE_OPEN |
||||
} |
||||
|
||||
func (m *Milestone) ChangeStatus(isClosed bool) error { |
||||
return ChangeMilestoneStatus(m, isClosed) |
||||
} |
||||
|
||||
func (m *Milestone) APIFormat() *api.Milestone { |
||||
apiMilestone := &api.Milestone{ |
||||
ID: m.ID, |
||||
State: m.State(), |
||||
Title: m.Name, |
||||
Description: m.Content, |
||||
OpenIssues: m.NumOpenIssues, |
||||
ClosedIssues: m.NumClosedIssues, |
||||
} |
||||
if m.IsClosed { |
||||
apiMilestone.Closed = &m.ClosedDate |
||||
} |
||||
if m.Deadline.Year() < 9999 { |
||||
apiMilestone.Deadline = &m.Deadline |
||||
} |
||||
return apiMilestone |
||||
} |
||||
|
||||
// NewMilestone creates new milestone of repository.
|
||||
func NewMilestone(m *Milestone) (err error) { |
||||
sess := x.NewSession() |
||||
defer sessionRelease(sess) |
||||
if err = sess.Begin(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if _, err = sess.Insert(m); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if _, err = sess.Exec("UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?", m.RepoID); err != nil { |
||||
return err |
||||
} |
||||
return sess.Commit() |
||||
} |
||||
|
||||
func getMilestoneByRepoID(e Engine, repoID, id int64) (*Milestone, error) { |
||||
m := &Milestone{ |
||||
ID: id, |
||||
RepoID: repoID, |
||||
} |
||||
has, err := e.Get(m) |
||||
if err != nil { |
||||
return nil, err |
||||
} else if !has { |
||||
return nil, ErrMilestoneNotExist{id, repoID} |
||||
} |
||||
return m, nil |
||||
} |
||||
|
||||
// GetWebhookByRepoID returns the milestone in a repository.
|
||||
func GetMilestoneByRepoID(repoID, id int64) (*Milestone, error) { |
||||
return getMilestoneByRepoID(x, repoID, id) |
||||
} |
||||
|
||||
// GetMilestonesByRepoID returns all milestones of a repository.
|
||||
func GetMilestonesByRepoID(repoID int64) ([]*Milestone, error) { |
||||
miles := make([]*Milestone, 0, 10) |
||||
return miles, x.Where("repo_id = ?", repoID).Find(&miles) |
||||
} |
||||
|
||||
// GetMilestones returns a list of milestones of given repository and status.
|
||||
func GetMilestones(repoID int64, page int, isClosed bool) ([]*Milestone, error) { |
||||
miles := make([]*Milestone, 0, setting.UI.IssuePagingNum) |
||||
sess := x.Where("repo_id = ? AND is_closed = ?", repoID, isClosed) |
||||
if page > 0 { |
||||
sess = sess.Limit(setting.UI.IssuePagingNum, (page-1)*setting.UI.IssuePagingNum) |
||||
} |
||||
return miles, sess.Find(&miles) |
||||
} |
||||
|
||||
func updateMilestone(e Engine, m *Milestone) error { |
||||
_, err := e.Id(m.ID).AllCols().Update(m) |
||||
return err |
||||
} |
||||
|
||||
// UpdateMilestone updates information of given milestone.
|
||||
func UpdateMilestone(m *Milestone) error { |
||||
return updateMilestone(x, m) |
||||
} |
||||
|
||||
func countRepoMilestones(e Engine, repoID int64) int64 { |
||||
count, _ := e.Where("repo_id=?", repoID).Count(new(Milestone)) |
||||
return count |
||||
} |
||||
|
||||
// CountRepoMilestones returns number of milestones in given repository.
|
||||
func CountRepoMilestones(repoID int64) int64 { |
||||
return countRepoMilestones(x, repoID) |
||||
} |
||||
|
||||
func countRepoClosedMilestones(e Engine, repoID int64) int64 { |
||||
closed, _ := e.Where("repo_id=? AND is_closed=?", repoID, true).Count(new(Milestone)) |
||||
return closed |
||||
} |
||||
|
||||
// CountRepoClosedMilestones returns number of closed milestones in given repository.
|
||||
func CountRepoClosedMilestones(repoID int64) int64 { |
||||
return countRepoClosedMilestones(x, repoID) |
||||
} |
||||
|
||||
// MilestoneStats returns number of open and closed milestones of given repository.
|
||||
func MilestoneStats(repoID int64) (open int64, closed int64) { |
||||
open, _ = x.Where("repo_id=? AND is_closed=?", repoID, false).Count(new(Milestone)) |
||||
return open, CountRepoClosedMilestones(repoID) |
||||
} |
||||
|
||||
// ChangeMilestoneStatus changes the milestone open/closed status.
|
||||
// If milestone passes with changed values, those values will be
|
||||
// updated to database as well.
|
||||
func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) { |
||||
repo, err := GetRepositoryByID(m.RepoID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
sess := x.NewSession() |
||||
defer sessionRelease(sess) |
||||
if err = sess.Begin(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
m.IsClosed = isClosed |
||||
if err = updateMilestone(sess, m); err != nil { |
||||
return err |
||||
} |
||||
|
||||
repo.NumMilestones = int(countRepoMilestones(sess, repo.ID)) |
||||
repo.NumClosedMilestones = int(countRepoClosedMilestones(sess, repo.ID)) |
||||
if _, err = sess.Id(repo.ID).AllCols().Update(repo); err != nil { |
||||
return err |
||||
} |
||||
return sess.Commit() |
||||
} |
||||
|
||||
func changeMilestoneIssueStats(e *xorm.Session, issue *Issue) error { |
||||
if issue.MilestoneID == 0 { |
||||
return nil |
||||
} |
||||
|
||||
m, err := getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if issue.IsClosed { |
||||
m.NumOpenIssues-- |
||||
m.NumClosedIssues++ |
||||
} else { |
||||
m.NumOpenIssues++ |
||||
m.NumClosedIssues-- |
||||
} |
||||
|
||||
return updateMilestone(e, m) |
||||
} |
||||
|
||||
// ChangeMilestoneIssueStats updates the open/closed issues counter and progress
|
||||
// for the milestone associated with the given issue.
|
||||
func ChangeMilestoneIssueStats(issue *Issue) (err error) { |
||||
sess := x.NewSession() |
||||
defer sessionRelease(sess) |
||||
if err = sess.Begin(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if err = changeMilestoneIssueStats(sess, issue); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return sess.Commit() |
||||
} |
||||
|
||||
func changeMilestoneAssign(e *xorm.Session, issue *Issue, oldMilestoneID int64) error { |
||||
if oldMilestoneID > 0 { |
||||
m, err := getMilestoneByRepoID(e, issue.RepoID, oldMilestoneID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
m.NumIssues-- |
||||
if issue.IsClosed { |
||||
m.NumClosedIssues-- |
||||
} |
||||
|
||||
if err = updateMilestone(e, m); err != nil { |
||||
return err |
||||
} else if _, err = e.Exec("UPDATE `issue_user` SET milestone_id = 0 WHERE issue_id = ?", issue.ID); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
if issue.MilestoneID > 0 { |
||||
m, err := getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
m.NumIssues++ |
||||
if issue.IsClosed { |
||||
m.NumClosedIssues++ |
||||
} |
||||
|
||||
if err = updateMilestone(e, m); err != nil { |
||||
return err |
||||
} else if _, err = e.Exec("UPDATE `issue_user` SET milestone_id = ? WHERE issue_id = ?", m.ID, issue.ID); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
return updateIssue(e, issue) |
||||
} |
||||
|
||||
// ChangeMilestoneAssign changes assignment of milestone for issue.
|
||||
func ChangeMilestoneAssign(issue *Issue, oldMilestoneID int64) (err error) { |
||||
sess := x.NewSession() |
||||
defer sess.Close() |
||||
if err = sess.Begin(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if err = changeMilestoneAssign(sess, issue, oldMilestoneID); err != nil { |
||||
return err |
||||
} |
||||
return sess.Commit() |
||||
} |
||||
|
||||
// DeleteMilestoneOfRepoByID deletes a milestone from a repository.
|
||||
func DeleteMilestoneOfRepoByID(repoID, id int64) error { |
||||
m, err := GetMilestoneByRepoID(repoID, id) |
||||
if err != nil { |
||||
if IsErrMilestoneNotExist(err) { |
||||
return nil |
||||
} |
||||
return err |
||||
} |
||||
|
||||
repo, err := GetRepositoryByID(m.RepoID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
sess := x.NewSession() |
||||
defer sessionRelease(sess) |
||||
if err = sess.Begin(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if _, err = sess.Id(m.ID).Delete(new(Milestone)); err != nil { |
||||
return err |
||||
} |
||||
|
||||
repo.NumMilestones = int(countRepoMilestones(sess, repo.ID)) |
||||
repo.NumClosedMilestones = int(countRepoClosedMilestones(sess, repo.ID)) |
||||
if _, err = sess.Id(repo.ID).AllCols().Update(repo); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if _, err = sess.Exec("UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?", m.ID); err != nil { |
||||
return err |
||||
} else if _, err = sess.Exec("UPDATE `issue_user` SET milestone_id = 0 WHERE milestone_id = ?", m.ID); err != nil { |
||||
return err |
||||
} |
||||
return sess.Commit() |
||||
} |
@ -1,18 +0,0 @@
|
||||
// +build tidb go1.4.2
|
||||
|
||||
// Copyright 2015 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 models |
||||
|
||||
import ( |
||||
_ "github.com/go-xorm/tidb" |
||||
"github.com/ngaut/log" |
||||
_ "github.com/pingcap/tidb" |
||||
) |
||||
|
||||
func init() { |
||||
EnableTiDB = true |
||||
log.SetLevelByString("error") |
||||
} |
@ -1,104 +0,0 @@
|
||||
// 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) |
||||
} |
@ -1,73 +0,0 @@
|
||||
// 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" |
||||
"log" |
||||
"os" |
||||
"runtime" |
||||
) |
||||
|
||||
type Brush func(string) string |
||||
|
||||
func NewBrush(color string) Brush { |
||||
pre := "\033[" |
||||
reset := "\033[0m" |
||||
return func(text string) string { |
||||
return pre + color + "m" + text + reset |
||||
} |
||||
} |
||||
|
||||
var colors = []Brush{ |
||||
NewBrush("1;36"), // Trace cyan
|
||||
NewBrush("1;34"), // Debug blue
|
||||
NewBrush("1;32"), // Info green
|
||||
NewBrush("1;33"), // Warn yellow
|
||||
NewBrush("1;31"), // Error red
|
||||
NewBrush("1;35"), // Critical purple
|
||||
NewBrush("1;31"), // Fatal red
|
||||
} |
||||
|
||||
// ConsoleWriter implements LoggerInterface and writes messages to terminal.
|
||||
type ConsoleWriter struct { |
||||
lg *log.Logger |
||||
Level int `json:"level"` |
||||
} |
||||
|
||||
// create ConsoleWriter returning as LoggerInterface.
|
||||
func NewConsole() LoggerInterface { |
||||
return &ConsoleWriter{ |
||||
lg: log.New(os.Stdout, "", log.Ldate|log.Ltime), |
||||
Level: TRACE, |
||||
} |
||||
} |
||||
|
||||
func (cw *ConsoleWriter) Init(config string) error { |
||||
return json.Unmarshal([]byte(config), cw) |
||||
} |
||||
|
||||
func (cw *ConsoleWriter) WriteMsg(msg string, skip, level int) error { |
||||
if cw.Level > level { |
||||
return nil |
||||
} |
||||
if runtime.GOOS == "windows" { |
||||
cw.lg.Println(msg) |
||||
} else { |
||||
cw.lg.Println(colors[level](msg)) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (_ *ConsoleWriter) Flush() { |
||||
|
||||
} |
||||
|
||||
func (_ *ConsoleWriter) Destroy() { |
||||
} |
||||
|
||||
func init() { |
||||
Register("console", NewConsole) |
||||
} |
@ -1,243 +0,0 @@
|
||||
// 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" |
||||
"errors" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"log" |
||||
"os" |
||||
"path/filepath" |
||||
"strings" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
// FileLogWriter implements LoggerInterface.
|
||||
// It writes messages by lines limit, file size limit, or time frequency.
|
||||
type FileLogWriter struct { |
||||
*log.Logger |
||||
mw *MuxWriter |
||||
// The opened file
|
||||
Filename string `json:"filename"` |
||||
|
||||
Maxlines int `json:"maxlines"` |
||||
maxlines_curlines int |
||||
|
||||
// Rotate at size
|
||||
Maxsize int `json:"maxsize"` |
||||
maxsize_cursize int |
||||
|
||||
// Rotate daily
|
||||
Daily bool `json:"daily"` |
||||
Maxdays int64 `json:"maxdays"` |
||||
daily_opendate int |
||||
|
||||
Rotate bool `json:"rotate"` |
||||
|
||||
startLock sync.Mutex // Only one log can write to the file
|
||||
|
||||
Level int `json:"level"` |
||||
} |
||||
|
||||
// an *os.File writer with locker.
|
||||
type MuxWriter struct { |
||||
sync.Mutex |
||||
fd *os.File |
||||
} |
||||
|
||||
// write to os.File.
|
||||
func (l *MuxWriter) Write(b []byte) (int, error) { |
||||
l.Lock() |
||||
defer l.Unlock() |
||||
return l.fd.Write(b) |
||||
} |
||||
|
||||
// set os.File in writer.
|
||||
func (l *MuxWriter) SetFd(fd *os.File) { |
||||
if l.fd != nil { |
||||
l.fd.Close() |
||||
} |
||||
l.fd = fd |
||||
} |
||||
|
||||
// create a FileLogWriter returning as LoggerInterface.
|
||||
func NewFileWriter() LoggerInterface { |
||||
w := &FileLogWriter{ |
||||
Filename: "", |
||||
Maxlines: 1000000, |
||||
Maxsize: 1 << 28, //256 MB
|
||||
Daily: true, |
||||
Maxdays: 7, |
||||
Rotate: true, |
||||
Level: TRACE, |
||||
} |
||||
// use MuxWriter instead direct use os.File for lock write when rotate
|
||||
w.mw = new(MuxWriter) |
||||
// set MuxWriter as Logger's io.Writer
|
||||
w.Logger = log.New(w.mw, "", log.Ldate|log.Ltime) |
||||
return w |
||||
} |
||||
|
||||
// Init file logger with json config.
|
||||
// config like:
|
||||
// {
|
||||
// "filename":"log/gogs.log",
|
||||
// "maxlines":10000,
|
||||
// "maxsize":1<<30,
|
||||
// "daily":true,
|
||||
// "maxdays":15,
|
||||
// "rotate":true
|
||||
// }
|
||||
func (w *FileLogWriter) Init(config string) error { |
||||
if err := json.Unmarshal([]byte(config), w); err != nil { |
||||
return err |
||||
} |
||||
if len(w.Filename) == 0 { |
||||
return errors.New("config must have filename") |
||||
} |
||||
return w.StartLogger() |
||||
} |
||||
|
||||
// start file logger. create log file and set to locker-inside file writer.
|
||||
func (w *FileLogWriter) StartLogger() error { |
||||
fd, err := w.createLogFile() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
w.mw.SetFd(fd) |
||||
if err = w.initFd(); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (w *FileLogWriter) docheck(size int) { |
||||
w.startLock.Lock() |
||||
defer w.startLock.Unlock() |
||||
if w.Rotate && ((w.Maxlines > 0 && w.maxlines_curlines >= w.Maxlines) || |
||||
(w.Maxsize > 0 && w.maxsize_cursize >= w.Maxsize) || |
||||
(w.Daily && time.Now().Day() != w.daily_opendate)) { |
||||
if err := w.DoRotate(); err != nil { |
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) |
||||
return |
||||
} |
||||
} |
||||
w.maxlines_curlines++ |
||||
w.maxsize_cursize += size |
||||
} |
||||
|
||||
// write logger message into file.
|
||||
func (w *FileLogWriter) WriteMsg(msg string, skip, level int) error { |
||||
if level < w.Level { |
||||
return nil |
||||
} |
||||
n := 24 + len(msg) // 24 stand for the length "2013/06/23 21:00:22 [T] "
|
||||
w.docheck(n) |
||||
w.Logger.Println(msg) |
||||
return nil |
||||
} |
||||
|
||||
func (w *FileLogWriter) createLogFile() (*os.File, error) { |
||||
// Open the log file
|
||||
return os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660) |
||||
} |
||||
|
||||
func (w *FileLogWriter) initFd() error { |
||||
fd := w.mw.fd |
||||
finfo, err := fd.Stat() |
||||
if err != nil { |
||||
return fmt.Errorf("get stat: %s\n", err) |
||||
} |
||||
w.maxsize_cursize = int(finfo.Size()) |
||||
w.daily_opendate = time.Now().Day() |
||||
if finfo.Size() > 0 { |
||||
content, err := ioutil.ReadFile(w.Filename) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
w.maxlines_curlines = len(strings.Split(string(content), "\n")) |
||||
} else { |
||||
w.maxlines_curlines = 0 |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// DoRotate means it need to write file in new file.
|
||||
// new file name like xx.log.2013-01-01.2
|
||||
func (w *FileLogWriter) DoRotate() error { |
||||
_, err := os.Lstat(w.Filename) |
||||
if err == nil { // file exists
|
||||
// Find the next available number
|
||||
num := 1 |
||||
fname := "" |
||||
for ; err == nil && num <= 999; num++ { |
||||
fname = w.Filename + fmt.Sprintf(".%s.%03d", time.Now().Format("2006-01-02"), num) |
||||
_, err = os.Lstat(fname) |
||||
} |
||||
// return error if the last file checked still existed
|
||||
if err == nil { |
||||
return fmt.Errorf("rotate: cannot find free log number to rename %s\n", w.Filename) |
||||
} |
||||
|
||||
// block Logger's io.Writer
|
||||
w.mw.Lock() |
||||
defer w.mw.Unlock() |
||||
|
||||
fd := w.mw.fd |
||||
fd.Close() |
||||
|
||||
// close fd before rename
|
||||
// Rename the file to its newfound home
|
||||
if err = os.Rename(w.Filename, fname); err != nil { |
||||
return fmt.Errorf("Rotate: %s\n", err) |
||||
} |
||||
|
||||
// re-start logger
|
||||
if err = w.StartLogger(); err != nil { |
||||
return fmt.Errorf("Rotate StartLogger: %s\n", err) |
||||
} |
||||
|
||||
go w.deleteOldLog() |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func (w *FileLogWriter) deleteOldLog() { |
||||
dir := filepath.Dir(w.Filename) |
||||
filepath.Walk(dir, func(path string, info os.FileInfo, err error) (returnErr error) { |
||||
defer func() { |
||||
if r := recover(); r != nil { |
||||
returnErr = fmt.Errorf("Unable to delete old log '%s', error: %+v", path, r) |
||||
} |
||||
}() |
||||
|
||||
if !info.IsDir() && info.ModTime().Unix() < (time.Now().Unix()-60*60*24*w.Maxdays) { |
||||
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.Filename)) { |
||||
os.Remove(path) |
||||
} |
||||
} |
||||
return returnErr |
||||
}) |
||||
} |
||||
|
||||
// destroy file logger, close file writer.
|
||||
func (w *FileLogWriter) Destroy() { |
||||
w.mw.fd.Close() |
||||
} |
||||
|
||||
// flush file logger.
|
||||
// there are no buffering messages in file logger in memory.
|
||||
// flush file means sync file from disk.
|
||||
func (w *FileLogWriter) Flush() { |
||||
w.mw.fd.Sync() |
||||
} |
||||
|
||||
func init() { |
||||
Register("file", NewFileWriter) |
||||
} |
@ -1,312 +0,0 @@
|
||||
// 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 ( |
||||
"fmt" |
||||
"os" |
||||
"path" |
||||
"path/filepath" |
||||
"runtime" |
||||
"strings" |
||||
"sync" |
||||
) |
||||
|
||||
var ( |
||||
loggers []*Logger |
||||
GitLogger *Logger |
||||
) |
||||
|
||||
func NewLogger(bufLen int64, mode, config string) { |
||||
logger := newLogger(bufLen) |
||||
|
||||
isExist := false |
||||
for i, l := range loggers { |
||||
if l.adapter == mode { |
||||
isExist = true |
||||
loggers[i] = logger |
||||
} |
||||
} |
||||
if !isExist { |
||||
loggers = append(loggers, logger) |
||||
} |
||||
if err := logger.SetLogger(mode, config); err != nil { |
||||
Fatal(2, "Fail to set logger (%s): %v", mode, err) |
||||
} |
||||
} |
||||
|
||||
// FIXME: use same log level as other loggers.
|
||||
func NewGitLogger(logPath string) { |
||||
os.MkdirAll(path.Dir(logPath), os.ModePerm) |
||||
GitLogger = newLogger(0) |
||||
GitLogger.SetLogger("file", fmt.Sprintf(`{"level":0,"filename":"%s","rotate":false}`, logPath)) |
||||
} |
||||
|
||||
func Trace(format string, v ...interface{}) { |
||||
for _, logger := range loggers { |
||||
logger.Trace(format, v...) |
||||
} |
||||
} |
||||
|
||||
func Debug(format string, v ...interface{}) { |
||||
for _, logger := range loggers { |
||||
logger.Debug(format, v...) |
||||
} |
||||
} |
||||
|
||||
func Info(format string, v ...interface{}) { |
||||
for _, logger := range loggers { |
||||
logger.Info(format, v...) |
||||
} |
||||
} |
||||
|
||||
func Warn(format string, v ...interface{}) { |
||||
for _, logger := range loggers { |
||||
logger.Warn(format, v...) |
||||
} |
||||
} |
||||
|
||||
func Error(skip int, format string, v ...interface{}) { |
||||
for _, logger := range loggers { |
||||
logger.Error(skip, format, v...) |
||||
} |
||||
} |
||||
|
||||
func Critical(skip int, format string, v ...interface{}) { |
||||
for _, logger := range loggers { |
||||
logger.Critical(skip, format, v...) |
||||
} |
||||
} |
||||
|
||||
func Fatal(skip int, format string, v ...interface{}) { |
||||
Error(skip, format, v...) |
||||
for _, l := range loggers { |
||||
l.Close() |
||||
} |
||||
os.Exit(1) |
||||
} |
||||
|
||||
func Close() { |
||||
for _, l := range loggers { |
||||
l.Close() |
||||
} |
||||
} |
||||
|
||||
// .___ __ _____
|
||||
// | | _____/ |_ ____________/ ____\____ ____ ____
|
||||
// | |/ \ __\/ __ \_ __ \ __\\__ \ _/ ___\/ __ \
|
||||
// | | | \ | \ ___/| | \/| | / __ \\ \__\ ___/
|
||||
// |___|___| /__| \___ >__| |__| (____ /\___ >___ >
|
||||
// \/ \/ \/ \/ \/
|
||||
|
||||
type LogLevel int |
||||
|
||||
const ( |
||||
TRACE = iota |
||||
DEBUG |
||||
INFO |
||||
WARN |
||||
ERROR |
||||
CRITICAL |
||||
FATAL |
||||
) |
||||
|
||||
// LoggerInterface represents behaviors of a logger provider.
|
||||
type LoggerInterface interface { |
||||
Init(config string) error |
||||
WriteMsg(msg string, skip, level int) error |
||||
Destroy() |
||||
Flush() |
||||
} |
||||
|
||||
type loggerType func() LoggerInterface |
||||
|
||||
var adapters = make(map[string]loggerType) |
||||
|
||||
// Register registers given logger provider to adapters.
|
||||
func Register(name string, log loggerType) { |
||||
if log == nil { |
||||
panic("log: register provider is nil") |
||||
} |
||||
if _, dup := adapters[name]; dup { |
||||
panic("log: register called twice for provider \"" + name + "\"") |
||||
} |
||||
adapters[name] = log |
||||
} |
||||
|
||||
type logMsg struct { |
||||
skip, level int |
||||
msg string |
||||
} |
||||
|
||||
// Logger is default logger in beego application.
|
||||
// it can contain several providers and log message into all providers.
|
||||
type Logger struct { |
||||
adapter string |
||||
lock sync.Mutex |
||||
level int |
||||
msg chan *logMsg |
||||
outputs map[string]LoggerInterface |
||||
quit chan bool |
||||
} |
||||
|
||||
// newLogger initializes and returns a new logger.
|
||||
func newLogger(buffer int64) *Logger { |
||||
l := &Logger{ |
||||
msg: make(chan *logMsg, buffer), |
||||
outputs: make(map[string]LoggerInterface), |
||||
quit: make(chan bool), |
||||
} |
||||
go l.StartLogger() |
||||
return l |
||||
} |
||||
|
||||
// SetLogger sets new logger instance with given logger adapter and config.
|
||||
func (l *Logger) SetLogger(adapter string, config string) error { |
||||
l.lock.Lock() |
||||
defer l.lock.Unlock() |
||||
if log, ok := adapters[adapter]; ok { |
||||
lg := log() |
||||
if err := lg.Init(config); err != nil { |
||||
return err |
||||
} |
||||
l.outputs[adapter] = lg |
||||
l.adapter = adapter |
||||
} else { |
||||
panic("log: unknown adapter \"" + adapter + "\" (forgotten register?)") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// DelLogger removes a logger adapter instance.
|
||||
func (l *Logger) DelLogger(adapter string) error { |
||||
l.lock.Lock() |
||||
defer l.lock.Unlock() |
||||
if lg, ok := l.outputs[adapter]; ok { |
||||
lg.Destroy() |
||||
delete(l.outputs, adapter) |
||||
} else { |
||||
panic("log: unknown adapter \"" + adapter + "\" (forgotten register?)") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (l *Logger) writerMsg(skip, level int, msg string) error { |
||||
if l.level > level { |
||||
return nil |
||||
} |
||||
lm := &logMsg{ |
||||
skip: skip, |
||||
level: level, |
||||
} |
||||
|
||||
// Only error information needs locate position for debugging.
|
||||
if lm.level >= ERROR { |
||||
pc, file, line, ok := runtime.Caller(skip) |
||||
if ok { |
||||
// Get caller function name.
|
||||
fn := runtime.FuncForPC(pc) |
||||
var fnName string |
||||
if fn == nil { |
||||
fnName = "?()" |
||||
} else { |
||||
fnName = strings.TrimLeft(filepath.Ext(fn.Name()), ".") + "()" |
||||
} |
||||
|
||||
fileName := file |
||||
if len(fileName) > 20 { |
||||
fileName = "..." + fileName[len(fileName)-20:] |
||||
} |
||||
lm.msg = fmt.Sprintf("[%s:%d %s] %s", fileName, line, fnName, msg) |
||||
} else { |
||||
lm.msg = msg |
||||
} |
||||
} else { |
||||
lm.msg = msg |
||||
} |
||||
l.msg <- lm |
||||
return nil |
||||
} |
||||
|
||||
// StartLogger starts logger chan reading.
|
||||
func (l *Logger) StartLogger() { |
||||
for { |
||||
select { |
||||
case bm := <-l.msg: |
||||
for _, l := range l.outputs { |
||||
if err := l.WriteMsg(bm.msg, bm.skip, bm.level); err != nil { |
||||
fmt.Println("ERROR, unable to WriteMsg:", err) |
||||
} |
||||
} |
||||
case <-l.quit: |
||||
return |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Flush flushs all chan data.
|
||||
func (l *Logger) Flush() { |
||||
for _, l := range l.outputs { |
||||
l.Flush() |
||||
} |
||||
} |
||||
|
||||
// Close closes logger, flush all chan data and destroy all adapter instances.
|
||||
func (l *Logger) Close() { |
||||
l.quit <- true |
||||
for { |
||||
if len(l.msg) > 0 { |
||||
bm := <-l.msg |
||||
for _, l := range l.outputs { |
||||
if err := l.WriteMsg(bm.msg, bm.skip, bm.level); err != nil { |
||||
fmt.Println("ERROR, unable to WriteMsg:", err) |
||||
} |
||||
} |
||||
} else { |
||||
break |
||||
} |
||||
} |
||||
for _, l := range l.outputs { |
||||
l.Flush() |
||||
l.Destroy() |
||||
} |
||||
} |
||||
|
||||
func (l *Logger) Trace(format string, v ...interface{}) { |
||||
msg := fmt.Sprintf("[T] "+format, v...) |
||||
l.writerMsg(0, TRACE, msg) |
||||
} |
||||
|
||||
func (l *Logger) Debug(format string, v ...interface{}) { |
||||
msg := fmt.Sprintf("[D] "+format, v...) |
||||
l.writerMsg(0, DEBUG, msg) |
||||
} |
||||
|
||||
func (l *Logger) Info(format string, v ...interface{}) { |
||||
msg := fmt.Sprintf("[I] "+format, v...) |
||||
l.writerMsg(0, INFO, msg) |
||||
} |
||||
|
||||
func (l *Logger) Warn(format string, v ...interface{}) { |
||||
msg := fmt.Sprintf("[W] "+format, v...) |
||||
l.writerMsg(0, WARN, msg) |
||||
} |
||||
|
||||
func (l *Logger) Error(skip int, format string, v ...interface{}) { |
||||
msg := fmt.Sprintf("[E] "+format, v...) |
||||
l.writerMsg(skip, ERROR, msg) |
||||
} |
||||
|
||||
func (l *Logger) Critical(skip int, format string, v ...interface{}) { |
||||
msg := fmt.Sprintf("[C] "+format, v...) |
||||
l.writerMsg(skip, CRITICAL, msg) |
||||
} |
||||
|
||||
func (l *Logger) Fatal(skip int, format string, v ...interface{}) { |
||||
msg := fmt.Sprintf("[F] "+format, v...) |
||||
l.writerMsg(skip, FATAL, msg) |
||||
l.Close() |
||||
os.Exit(1) |
||||
} |
@ -1,87 +0,0 @@
|
||||
// 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) |
||||
} |
@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="UTF-8"> |
||||
<title>JavaScript license information</title> |
||||
</head> |
||||
<body> |
||||
<table id="jslicense-labels1"> |
||||
<tr> |
||||
<td><a href="/js/libs/jquery.are-you-sure.js">jquery.are-you-sure.js</a></td> |
||||
<td> |
||||
<a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a> |
||||
<br> |
||||
<a href="http://www.gnu.org/licenses/gpl-2.0.html">GNU-GPL-2.0-or-later</a> |
||||
</td> |
||||
<td><a href="/js/libs/jquery.are-you-sure.js">jquery.are-you-sure.js</a></td> |
||||
</tr> |
||||
<tr> |
||||
<td><a href="/js/semantic-2.2.7.min.js">semantic-2.2.1.min.js</a></td> |
||||
<td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td> |
||||
<td><a href="https://github.com/Semantic-Org/Semantic-UI/archive/2.2.1.tar.gz">semantic-UI-2.2.1.tar.gz</a></td> |
||||
</tr> |
||||
<tr> |
||||
<td><a href="/js/gogs.js">gogs.js</a></td> |
||||
<td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td> |
||||
<td><a href="/js/gogs.js">gogs.js</a></td> |
||||
</tr> |
||||
<tr> |
||||
<td><a href="/js/libs/clipboard-1.5.9.min.js">clipboard-1.5.9.min.js</a></td> |
||||
<td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td> |
||||
<td><a href="https://github.com/zenorocha/clipboard.js/archive/v1.5.9.tar.gz">clipboard-1.5.9.tar.gz</a></td> |
||||
</tr> |
||||
<tr> |
||||
<td><a href="/js/libs/emojify-1.1.0.min.js">emojify-1.1.0.min.js</a></td> |
||||
<td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td> |
||||
<td><a href="https://github.com/Ranks/emojify.js/archive/1.1.0.tar.gz">emojify-1.1.0.tar.gz</a></td> |
||||
</tr> |
||||
<tr> |
||||
<td><a href="/plugins/dropzone-4.2.0/dropzone.js">dropzone.js</a></td> |
||||
<td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td> |
||||
<td><a href="/plugins/dropzone-4.2.0/dropzone.js">dropzone.js</a></td> |
||||
</tr> |
||||
<tr> |
||||
<td><a href="/plugins/highlight-9.6.0/highlight.pack.js">highlight.pack.js</a></td> |
||||
<td><a href="https://github.com/isagalaev/highlight.js/blob/master/LICENSE">BSD 3 Clause</a></td> |
||||
<td><a href="https://github.com/isagalaev/highlight.js/archive/9.6.0.tar.gz">highlight.js-9.6.0.tar.gz</a></td> |
||||
</tr> |
||||
<tr> |
||||
<td><a href="/plugins/jquery.datetimepicker-2.4.5/jquery.datetimepicker.js">jquery.datetimepicker.js</a></td> |
||||
<td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td> |
||||
<td><a href="/plugins/jquery.datetimepicker-2.4.5/jquery.datetimepicker.js">jquery.datetimepicker.js</a></td> |
||||
</tr> |
||||
<tr> |
||||
<td><a href="/plugins/jquery.minicolors-2.2.3/jquery.minicolors.min.js">jquery.minicolors.min.js</a></td> |
||||
<td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td> |
||||
<td><a href="https://github.com/claviska/jquery-minicolors/archive/2.2.3.tar.gz">jquery.minicolors-2.2.3.tar.gz</a></td> |
||||
</tr> |
||||
<tr> |
||||
<td><a href="/plugins/simplemde-1.10.1/simplemde.min.js">simplemde.min.js</a></td> |
||||
<td><a href="http://www.freebsd.org/copyright/freebsd-license.html">Expat</a></td> |
||||
<td><a href="https://github.com/NextStepWebs/simplemde-markdown-editor/archive/1.10.1.tar.gz">simplemde-markdown-editor-1.10.1.tar.gz</a></td> |
||||
</tr> |
||||
<tr> |
||||
<td><a href="/plugins/marked-0.3.6/marked.min.js">marked.min.js</a></td> |
||||
<td><a href="https://github.com/chjj/marked/blob/master/LICENSE">Expat</a></td> |
||||
<td><a href="https://github.com/chjj/marked/archive/v0.3.6.tar.gz">marked-0.3.6.tar.gz</a></td> |
||||
</tr> |
||||
<tr> |
||||
<td><a href="/plugins/notebookjs-0.2.6/notebook.min.js">notebook.min.js</a></td> |
||||
<td><a href="https://github.com/jsvine/notebookjs/blob/master/LICENSE.txt">Expat</a></td> |
||||
<td><a href="https://github.com/jsvine/notebookjs/archive/v0.2.6.tar.gz">notebookjs-0.2.6.tar.gz</a></td> |
||||
</tr> |
||||
</table> |
||||
</body> |
||||
</html> |
||||
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 502 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 164 KiB |