diff --git a/modules/middleware/context.go b/modules/middleware/context.go
index 619a13b1ac..1330172fde 100644
--- a/modules/middleware/context.go
+++ b/modules/middleware/context.go
@@ -10,8 +10,10 @@ import (
 	"encoding/base64"
 	"fmt"
 	"html/template"
+	"io"
 	"net/http"
 	"net/url"
+	"path/filepath"
 	"strconv"
 	"strings"
 	"time"
@@ -62,7 +64,7 @@ type Context struct {
 			HTTPS string
 			Git   string
 		}
-		*models.Mirror
+		Mirror *models.Mirror
 	}
 }
 
@@ -243,6 +245,41 @@ func (ctx *Context) CsrfTokenValid() bool {
 	return true
 }
 
+func (ctx *Context) ServeFile(file string, names ...string) {
+	var name string
+	if len(names) > 0 {
+		name = names[0]
+	} else {
+		name = filepath.Base(file)
+	}
+	ctx.Res.Header().Set("Content-Description", "File Transfer")
+	ctx.Res.Header().Set("Content-Type", "application/octet-stream")
+	ctx.Res.Header().Set("Content-Disposition", "attachment; filename="+name)
+	ctx.Res.Header().Set("Content-Transfer-Encoding", "binary")
+	ctx.Res.Header().Set("Expires", "0")
+	ctx.Res.Header().Set("Cache-Control", "must-revalidate")
+	ctx.Res.Header().Set("Pragma", "public")
+	http.ServeFile(ctx.Res, ctx.Req, file)
+}
+
+func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interface{}) {
+	modtime := time.Now()
+	for _, p := range params {
+		switch v := p.(type) {
+		case time.Time:
+			modtime = v
+		}
+	}
+	ctx.Res.Header().Set("Content-Description", "File Transfer")
+	ctx.Res.Header().Set("Content-Type", "application/octet-stream")
+	ctx.Res.Header().Set("Content-Disposition", "attachment; filename="+name)
+	ctx.Res.Header().Set("Content-Transfer-Encoding", "binary")
+	ctx.Res.Header().Set("Expires", "0")
+	ctx.Res.Header().Set("Cache-Control", "must-revalidate")
+	ctx.Res.Header().Set("Pragma", "public")
+	http.ServeContent(ctx.Res, ctx.Req, name, modtime, r)
+}
+
 type Flash struct {
 	url.Values
 	ErrorMsg, SuccessMsg string
diff --git a/routers/repo/download.go b/routers/repo/download.go
new file mode 100644
index 0000000000..017d957155
--- /dev/null
+++ b/routers/repo/download.go
@@ -0,0 +1,68 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package repo
+
+import (
+	"os"
+	"path/filepath"
+
+	"github.com/Unknwon/com"
+	"github.com/go-martini/martini"
+
+	"github.com/gogits/gogs/modules/base"
+	"github.com/gogits/gogs/modules/middleware"
+)
+
+func SingleDownload(ctx *middleware.Context, params martini.Params) {
+	// Get tree path
+	treename := params["_1"]
+
+	blob, err := ctx.Repo.Commit.GetBlobByPath(treename)
+	if err != nil {
+		ctx.Handle(404, "repo.SingleDownload(GetBlobByPath)", err)
+		return
+	}
+
+	data, err := blob.Data()
+	if err != nil {
+		ctx.Handle(404, "repo.SingleDownload(Data)", err)
+		return
+	}
+
+	contentType, isTextFile := base.IsTextFile(data)
+	_, isImageFile := base.IsImageFile(data)
+	ctx.Res.Header().Set("Content-Type", contentType)
+	if !isTextFile && !isImageFile {
+		ctx.Res.Header().Set("Content-Disposition", "attachment; filename="+filepath.Base(treename))
+		ctx.Res.Header().Set("Content-Transfer-Encoding", "binary")
+	}
+	ctx.Res.Write(data)
+}
+
+func ZipDownload(ctx *middleware.Context, params martini.Params) {
+	commitId := ctx.Repo.CommitId
+	archivesPath := filepath.Join(ctx.Repo.GitRepo.Path, "archives")
+	if !com.IsDir(archivesPath) {
+		if err := os.Mkdir(archivesPath, 0755); err != nil {
+			ctx.Handle(404, "ZipDownload -> os.Mkdir(archivesPath)", err)
+			return
+		}
+	}
+
+	zipPath := filepath.Join(archivesPath, commitId+".zip")
+
+	if com.IsFile(zipPath) {
+		ctx.ServeFile(zipPath, ctx.Repo.Repository.Name+".zip")
+		return
+	}
+
+	err := ctx.Repo.Commit.CreateArchive(zipPath)
+	if err != nil {
+		ctx.Handle(404, "ZipDownload -> CreateArchive "+zipPath, err)
+		return
+	}
+
+	ctx.ServeFile(zipPath, ctx.Repo.Repository.Name+".zip")
+}
diff --git a/routers/repo/repo.go b/routers/repo/repo.go
index dda26899d0..14a3c7622c 100644
--- a/routers/repo/repo.go
+++ b/routers/repo/repo.go
@@ -145,7 +145,7 @@ func Single(ctx *middleware.Context, params martini.Params) {
 		return
 	}
 
-	if entry != nil && entry.IsFile() {
+	if entry != nil && !entry.IsDir() {
 		blob := entry.Blob()
 
 		if data, err := blob.Data(); err != nil {
@@ -154,7 +154,7 @@ func Single(ctx *middleware.Context, params martini.Params) {
 			ctx.Data["FileSize"] = blob.Size()
 			ctx.Data["IsFile"] = true
 			ctx.Data["FileName"] = blob.Name
-			ext := path.Ext(blob.Name)
+			ext := path.Ext(blob.Name())
 			if len(ext) > 0 {
 				ext = ext[1:]
 			}
@@ -168,7 +168,7 @@ func Single(ctx *middleware.Context, params martini.Params) {
 			if isImageFile {
 				ctx.Data["IsImageFile"] = true
 			} else {
-				readmeExist := base.IsMarkdownFile(blob.Name) || base.IsReadmeFile(blob.Name)
+				readmeExist := base.IsMarkdownFile(blob.Name()) || base.IsReadmeFile(blob.Name())
 				ctx.Data["ReadmeExist"] = readmeExist
 				if readmeExist {
 					ctx.Data["FileContent"] = string(base.RenderMarkdown(data, ""))
@@ -193,7 +193,7 @@ func Single(ctx *middleware.Context, params martini.Params) {
 		files := make([][]interface{}, 0, len(entries))
 
 		for _, te := range entries {
-			c, err := ctx.Repo.Commit.GetCommitOfRelPath(filepath.Join(treePath, te.Name))
+			c, err := ctx.Repo.Commit.GetCommitOfRelPath(filepath.Join(treePath, te.Name()))
 			if err != nil {
 				ctx.Handle(404, "repo.Single(SubTree)", err)
 				return
@@ -207,7 +207,7 @@ func Single(ctx *middleware.Context, params martini.Params) {
 		var readmeFile *git.Blob
 
 		for _, f := range entries {
-			if !f.IsFile() || !base.IsReadmeFile(f.Name) {
+			if f.IsDir() || !base.IsReadmeFile(f.Name()) {
 				continue
 			} else {
 				readmeFile = f.Blob()
@@ -260,32 +260,6 @@ func Single(ctx *middleware.Context, params martini.Params) {
 	ctx.HTML(200, "repo/single")
 }
 
-func SingleDownload(ctx *middleware.Context, params martini.Params) {
-	// Get tree path
-	treename := params["_1"]
-
-	blob, err := ctx.Repo.Commit.GetBlobByPath(treename)
-	if err != nil {
-		ctx.Handle(404, "repo.SingleDownload(GetBlobByPath)", err)
-		return
-	}
-
-	data, err := blob.Data()
-	if err != nil {
-		ctx.Handle(404, "repo.SingleDownload(Data)", err)
-		return
-	}
-
-	contentType, isTextFile := base.IsTextFile(data)
-	_, isImageFile := base.IsImageFile(data)
-	ctx.Res.Header().Set("Content-Type", contentType)
-	if !isTextFile && !isImageFile {
-		ctx.Res.Header().Set("Content-Disposition", "attachment; filename="+filepath.Base(treename))
-		ctx.Res.Header().Set("Content-Transfer-Encoding", "binary")
-	}
-	ctx.Res.Write(data)
-}
-
 func basicEncode(username, password string) string {
 	auth := username + ":" + password
 	return base64.StdEncoding.EncodeToString([]byte(auth))
diff --git a/web.go b/web.go
index 04c41b9d29..ed61063b1e 100644
--- a/web.go
+++ b/web.go
@@ -176,6 +176,7 @@ func runWeb(*cli.Context) {
 		r.Get("/commit/:branchname", repo.Diff)
 		r.Get("/commit/:branchname/**", repo.Diff)
 		r.Get("/releases", repo.Releases)
+		r.Get("/archive/:branchname/:reponame.zip", repo.ZipDownload)
 	}, ignSignIn, middleware.RepoAssignment(true, true))
 
 	m.Group("/:username", func(r martini.Router) {