`+
+ ``, c.FieldIdName, value, c.URLPrefix, value, c.URLPrefix, value))
+}
+
+// create a new captcha id
+func (c *Captcha) CreateCaptcha() (string, error) {
+ // generate captcha id
+ id := string(base.RandomCreateBytes(15))
+
+ // get the captcha chars
+ chars := c.genRandChars()
+
+ // save to store
+ if err := c.store.Put(c.key(id), chars, c.Expiration); err != nil {
+ return "", err
+ }
+
+ return id, nil
+}
+
+// verify from a request
+func (c *Captcha) VerifyReq(req *http.Request) bool {
+ req.ParseForm()
+ return c.Verify(req.Form.Get(c.FieldIdName), req.Form.Get(c.FieldCaptchaName))
+}
+
+// direct verify id and challenge string
+func (c *Captcha) Verify(id string, challenge string) (success bool) {
+ if len(challenge) == 0 || len(id) == 0 {
+ return
+ }
+
+ var chars []byte
+
+ key := c.key(id)
+
+ if v, ok := c.store.Get(key).([]byte); ok && len(v) == len(challenge) {
+ chars = v
+ } else {
+ return
+ }
+
+ defer func() {
+ // finally remove it
+ c.store.Delete(key)
+ }()
+
+ // verify challenge
+ for i, c := range chars {
+ if c != challenge[i]-48 {
+ return
+ }
+ }
+
+ return true
+}
+
+// create a new captcha.Captcha
+func NewCaptcha(urlPrefix string, store cache.Cache) *Captcha {
+ cpt := &Captcha{}
+ cpt.store = store
+ cpt.FieldIdName = fieldIdName
+ cpt.FieldCaptchaName = fieldCaptchaName
+ cpt.ChallengeNums = challengeNums
+ cpt.Expiration = expiration
+ cpt.CachePrefix = cachePrefix
+ cpt.StdWidth = stdWidth
+ cpt.StdHeight = stdHeight
+
+ if len(urlPrefix) == 0 {
+ urlPrefix = defaultURLPrefix
+ }
+
+ if urlPrefix[len(urlPrefix)-1] != '/' {
+ urlPrefix += "/"
+ }
+
+ cpt.URLPrefix = urlPrefix
+
+ base.TemplateFuncs["CreateCaptcha"] = cpt.CreateCaptchaHtml
+ return cpt
+}
diff --git a/modules/captcha/image.go b/modules/captcha/image.go
new file mode 100644
index 0000000000..c9972ba2c3
--- /dev/null
+++ b/modules/captcha/image.go
@@ -0,0 +1,487 @@
+// 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 captcha
+
+import (
+ "bytes"
+ "image"
+ "image/color"
+ "image/png"
+ "io"
+ "math"
+)
+
+const (
+ fontWidth = 11
+ fontHeight = 18
+ blackChar = 1
+
+ // Standard width and height of a captcha image.
+ stdWidth = 240
+ stdHeight = 80
+ // Maximum absolute skew factor of a single digit.
+ maxSkew = 0.7
+ // Number of background circles.
+ circleCount = 20
+)
+
+var font = [][]byte{
+ { // 0
+ 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
+ 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
+ 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
+ 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
+ },
+ { // 1
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ },
+ { // 2
+ 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ },
+ { // 3
+ 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
+ 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
+ 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+ },
+ { // 4
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
+ 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
+ 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0,
+ 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
+ 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ },
+ { // 5
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+ },
+ { // 6
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0,
+ 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0,
+ 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0,
+ 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0,
+ 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
+ 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
+ },
+ { // 7
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ },
+ { // 8
+ 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+ 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
+ 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
+ 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0,
+ 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
+ 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
+ },
+ { // 9
+ 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
+ 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
+ 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
+ 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
+ 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,
+ 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
+ 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
+ 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+ },
+}
+
+type Image struct {
+ *image.Paletted
+ numWidth int
+ numHeight int
+ dotSize int
+}
+
+var prng = &siprng{}
+
+// randIntn returns a pseudorandom non-negative int in range [0, n).
+func randIntn(n int) int {
+ return prng.Intn(n)
+}
+
+// randInt returns a pseudorandom int in range [from, to].
+func randInt(from, to int) int {
+ return prng.Intn(to+1-from) + from
+}
+
+// randFloat returns a pseudorandom float64 in range [from, to].
+func randFloat(from, to float64) float64 {
+ return (to-from)*prng.Float64() + from
+}
+
+func randomPalette() color.Palette {
+ p := make([]color.Color, circleCount+1)
+ // Transparent color.
+ p[0] = color.RGBA{0xFF, 0xFF, 0xFF, 0x00}
+ // Primary color.
+ prim := color.RGBA{
+ uint8(randIntn(129)),
+ uint8(randIntn(129)),
+ uint8(randIntn(129)),
+ 0xFF,
+ }
+ p[1] = prim
+ // Circle colors.
+ for i := 2; i <= circleCount; i++ {
+ p[i] = randomBrightness(prim, 255)
+ }
+ return p
+}
+
+// NewImage returns a new captcha image of the given width and height with the
+// given digits, where each digit must be in range 0-9.
+func NewImage(digits []byte, width, height int) *Image {
+ m := new(Image)
+ m.Paletted = image.NewPaletted(image.Rect(0, 0, width, height), randomPalette())
+ m.calculateSizes(width, height, len(digits))
+ // Randomly position captcha inside the image.
+ maxx := width - (m.numWidth+m.dotSize)*len(digits) - m.dotSize
+ maxy := height - m.numHeight - m.dotSize*2
+ var border int
+ if width > height {
+ border = height / 5
+ } else {
+ border = width / 5
+ }
+ x := randInt(border, maxx-border)
+ y := randInt(border, maxy-border)
+ // Draw digits.
+ for _, n := range digits {
+ m.drawDigit(font[n], x, y)
+ x += m.numWidth + m.dotSize
+ }
+ // Draw strike-through line.
+ m.strikeThrough()
+ // Apply wave distortion.
+ m.distort(randFloat(5, 10), randFloat(100, 200))
+ // Fill image with random circles.
+ m.fillWithCircles(circleCount, m.dotSize)
+ return m
+}
+
+// encodedPNG encodes an image to PNG and returns
+// the result as a byte slice.
+func (m *Image) encodedPNG() []byte {
+ var buf bytes.Buffer
+ if err := png.Encode(&buf, m.Paletted); err != nil {
+ panic(err.Error())
+ }
+ return buf.Bytes()
+}
+
+// WriteTo writes captcha image in PNG format into the given writer.
+func (m *Image) WriteTo(w io.Writer) (int64, error) {
+ n, err := w.Write(m.encodedPNG())
+ return int64(n), err
+}
+
+func (m *Image) calculateSizes(width, height, ncount int) {
+ // Goal: fit all digits inside the image.
+ var border int
+ if width > height {
+ border = height / 4
+ } else {
+ border = width / 4
+ }
+ // Convert everything to floats for calculations.
+ w := float64(width - border*2)
+ h := float64(height - border*2)
+ // fw takes into account 1-dot spacing between digits.
+ fw := float64(fontWidth + 1)
+ fh := float64(fontHeight)
+ nc := float64(ncount)
+ // Calculate the width of a single digit taking into account only the
+ // width of the image.
+ nw := w / nc
+ // Calculate the height of a digit from this width.
+ nh := nw * fh / fw
+ // Digit too high?
+ if nh > h {
+ // Fit digits based on height.
+ nh = h
+ nw = fw / fh * nh
+ }
+ // Calculate dot size.
+ m.dotSize = int(nh / fh)
+ // Save everything, making the actual width smaller by 1 dot to account
+ // for spacing between digits.
+ m.numWidth = int(nw) - m.dotSize
+ m.numHeight = int(nh)
+}
+
+func (m *Image) drawHorizLine(fromX, toX, y int, colorIdx uint8) {
+ for x := fromX; x <= toX; x++ {
+ m.SetColorIndex(x, y, colorIdx)
+ }
+}
+
+func (m *Image) drawCircle(x, y, radius int, colorIdx uint8) {
+ f := 1 - radius
+ dfx := 1
+ dfy := -2 * radius
+ xo := 0
+ yo := radius
+
+ m.SetColorIndex(x, y+radius, colorIdx)
+ m.SetColorIndex(x, y-radius, colorIdx)
+ m.drawHorizLine(x-radius, x+radius, y, colorIdx)
+
+ for xo < yo {
+ if f >= 0 {
+ yo--
+ dfy += 2
+ f += dfy
+ }
+ xo++
+ dfx += 2
+ f += dfx
+ m.drawHorizLine(x-xo, x+xo, y+yo, colorIdx)
+ m.drawHorizLine(x-xo, x+xo, y-yo, colorIdx)
+ m.drawHorizLine(x-yo, x+yo, y+xo, colorIdx)
+ m.drawHorizLine(x-yo, x+yo, y-xo, colorIdx)
+ }
+}
+
+func (m *Image) fillWithCircles(n, maxradius int) {
+ maxx := m.Bounds().Max.X
+ maxy := m.Bounds().Max.Y
+ for i := 0; i < n; i++ {
+ colorIdx := uint8(randInt(1, circleCount-1))
+ r := randInt(1, maxradius)
+ m.drawCircle(randInt(r, maxx-r), randInt(r, maxy-r), r, colorIdx)
+ }
+}
+
+func (m *Image) strikeThrough() {
+ maxx := m.Bounds().Max.X
+ maxy := m.Bounds().Max.Y
+ y := randInt(maxy/3, maxy-maxy/3)
+ amplitude := randFloat(5, 20)
+ period := randFloat(80, 180)
+ dx := 2.0 * math.Pi / period
+ for x := 0; x < maxx; x++ {
+ xo := amplitude * math.Cos(float64(y)*dx)
+ yo := amplitude * math.Sin(float64(x)*dx)
+ for yn := 0; yn < m.dotSize; yn++ {
+ r := randInt(0, m.dotSize)
+ m.drawCircle(x+int(xo), y+int(yo)+(yn*m.dotSize), r/2, 1)
+ }
+ }
+}
+
+func (m *Image) drawDigit(digit []byte, x, y int) {
+ skf := randFloat(-maxSkew, maxSkew)
+ xs := float64(x)
+ r := m.dotSize / 2
+ y += randInt(-r, r)
+ for yo := 0; yo < fontHeight; yo++ {
+ for xo := 0; xo < fontWidth; xo++ {
+ if digit[yo*fontWidth+xo] != blackChar {
+ continue
+ }
+ m.drawCircle(x+xo*m.dotSize, y+yo*m.dotSize, r, 1)
+ }
+ xs += skf
+ x = int(xs)
+ }
+}
+
+func (m *Image) distort(amplude float64, period float64) {
+ w := m.Bounds().Max.X
+ h := m.Bounds().Max.Y
+
+ oldm := m.Paletted
+ newm := image.NewPaletted(image.Rect(0, 0, w, h), oldm.Palette)
+
+ dx := 2.0 * math.Pi / period
+ for x := 0; x < w; x++ {
+ for y := 0; y < h; y++ {
+ xo := amplude * math.Sin(float64(y)*dx)
+ yo := amplude * math.Cos(float64(x)*dx)
+ newm.SetColorIndex(x, y, oldm.ColorIndexAt(x+int(xo), y+int(yo)))
+ }
+ }
+ m.Paletted = newm
+}
+
+func randomBrightness(c color.RGBA, max uint8) color.RGBA {
+ minc := min3(c.R, c.G, c.B)
+ maxc := max3(c.R, c.G, c.B)
+ if maxc > max {
+ return c
+ }
+ n := randIntn(int(max-maxc)) - int(minc)
+ return color.RGBA{
+ uint8(int(c.R) + n),
+ uint8(int(c.G) + n),
+ uint8(int(c.B) + n),
+ uint8(c.A),
+ }
+}
+
+func min3(x, y, z uint8) (m uint8) {
+ m = x
+ if y < m {
+ m = y
+ }
+ if z < m {
+ m = z
+ }
+ return
+}
+
+func max3(x, y, z uint8) (m uint8) {
+ m = x
+ if y > m {
+ m = y
+ }
+ if z > m {
+ m = z
+ }
+ return
+}
diff --git a/modules/captcha/image_test.go b/modules/captcha/image_test.go
new file mode 100644
index 0000000000..30b3ea4ae3
--- /dev/null
+++ b/modules/captcha/image_test.go
@@ -0,0 +1,42 @@
+// 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 captcha
+
+import (
+ "testing"
+
+ "github.com/astaxie/beego/utils"
+)
+
+type byteCounter struct {
+ n int64
+}
+
+func (bc *byteCounter) Write(b []byte) (int, error) {
+ bc.n += int64(len(b))
+ return len(b), nil
+}
+
+func BenchmarkNewImage(b *testing.B) {
+ b.StopTimer()
+ d := utils.RandomCreateBytes(challengeNums, defaultChars...)
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ NewImage(d, stdWidth, stdHeight)
+ }
+}
+
+func BenchmarkImageWriteTo(b *testing.B) {
+ b.StopTimer()
+ d := utils.RandomCreateBytes(challengeNums, defaultChars...)
+ b.StartTimer()
+ counter := &byteCounter{}
+ for i := 0; i < b.N; i++ {
+ img := NewImage(d, stdWidth, stdHeight)
+ img.WriteTo(counter)
+ b.SetBytes(counter.n)
+ counter.n = 0
+ }
+}
diff --git a/modules/captcha/siprng.go b/modules/captcha/siprng.go
new file mode 100644
index 0000000000..c059b9f7bf
--- /dev/null
+++ b/modules/captcha/siprng.go
@@ -0,0 +1,267 @@
+// 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 captcha
+
+import (
+ "crypto/rand"
+ "encoding/binary"
+ "io"
+ "sync"
+)
+
+// siprng is PRNG based on SipHash-2-4.
+type siprng struct {
+ mu sync.Mutex
+ k0, k1, ctr uint64
+}
+
+// siphash implements SipHash-2-4, accepting a uint64 as a message.
+func siphash(k0, k1, m uint64) uint64 {
+ // Initialization.
+ v0 := k0 ^ 0x736f6d6570736575
+ v1 := k1 ^ 0x646f72616e646f6d
+ v2 := k0 ^ 0x6c7967656e657261
+ v3 := k1 ^ 0x7465646279746573
+ t := uint64(8) << 56
+
+ // Compression.
+ v3 ^= m
+
+ // Round 1.
+ v0 += v1
+ v1 = v1<<13 | v1>>(64-13)
+ v1 ^= v0
+ v0 = v0<<32 | v0>>(64-32)
+
+ v2 += v3
+ v3 = v3<<16 | v3>>(64-16)
+ v3 ^= v2
+
+ v0 += v3
+ v3 = v3<<21 | v3>>(64-21)
+ v3 ^= v0
+
+ v2 += v1
+ v1 = v1<<17 | v1>>(64-17)
+ v1 ^= v2
+ v2 = v2<<32 | v2>>(64-32)
+
+ // Round 2.
+ v0 += v1
+ v1 = v1<<13 | v1>>(64-13)
+ v1 ^= v0
+ v0 = v0<<32 | v0>>(64-32)
+
+ v2 += v3
+ v3 = v3<<16 | v3>>(64-16)
+ v3 ^= v2
+
+ v0 += v3
+ v3 = v3<<21 | v3>>(64-21)
+ v3 ^= v0
+
+ v2 += v1
+ v1 = v1<<17 | v1>>(64-17)
+ v1 ^= v2
+ v2 = v2<<32 | v2>>(64-32)
+
+ v0 ^= m
+
+ // Compress last block.
+ v3 ^= t
+
+ // Round 1.
+ v0 += v1
+ v1 = v1<<13 | v1>>(64-13)
+ v1 ^= v0
+ v0 = v0<<32 | v0>>(64-32)
+
+ v2 += v3
+ v3 = v3<<16 | v3>>(64-16)
+ v3 ^= v2
+
+ v0 += v3
+ v3 = v3<<21 | v3>>(64-21)
+ v3 ^= v0
+
+ v2 += v1
+ v1 = v1<<17 | v1>>(64-17)
+ v1 ^= v2
+ v2 = v2<<32 | v2>>(64-32)
+
+ // Round 2.
+ v0 += v1
+ v1 = v1<<13 | v1>>(64-13)
+ v1 ^= v0
+ v0 = v0<<32 | v0>>(64-32)
+
+ v2 += v3
+ v3 = v3<<16 | v3>>(64-16)
+ v3 ^= v2
+
+ v0 += v3
+ v3 = v3<<21 | v3>>(64-21)
+ v3 ^= v0
+
+ v2 += v1
+ v1 = v1<<17 | v1>>(64-17)
+ v1 ^= v2
+ v2 = v2<<32 | v2>>(64-32)
+
+ v0 ^= t
+
+ // Finalization.
+ v2 ^= 0xff
+
+ // Round 1.
+ v0 += v1
+ v1 = v1<<13 | v1>>(64-13)
+ v1 ^= v0
+ v0 = v0<<32 | v0>>(64-32)
+
+ v2 += v3
+ v3 = v3<<16 | v3>>(64-16)
+ v3 ^= v2
+
+ v0 += v3
+ v3 = v3<<21 | v3>>(64-21)
+ v3 ^= v0
+
+ v2 += v1
+ v1 = v1<<17 | v1>>(64-17)
+ v1 ^= v2
+ v2 = v2<<32 | v2>>(64-32)
+
+ // Round 2.
+ v0 += v1
+ v1 = v1<<13 | v1>>(64-13)
+ v1 ^= v0
+ v0 = v0<<32 | v0>>(64-32)
+
+ v2 += v3
+ v3 = v3<<16 | v3>>(64-16)
+ v3 ^= v2
+
+ v0 += v3
+ v3 = v3<<21 | v3>>(64-21)
+ v3 ^= v0
+
+ v2 += v1
+ v1 = v1<<17 | v1>>(64-17)
+ v1 ^= v2
+ v2 = v2<<32 | v2>>(64-32)
+
+ // Round 3.
+ v0 += v1
+ v1 = v1<<13 | v1>>(64-13)
+ v1 ^= v0
+ v0 = v0<<32 | v0>>(64-32)
+
+ v2 += v3
+ v3 = v3<<16 | v3>>(64-16)
+ v3 ^= v2
+
+ v0 += v3
+ v3 = v3<<21 | v3>>(64-21)
+ v3 ^= v0
+
+ v2 += v1
+ v1 = v1<<17 | v1>>(64-17)
+ v1 ^= v2
+ v2 = v2<<32 | v2>>(64-32)
+
+ // Round 4.
+ v0 += v1
+ v1 = v1<<13 | v1>>(64-13)
+ v1 ^= v0
+ v0 = v0<<32 | v0>>(64-32)
+
+ v2 += v3
+ v3 = v3<<16 | v3>>(64-16)
+ v3 ^= v2
+
+ v0 += v3
+ v3 = v3<<21 | v3>>(64-21)
+ v3 ^= v0
+
+ v2 += v1
+ v1 = v1<<17 | v1>>(64-17)
+ v1 ^= v2
+ v2 = v2<<32 | v2>>(64-32)
+
+ return v0 ^ v1 ^ v2 ^ v3
+}
+
+// rekey sets a new PRNG key, which is read from crypto/rand.
+func (p *siprng) rekey() {
+ var k [16]byte
+ if _, err := io.ReadFull(rand.Reader, k[:]); err != nil {
+ panic(err.Error())
+ }
+ p.k0 = binary.LittleEndian.Uint64(k[0:8])
+ p.k1 = binary.LittleEndian.Uint64(k[8:16])
+ p.ctr = 1
+}
+
+// Uint64 returns a new pseudorandom uint64.
+// It rekeys PRNG on the first call and every 64 MB of generated data.
+func (p *siprng) Uint64() uint64 {
+ p.mu.Lock()
+ if p.ctr == 0 || p.ctr > 8*1024*1024 {
+ p.rekey()
+ }
+ v := siphash(p.k0, p.k1, p.ctr)
+ p.ctr++
+ p.mu.Unlock()
+ return v
+}
+
+func (p *siprng) Int63() int64 {
+ return int64(p.Uint64() & 0x7fffffffffffffff)
+}
+
+func (p *siprng) Uint32() uint32 {
+ return uint32(p.Uint64())
+}
+
+func (p *siprng) Int31() int32 {
+ return int32(p.Uint32() & 0x7fffffff)
+}
+
+func (p *siprng) Intn(n int) int {
+ if n <= 0 {
+ panic("invalid argument to Intn")
+ }
+ if n <= 1<<31-1 {
+ return int(p.Int31n(int32(n)))
+ }
+ return int(p.Int63n(int64(n)))
+}
+
+func (p *siprng) Int63n(n int64) int64 {
+ if n <= 0 {
+ panic("invalid argument to Int63n")
+ }
+ max := int64((1 << 63) - 1 - (1<<63)%uint64(n))
+ v := p.Int63()
+ for v > max {
+ v = p.Int63()
+ }
+ return v % n
+}
+
+func (p *siprng) Int31n(n int32) int32 {
+ if n <= 0 {
+ panic("invalid argument to Int31n")
+ }
+ max := int32((1 << 31) - 1 - (1<<31)%uint32(n))
+ v := p.Int31()
+ for v > max {
+ v = p.Int31()
+ }
+ return v % n
+}
+
+func (p *siprng) Float64() float64 { return float64(p.Int63()) / (1 << 63) }
diff --git a/modules/captcha/siprng_test.go b/modules/captcha/siprng_test.go
new file mode 100644
index 0000000000..3b10fe58bf
--- /dev/null
+++ b/modules/captcha/siprng_test.go
@@ -0,0 +1,23 @@
+// 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 captcha
+
+import "testing"
+
+func TestSiphash(t *testing.T) {
+ good := uint64(0xe849e8bb6ffe2567)
+ cur := siphash(0, 0, 0)
+ if cur != good {
+ t.Fatalf("siphash: expected %x, got %x", good, cur)
+ }
+}
+
+func BenchmarkSiprng(b *testing.B) {
+ b.SetBytes(8)
+ p := &siprng{}
+ for i := 0; i < b.N; i++ {
+ p.Uint64()
+ }
+}
diff --git a/modules/git/blob.go b/modules/git/blob.go
new file mode 100644
index 0000000000..3ce462a3c2
--- /dev/null
+++ b/modules/git/blob.go
@@ -0,0 +1,26 @@
+// 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 git
+
+import (
+ "bytes"
+ "errors"
+ "io"
+
+ "github.com/Unknwon/com"
+)
+
+type Blob struct {
+ repo *Repository
+ *TreeEntry
+}
+
+func (b *Blob) Data() (io.Reader, error) {
+ stdout, stderr, err := com.ExecCmdDirBytes(b.repo.Path, "git", "show", b.Id.String())
+ if err != nil {
+ return nil, errors.New(string(stderr))
+ }
+ return bytes.NewBuffer(stdout), nil
+}
diff --git a/modules/git/commit.go b/modules/git/commit.go
new file mode 100644
index 0000000000..f61f2927b9
--- /dev/null
+++ b/modules/git/commit.go
@@ -0,0 +1,78 @@
+// 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 git
+
+import (
+ "container/list"
+ "strings"
+)
+
+// Commit represents a git commit.
+type Commit struct {
+ Tree
+ Id sha1 // The id of this commit object
+ Author *Signature
+ Committer *Signature
+ CommitMessage string
+
+ parents []sha1 // sha1 strings
+}
+
+// Return the commit message. Same as retrieving CommitMessage directly.
+func (c *Commit) Message() string {
+ return c.CommitMessage
+}
+
+func (c *Commit) Summary() string {
+ return strings.Split(c.CommitMessage, "\n")[0]
+}
+
+// Return oid of the parent number n (0-based index). Return nil if no such parent exists.
+func (c *Commit) ParentId(n int) (id sha1, err error) {
+ if n >= len(c.parents) {
+ err = IdNotExist
+ return
+ }
+ return c.parents[n], nil
+}
+
+// Return parent number n (0-based index)
+func (c *Commit) Parent(n int) (*Commit, error) {
+ id, err := c.ParentId(n)
+ if err != nil {
+ return nil, err
+ }
+ parent, err := c.repo.getCommit(id)
+ if err != nil {
+ return nil, err
+ }
+ return parent, nil
+}
+
+// Return the number of parents of the commit. 0 if this is the
+// root commit, otherwise 1,2,...
+func (c *Commit) ParentCount() int {
+ return len(c.parents)
+}
+
+func (c *Commit) CommitsBefore() (*list.List, error) {
+ return c.repo.getCommitsBefore(c.Id)
+}
+
+func (c *Commit) CommitsBeforeUntil(commitId string) (*list.List, error) {
+ ec, err := c.repo.GetCommit(commitId)
+ if err != nil {
+ return nil, err
+ }
+ return c.repo.CommitsBetween(c, ec)
+}
+
+func (c *Commit) CommitsCount() (int, error) {
+ return c.repo.commitsCount(c.Id)
+}
+
+func (c *Commit) GetCommitOfRelPath(relPath string) (*Commit, error) {
+ return c.repo.getCommitOfRelPath(c.Id, relPath)
+}
diff --git a/modules/git/commit_archive.go b/modules/git/commit_archive.go
new file mode 100644
index 0000000000..23b4b058dd
--- /dev/null
+++ b/modules/git/commit_archive.go
@@ -0,0 +1,36 @@
+// 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 git
+
+import (
+ "fmt"
+
+ "github.com/Unknwon/com"
+)
+
+type ArchiveType int
+
+const (
+ ZIP ArchiveType = iota + 1
+ TARGZ
+)
+
+func (c *Commit) CreateArchive(path string, archiveType ArchiveType) error {
+ var format string
+ switch archiveType {
+ case ZIP:
+ format = "zip"
+ case TARGZ:
+ format = "tar.gz"
+ default:
+ return fmt.Errorf("unknown format: %v", archiveType)
+ }
+
+ _, stderr, err := com.ExecCmdDir(c.repo.Path, "git", "archive", "--format="+format, "-o", path, c.Id.String())
+ if err != nil {
+ return fmt.Errorf("%s", stderr)
+ }
+ return nil
+}
diff --git a/modules/git/repo.go b/modules/git/repo.go
new file mode 100644
index 0000000000..14ac39a3ed
--- /dev/null
+++ b/modules/git/repo.go
@@ -0,0 +1,27 @@
+// 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 git
+
+import (
+ "path/filepath"
+)
+
+// Repository represents a Git repository.
+type Repository struct {
+ Path string
+
+ commitCache map[sha1]*Commit
+ tagCache map[sha1]*Tag
+}
+
+// OpenRepository opens the repository at the given path.
+func OpenRepository(repoPath string) (*Repository, error) {
+ repoPath, err := filepath.Abs(repoPath)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Repository{Path: repoPath}, nil
+}
diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go
new file mode 100644
index 0000000000..726019c1b1
--- /dev/null
+++ b/modules/git/repo_branch.go
@@ -0,0 +1,38 @@
+// 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 git
+
+import (
+ "errors"
+ "strings"
+
+ "github.com/Unknwon/com"
+)
+
+func IsBranchExist(repoPath, branchName string) bool {
+ _, _, err := com.ExecCmdDir(repoPath, "git", "show-ref", "--verify", "refs/heads/"+branchName)
+ return err == nil
+}
+
+func (repo *Repository) IsBranchExist(branchName string) bool {
+ return IsBranchExist(repo.Path, branchName)
+}
+
+func (repo *Repository) GetBranches() ([]string, error) {
+ stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "show-ref", "--heads")
+ if err != nil {
+ return nil, errors.New(stderr)
+ }
+ infos := strings.Split(stdout, "\n")
+ branches := make([]string, len(infos)-1)
+ for i, info := range infos[:len(infos)-1] {
+ parts := strings.Split(info, " ")
+ if len(parts) != 2 {
+ continue // NOTE: I should believe git will not give me wrong string.
+ }
+ branches[i] = strings.TrimPrefix(parts[1], "refs/heads/")
+ }
+ return branches, nil
+}
diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go
new file mode 100644
index 0000000000..b1ea5a29a5
--- /dev/null
+++ b/modules/git/repo_commit.go
@@ -0,0 +1,234 @@
+// 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 git
+
+import (
+ "bytes"
+ "container/list"
+ "errors"
+ "strings"
+ "sync"
+
+ "github.com/Unknwon/com"
+)
+
+func (repo *Repository) getCommitIdOfRef(refpath string) (string, error) {
+ stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "show-ref", "--verify", refpath)
+ if err != nil {
+ return "", errors.New(stderr)
+ }
+ return strings.Split(stdout, " ")[0], nil
+}
+
+func (repo *Repository) GetCommitIdOfBranch(branchName string) (string, error) {
+ return repo.getCommitIdOfRef("refs/heads/" + branchName)
+}
+
+// get branch's last commit or a special commit by id string
+func (repo *Repository) GetCommitOfBranch(branchName string) (*Commit, error) {
+ commitId, err := repo.GetCommitIdOfBranch(branchName)
+ if err != nil {
+ return nil, err
+ }
+
+ return repo.GetCommit(commitId)
+}
+
+// Parse commit information from the (uncompressed) raw
+// data from the commit object.
+// \n\n separate headers from message
+func parseCommitData(data []byte) (*Commit, error) {
+ commit := new(Commit)
+ commit.parents = make([]sha1, 0, 1)
+ // we now have the contents of the commit object. Let's investigate...
+ nextline := 0
+l:
+ for {
+ eol := bytes.IndexByte(data[nextline:], '\n')
+ switch {
+ case eol > 0:
+ line := data[nextline : nextline+eol]
+ spacepos := bytes.IndexByte(line, ' ')
+ reftype := line[:spacepos]
+ switch string(reftype) {
+ case "tree":
+ id, err := NewIdFromString(string(line[spacepos+1:]))
+ if err != nil {
+ return nil, err
+ }
+ commit.Tree.Id = id
+ case "parent":
+ // A commit can have one or more parents
+ oid, err := NewIdFromString(string(line[spacepos+1:]))
+ if err != nil {
+ return nil, err
+ }
+ commit.parents = append(commit.parents, oid)
+ case "author":
+ sig, err := newSignatureFromCommitline(line[spacepos+1:])
+ if err != nil {
+ return nil, err
+ }
+ commit.Author = sig
+ case "committer":
+ sig, err := newSignatureFromCommitline(line[spacepos+1:])
+ if err != nil {
+ return nil, err
+ }
+ commit.Committer = sig
+ }
+ nextline += eol + 1
+ case eol == 0:
+ commit.CommitMessage = string(data[nextline+1:])
+ break l
+ default:
+ break l
+ }
+ }
+ return commit, nil
+}
+
+func (repo *Repository) getCommit(id sha1) (*Commit, error) {
+ if repo.commitCache != nil {
+ if c, ok := repo.commitCache[id]; ok {
+ return c, nil
+ }
+ } else {
+ repo.commitCache = make(map[sha1]*Commit, 10)
+ }
+
+ data, bytErr, err := com.ExecCmdDirBytes(repo.Path, "git", "cat-file", "-p", id.String())
+ if err != nil {
+ return nil, errors.New(string(bytErr))
+ }
+
+ commit, err := parseCommitData(data)
+ if err != nil {
+ return nil, err
+ }
+ commit.repo = repo
+ commit.Id = id
+
+ repo.commitCache[id] = commit
+ return commit, nil
+}
+
+// Find the commit object in the repository.
+func (repo *Repository) GetCommit(commitId string) (*Commit, error) {
+ id, err := NewIdFromString(commitId)
+ if err != nil {
+ return nil, err
+ }
+
+ return repo.getCommit(id)
+}
+
+func (repo *Repository) commitsCount(id sha1) (int, error) {
+ stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "rev-list", "--count", id.String())
+ if err != nil {
+ return 0, errors.New(stderr)
+ }
+ return com.StrTo(strings.TrimSpace(stdout)).Int()
+}
+
+// used only for single tree, (]
+func (repo *Repository) CommitsBetween(last *Commit, before *Commit) (*list.List, error) {
+ l := list.New()
+ if last == nil || last.ParentCount() == 0 {
+ return l, nil
+ }
+
+ var err error
+ cur := last
+ for {
+ if cur.Id.Equal(before.Id) {
+ break
+ }
+ l.PushBack(cur)
+ if cur.ParentCount() == 0 {
+ break
+ }
+ cur, err = cur.Parent(0)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return l, nil
+}
+
+func (repo *Repository) commitsBefore(lock *sync.Mutex, l *list.List, parent *list.Element, id sha1, limit int) error {
+ commit, err := repo.getCommit(id)
+ if err != nil {
+ return err
+ }
+
+ var e *list.Element
+ if parent == nil {
+ e = l.PushBack(commit)
+ } else {
+ var in = parent
+ for {
+ if in == nil {
+ break
+ } else if in.Value.(*Commit).Id.Equal(commit.Id) {
+ return nil
+ } else {
+ if in.Next() == nil {
+ break
+ }
+ if in.Value.(*Commit).Committer.When.Equal(commit.Committer.When) {
+ break
+ }
+
+ if in.Value.(*Commit).Committer.When.After(commit.Committer.When) &&
+ in.Next().Value.(*Commit).Committer.When.Before(commit.Committer.When) {
+ break
+ }
+ }
+ in = in.Next()
+ }
+
+ e = l.InsertAfter(commit, in)
+ }
+
+ var pr = parent
+ if commit.ParentCount() > 1 {
+ pr = e
+ }
+
+ for i := 0; i < commit.ParentCount(); i++ {
+ id, err := commit.ParentId(i)
+ if err != nil {
+ return err
+ }
+ err = repo.commitsBefore(lock, l, pr, id, 0)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (repo *Repository) getCommitsBefore(id sha1) (*list.List, error) {
+ l := list.New()
+ lock := new(sync.Mutex)
+ err := repo.commitsBefore(lock, l, nil, id, 0)
+ return l, err
+}
+
+func (repo *Repository) getCommitOfRelPath(id sha1, relPath string) (*Commit, error) {
+ stdout, _, err := com.ExecCmdDir(repo.Path, "git", "log", "-1", prettyLogFormat, id.String(), "--", relPath)
+ if err != nil {
+ return nil, err
+ }
+
+ id, err = NewIdFromString(string(stdout))
+ if err != nil {
+ return nil, err
+ }
+
+ return repo.getCommit(id)
+}
diff --git a/modules/git/repo_object.go b/modules/git/repo_object.go
new file mode 100644
index 0000000000..da79f2c731
--- /dev/null
+++ b/modules/git/repo_object.go
@@ -0,0 +1,14 @@
+// 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 git
+
+type ObjectType string
+
+const (
+ COMMIT ObjectType = "commit"
+ TREE ObjectType = "tree"
+ BLOB ObjectType = "blob"
+ TAG ObjectType = "tag"
+)
diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go
new file mode 100644
index 0000000000..cefbe783ab
--- /dev/null
+++ b/modules/git/repo_tag.go
@@ -0,0 +1,96 @@
+// 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 git
+
+import (
+ "errors"
+ "strings"
+
+ "github.com/Unknwon/com"
+)
+
+func IsTagExist(repoPath, tagName string) bool {
+ _, _, err := com.ExecCmdDir(repoPath, "git", "show-ref", "--verify", "refs/tags/"+tagName)
+ return err == nil
+}
+
+func (repo *Repository) IsTagExist(tagName string) bool {
+ return IsTagExist(repo.Path, tagName)
+}
+
+// GetTags returns all tags of given repository.
+func (repo *Repository) GetTags() ([]string, error) {
+ stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "tag", "-l")
+ if err != nil {
+ return nil, errors.New(stderr)
+ }
+ tags := strings.Split(stdout, "\n")
+ return tags[:len(tags)-1], nil
+}
+
+func (repo *Repository) getTag(id sha1) (*Tag, error) {
+ if repo.tagCache != nil {
+ if t, ok := repo.tagCache[id]; ok {
+ return t, nil
+ }
+ } else {
+ repo.tagCache = make(map[sha1]*Tag, 10)
+ }
+
+ // Get tag type.
+ tp, stderr, err := com.ExecCmdDir(repo.Path, "git", "cat-file", "-t", id.String())
+ if err != nil {
+ return nil, errors.New(stderr)
+ }
+
+ // Tag is a commit.
+ if ObjectType(tp) == COMMIT {
+ tag := &Tag{
+ Id: id,
+ Object: id,
+ Type: string(COMMIT),
+ repo: repo,
+ }
+ repo.tagCache[id] = tag
+ return tag, nil
+ }
+
+ // Tag with message.
+ data, bytErr, err := com.ExecCmdDirBytes(repo.Path, "git", "cat-file", "-p", id.String())
+ if err != nil {
+ return nil, errors.New(string(bytErr))
+ }
+
+ tag, err := parseTagData(data)
+ if err != nil {
+ return nil, err
+ }
+
+ tag.Id = id
+ tag.repo = repo
+
+ repo.tagCache[id] = tag
+ return tag, nil
+}
+
+// GetTag returns a Git tag by given name.
+func (repo *Repository) GetTag(tagName string) (*Tag, error) {
+ stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "show-ref", "--tags", tagName)
+ if err != nil {
+ return nil, errors.New(stderr)
+ }
+
+ id, err := NewIdFromString(strings.Split(stdout, " ")[0])
+ if err != nil {
+ return nil, err
+ }
+
+ tag, err := repo.getTag(id)
+ if err != nil {
+ return nil, err
+ }
+ tag.Name = tagName
+ return tag, nil
+}
diff --git a/modules/git/repo_tree.go b/modules/git/repo_tree.go
new file mode 100644
index 0000000000..da394c3605
--- /dev/null
+++ b/modules/git/repo_tree.go
@@ -0,0 +1,32 @@
+// 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 git
+
+import (
+ "fmt"
+
+ "github.com/Unknwon/com"
+)
+
+// Find the tree object in the repository.
+func (repo *Repository) GetTree(idStr string) (*Tree, error) {
+ id, err := NewIdFromString(idStr)
+ if err != nil {
+ return nil, err
+ }
+ return repo.getTree(id)
+}
+
+func (repo *Repository) getTree(id sha1) (*Tree, error) {
+ treePath := filepathFromSHA1(repo.Path, id.String())
+ if !com.IsFile(treePath) {
+ _, _, err := com.ExecCmdDir(repo.Path, "git", "ls-tree", id.String())
+ if err != nil {
+ return nil, fmt.Errorf("repo.getTree: %v", ErrNotExist)
+ }
+ }
+
+ return NewTree(repo, id), nil
+}
diff --git a/modules/git/sha1.go b/modules/git/sha1.go
new file mode 100644
index 0000000000..5c57e89b6a
--- /dev/null
+++ b/modules/git/sha1.go
@@ -0,0 +1,87 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package git
+
+import (
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "strings"
+)
+
+var (
+ IdNotExist = errors.New("sha1 id not exist")
+)
+
+type sha1 [20]byte
+
+// Return true if s has the same sha1 as caller.
+// Support 40-length-string, []byte, sha1
+func (id sha1) Equal(s2 interface{}) bool {
+ switch v := s2.(type) {
+ case string:
+ if len(v) != 40 {
+ return false
+ }
+ return v == id.String()
+ case []byte:
+ if len(v) != 20 {
+ return false
+ }
+ for i, v := range v {
+ if id[i] != v {
+ return false
+ }
+ }
+ case sha1:
+ for i, v := range v {
+ if id[i] != v {
+ return false
+ }
+ }
+ default:
+ return false
+ }
+ return true
+}
+
+// Return string (hex) representation of the Oid
+func (s sha1) String() string {
+ result := make([]byte, 0, 40)
+ hexvalues := []byte("0123456789abcdef")
+ for i := 0; i < 20; i++ {
+ result = append(result, hexvalues[s[i]>>4])
+ result = append(result, hexvalues[s[i]&0xf])
+ }
+ return string(result)
+}
+
+// Create a new sha1 from a 20 byte slice.
+func NewId(b []byte) (sha1, error) {
+ var id sha1
+ if len(b) != 20 {
+ return id, errors.New("Length must be 20")
+ }
+
+ for i := 0; i < 20; i++ {
+ id[i] = b[i]
+ }
+ return id, nil
+}
+
+// Create a new sha1 from a Sha1 string of length 40.
+func NewIdFromString(s string) (sha1, error) {
+ s = strings.TrimSpace(s)
+ var id sha1
+ if len(s) != 40 {
+ return id, fmt.Errorf("Length must be 40")
+ }
+ b, err := hex.DecodeString(s)
+ if err != nil {
+ return id, err
+ }
+
+ return NewId(b)
+}
diff --git a/modules/git/signature.go b/modules/git/signature.go
new file mode 100644
index 0000000000..20f647d266
--- /dev/null
+++ b/modules/git/signature.go
@@ -0,0 +1,40 @@
+// 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 git
+
+import (
+ "bytes"
+ "strconv"
+ "time"
+)
+
+// Author and Committer information
+type Signature struct {
+ Email string
+ Name string
+ When time.Time
+}
+
+// Helper to get a signature from the commit line, which looks like this:
+// author Patrick Gundlach | t |