Ver código fonte

git: migrate to github.com/gogs/git-module@v1.0.0 (#5958)

* WIP

* Finish `internal/db/git_diff.go`

* FInish internal/db/mirror.go

* Finish internal/db/pull.go

* Finish internal/db/release.go

* Finish internal/db/repo.go

* Finish internal/db/repo_branch.go

* Finish internal/db/repo_editor.go

* Finish internal/db/update.go

* Save my work

* Add license header

* Compile!

* Merge master

* Finish internal/cmd/hook.go

* Finish internal/conf/static.go

* Finish internal/context/repo.go

* Finish internal/db/action.go

* Finish internal/db/git_diff.go

* Fix submodule URL inferring

* Finish internal/db/mirror.go

* Updat to beta.4

* css: update fonts

* Finish internal/db/pull.go

* Finish internal/db/release.go

* Finish internal/db/repo_branch.go

* Finish internal/db/wiki.go

* gitutil: enhance infer submodule UR

* Finish internal/route/api/v1/repo/commits.go

* mirror: only collect branch commits after sync

* mirror: fix tag support

* Finish internal/db/repo.go

* Finish internal/db/repo_editor.go

* Finish internal/db/update.go

* Finish internal/gitutil/pull_request.go

* Make it compile

* Finish internal/route/repo/setting.go

* Finish internal/route/repo/branch.go

* Finish internal/route/api/v1/repo/file.go

* Finish internal/route/repo/download.go

* Finish internal/route/repo/editor.go

* Use helper

* Finish internal/route/repo/issue.go

* Finish internal/route/repo/pull.go

* Finish internal/route/repo/release.go

* Finish internal/route/repo/repo.go

* Finish internal/route/repo/wiki.go

* Finish internal/route/repo/commit.go

* Finish internal/route/repo/view.go

* Finish internal/gitutil/tag.go

* go.sum
ᴜɴᴋɴᴡᴏɴ 5 anos atrás
pai
commit
73d790a602
75 arquivos alterados com 3371 adições e 2787 exclusões
  1. 3 3
      conf/app.ini
  2. 23 20
      go.mod
  3. 111 0
      go.sum
  4. 1 1
      internal/assets/conf/conf_gen.go
  5. 4 4
      internal/assets/public/public_gen.go
  6. 9 9
      internal/assets/templates/templates_gen.go
  7. 12 11
      internal/cmd/hook.go
  8. 6 6
      internal/conf/static.go
  9. 1 1
      internal/context/context.go
  10. 15 15
      internal/context/context_gin.go
  11. 44 46
      internal/context/repo.go
  12. 5 5
      internal/dav/middle.go
  13. 15 15
      internal/db/action.go
  14. 0 194
      internal/db/git_diff.go
  15. 0 35
      internal/db/git_diff_test.go
  16. 6 6
      internal/db/migrations/v16.go
  17. 53 104
      internal/db/mirror.go
  18. 18 89
      internal/db/mirror_test.go
  19. 3 3
      internal/db/models_gin.go
  20. 59 44
      internal/db/pull.go
  21. 7 7
      internal/db/release.go
  22. 62 57
      internal/db/repo.go
  23. 15 15
      internal/db/repo_branch.go
  24. 60 66
      internal/db/repo_editor.go
  25. 23 28
      internal/db/update.go
  26. 12 19
      internal/db/user.go
  27. 4 4
      internal/db/webhook_dingtalk.go
  28. 4 4
      internal/db/webhook_discord.go
  29. 4 4
      internal/db/webhook_slack.go
  30. 15 21
      internal/db/wiki.go
  31. 196 0
      internal/gitutil/diff.go
  32. 49 0
      internal/gitutil/diff_test.go
  33. 19 0
      internal/gitutil/error.go
  34. 23 0
      internal/gitutil/error_test.go
  35. 23 0
      internal/gitutil/mock.go
  36. 90 0
      internal/gitutil/module.go
  37. 69 0
      internal/gitutil/pull_request.go
  38. 108 0
      internal/gitutil/pull_request_test.go
  39. 48 0
      internal/gitutil/submodule.go
  40. 58 0
      internal/gitutil/submodule_test.go
  41. 95 0
      internal/gitutil/tag.go
  42. 109 0
      internal/gitutil/tag_test.go
  43. 2 2
      internal/route/api/v1/convert/convert.go
  44. 20 19
      internal/route/api/v1/repo/commits.go
  45. 30 45
      internal/route/api/v1/repo/contents.go
  46. 10 11
      internal/route/api/v1/repo/file.go
  47. 14 9
      internal/route/api/v1/repo/tree.go
  48. 3 3
      internal/route/install.go
  49. 3 3
      internal/route/repo/branch.go
  50. 37 48
      internal/route/repo/commit.go
  51. 25 35
      internal/route/repo/download.go
  52. 24 31
      internal/route/repo/editor.go
  53. 5 14
      internal/route/repo/issue.go
  54. 73 69
      internal/route/repo/pull.go
  55. 23 21
      internal/route/repo/release.go
  56. 20 24
      internal/route/repo/repo.go
  57. 14 22
      internal/route/repo/repo_gin.go
  58. 15 18
      internal/route/repo/setting.go
  59. 54 65
      internal/route/repo/view.go
  60. 45 31
      internal/route/repo/webhook.go
  61. 32 37
      internal/route/repo/wiki.go
  62. 19 13
      internal/template/template.go
  63. 56 62
      public/css/gogs.css
  64. 0 0
      public/css/gogs.css.map
  65. 377 371
      public/less/_base.less
  66. 948 949
      public/less/_repository.less
  67. 3 3
      templates/admin/config.tmpl
  68. 2 2
      templates/base/head.tmpl
  69. 2 3
      templates/repo/commits_table.tmpl
  70. 17 17
      templates/repo/diff/box.tmpl
  71. 5 5
      templates/repo/diff/section_unified.tmpl
  72. 1 1
      templates/repo/settings/githook_edit.tmpl
  73. 1 1
      templates/repo/settings/githooks.tmpl
  74. 1 1
      templates/repo/settings/options.tmpl
  75. 9 16
      templates/repo/view_list.tmpl

+ 3 - 3
conf/app.ini

@@ -436,12 +436,12 @@ OLDER_THAN = 24h
 [git]
 ; Disables highlight of added and removed changes
 DISABLE_DIFF_HIGHLIGHT = false
+; Max number of files shown in diff view
+MAX_GIT_DIFF_FILES = 100
 ; Max number of lines allowed of a single file in diff view
 MAX_GIT_DIFF_LINES = 1000
 ; Max number of characters of a line allowed in diff view
-MAX_GIT_DIFF_LINE_CHARACTERS = 500
-; Max number of files shown in diff view
-MAX_GIT_DIFF_FILES = 100
+MAX_GIT_DIFF_LINE_CHARACTERS = 2000
 ; Arguments for command 'git gc', e.g. "--aggressive --auto"
 ; see more on http://git-scm.com/docs/git-gc/1.7.5
 GC_ARGS =

+ 23 - 20
go.mod

@@ -6,68 +6,71 @@ require (
 	github.com/G-Node/git-module v0.8.4-gnode
 	github.com/G-Node/libgin v0.3.2
 	github.com/bgentry/speakeasy v0.1.0 // indirect
-	github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0
+	github.com/blang/semver v3.5.1+incompatible // indirect
+	github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e
 	github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0
-	github.com/editorconfig/editorconfig-core-go/v2 v2.2.1
+	github.com/editorconfig/editorconfig-core-go/v2 v2.3.2
 	github.com/fatih/color v1.9.0 // indirect
-	github.com/go-macaron/binding v1.0.1
+	github.com/go-macaron/binding v1.1.0
 	github.com/go-macaron/cache v0.0.0-20190810181446-10f7c57e2196
-	github.com/go-macaron/captcha v0.0.0-20190813234938-24f40749f36d
+	github.com/go-macaron/captcha v0.2.0
 	github.com/go-macaron/csrf v0.0.0-20190812063352-946f6d303a4c
 	github.com/go-macaron/gzip v0.0.0-20160222043647-cad1c6580a07
 	github.com/go-macaron/i18n v0.5.0
 	github.com/go-macaron/session v0.0.0-20190805070824-1a3cdc6f5659
 	github.com/go-macaron/toolbox v0.0.0-20190813233741-94defb8383c6
-	github.com/go-sql-driver/mysql v1.4.1
+	github.com/go-sql-driver/mysql v1.5.0
 	github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561
 	github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14
+	github.com/gogs/git-module v1.1.2
 	github.com/gogs/go-gogs-client v0.0.0-20200128182646-c69cb7680fd4
 	github.com/gogs/go-libravatar v0.0.0-20191106065024-33a75213d0a0
 	github.com/gogs/minwinsvc v0.0.0-20170301035411-95be6356811a
+	github.com/google/go-cmp v0.4.0
 	github.com/google/go-github v17.0.0+incompatible
 	github.com/google/go-querystring v1.0.0 // indirect
 	github.com/issue9/identicon v1.0.1
 	github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43
-	github.com/json-iterator/go v1.1.7
+	github.com/json-iterator/go v1.1.10
 	github.com/klauspost/compress v1.8.6 // indirect
 	github.com/klauspost/cpuid v1.2.1 // indirect
-	github.com/lib/pq v1.2.0
+	github.com/lib/pq v1.3.0
 	github.com/mattn/go-isatty v0.0.12 // indirect
 	github.com/mattn/go-runewidth v0.0.4 // indirect
-	github.com/mattn/go-sqlite3 v1.11.0
+	github.com/mattn/go-sqlite3 v2.0.3+incompatible
 	github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2
-	github.com/microcosm-cc/bluemonday v1.0.2
+	github.com/microcosm-cc/bluemonday v1.0.4
 	github.com/msteinert/pam v0.0.0-20190215180659-f29b9f28d6f9
 	github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
-	github.com/niklasfasching/go-org v0.1.6
+	github.com/niklasfasching/go-org v0.1.9
 	github.com/olekukonko/tablewriter v0.0.1 // indirect
-	github.com/pkg/errors v0.8.1
+	github.com/pkg/errors v0.9.1
 	github.com/pquerna/otp v1.2.0
-	github.com/prometheus/client_golang v1.2.1
+	github.com/prometheus/client_golang v1.6.0
 	github.com/russross/blackfriday v1.5.2
 	github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect
 	github.com/satori/go.uuid v1.2.0
-	github.com/sergi/go-diff v1.0.0
+	github.com/sergi/go-diff v1.1.0
 	github.com/smartystreets/goconvey v1.6.4
 	github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
-	github.com/stretchr/testify v1.4.0
+	github.com/stretchr/testify v1.6.1
 	github.com/unknwon/cae v1.0.0
 	github.com/unknwon/com v1.0.1
 	github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6
 	github.com/unknwon/paginater v0.0.0-20170405233947-45e5d631308e
-	github.com/urfave/cli v1.22.1
+	github.com/urfave/cli v1.22.4
+	gogs.io/gogs v0.12.1
 	golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975
 	golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582
-	golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c // indirect
-	golang.org/x/text v0.3.2
+	golang.org/x/text v0.3.3
 	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
 	gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
 	gopkg.in/clog.v1 v1.2.0
 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
-	gopkg.in/ini.v1 v1.52.0
+	gopkg.in/ini.v1 v1.56.0
 	gopkg.in/ldap.v2 v2.5.1
-	gopkg.in/macaron.v1 v1.3.4
-	gopkg.in/yaml.v2 v2.2.2
+	gopkg.in/macaron.v1 v1.3.9
+	gopkg.in/yaml.v2 v2.2.7
 	unknwon.dev/clog/v2 v2.1.2
 	xorm.io/builder v0.3.6
 	xorm.io/core v0.7.2

+ 111 - 0
go.sum

@@ -10,6 +10,8 @@ github.com/G-Node/libgin v0.0.0-20191216094436-47f8aadc0067/go.mod h1:2yLXQnNbwj
 github.com/G-Node/libgin v0.3.0/go.mod h1:VjulCBq7k/kgf4Eabk2f4w9SDNowWhLnK+yZvy5Nppk=
 github.com/G-Node/libgin v0.3.2 h1:pbOIm+paF4VqfrTvSu4sPXmo2j5XSOnfxf137uzAHIs=
 github.com/G-Node/libgin v0.3.2/go.mod h1:/2l42QsLZTneVp2LjPoPOWhVjvfbO26atvcbvWA+axI=
+github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
+github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
 github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@@ -17,6 +19,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
+github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -31,6 +35,10 @@ github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 h1:U/lr3Dgy4WK
 github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
 github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
 github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
+github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU=
+github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/couchbase/gomemcached v0.0.0-20190515232915-c4b4ca0eb21d/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
 github.com/couchbase/goutils v0.0.0-20190315194238-f9d42b11473b/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
@@ -41,9 +49,13 @@ github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGii
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
 github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
 github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0 h1:epsH3lb7KVbXHYk7LYGN5EiE0MxcevHU85CKITJ0wUY=
 github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
+github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e h1:LzwWXEScfcTu7vUZNlDDWDARoSGEtvlDKK2BYHowNeE=
+github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
 github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0 h1:90Ly+6UfUypEF6vvvW5rQIv9opIL8CbmW9FT20LDQoY=
 github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0/go.mod h1:V+Qd57rJe8gd4eiGzZyg4h54VLHmYVVw54iMnlAMrF8=
@@ -52,7 +64,12 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1
 github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
 github.com/editorconfig/editorconfig-core-go/v2 v2.2.1 h1:jY5PCRQf4V0oqpim/Ympl6MwHcb9+nBHEnHOPXqNZ/A=
 github.com/editorconfig/editorconfig-core-go/v2 v2.2.1/go.mod h1:6XDmqAZsQu8ikS+onLRJfLZvTP3RWTVT8ROX6qcdkio=
+github.com/editorconfig/editorconfig-core-go/v2 v2.3.0 h1:QD1YB/rbntMEQIKM42kQOaqGdS13UvGsl9c8m/nFNWY=
+github.com/editorconfig/editorconfig-core-go/v2 v2.3.0/go.mod h1:RNdPfKd9PliYEUZ3r+GxbDsSHNnEluC1wdkQJc3jD4k=
+github.com/editorconfig/editorconfig-core-go/v2 v2.3.2 h1:j9GLz0kWF9+1T3IX0MOhhvzLtqhFOvIKLhZFxtY95Qc=
+github.com/editorconfig/editorconfig-core-go/v2 v2.3.2/go.mod h1:+u4rFiKVvlbukHyJM76GYXqQcnHScxvQCuKpMLRtJVw=
 github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
+github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
 github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
@@ -64,10 +81,14 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-macaron/binding v1.0.1 h1:4LASxd4EKsESZ6ZMyzNVX+TM4Yuex4bTHYyz/PQjsRA=
 github.com/go-macaron/binding v1.0.1/go.mod h1:AG8Z6qkQM8s47aUDJOco/SNwJ8Czif2hMm7rc0abDog=
+github.com/go-macaron/binding v1.1.0 h1:A5jpr5UdHr81Hfmb6QUAMTHyvniudOMcgtEg13TJ1ig=
+github.com/go-macaron/binding v1.1.0/go.mod h1:dJU/AtPKG0gUiFra1K5TTGduFGMNxMvfJzV/zmXwyGM=
 github.com/go-macaron/cache v0.0.0-20190810181446-10f7c57e2196 h1:fqWZxyMLF6RVGmjvsZ9FijiU9UlAjuE6nu9RfNBZ+iE=
 github.com/go-macaron/cache v0.0.0-20190810181446-10f7c57e2196/go.mod h1:O6fSdaYZbGh4clVMGMGO5k2KbMO0Cz8YdBnPrD0I8dM=
 github.com/go-macaron/captcha v0.0.0-20190813234938-24f40749f36d h1:aSJXLVjEjbLeHo8aCTDcD3/gMWizaRjMBb3VCsEWEHs=
 github.com/go-macaron/captcha v0.0.0-20190813234938-24f40749f36d/go.mod h1:lmhlZnu9cTRGNQEkSh1qZi2IK3HJH4Z1MXkg6ARQKZA=
+github.com/go-macaron/captcha v0.2.0 h1:d38eYDDF8tdqoM0hJbk+Jb7WQGWlwYNnQwRqLRmSk1Y=
+github.com/go-macaron/captcha v0.2.0/go.mod h1:lmhlZnu9cTRGNQEkSh1qZi2IK3HJH4Z1MXkg6ARQKZA=
 github.com/go-macaron/csrf v0.0.0-20190812063352-946f6d303a4c h1:kFFz1OpaH3+efG7RA33z+D0piwpA/a3x/Zn2d8z9rfw=
 github.com/go-macaron/csrf v0.0.0-20190812063352-946f6d303a4c/go.mod h1:FX53Xq0NNlUj0E5in5J8Dq5nrbdK3ZyDIy6y5VWOiUo=
 github.com/go-macaron/gzip v0.0.0-20160222043647-cad1c6580a07 h1:YSIA98PevNf1NtCa/J6cz7gjzpz99WVAOa9Eg0klKps=
@@ -82,6 +103,8 @@ github.com/go-macaron/toolbox v0.0.0-20190813233741-94defb8383c6 h1:x/v1iUWlqXTK
 github.com/go-macaron/toolbox v0.0.0-20190813233741-94defb8383c6/go.mod h1:YFNJ/JT4yLnpuIXTFef30SZkxGHUczjGZGFaZpPcdn0=
 github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
+github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
 github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
@@ -91,6 +114,14 @@ github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561 h1:aBzukfDxQlCTVS0NBU
 github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
 github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14 h1:yXtpJr/LV6PFu4nTLgfjQdcMdzjbqqXMEnHfq0Or6p8=
 github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14/go.mod h1:jPoNZLWDAqA5N3G5amEoiNbhVrmM+ZQEcnQvNQ2KaZk=
+github.com/gogs/git-module v0.8.3 h1:9f8oxSs9OACWrGBYMVnnQNzyTcVN+zzcBM7CXnbmezw=
+github.com/gogs/git-module v0.8.3/go.mod h1:aj4tcm7DxaszJWpZLZIRL6gfPXyguAHiE1PDfAAPrCw=
+github.com/gogs/git-module v1.0.0-beta.4 h1:5CyCvTfrb2n5LRpHcNIaFnywHDkM/NxSZVP6t4tpTXI=
+github.com/gogs/git-module v1.0.0-beta.4/go.mod h1:oN37FFStFjdnTJXsSbhIHKJXh2YeDsEcXPATVz/oeuQ=
+github.com/gogs/git-module v1.0.0 h1:iOlCZ5kPc3RjnWRxdziL5hjCaosYyZw/Lf2odzR/kjw=
+github.com/gogs/git-module v1.0.0/go.mod h1:oN37FFStFjdnTJXsSbhIHKJXh2YeDsEcXPATVz/oeuQ=
+github.com/gogs/git-module v1.1.2 h1:30jO+rKEmCDk/O6Mnl7MVrw6rI1qLDByXpkRB+bpYwM=
+github.com/gogs/git-module v1.1.2/go.mod h1:oN37FFStFjdnTJXsSbhIHKJXh2YeDsEcXPATVz/oeuQ=
 github.com/gogs/go-gogs-client v0.0.0-20200128182646-c69cb7680fd4 h1:C7NryI/RQhsIWwC2bHN601P1wJKeuQ6U/UCOYTn3Cic=
 github.com/gogs/go-gogs-client v0.0.0-20200128182646-c69cb7680fd4/go.mod h1:fR6z1Ie6rtF7kl/vBYMfgD5/G5B1blui7z426/sj2DU=
 github.com/gogs/go-libravatar v0.0.0-20191106065024-33a75213d0a0 h1:K02vod+sn3M1OOkdqi2tPxN2+xESK4qyITVQ3JkGEv4=
@@ -106,12 +137,20 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
 github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
 github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
 github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
@@ -125,6 +164,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORR
 github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
 github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
+github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
@@ -134,9 +175,16 @@ github.com/issue9/identicon v1.0.1 h1:pCDfjMDM6xWK0Chxo8Lif+ST/nOEtmXgMITgV1YA9O
 github.com/issue9/identicon v1.0.1/go.mod h1:UKNVkUFI68RPz/RlLhsAr1aX6bBSaYEWRHVfdjrMUmk=
 github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43 h1:jTkyeF7NZ5oIr0ESmcrpiDgAfoidCBF4F5kJhjtaRwE=
 github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
+github.com/jinzhu/gorm v1.9.11/go.mod h1:bu/pK8szGZ2puuErfU0RwyeNdsf3e6nCX/noXaVxkfw=
+github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
 github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
@@ -155,8 +203,11 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
 github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
+github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ=
 github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af/go.mod h1:Cqz6pqow14VObJ7peltM+2n3PWOz7yTrfUuGbVFkzN0=
 github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
@@ -172,12 +223,17 @@ github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
 github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
 github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
 github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 h1:YocNLcTBdEdvY3iDK6jfWXvEaM5OCKkjxPKoJRdB3Gg=
 github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
 github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
 github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
+github.com/microcosm-cc/bluemonday v1.0.4 h1:p0L+CTpo/PLFdkoPcJemLXG+fpMD7pYOoDEq1axMbGg=
+github.com/microcosm-cc/bluemonday v1.0.4/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -191,6 +247,8 @@ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6
 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
 github.com/niklasfasching/go-org v0.1.6 h1:F521WcqRNl8OJumlgAnekZgERaTA2HpfOYYfVEKOeI8=
 github.com/niklasfasching/go-org v0.1.6/go.mod h1:AsLD6X7djzRIz4/RFZu8vwRL0VGjUvGZCCH1Nz0VdrU=
+github.com/niklasfasching/go-org v0.1.9 h1:Toz8WMIt+qJb52uYEk1YD/muLuOOmRt1CfkV+bKVMkI=
+github.com/niklasfasching/go-org v0.1.9/go.mod h1:AsLD6X7djzRIz4/RFZu8vwRL0VGjUvGZCCH1Nz0VdrU=
 github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
 github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -204,6 +262,8 @@ github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok=
@@ -213,20 +273,28 @@ github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI=
 github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=
+github.com/prometheus/client_golang v1.6.0 h1:YVPodQOcK15POxhgARIvnDRVpLcuK8mglnMrWfyrw6A=
+github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
 github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
+github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
+github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
 github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
+github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI=
+github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@@ -238,6 +306,8 @@ github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
+github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
@@ -264,7 +334,9 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
+github.com/t-tiger/gorm-bulk-insert v1.3.0/go.mod h1:ruDlk8xDl+8sX4bA7PQuYly9YEb3pbp1eP2LCyeRrFY=
 github.com/unknwon/cae v1.0.0 h1:i39lOFaBXZxhGjQOy/RNbi8uzettCs6OQxpR0xXohGU=
 github.com/unknwon/cae v1.0.0/go.mod h1:QaSeRctcea9fK6piJpAMCCPKxzJ01+xFcr2k1m3WRPU=
 github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
@@ -276,20 +348,28 @@ github.com/unknwon/paginater v0.0.0-20170405233947-45e5d631308e h1:Qf3QQl/zmEbWD
 github.com/unknwon/paginater v0.0.0-20170405233947-45e5d631308e/go.mod h1:TBwoao3Q4Eb/cp+dHbXDfRTrZSsj/k7kLr2j1oWRWC0=
 github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
 github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
+github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
 go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+gogs.io/gogs v0.12.1 h1:TmCr+FgRjzOvr8s/tX8X9uOTAZ3eNEvfon8F9oN3R/4=
+gogs.io/gogs v0.12.1/go.mod h1:FwcFYxh13Lw+BdOuVs+caeNNnfNFc1amnyLkwIV4kjk=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -313,6 +393,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -328,13 +409,18 @@ golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
 golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c h1:jceGD5YNJGgGMkJz79agzOln1K9TaZUjv5ird16qniQ=
 golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8=
+golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -345,6 +431,10 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
 golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20190802220118-1d1727260058/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
 golang.org/x/tools v0.0.0-20190805222050-c5a2fd39b72a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -356,6 +446,12 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn
 google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
@@ -366,6 +462,7 @@ gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e/go.mod h1:xsQCaysVCudhrYTfz
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/clog.v1 v1.2.0 h1:BHfwHRNQy497iBNsRBassPixSAxRbn2z5KVkdBFbwxc=
 gopkg.in/clog.v1 v1.2.0/go.mod h1:L6fgdpdhFgKX4eGuDvt+N6X2GwZE160NRrIHzvaF8ZM=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
@@ -373,18 +470,32 @@ gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AW
 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
 gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.52.0 h1:j+Lt/M1oPPejkniCg1TkWE2J3Eh1oZTsHSXzMTzUXn4=
 gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.53.0 h1:c7ruDvTQi0MUTFuNpDRXLSjs7xT4TerM1icIg4uKWRg=
+gopkg.in/ini.v1 v1.53.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.56.0 h1:DPMeDvGTM54DXbPkVIZsp19fp/I2K7zwA/itHYHKo8Y=
+gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU=
 gopkg.in/ldap.v2 v2.5.1/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk=
 gopkg.in/macaron.v1 v1.3.4 h1:HvIscOwxhFhx3swWM/979wh2QMYyuXrNmrF9l+j3HZs=
 gopkg.in/macaron.v1 v1.3.4/go.mod h1:/RoHTdC8ALpyJ3+QR36mKjwnT1F1dyYtsGM9Ate6ZFI=
+gopkg.in/macaron.v1 v1.3.5/go.mod h1:uMZCFccv9yr5TipIalVOyAyZQuOH3OkmXvgcWwhJuP4=
+gopkg.in/macaron.v1 v1.3.9 h1:Dw+DDRYdXgQyEsPlfAfKz+UA5qVUrH3KPD7JhmZ9MFc=
+gopkg.in/macaron.v1 v1.3.9/go.mod h1:uMZCFccv9yr5TipIalVOyAyZQuOH3OkmXvgcWwhJuP4=
 gopkg.in/redis.v2 v2.3.2 h1:GPVIIB/JnL1wvfULefy3qXmPu1nfNu2d0yA09FHgwfs=
 gopkg.in/redis.v2 v2.3.2/go.mod h1:4wl9PJ/CqzeHk3LVq1hNLHH8krm3+AXEgut4jVc++LU=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
+gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

Diferenças do arquivo suprimidas por serem muito extensas
+ 1 - 1
internal/assets/conf/conf_gen.go


Diferenças do arquivo suprimidas por serem muito extensas
+ 4 - 4
internal/assets/public/public_gen.go


Diferenças do arquivo suprimidas por serem muito extensas
+ 9 - 9
internal/assets/templates/templates_gen.go


+ 12 - 11
internal/cmd/hook.go

@@ -9,6 +9,7 @@ import (
 	"bytes"
 	"crypto/tls"
 	"fmt"
+	"net/url"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -18,14 +19,13 @@ import (
 	"github.com/urfave/cli"
 	log "unknwon.dev/clog/v2"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 
 	"github.com/G-Node/gogs/internal/conf"
 	"github.com/G-Node/gogs/internal/db"
 	"github.com/G-Node/gogs/internal/db/errors"
 	"github.com/G-Node/gogs/internal/email"
 	"github.com/G-Node/gogs/internal/httplib"
-	"github.com/G-Node/gogs/internal/template"
 )
 
 var (
@@ -87,7 +87,7 @@ func runHookPreReceive(c *cli.Context) error {
 		}
 		oldCommitID := string(fields[0])
 		newCommitID := string(fields[1])
-		branchName := strings.TrimPrefix(string(fields[2]), git.BRANCH_PREFIX)
+		branchName := git.RefShortName(string(fields[2]))
 
 		// Branch protection
 		repoID := com.StrTo(os.Getenv(db.ENV_REPO_ID)).MustInt64()
@@ -121,7 +121,7 @@ func runHookPreReceive(c *cli.Context) error {
 		}
 
 		// check and deletion
-		if newCommitID == git.EMPTY_SHA {
+		if newCommitID == git.EmptyID {
 			fail(fmt.Sprintf("Branch '%s' is protected from deletion", branchName), "")
 		}
 
@@ -221,7 +221,7 @@ func runHookPostReceive(c *cli.Context) error {
 		options := db.PushUpdateOptions{
 			OldCommitID:  string(fields[0]),
 			NewCommitID:  string(fields[1]),
-			RefFullName:  string(fields[2]),
+			FullRefspec:  string(fields[2]),
 			PusherID:     com.StrTo(os.Getenv(db.ENV_AUTH_USER_ID)).MustInt64(),
 			PusherName:   os.Getenv(db.ENV_AUTH_USER_NAME),
 			RepoUserName: os.Getenv(db.ENV_REPO_OWNER_NAME),
@@ -232,19 +232,20 @@ func runHookPostReceive(c *cli.Context) error {
 		}
 
 		// Ask for running deliver hook and test pull request tasks
-		reqURL := conf.Server.LocalRootURL + options.RepoUserName + "/" + options.RepoName + "/tasks/trigger?branch=" +
-			template.EscapePound(strings.TrimPrefix(options.RefFullName, git.BRANCH_PREFIX)) +
-			"&secret=" + os.Getenv(db.ENV_REPO_OWNER_SALT_MD5) +
-			"&pusher=" + os.Getenv(db.ENV_AUTH_USER_ID)
+		q := make(url.Values)
+		q.Add("branch", git.RefShortName(options.FullRefspec))
+		q.Add("secret", os.Getenv(db.ENV_REPO_OWNER_SALT_MD5))
+		q.Add("pusher", os.Getenv(db.ENV_AUTH_USER_ID))
+		reqURL := fmt.Sprintf("%s%s/%s/tasks/trigger?%s", conf.Server.LocalRootURL, options.RepoUserName, options.RepoName, q.Encode())
 		log.Trace("Trigger task: %s", reqURL)
 
 		resp, err := httplib.Head(reqURL).SetTLSClientConfig(&tls.Config{
 			InsecureSkipVerify: true,
 		}).Response()
 		if err == nil {
-			resp.Body.Close()
+			_ = resp.Body.Close()
 			if resp.StatusCode/100 != 2 {
-				log.Error("Failed to trigger task: not 2xx response code")
+				log.Error("Failed to trigger task: unsuccessful response code %d", resp.StatusCode)
 			}
 		} else {
 			log.Error("Failed to trigger task: %v", err)

+ 6 - 6
internal/conf/static.go

@@ -358,12 +358,12 @@ var (
 		// ⚠️ WARNING: Should only be set by "internal/db/repo.go".
 		Version string `ini:"-"`
 
-		DisableDiffHighlight     bool
-		MaxGitDiffLines          int
-		MaxGitDiffLineCharacters int
-		MaxGitDiffFiles          int
-		GCArgs                   []string `ini:"GC_ARGS" delim:" "`
-		Timeout                  struct {
+		DisableDiffHighlight bool
+		MaxDiffFiles         int      `ini:"MAX_GIT_DIFF_FILES"`
+		MaxDiffLines         int      `ini:"MAX_GIT_DIFF_LINES"`
+		MaxDiffLineChars     int      `ini:"MAX_GIT_DIFF_LINE_CHARACTERS"`
+		GCArgs               []string `ini:"GC_ARGS" delim:" "`
+		Timeout              struct {
 			Migrate int
 			Mirror  int
 			Clone   int

+ 1 - 1
internal/context/context.go

@@ -173,7 +173,7 @@ func (c *Context) Handle(status int, msg string, err error) {
 		c.Data["Title"] = "Page Not Found"
 	case http.StatusInternalServerError:
 		c.Data["Title"] = "Internal Server Error"
-		log.Error("%s: %v", msg, err)
+		log.ErrorDepth(5, "%s: %v", msg, err)
 		if !conf.IsProdMode() || (c.IsLogged && c.User.IsAdmin) {
 			c.Data["ErrorMsg"] = err
 		}

+ 15 - 15
internal/context/context_gin.go

@@ -3,10 +3,10 @@ package context
 import (
 	"strings"
 
-	"github.com/G-Node/git-module"
 	"github.com/G-Node/gogs/internal/conf"
 	"github.com/G-Node/gogs/internal/db"
 	"github.com/G-Node/libgin/libgin"
+	"github.com/gogs/git-module"
 	log "gopkg.in/clog.v1"
 )
 
@@ -48,26 +48,26 @@ func getRepoDOI(c *Context) string {
 	// if multiple exit, get the latest one
 	doiBase := conf.DOI.Base
 
-	doiForkGit, err := git.OpenRepository(doiFork.RepoPath())
+	doiForkGit, err := git.Open(doiFork.RepoPath())
 	if err != nil {
 		log.Error(2, "failed to open git repository at %q (%d): %v", doiFork.RepoPath(), doiFork.ID, err)
 		return ""
 	}
-	if tags, err := doiForkGit.GetTags(); err == nil {
+	if tags, err := doiForkGit.Tags(); err == nil {
 		var latestTime int64
 		latestTag := ""
 		for _, tagName := range tags {
 			if strings.Contains(tagName, doiBase) {
-				tag, err := doiForkGit.GetTag(tagName)
+				tag, err := doiForkGit.Tag(tagName)
 				if err != nil {
 					// log the error and continue to the next tag
-					log.Error(2, "failed to get information for tag %q for repository at %q: %v", tagName, doiForkGit.Path, err)
+					log.Error(2, "failed to get information for tag %q for repository at %q: %v", tagName, doiForkGit.Path(), err)
 					continue
 				}
 				commit, err := tag.Commit()
 				if err != nil {
 					// log the error and continue to the next tag
-					log.Error(2, "failed to get commit for tag %q for repository at %q: %v", tagName, doiForkGit.Path, err)
+					log.Error(2, "failed to get commit for tag %q for repository at %q: %v", tagName, doiForkGit.Path(), err)
 					continue
 				}
 				commitTime := commit.Committer.When.Unix()
@@ -81,7 +81,7 @@ func getRepoDOI(c *Context) string {
 	} else {
 		// this shouldn't happen even if there are no tags
 		// log the error, but fall back to the old method anyway
-		log.Error(2, "failed to get tags for repository at %q: %v", doiForkGit.Path, err)
+		log.Error(2, "failed to get tags for repository at %q: %v", doiForkGit.Path(), err)
 	}
 
 	// Has DOI fork but isn't tagged: return old style has-based DOI
@@ -99,12 +99,12 @@ func getRepoDOI(c *Context) string {
 // valid.  If any error occurs, for example due to an uninitialised repository
 // or missing repository root, it returns 'false' without error.
 func hasDataCite(c *Context) bool {
-	commit, err := c.Repo.GitRepo.GetBranchCommit(c.Repo.Repository.DefaultBranch)
+	commit, err := c.Repo.GitRepo.BranchCommit(c.Repo.Repository.DefaultBranch)
 	if err != nil {
 		log.Trace("Couldn't get commit: %v", err)
 		return false
 	}
-	_, err = commit.GetBlobByPath("/datacite.yml")
+	_, err = commit.Blob("/datacite.yml")
 
 	log.Trace("Found datacite? %t", err == nil)
 	return err == nil
@@ -125,23 +125,23 @@ func isDOIReady(c *Context) bool {
 			return false
 		}
 
-		headBranch, err := gitrepo.GetHEADBranch()
+		headBranch, err := gitrepo.SymbolicRef(git.SymbolicRefOptions{Name: "HEAD"})
 		if err != nil {
-			log.Error(2, "Failed to get HEAD branch for repo at %q: %v", gitrepo.Path, err)
+			log.Error(2, "Failed to get HEAD branch for repo at %q: %v", gitrepo.Path(), err)
 			return false
 		}
 
-		headCommit, err := gitrepo.GetBranchCommitID(headBranch.Name)
+		headCommit, err := gitrepo.BranchCommitID(headBranch)
 		if err != nil {
-			log.Error(2, "Failed to get commit ID of branch %q for repo at %q: %v", headBranch.Name, gitrepo.Path, err)
+			log.Error(2, "Failed to get commit ID of branch %q for repo at %q: %v", headBranch, gitrepo.Path(), err)
 			return false
 		}
 
 		// if current valid and registered DOI matches the HEAD commit, can't
 		// register again
-		doiCommit, err := gitrepo.GetTagCommitID(currentDOI.(string))
+		doiCommit, err := gitrepo.TagCommitID(currentDOI.(string))
 		if err != nil {
-			log.Error(2, "Failed to get commit ID of tag %q for repo at %q: %v", currentDOI, gitrepo.Path, err)
+			log.Error(2, "Failed to get commit ID of tag %q for repo at %q: %v", currentDOI, gitrepo.Path(), err)
 			return false
 		}
 

+ 44 - 46
internal/context/repo.go

@@ -5,19 +5,20 @@
 package context
 
 import (
+	"bytes"
 	"fmt"
-	"io/ioutil"
 	"net/url"
 	"strings"
 
 	"github.com/editorconfig/editorconfig-core-go/v2"
+	"github.com/pkg/errors"
 	"gopkg.in/macaron.v1"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 
 	"github.com/G-Node/gogs/internal/conf"
 	"github.com/G-Node/gogs/internal/db"
-	"github.com/G-Node/gogs/internal/db/errors"
+	dberrors "github.com/G-Node/gogs/internal/db/errors"
 	"github.com/G-Node/libgin/libgin"
 )
 
@@ -76,26 +77,23 @@ func (r *Repository) CanEnableEditor() bool {
 	return r.Repository.CanEnableEditor() && r.IsViewBranch && r.IsWriter() && !r.Repository.IsBranchRequirePullRequest(r.BranchName)
 }
 
-// GetEditorconfig returns the .editorconfig definition if found in the
-// HEAD of the default repo branch.
-func (r *Repository) GetEditorconfig() (*editorconfig.Editorconfig, error) {
-	commit, err := r.GitRepo.GetBranchCommit(r.Repository.DefaultBranch)
+// Editorconfig returns the ".editorconfig" definition if found in the HEAD of the default branch.
+func (r *Repository) Editorconfig() (*editorconfig.Editorconfig, error) {
+	commit, err := r.GitRepo.BranchCommit(r.Repository.DefaultBranch)
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrapf(err, "get commit of branch %q ", r.Repository.DefaultBranch)
 	}
-	treeEntry, err := commit.GetTreeEntryByPath(".editorconfig")
-	if err != nil {
-		return nil, err
-	}
-	reader, err := treeEntry.Blob().Data()
+
+	entry, err := commit.TreeEntry(".editorconfig")
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "get .editorconfig")
 	}
-	data, err := ioutil.ReadAll(reader)
+
+	p, err := entry.Blob().Bytes()
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "read .editorconfig")
 	}
-	return editorconfig.ParseBytes(data)
+	return editorconfig.Parse(bytes.NewReader(p))
 }
 
 // MakeURL accepts a string or url.URL as argument and returns escaped URL prepended with repository URL.
@@ -150,7 +148,7 @@ func RepoAssignment(pages ...bool) macaron.Handler {
 		} else {
 			owner, err = db.GetUserByName(ownerName)
 			if err != nil {
-				c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
+				c.NotFoundOrServerError("GetUserByName", dberrors.IsUserNotExist, err)
 				return
 			}
 		}
@@ -159,7 +157,7 @@ func RepoAssignment(pages ...bool) macaron.Handler {
 
 		repo, err := db.GetRepositoryByName(owner.ID, repoName)
 		if err != nil {
-			c.NotFoundOrServerError("GetRepositoryByName", errors.IsRepoNotExist, err)
+			c.NotFoundOrServerError("GetRepositoryByName", dberrors.IsRepoNotExist, err)
 			return
 		}
 
@@ -223,16 +221,16 @@ func RepoAssignment(pages ...bool) macaron.Handler {
 			c.Data["Mirror"] = c.Repo.Mirror
 		}
 
-		gitRepo, err := git.OpenRepository(db.RepoPath(ownerName, repoName))
+		gitRepo, err := git.Open(db.RepoPath(ownerName, repoName))
 		if err != nil {
-			c.ServerError(fmt.Sprintf("RepoAssignment Invalid repo '%s'", c.Repo.Repository.RepoPath()), err)
+			c.ServerError("open repository", err)
 			return
 		}
 		c.Repo.GitRepo = gitRepo
 
-		tags, err := c.Repo.GitRepo.GetTags()
+		tags, err := c.Repo.GitRepo.Tags()
 		if err != nil {
-			c.ServerError(fmt.Sprintf("GetTags '%s'", c.Repo.Repository.RepoPath()), err)
+			c.ServerError("get tags", err)
 			return
 		}
 		c.Data["Tags"] = tags
@@ -263,21 +261,21 @@ func RepoAssignment(pages ...bool) macaron.Handler {
 		}
 
 		c.Data["TagName"] = c.Repo.TagName
-		brs, err := c.Repo.GitRepo.GetBranches()
+		branches, err := c.Repo.GitRepo.Branches()
 		if err != nil {
-			c.ServerError("GetBranches", err)
+			c.ServerError("get branches", err)
 			return
 		}
-		c.Data["Branches"] = brs
-		c.Data["BrancheCount"] = len(brs)
+		c.Data["Branches"] = branches
+		c.Data["BrancheCount"] = len(branches)
 
 		// If not branch selected, try default one.
 		// If default branch doesn't exists, fall back to some other branch.
 		if len(c.Repo.BranchName) == 0 {
-			if len(c.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(c.Repo.Repository.DefaultBranch) {
+			if len(c.Repo.Repository.DefaultBranch) > 0 && gitRepo.HasBranch(c.Repo.Repository.DefaultBranch) {
 				c.Repo.BranchName = c.Repo.Repository.DefaultBranch
-			} else if len(brs) > 0 {
-				c.Repo.BranchName = brs[0]
+			} else if len(branches) > 0 {
+				c.Repo.BranchName = branches[0]
 			}
 		}
 		c.Data["BranchName"] = c.Repo.BranchName
@@ -314,7 +312,7 @@ func RepoRef() macaron.Handler {
 		// For API calls.
 		if c.Repo.GitRepo == nil {
 			repoPath := db.RepoPath(c.Repo.Owner.Name, c.Repo.Repository.Name)
-			c.Repo.GitRepo, err = git.OpenRepository(repoPath)
+			c.Repo.GitRepo, err = git.Open(repoPath)
 			if err != nil {
 				c.Handle(500, "RepoRef Invalid repo "+repoPath, err)
 				return
@@ -324,17 +322,17 @@ func RepoRef() macaron.Handler {
 		// Get default branch.
 		if len(c.Params("*")) == 0 {
 			refName = c.Repo.Repository.DefaultBranch
-			if !c.Repo.GitRepo.IsBranchExist(refName) {
-				brs, err := c.Repo.GitRepo.GetBranches()
+			if !c.Repo.GitRepo.HasBranch(refName) {
+				branches, err := c.Repo.GitRepo.Branches()
 				if err != nil {
-					c.Handle(500, "GetBranches", err)
+					c.ServerError("get branches", err)
 					return
 				}
-				refName = brs[0]
+				refName = branches[0]
 			}
-			c.Repo.Commit, err = c.Repo.GitRepo.GetBranchCommit(refName)
+			c.Repo.Commit, err = c.Repo.GitRepo.BranchCommit(refName)
 			if err != nil {
-				c.Handle(500, "GetBranchCommit", err)
+				c.ServerError("get branch commit", err)
 				return
 			}
 			c.Repo.CommitID = c.Repo.Commit.ID.String()
@@ -346,8 +344,8 @@ func RepoRef() macaron.Handler {
 			for i, part := range parts {
 				refName = strings.TrimPrefix(refName+"/"+part, "/")
 
-				if c.Repo.GitRepo.IsBranchExist(refName) ||
-					c.Repo.GitRepo.IsTagExist(refName) {
+				if c.Repo.GitRepo.HasBranch(refName) ||
+					c.Repo.GitRepo.HasTag(refName) {
 					if i < len(parts)-1 {
 						c.Repo.TreePath = strings.Join(parts[i+1:], "/")
 					}
@@ -360,21 +358,21 @@ func RepoRef() macaron.Handler {
 				c.Repo.TreePath = strings.Join(parts[1:], "/")
 			}
 
-			if c.Repo.GitRepo.IsBranchExist(refName) {
+			if c.Repo.GitRepo.HasBranch(refName) {
 				c.Repo.IsViewBranch = true
 
-				c.Repo.Commit, err = c.Repo.GitRepo.GetBranchCommit(refName)
+				c.Repo.Commit, err = c.Repo.GitRepo.BranchCommit(refName)
 				if err != nil {
-					c.Handle(500, "GetBranchCommit", err)
+					c.ServerError("get branch commit", err)
 					return
 				}
 				c.Repo.CommitID = c.Repo.Commit.ID.String()
 
-			} else if c.Repo.GitRepo.IsTagExist(refName) {
+			} else if c.Repo.GitRepo.HasTag(refName) {
 				c.Repo.IsViewTag = true
-				c.Repo.Commit, err = c.Repo.GitRepo.GetTagCommit(refName)
+				c.Repo.Commit, err = c.Repo.GitRepo.TagCommit(refName)
 				if err != nil {
-					c.Handle(500, "GetTagCommit", err)
+					c.ServerError("get tag commit", err)
 					return
 				}
 				c.Repo.CommitID = c.Repo.Commit.ID.String()
@@ -382,7 +380,7 @@ func RepoRef() macaron.Handler {
 				c.Repo.IsViewCommit = true
 				c.Repo.CommitID = refName
 
-				c.Repo.Commit, err = c.Repo.GitRepo.GetCommit(refName)
+				c.Repo.Commit, err = c.Repo.GitRepo.CatFileCommit(refName)
 				if err != nil {
 					c.NotFound()
 					return

+ 5 - 5
internal/dav/middle.go

@@ -4,9 +4,9 @@ import (
 	"net/http"
 	"strings"
 
-	"github.com/G-Node/git-module"
 	"github.com/G-Node/gogs/internal/context"
 	"github.com/G-Node/gogs/internal/db"
+	"github.com/gogs/git-module"
 	"gopkg.in/macaron.v1"
 )
 
@@ -63,14 +63,14 @@ func DavMiddle() macaron.Handler {
 			}
 		}
 
-		gitRepo, err := git.OpenRepository(db.RepoPath(ownerName, repoName))
+		gitRepo, err := git.Open(db.RepoPath(ownerName, repoName))
 		if err != nil {
 			c.WriteHeader(http.StatusInternalServerError)
 			return
 		}
 		c.Repo.GitRepo = gitRepo
 
-		tags, err := c.Repo.GitRepo.GetTags()
+		tags, err := c.Repo.GitRepo.Tags()
 		if err != nil {
 			c.WriteHeader(http.StatusInternalServerError)
 			return
@@ -82,7 +82,7 @@ func DavMiddle() macaron.Handler {
 			return
 		}
 
-		brs, err := c.Repo.GitRepo.GetBranches()
+		brs, err := c.Repo.GitRepo.Branches()
 		if err != nil {
 			c.WriteHeader(http.StatusInternalServerError)
 			return
@@ -90,7 +90,7 @@ func DavMiddle() macaron.Handler {
 		// If not branch selected, try default one.
 		// If default branch doesn't exists, fall back to some other branch.
 		if len(c.Repo.BranchName) == 0 {
-			if len(c.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(c.Repo.Repository.DefaultBranch) {
+			if len(c.Repo.Repository.DefaultBranch) > 0 && gitRepo.HasBranch(c.Repo.Repository.DefaultBranch) {
 				c.Repo.BranchName = c.Repo.Repository.DefaultBranch
 			} else if len(brs) > 0 {
 				c.Repo.BranchName = brs[0]

+ 15 - 15
internal/db/action.go

@@ -16,7 +16,7 @@ import (
 	log "unknwon.dev/clog/v2"
 	"xorm.io/xorm"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 	api "github.com/gogs/go-gogs-client"
 
 	"github.com/G-Node/gogs/internal/conf"
@@ -268,9 +268,9 @@ func (pc *PushCommits) ToApiPayloadCommits(repoPath, repoURL string) ([]*api.Pay
 			return nil, fmt.Errorf("GetUserByEmail: %v", err)
 		}
 
-		fileStatus, err := git.GetCommitFileStatus(repoPath, commit.Sha1)
+		nameStatus, err := git.RepoShowNameStatus(repoPath, commit.Sha1)
 		if err != nil {
-			return nil, fmt.Errorf("FileStatus [commit_sha1: %s]: %v", commit.Sha1, err)
+			return nil, fmt.Errorf("show name status [commit_sha1: %s]: %v", commit.Sha1, err)
 		}
 
 		commits[i] = &api.PayloadCommit{
@@ -287,9 +287,9 @@ func (pc *PushCommits) ToApiPayloadCommits(repoPath, repoURL string) ([]*api.Pay
 				Email:    commit.CommitterEmail,
 				UserName: committerUsername,
 			},
-			Added:     fileStatus.Added,
-			Removed:   fileStatus.Removed,
-			Modified:  fileStatus.Modified,
+			Added:     nameStatus.Added,
+			Removed:   nameStatus.Removed,
+			Modified:  nameStatus.Modified,
 			Timestamp: commit.Timestamp,
 		}
 	}
@@ -298,21 +298,21 @@ func (pc *PushCommits) ToApiPayloadCommits(repoPath, repoURL string) ([]*api.Pay
 
 // AvatarLink tries to match user in database with e-mail
 // in order to show custom avatar, and falls back to general avatar link.
-func (push *PushCommits) AvatarLink(email string) string {
-	_, ok := push.avatars[email]
+func (pcs *PushCommits) AvatarLink(email string) string {
+	_, ok := pcs.avatars[email]
 	if !ok {
 		u, err := GetUserByEmail(email)
 		if err != nil {
-			push.avatars[email] = tool.AvatarLink(email)
+			pcs.avatars[email] = tool.AvatarLink(email)
 			if !errors.IsUserNotExist(err) {
 				log.Error("GetUserByEmail: %v", err)
 			}
 		} else {
-			push.avatars[email] = u.RelAvatarLink()
+			pcs.avatars[email] = u.RelAvatarLink()
 		}
 	}
 
-	return push.avatars[email]
+	return pcs.avatars[email]
 }
 
 // UpdateIssuesCommit checks if issues are manipulated by commit message.
@@ -474,12 +474,12 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
 		return fmt.Errorf("UpdateRepository: %v", err)
 	}
 
-	isNewRef := opts.OldCommitID == git.EMPTY_SHA
-	isDelRef := opts.NewCommitID == git.EMPTY_SHA
+	isNewRef := opts.OldCommitID == git.EmptyID
+	isDelRef := opts.NewCommitID == git.EmptyID
 
 	opType := ACTION_COMMIT_REPO
 	// Check if it's tag push or branch.
-	if strings.HasPrefix(opts.RefFullName, git.TAG_PREFIX) {
+	if strings.HasPrefix(opts.RefFullName, git.RefsTags) {
 		opType = ACTION_PUSH_TAG
 	} else {
 		// if not the first commit, set the compare URL.
@@ -504,7 +504,7 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
 		return fmt.Errorf("Marshal: %v", err)
 	}
 
-	refName := git.RefEndName(opts.RefFullName)
+	refName := git.RefShortName(opts.RefFullName)
 	action := &Action{
 		ActUserID:    pusher.ID,
 		ActUserName:  pusher.Name,

+ 0 - 194
internal/db/git_diff.go

@@ -1,194 +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 db
-
-import (
-	"bytes"
-	"fmt"
-	"html"
-	"html/template"
-	"io"
-
-	"github.com/sergi/go-diff/diffmatchpatch"
-	"golang.org/x/net/html/charset"
-	"golang.org/x/text/transform"
-
-	"github.com/G-Node/git-module"
-
-	"github.com/G-Node/gogs/internal/conf"
-	"github.com/G-Node/gogs/internal/template/highlight"
-	"github.com/G-Node/gogs/internal/tool"
-)
-
-type DiffSection struct {
-	*git.DiffSection
-}
-
-var (
-	addedCodePrefix   = []byte("<span class=\"added-code\">")
-	removedCodePrefix = []byte("<span class=\"removed-code\">")
-	codeTagSuffix     = []byte("</span>")
-)
-
-func diffToHTML(diffs []diffmatchpatch.Diff, lineType git.DiffLineType) template.HTML {
-	buf := bytes.NewBuffer(nil)
-
-	// Reproduce signs which are cutted for inline diff before.
-	switch lineType {
-	case git.DIFF_LINE_ADD:
-		buf.WriteByte('+')
-	case git.DIFF_LINE_DEL:
-		buf.WriteByte('-')
-	}
-
-	for i := range diffs {
-		switch {
-		case diffs[i].Type == diffmatchpatch.DiffInsert && lineType == git.DIFF_LINE_ADD:
-			buf.Write(addedCodePrefix)
-			buf.WriteString(html.EscapeString(diffs[i].Text))
-			buf.Write(codeTagSuffix)
-		case diffs[i].Type == diffmatchpatch.DiffDelete && lineType == git.DIFF_LINE_DEL:
-			buf.Write(removedCodePrefix)
-			buf.WriteString(html.EscapeString(diffs[i].Text))
-			buf.Write(codeTagSuffix)
-		case diffs[i].Type == diffmatchpatch.DiffEqual:
-			buf.WriteString(html.EscapeString(diffs[i].Text))
-		}
-	}
-
-	return template.HTML(buf.Bytes())
-}
-
-var diffMatchPatch = diffmatchpatch.New()
-
-func init() {
-	diffMatchPatch.DiffEditCost = 100
-}
-
-// ComputedInlineDiffFor computes inline diff for the given line.
-func (diffSection *DiffSection) ComputedInlineDiffFor(diffLine *git.DiffLine) template.HTML {
-	if conf.Git.DisableDiffHighlight {
-		return template.HTML(html.EscapeString(diffLine.Content[1:]))
-	}
-	var (
-		compareDiffLine *git.DiffLine
-		diff1           string
-		diff2           string
-	)
-
-	// try to find equivalent diff line. ignore, otherwise
-	switch diffLine.Type {
-	case git.DIFF_LINE_ADD:
-		compareDiffLine = diffSection.Line(git.DIFF_LINE_DEL, diffLine.RightIdx)
-		if compareDiffLine == nil {
-			return template.HTML(html.EscapeString(diffLine.Content))
-		}
-		diff1 = compareDiffLine.Content
-		diff2 = diffLine.Content
-	case git.DIFF_LINE_DEL:
-		compareDiffLine = diffSection.Line(git.DIFF_LINE_ADD, diffLine.LeftIdx)
-		if compareDiffLine == nil {
-			return template.HTML(html.EscapeString(diffLine.Content))
-		}
-		diff1 = diffLine.Content
-		diff2 = compareDiffLine.Content
-	default:
-		return template.HTML(html.EscapeString(diffLine.Content))
-	}
-
-	diffRecord := diffMatchPatch.DiffMain(diff1[1:], diff2[1:], true)
-	diffRecord = diffMatchPatch.DiffCleanupEfficiency(diffRecord)
-
-	return diffToHTML(diffRecord, diffLine.Type)
-}
-
-type DiffFile struct {
-	*git.DiffFile
-	Sections []*DiffSection
-}
-
-func (diffFile *DiffFile) HighlightClass() string {
-	return highlight.FileNameToHighlightClass(diffFile.Name)
-}
-
-type Diff struct {
-	*git.Diff
-	Files []*DiffFile
-}
-
-func NewDiff(gitDiff *git.Diff) *Diff {
-	diff := &Diff{
-		Diff:  gitDiff,
-		Files: make([]*DiffFile, gitDiff.NumFiles()),
-	}
-
-	// FIXME: detect encoding while parsing.
-	var buf bytes.Buffer
-	for i := range gitDiff.Files {
-		buf.Reset()
-
-		diff.Files[i] = &DiffFile{
-			DiffFile: gitDiff.Files[i],
-			Sections: make([]*DiffSection, gitDiff.Files[i].NumSections()),
-		}
-
-		for j := range gitDiff.Files[i].Sections {
-			diff.Files[i].Sections[j] = &DiffSection{
-				DiffSection: gitDiff.Files[i].Sections[j],
-			}
-
-			for k := range diff.Files[i].Sections[j].Lines {
-				buf.WriteString(diff.Files[i].Sections[j].Lines[k].Content)
-				buf.WriteString("\n")
-			}
-		}
-
-		charsetLabel, err := tool.DetectEncoding(buf.Bytes())
-		if charsetLabel != "UTF-8" && err == nil {
-			encoding, _ := charset.Lookup(charsetLabel)
-			if encoding != nil {
-				d := encoding.NewDecoder()
-				for j := range diff.Files[i].Sections {
-					for k := range diff.Files[i].Sections[j].Lines {
-						if c, _, err := transform.String(d, diff.Files[i].Sections[j].Lines[k].Content); err == nil {
-							diff.Files[i].Sections[j].Lines[k].Content = c
-						}
-					}
-				}
-			}
-		}
-	}
-
-	return diff
-}
-
-func ParsePatch(maxLines, maxLineCharacteres, maxFiles int, reader io.Reader) (*Diff, error) {
-	done := make(chan error)
-	var gitDiff *git.Diff
-	go func() {
-		gitDiff = git.ParsePatch(done, maxLines, maxLineCharacteres, maxFiles, reader)
-	}()
-
-	if err := <-done; err != nil {
-		return nil, fmt.Errorf("ParsePatch: %v", err)
-	}
-	return NewDiff(gitDiff), nil
-}
-
-func GetDiffRange(repoPath, beforeCommitID, afterCommitID string, maxLines, maxLineCharacteres, maxFiles int) (*Diff, error) {
-	gitDiff, err := git.GetDiffRange(repoPath, beforeCommitID, afterCommitID, maxLines, maxLineCharacteres, maxFiles)
-	if err != nil {
-		return nil, fmt.Errorf("GetDiffRange: %v", err)
-	}
-	return NewDiff(gitDiff), nil
-}
-
-func GetDiffCommit(repoPath, commitID string, maxLines, maxLineCharacteres, maxFiles int) (*Diff, error) {
-	gitDiff, err := git.GetDiffCommit(repoPath, commitID, maxLines, maxLineCharacteres, maxFiles)
-	if err != nil {
-		return nil, fmt.Errorf("GetDiffCommit: %v", err)
-	}
-	return NewDiff(gitDiff), nil
-}

+ 0 - 35
internal/db/git_diff_test.go

@@ -1,35 +0,0 @@
-// Copyright 2016 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 db
-
-import (
-	"html/template"
-	"testing"
-
-	"github.com/G-Node/git-module"
-	dmp "github.com/sergi/go-diff/diffmatchpatch"
-)
-
-func assertEqual(t *testing.T, s1 string, s2 template.HTML) {
-	if s1 != string(s2) {
-		t.Errorf("%s should be equal %s", s2, s1)
-	}
-}
-
-func Test_diffToHTML(t *testing.T) {
-	assertEqual(t, "+foo <span class=\"added-code\">bar</span> biz", diffToHTML([]dmp.Diff{
-		{Type: dmp.DiffEqual, Text: "foo "},
-		{Type: dmp.DiffInsert, Text: "bar"},
-		{Type: dmp.DiffDelete, Text: " baz"},
-		{Type: dmp.DiffEqual, Text: " biz"},
-	}, git.DiffLineAdd))
-
-	assertEqual(t, "-foo <span class=\"removed-code\">bar</span> biz", diffToHTML([]dmp.Diff{
-		{Type: dmp.DiffEqual, Text: "foo "},
-		{Type: dmp.DiffDelete, Text: "bar"},
-		{Type: dmp.DiffInsert, Text: " baz"},
-		{Type: dmp.DiffEqual, Text: " biz"},
-	}, git.DiffLineDel))
-}

+ 6 - 6
internal/db/migrations/v16.go

@@ -12,13 +12,13 @@ import (
 	log "unknwon.dev/clog/v2"
 	"xorm.io/xorm"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 
 	"github.com/G-Node/gogs/internal/conf"
 )
 
 func updateRepositorySizes(x *xorm.Engine) (err error) {
-	log.Info("This migration could take up to minutes, please be patient.")
+	log.Info("[migrations.v16] This migration could take up to minutes, please be patient.")
 	type Repository struct {
 		ID      int64
 		OwnerID int64
@@ -41,7 +41,7 @@ func updateRepositorySizes(x *xorm.Engine) (err error) {
 			Find(&repos); err != nil {
 			return fmt.Errorf("select repos [offset: %d]: %v", offset, err)
 		}
-		log.Trace("Select [offset: %d, repos: %d]", offset, len(repos))
+		log.Trace("[migrations.v16] Select [offset: %d, repos: %d]", offset, len(repos))
 		if len(repos) == 0 {
 			break
 		}
@@ -60,10 +60,10 @@ func updateRepositorySizes(x *xorm.Engine) (err error) {
 				continue
 			}
 
-			repoPath := filepath.Join(conf.Repository.Root, strings.ToLower(user.Name), strings.ToLower(repo.Name)) + ".git"
-			countObject, err := git.GetRepoSize(repoPath)
+			repoPath := strings.ToLower(filepath.Join(conf.Repository.Root, user.Name, repo.Name)) + ".git"
+			countObject, err := git.RepoCountObjects(repoPath)
 			if err != nil {
-				log.Warn("GetRepoSize: %v", err)
+				log.Warn("[migrations.v16] Count repository objects: %v", err)
 				continue
 			}
 

+ 53 - 104
internal/db/mirror.go

@@ -5,7 +5,6 @@
 package db
 
 import (
-	"container/list"
 	"fmt"
 	"net/url"
 	"strings"
@@ -16,7 +15,7 @@ import (
 	log "unknwon.dev/clog/v2"
 	"xorm.io/xorm"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 
 	"github.com/G-Node/gogs/internal/conf"
 	"github.com/G-Node/gogs/internal/db/errors"
@@ -72,54 +71,17 @@ func (m *Mirror) ScheduleNextSync() {
 	m.NextSync = time.Now().Add(time.Duration(m.Interval) * time.Hour)
 }
 
-// findPasswordInMirrorAddress returns start (inclusive) and end index (exclusive)
-// of password portion of credentials in given mirror address.
-// It returns a boolean value to indicate whether password portion is found.
-func findPasswordInMirrorAddress(addr string) (start int, end int, found bool) {
-	// Find end of credentials (start of path)
-	end = strings.LastIndex(addr, "@")
-	if end == -1 {
-		return -1, -1, false
-	}
-
-	// Find delimiter of credentials (end of username)
-	start = strings.Index(addr, "://")
-	if start == -1 {
-		return -1, -1, false
-	}
-	start += 3
-	delim := strings.Index(addr[start:], ":")
-	if delim == -1 {
-		return -1, -1, false
-	}
-	delim += 1
-
-	if start+delim >= end {
-		return -1, -1, false // No password portion presented
-	}
-
-	return start + delim, end, true
-}
-
-// unescapeMirrorCredentials returns mirror address with unescaped credentials.
-func unescapeMirrorCredentials(addr string) string {
-	start, end, found := findPasswordInMirrorAddress(addr)
-	if !found {
-		return addr
-	}
-
-	password, _ := url.QueryUnescape(addr[start:end])
-	return addr[:start] + password + addr[end:]
-}
-
 func (m *Mirror) readAddress() {
 	if len(m.address) > 0 {
 		return
 	}
 
-	cfg, err := ini.Load(m.Repo.GitConfigPath())
+	cfg, err := ini.LoadSources(
+		ini.LoadOptions{IgnoreInlineComment: true},
+		m.Repo.GitConfigPath(),
+	)
 	if err != nil {
-		log.Error("Load: %v", err)
+		log.Error("load config: %v", err)
 		return
 	}
 	m.address = cfg.Section("remote \"origin\"").Key("url").Value()
@@ -128,6 +90,7 @@ func (m *Mirror) readAddress() {
 // HandleMirrorCredentials replaces user credentials from HTTP/HTTPS URL
 // with placeholder <credentials>.
 // It returns original string if protocol is not HTTP/HTTPS.
+// TODO(unknwon): Use url.Parse.
 func HandleMirrorCredentials(url string, mosaics bool) string {
 	i := strings.Index(url, "@")
 	if i == -1 {
@@ -161,34 +124,21 @@ func (m *Mirror) RawAddress() string {
 	return m.address
 }
 
-// FullAddress returns mirror address from Git repository config with unescaped credentials.
-func (m *Mirror) FullAddress() string {
-	m.readAddress()
-	return unescapeMirrorCredentials(m.address)
-}
-
-// escapeCredentials returns mirror address with escaped credentials.
-func escapeMirrorCredentials(addr string) string {
-	start, end, found := findPasswordInMirrorAddress(addr)
-	if !found {
-		return addr
-	}
-
-	return addr[:start] + url.QueryEscape(addr[start:end]) + addr[end:]
-}
-
 // SaveAddress writes new address to Git repository config.
 func (m *Mirror) SaveAddress(addr string) error {
 	repoPath := m.Repo.RepoPath()
 
-	err := git.RemoveRemote(repoPath, "origin")
+	err := git.RepoRemoveRemote(repoPath, "origin")
 	if err != nil {
 		return fmt.Errorf("remove remote 'origin': %v", err)
 	}
 
-	err = git.AddRemote(repoPath, "origin", addr, git.AddRemoteOptions{
-		Mirror: true,
-	})
+	addrURL, err := url.Parse(addr)
+	if err != nil {
+		return err
+	}
+
+	err = git.RepoAddRemote(repoPath, "origin", addrURL.String(), git.AddRemoteOptions{MirrorFetch: true})
 	if err != nil {
 		return fmt.Errorf("add remote 'origin': %v", err)
 	}
@@ -196,7 +146,7 @@ func (m *Mirror) SaveAddress(addr string) error {
 	return nil
 }
 
-const GIT_SHORT_EMPTY_SHA = "0000000"
+const gitShortEmptyID = "0000000"
 
 // mirrorSyncResult contains information of a updated reference.
 // If the oldCommitID is "0000000", it means a new reference, the value of newCommitID is empty.
@@ -223,12 +173,12 @@ func parseRemoteUpdateOutput(output string) []*mirrorSyncResult {
 		case strings.HasPrefix(lines[i], " * "): // New reference
 			results = append(results, &mirrorSyncResult{
 				refName:     refName,
-				oldCommitID: GIT_SHORT_EMPTY_SHA,
+				oldCommitID: gitShortEmptyID,
 			})
 		case strings.HasPrefix(lines[i], " - "): // Delete reference
 			results = append(results, &mirrorSyncResult{
 				refName:     refName,
-				newCommitID: GIT_SHORT_EMPTY_SHA,
+				newCommitID: gitShortEmptyID,
 			})
 		case strings.HasPrefix(lines[i], "   "): // New commits of a reference
 			delimIdx := strings.Index(lines[i][3:], " ")
@@ -262,10 +212,7 @@ func (m *Mirror) runSync() ([]*mirrorSyncResult, bool) {
 
 	// Do a fast-fail testing against on repository URL to ensure it is accessible under
 	// good condition to prevent long blocking on URL resolution without syncing anything.
-	if !git.IsRepoURLAccessible(git.NetworkOptions{
-		URL:     m.RawAddress(),
-		Timeout: 10 * time.Second,
-	}) {
+	if !git.IsURLAccessible(time.Minute, m.RawAddress()) {
 		desc := fmt.Sprintf("Source URL of mirror repository '%s' is not accessible: %s", m.Repo.FullName(), m.MosaicsAddress())
 		if err := CreateRepositoryNotice(desc); err != nil {
 			log.Error("CreateRepositoryNotice: %v", err)
@@ -393,15 +340,14 @@ func SyncMirrors() {
 		// - Create "Mirror Sync" webhook event
 		// - Create mirror sync (create, push and delete) events and trigger the "mirror sync" webhooks
 
-		var gitRepo *git.Repository
 		if len(results) == 0 {
 			log.Trace("SyncMirrors [repo_id: %d]: no commits fetched", m.RepoID)
-		} else {
-			gitRepo, err = git.OpenRepository(m.Repo.RepoPath())
-			if err != nil {
-				log.Error("OpenRepository [%d]: %v", m.RepoID, err)
-				continue
-			}
+		}
+
+		gitRepo, err := git.Open(m.Repo.RepoPath())
+		if err != nil {
+			log.Error("Failed to open repository [repo_id: %d]: %v", m.RepoID, err)
+			continue
 		}
 
 		for _, result := range results {
@@ -411,7 +357,7 @@ func SyncMirrors() {
 			}
 
 			// Delete reference
-			if result.newCommitID == GIT_SHORT_EMPTY_SHA {
+			if result.newCommitID == gitShortEmptyID {
 				if err = MirrorSyncDeleteAction(m.Repo, result.refName); err != nil {
 					log.Error("MirrorSyncDeleteAction [repo_id: %d]: %v", m.RepoID, err)
 				}
@@ -420,7 +366,7 @@ func SyncMirrors() {
 
 			// New reference
 			isNewRef := false
-			if result.oldCommitID == GIT_SHORT_EMPTY_SHA {
+			if result.oldCommitID == gitShortEmptyID {
 				if err = MirrorSyncCreateAction(m.Repo, result.refName); err != nil {
 					log.Error("MirrorSyncCreateAction [repo_id: %d]: %v", m.RepoID, err)
 					continue
@@ -429,49 +375,52 @@ func SyncMirrors() {
 			}
 
 			// Push commits
-			var commits *list.List
+			var commits []*git.Commit
 			var oldCommitID string
 			var newCommitID string
 			if !isNewRef {
-				oldCommitID, err = git.GetFullCommitID(gitRepo.Path, result.oldCommitID)
+				oldCommitID, err = gitRepo.RevParse(result.oldCommitID)
 				if err != nil {
-					log.Error("GetFullCommitID [%d]: %v", m.RepoID, err)
+					log.Error("Failed to parse revision [repo_id: %d, old_commit_id: %s]: %v", m.RepoID, result.oldCommitID, err)
 					continue
 				}
-				newCommitID, err = git.GetFullCommitID(gitRepo.Path, result.newCommitID)
+				newCommitID, err = gitRepo.RevParse(result.newCommitID)
 				if err != nil {
-					log.Error("GetFullCommitID [%d]: %v", m.RepoID, err)
+					log.Error("Failed to parse revision [repo_id: %d, new_commit_id: %s]: %v", m.RepoID, result.newCommitID, err)
 					continue
 				}
-				commits, err = gitRepo.CommitsBetweenIDs(newCommitID, oldCommitID)
+				commits, err = gitRepo.RevList([]string{oldCommitID + "..." + newCommitID})
 				if err != nil {
-					log.Error("CommitsBetweenIDs [repo_id: %d, new_commit_id: %s, old_commit_id: %s]: %v", m.RepoID, newCommitID, oldCommitID, err)
+					log.Error("Failed to list commits [repo_id: %d, old_commit_id: %s, new_commit_id: %s]: %v", m.RepoID, oldCommitID, newCommitID, err)
 					continue
 				}
-			} else {
-				refNewCommitID, err := gitRepo.GetBranchCommitID(result.refName)
+
+			} else if gitRepo.HasBranch(result.refName) {
+				refNewCommit, err := gitRepo.BranchCommit(result.refName)
 				if err != nil {
-					log.Error("GetFullCommitID [%d]: %v", m.RepoID, err)
+					log.Error("Failed to get branch commit [repo_id: %d, branch: %s]: %v", m.RepoID, result.refName, err)
 					continue
 				}
-				if newCommit, err := gitRepo.GetCommit(refNewCommitID); err != nil {
-					log.Error("GetCommit [repo_id: %d, commit_id: %s]: %v", m.RepoID, refNewCommitID, err)
+
+				// TODO(unknwon): Get the commits for the new ref until the closest ancestor branch like GitHub does.
+				commits, err = refNewCommit.Ancestors(git.LogOptions{MaxCount: 9})
+				if err != nil {
+					log.Error("Failed to get ancestors [repo_id: %d, commit_id: %s]: %v", m.RepoID, refNewCommit.ID, err)
 					continue
-				} else {
-					// TODO: Get the commits for the new ref until the closest ancestor branch like Github does
-					commits, err = newCommit.CommitsBeforeLimit(10)
-					if err != nil {
-						log.Error("CommitsBeforeLimit [repo_id: %d, commit_id: %s]: %v", m.RepoID, refNewCommitID, err)
-					}
-					oldCommitID = git.EMPTY_SHA
-					newCommitID = refNewCommitID
 				}
+
+				// Put the latest commit in front of ancestors
+				commits = append([]*git.Commit{refNewCommit}, commits...)
+
+				oldCommitID = git.EmptyID
+				newCommitID = refNewCommit.ID.String()
 			}
+
 			if err = MirrorSyncPushAction(m.Repo, MirrorSyncPushActionOptions{
 				RefName:     result.refName,
 				OldCommitID: oldCommitID,
 				NewCommitID: newCommitID,
-				Commits:     ListToPushCommits(commits),
+				Commits:     CommitsToPushCommits(commits),
 			}); err != nil {
 				log.Error("MirrorSyncPushAction [repo_id: %d]: %v", m.RepoID, err)
 				continue
@@ -485,15 +434,15 @@ func SyncMirrors() {
 
 		// Get latest commit date and compare to current repository updated time,
 		// update if latest commit date is newer.
-		commitDate, err := git.GetLatestCommitDate(m.Repo.RepoPath(), "")
+		latestCommitTime, err := gitRepo.LatestCommitTime()
 		if err != nil {
 			log.Error("GetLatestCommitDate [%d]: %v", m.RepoID, err)
 			continue
-		} else if commitDate.Before(m.Repo.Updated) {
+		} else if !latestCommitTime.After(m.Repo.Updated) {
 			continue
 		}
 
-		if _, err = x.Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", commitDate.Unix(), m.RepoID); err != nil {
+		if _, err = x.Exec("UPDATE repository SET updated_unix = ? WHERE id = ?", latestCommitTime.Unix(), m.RepoID); err != nil {
 			log.Error("Update 'repository.updated_unix' [%d]: %v", m.RepoID, err)
 			continue
 		}

+ 18 - 89
internal/db/mirror_test.go

@@ -7,102 +7,31 @@ package db
 import (
 	"testing"
 
-	. "github.com/smartystreets/goconvey/convey"
+	"github.com/stretchr/testify/assert"
 )
 
 func Test_parseRemoteUpdateOutput(t *testing.T) {
-	Convey("Parse mirror remote update output", t, func() {
-		testCases := []struct {
-			output  string
-			results []*mirrorSyncResult
-		}{
-			{
-				`
+	tests := []struct {
+		output     string
+		expResults []*mirrorSyncResult
+	}{
+		{
+			`
 From https://try.gogs.io/unknwon/upsteam
  * [new branch]      develop    -> develop
    b0bb24f..1d85a4f  master     -> master
  - [deleted]         (none)     -> bugfix
 `,
-				[]*mirrorSyncResult{
-					{"develop", GIT_SHORT_EMPTY_SHA, ""},
-					{"master", "b0bb24f", "1d85a4f"},
-					{"bugfix", "", GIT_SHORT_EMPTY_SHA},
-				},
+			[]*mirrorSyncResult{
+				{"develop", gitShortEmptyID, ""},
+				{"master", "b0bb24f", "1d85a4f"},
+				{"bugfix", "", gitShortEmptyID},
 			},
-		}
-
-		for _, tc := range testCases {
-			results := parseRemoteUpdateOutput(tc.output)
-			So(len(results), ShouldEqual, len(tc.results))
-
-			for i := range tc.results {
-				So(tc.results[i].refName, ShouldEqual, results[i].refName)
-				So(tc.results[i].oldCommitID, ShouldEqual, results[i].oldCommitID)
-				So(tc.results[i].newCommitID, ShouldEqual, results[i].newCommitID)
-			}
-		}
-	})
-}
-
-func Test_findPasswordInMirrorAddress(t *testing.T) {
-	Convey("Find password portion in mirror address", t, func() {
-		testCases := []struct {
-			addr       string
-			start, end int
-			found      bool
-			password   string
-		}{
-			{"http://localhost:3000/user/repo.git", -1, -1, false, ""},
-			{"http://user@localhost:3000/user/repo.git", -1, -1, false, ""},
-			{"http://user:@localhost:3000/user/repo.git", -1, -1, false, ""},
-			{"http://user:password@localhost:3000/user/repo.git", 12, 20, true, "password"},
-			{"http://username:my%3Asecure%3Bpassword@localhost:3000/user/repo.git", 16, 38, true, "my%3Asecure%3Bpassword"},
-			{"http://username:my%40secure%23password@localhost:3000/user/repo.git", 16, 38, true, "my%40secure%23password"},
-			{"http://username:@@localhost:3000/user/repo.git", 16, 17, true, "@"},
-		}
-
-		for _, tc := range testCases {
-			start, end, found := findPasswordInMirrorAddress(tc.addr)
-			So(start, ShouldEqual, tc.start)
-			So(end, ShouldEqual, tc.end)
-			So(found, ShouldEqual, tc.found)
-			if found {
-				So(tc.addr[start:end], ShouldEqual, tc.password)
-			}
-		}
-	})
-}
-
-func Test_unescapeMirrorCredentials(t *testing.T) {
-	Convey("Escape credentials in mirror address", t, func() {
-		testCases := []string{
-			"http://localhost:3000/user/repo.git", "http://localhost:3000/user/repo.git",
-			"http://user@localhost:3000/user/repo.git", "http://user@localhost:3000/user/repo.git",
-			"http://user:@localhost:3000/user/repo.git", "http://user:@localhost:3000/user/repo.git",
-			"http://user:password@localhost:3000/user/repo.git", "http://user:password@localhost:3000/user/repo.git",
-			"http://user:my%3Asecure%3Bpassword@localhost:3000/user/repo.git", "http://user:my:secure;password@localhost:3000/user/repo.git",
-			"http://user:my%40secure%23password@localhost:3000/user/repo.git", "http://user:my@secure#password@localhost:3000/user/repo.git",
-		}
-
-		for i := 0; i < len(testCases); i += 2 {
-			So(unescapeMirrorCredentials(testCases[i]), ShouldEqual, testCases[i+1])
-		}
-	})
-}
-
-func Test_escapeMirrorCredentials(t *testing.T) {
-	Convey("Escape credentials in mirror address", t, func() {
-		testCases := []string{
-			"http://localhost:3000/user/repo.git", "http://localhost:3000/user/repo.git",
-			"http://user@localhost:3000/user/repo.git", "http://user@localhost:3000/user/repo.git",
-			"http://user:@localhost:3000/user/repo.git", "http://user:@localhost:3000/user/repo.git",
-			"http://user:password@localhost:3000/user/repo.git", "http://user:password@localhost:3000/user/repo.git",
-			"http://user:my:secure;password@localhost:3000/user/repo.git", "http://user:my%3Asecure%3Bpassword@localhost:3000/user/repo.git",
-			"http://user:my@secure#password@localhost:3000/user/repo.git", "http://user:my%40secure%23password@localhost:3000/user/repo.git",
-		}
-
-		for i := 0; i < len(testCases); i += 2 {
-			So(escapeMirrorCredentials(testCases[i]), ShouldEqual, testCases[i+1])
-		}
-	})
+		},
+	}
+	for _, test := range tests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.expResults, parseRemoteUpdateOutput(test.output))
+		})
+	}
 }

+ 3 - 3
internal/db/models_gin.go

@@ -8,10 +8,10 @@ import (
 	"path/filepath"
 	"strings"
 
-	"github.com/G-Node/git-module"
 	"github.com/G-Node/gogs/internal/conf"
 	"github.com/G-Node/libgin/libgin"
 	"github.com/G-Node/libgin/libgin/annex"
+	"github.com/gogs/git-module"
 	log "gopkg.in/clog.v1"
 )
 
@@ -150,9 +150,9 @@ func annexSync(path string) error {
 func annexAdd(repoPath string, all bool, files ...string) error {
 	cmd := git.NewCommand("annex", "add")
 	if all {
-		cmd.AddArguments(".")
+		cmd.AddArgs(".")
 	}
-	_, err := cmd.AddArguments(files...).RunInDir(repoPath)
+	_, err := cmd.AddArgs(files...).RunInDir(repoPath)
 	return err
 }
 

+ 59 - 44
internal/db/pull.go

@@ -7,7 +7,6 @@ package db
 import (
 	"fmt"
 	"os"
-	"path"
 	"path/filepath"
 	"strings"
 	"time"
@@ -16,7 +15,7 @@ import (
 	log "unknwon.dev/clog/v2"
 	"xorm.io/xorm"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 	api "github.com/gogs/go-gogs-client"
 
 	"github.com/G-Node/gogs/internal/conf"
@@ -212,9 +211,9 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
 	}
 
 	headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name)
-	headGitRepo, err := git.OpenRepository(headRepoPath)
+	headGitRepo, err := git.Open(headRepoPath)
 	if err != nil {
-		return fmt.Errorf("OpenRepository: %v", err)
+		return fmt.Errorf("open repository: %v", err)
 	}
 
 	// Create temporary directory to store temporary copy of the base repository,
@@ -228,7 +227,7 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
 	var stderr string
 	if _, stderr, err = process.ExecTimeout(5*time.Minute,
 		fmt.Sprintf("PullRequest.Merge (git clone): %s", tmpBasePath),
-		"git", "clone", "-b", pr.BaseBranch, baseGitRepo.Path, tmpBasePath); err != nil {
+		"git", "clone", "-b", pr.BaseBranch, baseGitRepo.Path(), tmpBasePath); err != nil {
 		return fmt.Errorf("git clone: %s", stderr)
 	}
 
@@ -311,13 +310,13 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
 	// Push changes on base branch to upstream.
 	if _, stderr, err = process.ExecDir(-1, tmpBasePath,
 		fmt.Sprintf("PullRequest.Merge (git push): %s", tmpBasePath),
-		"git", "push", baseGitRepo.Path, pr.BaseBranch); err != nil {
+		"git", "push", baseGitRepo.Path(), pr.BaseBranch); err != nil {
 		return fmt.Errorf("git push: %s", stderr)
 	}
 
-	pr.MergedCommitID, err = headGitRepo.GetBranchCommitID(pr.HeadBranch)
+	pr.MergedCommitID, err = headGitRepo.BranchCommitID(pr.HeadBranch)
 	if err != nil {
-		return fmt.Errorf("GetBranchCommit: %v", err)
+		return fmt.Errorf("get head branch %q commit ID: %v", pr.HeadBranch, err)
 	}
 
 	pr.HasMerged = true
@@ -351,42 +350,42 @@ func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository, mergeStyle
 		return nil
 	}
 
-	l, err := headGitRepo.CommitsBetweenIDs(pr.MergedCommitID, pr.MergeBase)
+	commits, err := headGitRepo.RevList([]string{pr.MergeBase + "..." + pr.MergedCommitID})
 	if err != nil {
-		log.Error("CommitsBetweenIDs: %v", err)
+		log.Error("Failed to list commits [merge_base: %s, merged_commit_id: %s]: %v", pr.MergeBase, pr.MergedCommitID, err)
 		return nil
 	}
 
-	// It is possible that head branch is not fully sync with base branch for merge commits,
-	// so we need to get latest head commit and append merge commit manully
-	// to avoid strange diff commits produced.
-	mergeCommit, err := baseGitRepo.GetBranchCommit(pr.BaseBranch)
+	// NOTE: It is possible that head branch is not fully sync with base branch
+	// for merge commits, so we need to get latest head commit and append merge
+	// commit manully to avoid strange diff commits produced.
+	mergeCommit, err := baseGitRepo.BranchCommit(pr.BaseBranch)
 	if err != nil {
-		log.Error("GetBranchCommit: %v", err)
+		log.Error("Failed to get base branch %q commit: %v", pr.BaseBranch, err)
 		return nil
 	}
 	if mergeStyle == MERGE_STYLE_REGULAR {
-		l.PushFront(mergeCommit)
+		commits = append([]*git.Commit{mergeCommit}, commits...)
 	}
 
-	commits, err := ListToPushCommits(l).ToApiPayloadCommits(pr.BaseRepo.RepoPath(), pr.BaseRepo.HTMLURL())
+	pcs, err := CommitsToPushCommits(commits).ToApiPayloadCommits(pr.BaseRepo.RepoPath(), pr.BaseRepo.HTMLURL())
 	if err != nil {
-		log.Error("ToApiPayloadCommits: %v", err)
+		log.Error("Failed to convert to API payload commits: %v", err)
 		return nil
 	}
 
 	p := &api.PushPayload{
-		Ref:        git.BRANCH_PREFIX + pr.BaseBranch,
+		Ref:        git.RefsHeads + pr.BaseBranch,
 		Before:     pr.MergeBase,
 		After:      mergeCommit.ID.String(),
 		CompareURL: conf.Server.ExternalURL + pr.BaseRepo.ComposeCompareURL(pr.MergeBase, pr.MergedCommitID),
-		Commits:    commits,
+		Commits:    pcs,
 		Repo:       pr.BaseRepo.APIFormat(nil),
 		Pusher:     pr.HeadRepo.MustOwner().APIFormat(),
 		Sender:     doer.APIFormat(),
 	}
 	if err = PrepareWebhooks(pr.BaseRepo, HOOK_EVENT_PUSH, p); err != nil {
-		log.Error("PrepareWebhooks: %v", err)
+		log.Error("Failed to prepare webhooks: %v", err)
 		return nil
 	}
 	return nil
@@ -599,36 +598,42 @@ func (pr *PullRequest) UpdatePatch() (err error) {
 		return nil
 	}
 
-	headGitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath())
+	headGitRepo, err := git.Open(pr.HeadRepo.RepoPath())
 	if err != nil {
-		return fmt.Errorf("OpenRepository: %v", err)
+		return fmt.Errorf("open repository: %v", err)
 	}
 
 	// Add a temporary remote.
 	tmpRemote := com.ToStr(time.Now().UnixNano())
-	if err = headGitRepo.AddRemote(tmpRemote, RepoPath(pr.BaseRepo.MustOwner().Name, pr.BaseRepo.Name), true); err != nil {
-		return fmt.Errorf("AddRemote: %v", err)
+	baseRepoPath := RepoPath(pr.BaseRepo.MustOwner().Name, pr.BaseRepo.Name)
+	err = headGitRepo.AddRemote(tmpRemote, baseRepoPath, git.AddRemoteOptions{Fetch: true})
+	if err != nil {
+		return fmt.Errorf("add remote %q [repo_id: %d]: %v", tmpRemote, pr.HeadRepoID, err)
 	}
 	defer func() {
-		headGitRepo.RemoveRemote(tmpRemote)
+		if err := headGitRepo.RemoveRemote(tmpRemote); err != nil {
+			log.Error("Failed to remove remote %q [repo_id: %d]: %v", tmpRemote, pr.HeadRepoID, err)
+		}
 	}()
+
 	remoteBranch := "remotes/" + tmpRemote + "/" + pr.BaseBranch
-	pr.MergeBase, err = headGitRepo.GetMergeBase(remoteBranch, pr.HeadBranch)
+	pr.MergeBase, err = headGitRepo.MergeBase(remoteBranch, pr.HeadBranch)
 	if err != nil {
-		return fmt.Errorf("GetMergeBase: %v", err)
+		return fmt.Errorf("get merge base: %v", err)
 	} else if err = pr.Update(); err != nil {
-		return fmt.Errorf("Update: %v", err)
+		return fmt.Errorf("update: %v", err)
 	}
 
-	patch, err := headGitRepo.GetPatch(pr.MergeBase, pr.HeadBranch)
+	patch, err := headGitRepo.DiffBinary(pr.MergeBase, pr.HeadBranch)
 	if err != nil {
-		return fmt.Errorf("GetPatch: %v", err)
+		return fmt.Errorf("get binary patch: %v", err)
 	}
 
 	if err = pr.BaseRepo.SavePatch(pr.Index, patch); err != nil {
-		return fmt.Errorf("BaseRepo.SavePatch: %v", err)
+		return fmt.Errorf("save patch: %v", err)
 	}
 
+	log.Trace("PullRequest[%d].UpdatePatch: patch saved", pr.ID)
 	return nil
 }
 
@@ -639,25 +644,35 @@ func (pr *PullRequest) PushToBaseRepo() (err error) {
 	log.Trace("PushToBaseRepo[%d]: pushing commits to base repo 'refs/pull/%d/head'", pr.BaseRepoID, pr.Index)
 
 	headRepoPath := pr.HeadRepo.RepoPath()
-	headGitRepo, err := git.OpenRepository(headRepoPath)
+	headGitRepo, err := git.Open(headRepoPath)
 	if err != nil {
-		return fmt.Errorf("OpenRepository: %v", err)
+		return fmt.Errorf("open repository: %v", err)
 	}
 
-	tmpRemoteName := fmt.Sprintf("tmp-pull-%d", pr.ID)
-	if err = headGitRepo.AddRemote(tmpRemoteName, pr.BaseRepo.RepoPath(), false); err != nil {
-		return fmt.Errorf("headGitRepo.AddRemote: %v", err)
+	tmpRemote := fmt.Sprintf("tmp-pull-%d", pr.ID)
+	if err = headGitRepo.AddRemote(tmpRemote, pr.BaseRepo.RepoPath()); err != nil {
+		return fmt.Errorf("add remote %q [repo_id: %d]: %v", tmpRemote, pr.HeadRepoID, err)
 	}
-	// Make sure to remove the remote even if the push fails
-	defer headGitRepo.RemoveRemote(tmpRemoteName)
 
-	headFile := fmt.Sprintf("refs/pull/%d/head", pr.Index)
+	// Make sure to remove the remote even if the push fails
+	defer func() {
+		if err := headGitRepo.RemoveRemote(tmpRemote); err != nil {
+			log.Error("Failed to remove remote %q [repo_id: %d]: %v", tmpRemote, pr.HeadRepoID, err)
+		}
+	}()
 
-	// Remove head in case there is a conflict.
-	os.Remove(path.Join(pr.BaseRepo.RepoPath(), headFile))
+	headRefspec := fmt.Sprintf("refs/pull/%d/head", pr.Index)
+	headFile := filepath.Join(pr.BaseRepo.RepoPath(), headRefspec)
+	if osutil.IsExist(headFile) {
+		err = os.Remove(headFile)
+		if err != nil {
+			return fmt.Errorf("remove head file [repo_id: %d]: %v", pr.BaseRepoID, err)
+		}
+	}
 
-	if err = git.Push(headRepoPath, tmpRemoteName, fmt.Sprintf("%s:%s", pr.HeadBranch, headFile)); err != nil {
-		return fmt.Errorf("Push: %v", err)
+	err = headGitRepo.Push(tmpRemote, fmt.Sprintf("%s:%s", pr.HeadBranch, headRefspec))
+	if err != nil {
+		return fmt.Errorf("push: %v", err)
 	}
 
 	return nil

+ 7 - 7
internal/db/release.go

@@ -13,7 +13,7 @@ import (
 	log "unknwon.dev/clog/v2"
 	"xorm.io/xorm"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 	api "github.com/gogs/go-gogs-client"
 
 	"github.com/G-Node/gogs/internal/db/errors"
@@ -119,10 +119,10 @@ func IsReleaseExist(repoID int64, tagName string) (bool, error) {
 func createTag(gitRepo *git.Repository, r *Release) error {
 	// Only actual create when publish.
 	if !r.IsDraft {
-		if !gitRepo.IsTagExist(r.TagName) {
-			commit, err := gitRepo.GetBranchCommit(r.Target)
+		if !gitRepo.HasTag(r.TagName) {
+			commit, err := gitRepo.BranchCommit(r.Target)
 			if err != nil {
-				return fmt.Errorf("GetBranchCommit: %v", err)
+				return fmt.Errorf("get branch commit: %v", err)
 			}
 
 			// Trim '--' prefix to prevent command line argument vulnerability.
@@ -134,15 +134,15 @@ func createTag(gitRepo *git.Repository, r *Release) error {
 				return err
 			}
 		} else {
-			commit, err := gitRepo.GetTagCommit(r.TagName)
+			commit, err := gitRepo.TagCommit(r.TagName)
 			if err != nil {
-				return fmt.Errorf("GetTagCommit: %v", err)
+				return fmt.Errorf("get tag commit: %v", err)
 			}
 
 			r.Sha1 = commit.ID.String()
 			r.NumCommits, err = commit.CommitsCount()
 			if err != nil {
-				return fmt.Errorf("CommitsCount: %v", err)
+				return fmt.Errorf("count commits: %v", err)
 			}
 		}
 	}

+ 62 - 57
internal/db/repo.go

@@ -27,7 +27,7 @@ import (
 	log "unknwon.dev/clog/v2"
 	"xorm.io/xorm"
 
-	git "github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 	api "github.com/gogs/go-gogs-client"
 
 	"github.com/G-Node/gogs/internal/avatar"
@@ -119,9 +119,6 @@ func NewRepoContext() {
 	if version.Compare("1.8.3", conf.Git.Version, ">") {
 		log.Fatal("Gogs requires Git version greater or equal to 1.8.3")
 	}
-	git.HookDir = "custom_hooks"
-	git.HookSampleDir = "hooks"
-	git.DefaultCommitsPageSize = conf.UI.User.CommitsPagingNum
 
 	// Git requires setting user.name and user.email in order to commit changes.
 	for configKey, defaultValue := range map[string]string{"user.name": "Gogs", "user.email": "gogs@fake.local"} {
@@ -421,9 +418,9 @@ func (repo *Repository) mustOwner(e Engine) *User {
 }
 
 func (repo *Repository) UpdateSize() error {
-	countObject, err := git.GetRepoSize(repo.RepoPath())
+	countObject, err := git.RepoCountObjects(repo.RepoPath())
 	if err != nil {
-		return fmt.Errorf("GetRepoSize: %v", err)
+		return fmt.Errorf("count repository objects: %v", err)
 	}
 
 	repo.Size = countObject.Size + countObject.SizePack
@@ -603,33 +600,41 @@ func (repo *Repository) LocalCopyPath() string {
 // assume subsequent operations are against target branch when caller has confidence
 // about no race condition.
 func UpdateLocalCopyBranch(repoPath, localPath, branch string, isWiki bool) (err error) {
-	if !com.IsExist(localPath) {
+	if !osutil.IsExist(localPath) {
 		// Checkout to a specific branch fails when wiki is an empty repository.
 		if isWiki {
 			branch = ""
 		}
-		if err = git.Clone(repoPath, localPath, git.CloneRepoOptions{
-			Timeout: time.Duration(conf.Git.Timeout.Clone) * time.Second,
+		if err = git.Clone(repoPath, localPath, git.CloneOptions{
 			Branch:  branch,
+			Timeout: time.Duration(conf.Git.Timeout.Clone) * time.Second,
 		}); err != nil {
-			return fmt.Errorf("git clone %s: %v", branch, err)
-		}
-	} else {
-		if err = git.Fetch(localPath, git.FetchRemoteOptions{
-			Prune: true,
-		}); err != nil {
-			return fmt.Errorf("git fetch: %v", err)
-		}
-		if err = git.Checkout(localPath, git.CheckoutOptions{
-			Branch: branch,
-		}); err != nil {
-			return fmt.Errorf("git checkout %s: %v", branch, err)
+			return fmt.Errorf("git clone [branch: %s]: %v", branch, err)
 		}
+		return nil
+	}
 
-		// Reset to align with remote in case of force push.
-		if err = git.ResetHEAD(localPath, true, "origin/"+branch); err != nil {
-			return fmt.Errorf("git reset --hard origin/%s: %v", branch, err)
-		}
+	gitRepo, err := git.Open(localPath)
+	if err != nil {
+		return fmt.Errorf("open repository: %v", err)
+	}
+
+	if err = gitRepo.Fetch(git.FetchOptions{
+		Prune: true,
+	}); err != nil {
+		return fmt.Errorf("fetch: %v", err)
+	}
+
+	if err = gitRepo.Checkout(branch); err != nil {
+		return fmt.Errorf("checkout [branch: %s]: %v", branch, err)
+	}
+
+	// Reset to align with remote in case of force push.
+	rev := "origin/" + branch
+	if err = gitRepo.Reset(rev, git.ResetOptions{
+		Hard: true,
+	}); err != nil {
+		return fmt.Errorf("reset [revision: %s]: %v", rev, err)
 	}
 	return nil
 }
@@ -732,9 +737,7 @@ func wikiRemoteURL(remote string) string {
 	remote = strings.TrimSuffix(remote, ".git")
 	for _, suffix := range commonWikiURLSuffixes {
 		wikiURL := remote + suffix
-		if git.IsRepoURLAccessible(git.NetworkOptions{
-			URL: wikiURL,
-		}) {
+		if git.IsURLAccessible(time.Minute, wikiURL) {
 			return wikiURL
 		}
 	}
@@ -769,23 +772,23 @@ func MigrateRepository(doer, owner *User, opts MigrateRepoOptions) (*Repository,
 	migrateTimeout := time.Duration(conf.Git.Timeout.Migrate) * time.Second
 
 	RemoveAllWithNotice("Repository path erase before creation", repoPath)
-	if err = git.Clone(opts.RemoteAddr, repoPath, git.CloneRepoOptions{
+	if err = git.Clone(opts.RemoteAddr, repoPath, git.CloneOptions{
 		Mirror:  true,
 		Quiet:   true,
 		Timeout: migrateTimeout,
 	}); err != nil {
-		return repo, fmt.Errorf("Clone: %v", err)
+		return repo, fmt.Errorf("clone: %v", err)
 	}
 
 	wikiRemotePath := wikiRemoteURL(opts.RemoteAddr)
 	if len(wikiRemotePath) > 0 {
 		RemoveAllWithNotice("Repository wiki path erase before creation", wikiPath)
-		if err = git.Clone(wikiRemotePath, wikiPath, git.CloneRepoOptions{
+		if err = git.Clone(wikiRemotePath, wikiPath, git.CloneOptions{
 			Mirror:  true,
 			Quiet:   true,
 			Timeout: migrateTimeout,
 		}); err != nil {
-			log.Trace("Failed to clone wiki: %v", err)
+			log.Error("Failed to clone wiki: %v", err)
 			RemoveAllWithNotice("Delete repository wiki for initialization failure", wikiPath)
 		}
 	}
@@ -802,17 +805,15 @@ func MigrateRepository(doer, owner *User, opts MigrateRepoOptions) (*Repository,
 
 	if !repo.IsBare {
 		// Try to get HEAD branch and set it as default branch.
-		gitRepo, err := git.OpenRepository(repoPath)
+		gitRepo, err := git.Open(repoPath)
 		if err != nil {
-			return repo, fmt.Errorf("OpenRepository: %v", err)
+			return repo, fmt.Errorf("open repository: %v", err)
 		}
-		headBranch, err := gitRepo.GetHEADBranch()
+		refspec, err := gitRepo.SymbolicRef()
 		if err != nil {
-			return repo, fmt.Errorf("GetHEADBranch: %v", err)
-		}
-		if headBranch != nil {
-			repo.DefaultBranch = headBranch.Name
+			return repo, fmt.Errorf("get HEAD branch: %v", err)
 		}
+		repo.DefaultBranch = git.RefShortName(refspec)
 
 		if err = repo.UpdateSize(); err != nil {
 			log.Error("UpdateSize [repo_id: %d]: %v", repo.ID, err)
@@ -850,15 +851,15 @@ func cleanUpMigrateGitConfig(configPath string) error {
 	return nil
 }
 
-var hooksTpls = map[string]string{
+var hooksTpls = map[git.HookName]string{
 	"pre-receive":  "#!/usr/bin/env %s\n\"%s\" hook --config='%s' pre-receive\n",
 	"update":       "#!/usr/bin/env %s\n\"%s\" hook --config='%s' update $1 $2 $3\n",
 	"post-receive": "#!/usr/bin/env %s\n\"%s\" hook --config='%s' post-receive\n",
 }
 
 func createDelegateHooks(repoPath string) (err error) {
-	for _, name := range git.HookNames {
-		hookPath := filepath.Join(repoPath, "hooks", name)
+	for _, name := range git.ServerSideHooks {
+		hookPath := filepath.Join(repoPath, "hooks", string(name))
 		if err = ioutil.WriteFile(hookPath,
 			[]byte(fmt.Sprintf(hooksTpls[name], conf.Repository.ScriptType, conf.AppPath(), conf.CustomConf)),
 			os.ModePerm); err != nil {
@@ -1008,8 +1009,8 @@ func initRepository(e Engine, repoPath string, doer *User, repo *Repository, opt
 	}
 
 	// Init bare new repository.
-	if err = git.InitRepository(repoPath, true); err != nil {
-		return fmt.Errorf("InitRepository: %v", err)
+	if err = git.Init(repoPath, git.InitOptions{Bare: true}); err != nil {
+		return fmt.Errorf("init repository: %v", err)
 	} else if err = createDelegateHooks(repoPath); err != nil {
 		return fmt.Errorf("createDelegateHooks: %v", err)
 	}
@@ -1883,9 +1884,9 @@ func ReinitMissingRepositories() error {
 
 	for _, repo := range repos {
 		log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID)
-		if err := git.InitRepository(repo.RepoPath(), true); err != nil {
-			if err2 := CreateRepositoryNotice(fmt.Sprintf("InitRepository [%d]: %v", repo.ID, err)); err2 != nil {
-				return fmt.Errorf("CreateRepositoryNotice: %v", err)
+		if err := git.Init(repo.RepoPath(), git.InitOptions{Bare: true}); err != nil {
+			if err2 := CreateRepositoryNotice(fmt.Sprintf("init repository [repo_id: %d]: %v", repo.ID, err)); err2 != nil {
+				return fmt.Errorf("create repository notice: %v", err)
 			}
 		}
 	}
@@ -1933,7 +1934,11 @@ func GitFsck() {
 		func(idx int, bean interface{}) error {
 			repo := bean.(*Repository)
 			repoPath := repo.RepoPath()
-			if err := git.Fsck(repoPath, conf.Cron.RepoHealthCheck.Timeout, conf.Cron.RepoHealthCheck.Args...); err != nil {
+			err := git.RepoFsck(repoPath, git.FsckOptions{
+				Args:    conf.Cron.RepoHealthCheck.Args,
+				Timeout: conf.Cron.RepoHealthCheck.Timeout,
+			})
+			if err != nil {
 				desc := fmt.Sprintf("Failed to perform health check on repository '%s': %v", repoPath, err)
 				log.Warn(desc)
 				if err = CreateRepositoryNotice(desc); err != nil {
@@ -2444,24 +2449,24 @@ func (repo *Repository) GetForks() ([]*Repository, error) {
 //         \/             \/     \/     \/     \/
 //
 
-func (repo *Repository) CreateNewBranch(doer *User, oldBranchName, branchName string) (err error) {
+func (repo *Repository) CreateNewBranch(oldBranch, newBranch string) (err error) {
 	repoWorkingPool.CheckIn(com.ToStr(repo.ID))
 	defer repoWorkingPool.CheckOut(com.ToStr(repo.ID))
 
 	localPath := repo.LocalCopyPath()
 
-	if err = discardLocalRepoBranchChanges(localPath, oldBranchName); err != nil {
-		return fmt.Errorf("discardLocalRepoChanges: %v", err)
-	} else if err = repo.UpdateLocalCopyBranch(oldBranchName); err != nil {
-		return fmt.Errorf("UpdateLocalCopyBranch: %v", err)
+	if err = discardLocalRepoBranchChanges(localPath, oldBranch); err != nil {
+		return fmt.Errorf("discard changes in local copy [path: %s, branch: %s]: %v", localPath, oldBranch, err)
+	} else if err = repo.UpdateLocalCopyBranch(oldBranch); err != nil {
+		return fmt.Errorf("update branch for local copy [path: %s, branch: %s]: %v", localPath, oldBranch, err)
 	}
 
-	if err = repo.CheckoutNewBranch(oldBranchName, branchName); err != nil {
-		return fmt.Errorf("CreateNewBranch: %v", err)
+	if err = repo.CheckoutNewBranch(oldBranch, newBranch); err != nil {
+		return fmt.Errorf("create new branch [base: %s, new: %s]: %v", oldBranch, newBranch, err)
 	}
 
-	if err = git.Push(localPath, "origin", branchName); err != nil {
-		return fmt.Errorf("Push: %v", err)
+	if err = git.RepoPush(localPath, "origin", newBranch); err != nil {
+		return fmt.Errorf("push [branch: %s]: %v", newBranch, err)
 	}
 
 	return nil

+ 15 - 15
internal/db/repo_branch.go

@@ -8,7 +8,7 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 	"github.com/unknwon/com"
 
 	"github.com/G-Node/gogs/internal/db/errors"
@@ -24,33 +24,33 @@ type Branch struct {
 }
 
 func GetBranchesByPath(path string) ([]*Branch, error) {
-	gitRepo, err := git.OpenRepository(path)
+	gitRepo, err := git.Open(path)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("open repository: %v", err)
 	}
 
-	brs, err := gitRepo.GetBranches()
+	names, err := gitRepo.Branches()
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("list branches")
 	}
 
-	branches := make([]*Branch, len(brs))
-	for i := range brs {
+	branches := make([]*Branch, len(names))
+	for i := range names {
 		branches[i] = &Branch{
 			RepoPath: path,
-			Name:     brs[i],
+			Name:     names[i],
 		}
 	}
 	return branches, nil
 }
 
-func (repo *Repository) GetBranch(br string) (*Branch, error) {
-	if !git.IsBranchExist(repo.RepoPath(), br) {
-		return nil, errors.ErrBranchNotExist{Name: br}
+func (repo *Repository) GetBranch(name string) (*Branch, error) {
+	if !git.RepoHasBranch(repo.RepoPath(), name) {
+		return nil, errors.ErrBranchNotExist{Name: name}
 	}
 	return &Branch{
 		RepoPath: repo.RepoPath(),
-		Name:     br,
+		Name:     name,
 	}, nil
 }
 
@@ -59,11 +59,11 @@ func (repo *Repository) GetBranches() ([]*Branch, error) {
 }
 
 func (br *Branch) GetCommit() (*git.Commit, error) {
-	gitRepo, err := git.OpenRepository(br.RepoPath)
+	gitRepo, err := git.Open(br.RepoPath)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("open repository: %v", err)
 	}
-	return gitRepo.GetBranchCommit(br.Name)
+	return gitRepo.BranchCommit(br.Name)
 }
 
 type ProtectBranchWhitelist struct {

+ 60 - 66
internal/db/repo_editor.go

@@ -19,10 +19,11 @@ import (
 	gouuid "github.com/satori/go.uuid"
 	"github.com/unknwon/com"
 
-	git "github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 
 	"github.com/G-Node/gogs/internal/conf"
 	"github.com/G-Node/gogs/internal/db/errors"
+	"github.com/G-Node/gogs/internal/gitutil"
 	"github.com/G-Node/gogs/internal/osutil"
 	"github.com/G-Node/gogs/internal/process"
 	"github.com/G-Node/gogs/internal/tool"
@@ -58,7 +59,7 @@ func ComposeHookEnvs(opts ComposeHookEnvsOptions) []string {
 		ENV_REPO_OWNER_SALT_MD5 + "=" + tool.MD5(opts.OwnerSalt),
 		ENV_REPO_ID + "=" + com.ToStr(opts.RepoID),
 		ENV_REPO_NAME + "=" + opts.RepoName,
-		ENV_REPO_CUSTOM_HOOKS_PATH + "=" + path.Join(opts.RepoPath, "custom_hooks"),
+		ENV_REPO_CUSTOM_HOOKS_PATH + "=" + filepath.Join(opts.RepoPath, "custom_hooks"),
 	}
 	return envs
 }
@@ -76,14 +77,15 @@ func discardLocalRepoBranchChanges(localPath, branch string) error {
 	if !com.IsExist(localPath) {
 		return nil
 	}
+
 	// No need to check if nothing in the repository.
-	if !git.IsBranchExist(localPath, branch) {
+	if !git.RepoHasBranch(localPath, branch) {
 		return nil
 	}
 
-	refName := "origin/" + branch
-	if err := git.ResetHEAD(localPath, true, refName); err != nil {
-		return fmt.Errorf("git reset --hard %s: %v", refName, err)
+	rev := "origin/" + branch
+	if err := git.RepoReset(localPath, rev, git.ResetOptions{Hard: true}); err != nil {
+		return fmt.Errorf("reset [revision: %s]: %v", rev, err)
 	}
 	return nil
 }
@@ -92,22 +94,17 @@ func (repo *Repository) DiscardLocalRepoBranchChanges(branch string) error {
 	return discardLocalRepoBranchChanges(repo.LocalCopyPath(), branch)
 }
 
-// checkoutNewBranch checks out to a new branch from the a branch name.
-func checkoutNewBranch(repoPath, localPath, oldBranch, newBranch string) error {
-	if err := git.Checkout(localPath, git.CheckoutOptions{
-		Timeout:   time.Duration(conf.Git.Timeout.Pull) * time.Second,
-		Branch:    newBranch,
-		OldBranch: oldBranch,
+// CheckoutNewBranch checks out to a new branch from the a branch name.
+func (repo *Repository) CheckoutNewBranch(oldBranch, newBranch string) error {
+	if err := git.RepoCheckout(repo.LocalCopyPath(), newBranch, git.CheckoutOptions{
+		BaseBranch: oldBranch,
+		Timeout:    time.Duration(conf.Git.Timeout.Pull) * time.Second,
 	}); err != nil {
-		return fmt.Errorf("git checkout -b %s %s: %v", newBranch, oldBranch, err)
+		return fmt.Errorf("checkout [base: %s, new: %s]: %v", oldBranch, newBranch, err)
 	}
 	return nil
 }
 
-func (repo *Repository) CheckoutNewBranch(oldBranch, newBranch string) error {
-	return checkoutNewBranch(repo.RepoPath(), repo.LocalCopyPath(), oldBranch, newBranch)
-}
-
 type UpdateRepoFileOptions struct {
 	LastCommitID string
 	OldBranch    string
@@ -135,16 +132,16 @@ func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) (
 
 	if opts.OldBranch != opts.NewBranch {
 		// Directly return error if new branch already exists in the server
-		if git.IsBranchExist(repoPath, opts.NewBranch) {
+		if git.RepoHasBranch(repoPath, opts.NewBranch) {
 			return errors.BranchAlreadyExists{Name: opts.NewBranch}
 		}
 
 		// Otherwise, delete branch from local copy in case out of sync
-		if git.IsBranchExist(localPath, opts.NewBranch) {
-			if err = git.DeleteBranch(localPath, opts.NewBranch, git.DeleteBranchOptions{
+		if git.RepoHasBranch(localPath, opts.NewBranch) {
+			if err = git.RepoDeleteBranch(localPath, opts.NewBranch, git.DeleteBranchOptions{
 				Force: true,
 			}); err != nil {
-				return fmt.Errorf("delete branch[%s]: %v", opts.NewBranch, err)
+				return fmt.Errorf("delete branch %q: %v", opts.NewBranch, err)
 			}
 		}
 
@@ -167,7 +164,7 @@ func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) (
 	// Ignore move step if it's a new file under a directory.
 	// Otherwise, move the file when name changed.
 	if osutil.IsFile(oldFilePath) && opts.OldTreeName != opts.NewTreeName {
-		if err = git.MoveFile(localPath, opts.OldTreeName, opts.NewTreeName); err != nil {
+		if err = git.RepoMove(localPath, opts.OldTreeName, opts.NewTreeName); err != nil {
 			return fmt.Errorf("git mv %q %q: %v", opts.OldTreeName, opts.NewTreeName, err)
 		}
 	}
@@ -176,22 +173,21 @@ func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) (
 		return fmt.Errorf("write file: %v", err)
 	}
 
-	if err = git.AddChanges(localPath, true); err != nil {
+	if err = git.RepoAdd(localPath, git.AddOptions{All: true}); err != nil {
 		return fmt.Errorf("git add --all: %v", err)
-	} else if err = git.CommitChanges(localPath, git.CommitChangesOptions{
-		Committer: doer.NewGitSig(),
-		Message:   opts.Message,
-	}); err != nil {
+	} else if err = git.RepoCommit(localPath, doer.NewGitSig(), opts.Message); err != nil {
 		return fmt.Errorf("commit changes on %q: %v", localPath, err)
-	} else if err = git.PushWithEnvs(localPath, "origin", opts.NewBranch,
-		ComposeHookEnvs(ComposeHookEnvsOptions{
-			AuthUser:  doer,
-			OwnerName: repo.MustOwner().Name,
-			OwnerSalt: repo.MustOwner().Salt,
-			RepoID:    repo.ID,
-			RepoName:  repo.Name,
-			RepoPath:  repo.RepoPath(),
-		})); err != nil {
+	}
+
+	envs := ComposeHookEnvs(ComposeHookEnvsOptions{
+		AuthUser:  doer,
+		OwnerName: repo.MustOwner().Name,
+		OwnerSalt: repo.MustOwner().Salt,
+		RepoID:    repo.ID,
+		RepoName:  repo.Name,
+		RepoPath:  repo.RepoPath(),
+	})
+	if err = git.RepoPush(localPath, "origin", opts.NewBranch, git.PushOptions{Envs: envs}); err != nil {
 		return fmt.Errorf("git push origin %s: %v", opts.NewBranch, err)
 	}
 
@@ -200,7 +196,7 @@ func (repo *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) (
 }
 
 // GetDiffPreview produces and returns diff result of a file which is not yet committed.
-func (repo *Repository) GetDiffPreview(branch, treePath, content string) (diff *Diff, err error) {
+func (repo *Repository) GetDiffPreview(branch, treePath, content string) (diff *gitutil.Diff, err error) {
 	repoWorkingPool.CheckIn(com.ToStr(repo.ID))
 	defer repoWorkingPool.CheckOut(com.ToStr(repo.ID))
 
@@ -233,9 +229,9 @@ func (repo *Repository) GetDiffPreview(branch, treePath, content string) (diff *
 	pid := process.Add(fmt.Sprintf("GetDiffPreview [repo_path: %s]", repo.RepoPath()), cmd)
 	defer process.Remove(pid)
 
-	diff, err = ParsePatch(conf.Git.MaxGitDiffLines, conf.Git.MaxGitDiffLineCharacters, conf.Git.MaxGitDiffFiles, stdout)
+	diff, err = gitutil.ParseDiff(stdout, conf.Git.MaxDiffFiles, conf.Git.MaxDiffLines, conf.Git.MaxDiffLineChars)
 	if err != nil {
-		return nil, fmt.Errorf("parse path: %v", err)
+		return nil, fmt.Errorf("parse diff: %v", err)
 	}
 
 	if err = cmd.Wait(); err != nil {
@@ -282,22 +278,21 @@ func (repo *Repository) DeleteRepoFile(doer *User, opts DeleteRepoFileOptions) (
 		return fmt.Errorf("remove file %q: %v", opts.TreePath, err)
 	}
 
-	if err = git.AddChanges(localPath, true); err != nil {
+	if err = git.RepoAdd(localPath, git.AddOptions{All: true}); err != nil {
 		return fmt.Errorf("git add --all: %v", err)
-	} else if err = git.CommitChanges(localPath, git.CommitChangesOptions{
-		Committer: doer.NewGitSig(),
-		Message:   opts.Message,
-	}); err != nil {
+	} else if err = git.RepoCommit(localPath, doer.NewGitSig(), opts.Message); err != nil {
 		return fmt.Errorf("commit changes to %q: %v", localPath, err)
-	} else if err = git.PushWithEnvs(localPath, "origin", opts.NewBranch,
-		ComposeHookEnvs(ComposeHookEnvsOptions{
-			AuthUser:  doer,
-			OwnerName: repo.MustOwner().Name,
-			OwnerSalt: repo.MustOwner().Salt,
-			RepoID:    repo.ID,
-			RepoName:  repo.Name,
-			RepoPath:  repo.RepoPath(),
-		})); err != nil {
+	}
+
+	envs := ComposeHookEnvs(ComposeHookEnvsOptions{
+		AuthUser:  doer,
+		OwnerName: repo.MustOwner().Name,
+		OwnerSalt: repo.MustOwner().Salt,
+		RepoID:    repo.ID,
+		RepoName:  repo.Name,
+		RepoPath:  repo.RepoPath(),
+	})
+	if err = git.RepoPush(localPath, "origin", opts.NewBranch, git.PushOptions{Envs: envs}); err != nil {
 		return fmt.Errorf("git push origin %s: %v", opts.NewBranch, err)
 	}
 	return nil
@@ -505,20 +500,19 @@ func (repo *Repository) UploadRepoFiles(doer *User, opts UploadRepoFileOptions)
 	annexSetup(localPath) // Initialise annex and set configuration (with add filter for filesizes)
 	if err = annexAdd(localPath, true); err != nil {
 		return fmt.Errorf("git annex add: %v", err)
-	} else if err = git.CommitChanges(localPath, git.CommitChangesOptions{
-		Committer: doer.NewGitSig(),
-		Message:   opts.Message,
-	}); err != nil {
+	} else if err = git.RepoCommit(localPath, doer.NewGitSig(), opts.Message); err != nil {
 		return fmt.Errorf("commit changes on %q: %v", localPath, err)
-	} else if err = git.PushWithEnvs(localPath, "origin", opts.NewBranch,
-		ComposeHookEnvs(ComposeHookEnvsOptions{
-			AuthUser:  doer,
-			OwnerName: repo.MustOwner().Name,
-			OwnerSalt: repo.MustOwner().Salt,
-			RepoID:    repo.ID,
-			RepoName:  repo.Name,
-			RepoPath:  repo.RepoPath(),
-		})); err != nil {
+	}
+
+	envs := ComposeHookEnvs(ComposeHookEnvsOptions{
+		AuthUser:  doer,
+		OwnerName: repo.MustOwner().Name,
+		OwnerSalt: repo.MustOwner().Salt,
+		RepoID:    repo.ID,
+		RepoName:  repo.Name,
+		RepoPath:  repo.RepoPath(),
+	})
+	if err = git.RepoPush(localPath, "origin", opts.NewBranch, git.PushOptions{Envs: envs}); err != nil {
 		return fmt.Errorf("git push origin %s: %v", opts.NewBranch, err)
 	}
 

+ 23 - 28
internal/db/update.go

@@ -5,19 +5,18 @@
 package db
 
 import (
-	"container/list"
 	"fmt"
 	"os/exec"
 	"strings"
 
-	git "github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 )
 
 // CommitToPushCommit transforms a git.Commit to PushCommit type.
 func CommitToPushCommit(commit *git.Commit) *PushCommit {
 	return &PushCommit{
 		Sha1:           commit.ID.String(),
-		Message:        commit.Message(),
+		Message:        commit.Message,
 		AuthorEmail:    commit.Author.Email,
 		AuthorName:     commit.Author.Name,
 		CommitterEmail: commit.Committer.Email,
@@ -26,27 +25,22 @@ func CommitToPushCommit(commit *git.Commit) *PushCommit {
 	}
 }
 
-func ListToPushCommits(l *list.List) *PushCommits {
-	if l == nil {
+func CommitsToPushCommits(commits []*git.Commit) *PushCommits {
+	if len(commits) == 0 {
 		return &PushCommits{}
 	}
 
-	commits := make([]*PushCommit, 0)
-	var actEmail string
-	for e := l.Front(); e != nil; e = e.Next() {
-		commit := e.Value.(*git.Commit)
-		if actEmail == "" {
-			actEmail = commit.Committer.Email
-		}
-		commits = append(commits, CommitToPushCommit(commit))
+	pcs := make([]*PushCommit, len(commits))
+	for i := range commits {
+		pcs[i] = CommitToPushCommit(commits[i])
 	}
-	return &PushCommits{l.Len(), commits, "", nil}
+	return &PushCommits{len(pcs), pcs, "", nil}
 }
 
 type PushUpdateOptions struct {
 	OldCommitID  string
 	NewCommitID  string
-	RefFullName  string
+	FullRefspec  string
 	PusherID     int64
 	PusherName   string
 	RepoUserName string
@@ -56,10 +50,10 @@ type PushUpdateOptions struct {
 // PushUpdate must be called for any push actions in order to
 // generates necessary push action history feeds.
 func PushUpdate(opts PushUpdateOptions) (err error) {
-	isNewRef := opts.OldCommitID == git.EMPTY_SHA
-	isDelRef := opts.NewCommitID == git.EMPTY_SHA
+	isNewRef := strings.HasPrefix(opts.OldCommitID, git.EmptyID)
+	isDelRef := strings.HasPrefix(opts.NewCommitID, git.EmptyID)
 	if isNewRef && isDelRef {
-		return fmt.Errorf("Old and new revisions are both %s", git.EMPTY_SHA)
+		return fmt.Errorf("both old and new revisions are %q", git.EmptyID)
 	}
 
 	repoPath := RepoPath(opts.RepoUserName, opts.RepoName)
@@ -70,9 +64,9 @@ func PushUpdate(opts PushUpdateOptions) (err error) {
 		return fmt.Errorf("run 'git update-server-info': %v", err)
 	}
 
-	gitRepo, err := git.OpenRepository(repoPath)
+	gitRepo, err := git.Open(repoPath)
 	if err != nil {
-		return fmt.Errorf("OpenRepository: %v", err)
+		return fmt.Errorf("open repository: %v", err)
 	}
 
 	owner, err := GetUserByName(opts.RepoUserName)
@@ -90,12 +84,12 @@ func PushUpdate(opts PushUpdateOptions) (err error) {
 	}
 
 	// Push tags
-	if strings.HasPrefix(opts.RefFullName, git.TAG_PREFIX) {
+	if strings.HasPrefix(opts.FullRefspec, git.RefsTags) {
 		if err := CommitRepoAction(CommitRepoActionOptions{
 			PusherName:  opts.PusherName,
 			RepoOwnerID: owner.ID,
 			RepoName:    repo.Name,
-			RefFullName: opts.RefFullName,
+			RefFullName: opts.FullRefspec,
 			OldCommitID: opts.OldCommitID,
 			NewCommitID: opts.NewCommitID,
 			Commits:     &PushCommits{},
@@ -105,22 +99,23 @@ func PushUpdate(opts PushUpdateOptions) (err error) {
 		return nil
 	}
 
-	var l *list.List
+	var commits []*git.Commit
 	// Skip read parent commits when delete branch
 	if !isDelRef {
 		// Push new branch
-		newCommit, err := gitRepo.GetCommit(opts.NewCommitID)
+		newCommit, err := gitRepo.CatFileCommit(opts.NewCommitID)
 		if err != nil {
 			return fmt.Errorf("GetCommit [commit_id: %s]: %v", opts.NewCommitID, err)
 		}
 
 		if isNewRef {
-			l, err = newCommit.CommitsBeforeLimit(10)
+			commits, err = newCommit.Ancestors(git.LogOptions{MaxCount: 9})
 			if err != nil {
 				return fmt.Errorf("CommitsBeforeLimit [commit_id: %s]: %v", newCommit.ID, err)
 			}
+			commits = append([]*git.Commit{newCommit}, commits...)
 		} else {
-			l, err = newCommit.CommitsBeforeUntil(opts.OldCommitID)
+			commits, err = newCommit.CommitsAfter(opts.OldCommitID)
 			if err != nil {
 				return fmt.Errorf("CommitsBeforeUntil [commit_id: %s]: %v", opts.OldCommitID, err)
 			}
@@ -131,10 +126,10 @@ func PushUpdate(opts PushUpdateOptions) (err error) {
 		PusherName:  opts.PusherName,
 		RepoOwnerID: owner.ID,
 		RepoName:    repo.Name,
-		RefFullName: opts.RefFullName,
+		RefFullName: opts.FullRefspec,
 		OldCommitID: opts.OldCommitID,
 		NewCommitID: opts.NewCommitID,
-		Commits:     ListToPushCommits(l),
+		Commits:     CommitsToPushCommits(commits),
 	}); err != nil {
 		return fmt.Errorf("CommitRepoAction.(branch): %v", err)
 	}

+ 12 - 19
internal/db/user.go

@@ -7,7 +7,6 @@ package db
 import (
 	"bufio"
 	"bytes"
-	"container/list"
 	"crypto/sha256"
 	"crypto/subtle"
 	"encoding/hex"
@@ -28,7 +27,7 @@ import (
 	log "unknwon.dev/clog/v2"
 	"xorm.io/xorm"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 	api "github.com/gogs/go-gogs-client"
 
 	"github.com/G-Node/gogs/internal/avatar"
@@ -1017,28 +1016,22 @@ func ValidateCommitWithEmail(c *git.Commit) *User {
 }
 
 // ValidateCommitsWithEmails checks if authors' e-mails of commits are corresponding to users.
-func ValidateCommitsWithEmails(oldCommits *list.List) *list.List {
-	var (
-		u          *User
-		emails     = map[string]*User{}
-		newCommits = list.New()
-		e          = oldCommits.Front()
-	)
-	for e != nil {
-		c := e.Value.(*git.Commit)
-
-		if v, ok := emails[c.Author.Email]; !ok {
-			u, _ = GetUserByEmail(c.Author.Email)
-			emails[c.Author.Email] = u
+func ValidateCommitsWithEmails(oldCommits []*git.Commit) []*UserCommit {
+	emails := make(map[string]*User)
+	newCommits := make([]*UserCommit, len(oldCommits))
+	for i := range oldCommits {
+		var u *User
+		if v, ok := emails[oldCommits[i].Author.Email]; !ok {
+			u, _ = GetUserByEmail(oldCommits[i].Author.Email)
+			emails[oldCommits[i].Author.Email] = u
 		} else {
 			u = v
 		}
 
-		newCommits.PushBack(UserCommit{
+		newCommits[i] = &UserCommit{
 			User:   u,
-			Commit: c,
-		})
-		e = e.Next()
+			Commit: oldCommits[i],
+		}
 	}
 	return newCommits
 }

+ 4 - 4
internal/db/webhook_dingtalk.go

@@ -8,7 +8,7 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 	api "github.com/gogs/go-gogs-client"
 	jsoniter "github.com/json-iterator/go"
 )
@@ -85,7 +85,7 @@ func GetDingtalkPayload(p api.Payloader, event HookEventType) (payload *Dingtalk
 }
 
 func getDingtalkCreatePayload(p *api.CreatePayload) (*DingtalkPayload, error) {
-	refName := git.RefEndName(p.Ref)
+	refName := git.RefShortName(p.Ref)
 	refType := strings.Title(p.RefType)
 
 	actionCard := NewDingtalkActionCard("View "+refType, p.Repo.HTMLURL+"/src/"+refName)
@@ -98,7 +98,7 @@ func getDingtalkCreatePayload(p *api.CreatePayload) (*DingtalkPayload, error) {
 }
 
 func getDingtalkDeletePayload(p *api.DeletePayload) (*DingtalkPayload, error) {
-	refName := git.RefEndName(p.Ref)
+	refName := git.RefShortName(p.Ref)
 	refType := strings.Title(p.RefType)
 
 	actionCard := NewDingtalkActionCard("View Repo", p.Repo.HTMLURL)
@@ -121,7 +121,7 @@ func getDingtalkForkPayload(p *api.ForkPayload) (*DingtalkPayload, error) {
 }
 
 func getDingtalkPushPayload(p *api.PushPayload) (*DingtalkPayload, error) {
-	refName := git.RefEndName(p.Ref)
+	refName := git.RefShortName(p.Ref)
 
 	pusher := p.Pusher.FullName
 	if pusher == "" {

+ 4 - 4
internal/db/webhook_discord.go

@@ -9,7 +9,7 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 	api "github.com/gogs/go-gogs-client"
 	jsoniter "github.com/json-iterator/go"
 
@@ -70,7 +70,7 @@ func DiscordSHALinkFormatter(url string, text string) string {
 
 // getDiscordCreatePayload composes Discord payload for create new branch or tag.
 func getDiscordCreatePayload(p *api.CreatePayload) (*DiscordPayload, error) {
-	refName := git.RefEndName(p.Ref)
+	refName := git.RefShortName(p.Ref)
 	repoLink := DiscordLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
 	refLink := DiscordLinkFormatter(p.Repo.HTMLURL+"/src/"+refName, refName)
 	content := fmt.Sprintf("Created new %s: %s/%s", p.RefType, repoLink, refLink)
@@ -88,7 +88,7 @@ func getDiscordCreatePayload(p *api.CreatePayload) (*DiscordPayload, error) {
 
 // getDiscordDeletePayload composes Discord payload for delete a branch or tag.
 func getDiscordDeletePayload(p *api.DeletePayload) (*DiscordPayload, error) {
-	refName := git.RefEndName(p.Ref)
+	refName := git.RefShortName(p.Ref)
 	repoLink := DiscordLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
 	content := fmt.Sprintf("Deleted %s: %s/%s", p.RefType, repoLink, refName)
 	return &DiscordPayload{
@@ -123,7 +123,7 @@ func getDiscordForkPayload(p *api.ForkPayload) (*DiscordPayload, error) {
 func getDiscordPushPayload(p *api.PushPayload, slack *SlackMeta) (*DiscordPayload, error) {
 	// n new commits
 	var (
-		branchName   = git.RefEndName(p.Ref)
+		branchName   = git.RefShortName(p.Ref)
 		commitDesc   string
 		commitString string
 	)

+ 4 - 4
internal/db/webhook_slack.go

@@ -8,7 +8,7 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 	api "github.com/gogs/go-gogs-client"
 	jsoniter "github.com/json-iterator/go"
 
@@ -71,7 +71,7 @@ func SlackLinkFormatter(url string, text string) string {
 
 // getSlackCreatePayload composes Slack payload for create new branch or tag.
 func getSlackCreatePayload(p *api.CreatePayload) (*SlackPayload, error) {
-	refName := git.RefEndName(p.Ref)
+	refName := git.RefShortName(p.Ref)
 	repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
 	refLink := SlackLinkFormatter(p.Repo.HTMLURL+"/src/"+refName, refName)
 	text := fmt.Sprintf("[%s:%s] %s created by %s", repoLink, refLink, p.RefType, p.Sender.UserName)
@@ -82,7 +82,7 @@ func getSlackCreatePayload(p *api.CreatePayload) (*SlackPayload, error) {
 
 // getSlackDeletePayload composes Slack payload for delete a branch or tag.
 func getSlackDeletePayload(p *api.DeletePayload) (*SlackPayload, error) {
-	refName := git.RefEndName(p.Ref)
+	refName := git.RefShortName(p.Ref)
 	repoLink := SlackLinkFormatter(p.Repo.HTMLURL, p.Repo.Name)
 	text := fmt.Sprintf("[%s:%s] %s deleted by %s", repoLink, refName, p.RefType, p.Sender.UserName)
 	return &SlackPayload{
@@ -103,7 +103,7 @@ func getSlackForkPayload(p *api.ForkPayload) (*SlackPayload, error) {
 func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, error) {
 	// n new commits
 	var (
-		branchName   = git.RefEndName(p.Ref)
+		branchName   = git.RefShortName(p.Ref)
 		commitDesc   string
 		commitString string
 	)

+ 15 - 21
internal/db/wiki.go

@@ -15,7 +15,7 @@ import (
 
 	"github.com/unknwon/com"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 
 	"github.com/G-Node/gogs/internal/conf"
 	"github.com/G-Node/gogs/internal/sync"
@@ -62,8 +62,8 @@ func (repo *Repository) InitWiki() error {
 		return nil
 	}
 
-	if err := git.InitRepository(repo.WikiPath(), true); err != nil {
-		return fmt.Errorf("InitRepository: %v", err)
+	if err := git.Init(repo.WikiPath(), git.InitOptions{Bare: true}); err != nil {
+		return fmt.Errorf("init repository: %v", err)
 	} else if err = createDelegateHooks(repo.WikiPath()); err != nil {
 		return fmt.Errorf("createDelegateHooks: %v", err)
 	}
@@ -125,15 +125,12 @@ func (repo *Repository) updateWikiPage(doer *User, oldTitle, title, content, mes
 	if len(message) == 0 {
 		message = "Update page '" + title + "'"
 	}
-	if err = git.AddChanges(localPath, true); err != nil {
-		return fmt.Errorf("AddChanges: %v", err)
-	} else if err = git.CommitChanges(localPath, git.CommitChangesOptions{
-		Committer: doer.NewGitSig(),
-		Message:   message,
-	}); err != nil {
-		return fmt.Errorf("CommitChanges: %v", err)
-	} else if err = git.Push(localPath, "origin", "master"); err != nil {
-		return fmt.Errorf("Push: %v", err)
+	if err = git.RepoAdd(localPath, git.AddOptions{All: true}); err != nil {
+		return fmt.Errorf("add all changes: %v", err)
+	} else if err = git.RepoCommit(localPath, doer.NewGitSig(), message); err != nil {
+		return fmt.Errorf("commit changes: %v", err)
+	} else if err = git.RepoPush(localPath, "origin", "master"); err != nil {
+		return fmt.Errorf("push: %v", err)
 	}
 
 	return nil
@@ -164,15 +161,12 @@ func (repo *Repository) DeleteWikiPage(doer *User, title string) (err error) {
 
 	message := "Delete page '" + title + "'"
 
-	if err = git.AddChanges(localPath, true); err != nil {
-		return fmt.Errorf("AddChanges: %v", err)
-	} else if err = git.CommitChanges(localPath, git.CommitChangesOptions{
-		Committer: doer.NewGitSig(),
-		Message:   message,
-	}); err != nil {
-		return fmt.Errorf("CommitChanges: %v", err)
-	} else if err = git.Push(localPath, "origin", "master"); err != nil {
-		return fmt.Errorf("Push: %v", err)
+	if err = git.RepoAdd(localPath, git.AddOptions{All: true}); err != nil {
+		return fmt.Errorf("add all changes: %v", err)
+	} else if err = git.RepoCommit(localPath, doer.NewGitSig(), message); err != nil {
+		return fmt.Errorf("commit changes: %v", err)
+	} else if err = git.RepoPush(localPath, "origin", "master"); err != nil {
+		return fmt.Errorf("push: %v", err)
 	}
 
 	return nil

+ 196 - 0
internal/gitutil/diff.go

@@ -0,0 +1,196 @@
+// 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 gitutil
+
+import (
+	"bytes"
+	"fmt"
+	"html"
+	"html/template"
+	"io"
+	"sync"
+
+	"github.com/sergi/go-diff/diffmatchpatch"
+	"golang.org/x/net/html/charset"
+	"golang.org/x/text/transform"
+
+	"github.com/gogs/git-module"
+
+	"github.com/G-Node/gogs/internal/conf"
+	"github.com/G-Node/gogs/internal/template/highlight"
+	"github.com/G-Node/gogs/internal/tool"
+)
+
+// DiffSection is a wrapper to git.DiffSection with helper methods.
+type DiffSection struct {
+	*git.DiffSection
+
+	initOnce sync.Once
+	dmp      *diffmatchpatch.DiffMatchPatch
+}
+
+// ComputedInlineDiffFor computes inline diff for the given line.
+func (s *DiffSection) ComputedInlineDiffFor(line *git.DiffLine) template.HTML {
+	fallback := template.HTML(html.EscapeString(line.Content))
+	if conf.Git.DisableDiffHighlight {
+		return fallback
+	}
+
+	// Find equivalent diff line, ignore when not found.
+	var diff1, diff2 string
+	switch line.Type {
+	case git.DiffLineAdd:
+		compareLine := s.Line(git.DiffLineDelete, line.RightLine)
+		if compareLine == nil {
+			return fallback
+		}
+
+		diff1 = compareLine.Content
+		diff2 = line.Content
+
+	case git.DiffLineDelete:
+		compareLine := s.Line(git.DiffLineAdd, line.LeftLine)
+		if compareLine == nil {
+			return fallback
+		}
+
+		diff1 = line.Content
+		diff2 = compareLine.Content
+
+	default:
+		return fallback
+	}
+
+	s.initOnce.Do(func() {
+		s.dmp = diffmatchpatch.New()
+		s.dmp.DiffEditCost = 100
+	})
+
+	diffs := s.dmp.DiffMain(diff1[1:], diff2[1:], true)
+	diffs = s.dmp.DiffCleanupEfficiency(diffs)
+
+	return diffsToHTML(diffs, line.Type)
+}
+
+func diffsToHTML(diffs []diffmatchpatch.Diff, lineType git.DiffLineType) template.HTML {
+	buf := bytes.NewBuffer(nil)
+
+	// Reproduce signs which are cutted for inline diff before.
+	switch lineType {
+	case git.DiffLineAdd:
+		buf.WriteByte('+')
+	case git.DiffLineDelete:
+		buf.WriteByte('-')
+	}
+	buf.WriteByte(' ')
+
+	const (
+		addedCodePrefix   = `<span class="added-code">`
+		removedCodePrefix = `<span class="removed-code">`
+		codeTagSuffix     = `</span>`
+	)
+
+	for i := range diffs {
+		switch {
+		case diffs[i].Type == diffmatchpatch.DiffInsert && lineType == git.DiffLineAdd:
+			buf.WriteString(addedCodePrefix)
+			buf.WriteString(html.EscapeString(diffs[i].Text))
+			buf.WriteString(codeTagSuffix)
+		case diffs[i].Type == diffmatchpatch.DiffDelete && lineType == git.DiffLineDelete:
+			buf.WriteString(removedCodePrefix)
+			buf.WriteString(html.EscapeString(diffs[i].Text))
+			buf.WriteString(codeTagSuffix)
+		case diffs[i].Type == diffmatchpatch.DiffEqual:
+			buf.WriteString(html.EscapeString(diffs[i].Text))
+		}
+	}
+
+	return template.HTML(buf.Bytes())
+}
+
+// DiffFile is a wrapper to git.DiffFile with helper methods.
+type DiffFile struct {
+	*git.DiffFile
+	Sections []*DiffSection
+}
+
+// HighlightClass returns the detected highlight class for the file.
+func (diffFile *DiffFile) HighlightClass() string {
+	return highlight.FileNameToHighlightClass(diffFile.Name)
+}
+
+// Diff is a wrapper to git.Diff with helper methods.
+type Diff struct {
+	*git.Diff
+	Files []*DiffFile
+}
+
+// NewDiff returns a new wrapper of given git.Diff.
+func NewDiff(oldDiff *git.Diff) *Diff {
+	newDiff := &Diff{
+		Diff:  oldDiff,
+		Files: make([]*DiffFile, oldDiff.NumFiles()),
+	}
+
+	// FIXME: detect encoding while parsing.
+	var buf bytes.Buffer
+	for i := range oldDiff.Files {
+		buf.Reset()
+
+		newDiff.Files[i] = &DiffFile{
+			DiffFile: oldDiff.Files[i],
+			Sections: make([]*DiffSection, oldDiff.Files[i].NumSections()),
+		}
+
+		for j := range oldDiff.Files[i].Sections {
+			newDiff.Files[i].Sections[j] = &DiffSection{
+				DiffSection: oldDiff.Files[i].Sections[j],
+			}
+
+			for k := range newDiff.Files[i].Sections[j].Lines {
+				buf.WriteString(newDiff.Files[i].Sections[j].Lines[k].Content)
+				buf.WriteString("\n")
+			}
+		}
+
+		charsetLabel, err := tool.DetectEncoding(buf.Bytes())
+		if charsetLabel != "UTF-8" && err == nil {
+			encoding, _ := charset.Lookup(charsetLabel)
+			if encoding != nil {
+				d := encoding.NewDecoder()
+				for j := range newDiff.Files[i].Sections {
+					for k := range newDiff.Files[i].Sections[j].Lines {
+						if c, _, err := transform.String(d, newDiff.Files[i].Sections[j].Lines[k].Content); err == nil {
+							newDiff.Files[i].Sections[j].Lines[k].Content = c
+						}
+					}
+				}
+			}
+		}
+	}
+
+	return newDiff
+}
+
+// ParseDiff parses the diff from given io.Reader.
+func ParseDiff(r io.Reader, maxFiles, maxFileLines, maxLineChars int) (*Diff, error) {
+	done := make(chan git.SteamParseDiffResult)
+	go git.StreamParseDiff(r, done, maxFiles, maxFileLines, maxLineChars)
+
+	result := <-done
+	if result.Err != nil {
+		return nil, fmt.Errorf("stream parse diff: %v", result.Err)
+	}
+	return NewDiff(result.Diff), nil
+}
+
+// RepoDiff parses the diff on given revisions of given repository.
+func RepoDiff(repo *git.Repository, rev string, maxFiles, maxFileLines, maxLineChars int, opts ...git.DiffOptions) (*Diff, error) {
+	diff, err := repo.Diff(rev, maxFiles, maxFileLines, maxLineChars, opts...)
+	if err != nil {
+		return nil, fmt.Errorf("get diff: %v", err)
+	}
+	return NewDiff(diff), nil
+}

+ 49 - 0
internal/gitutil/diff_test.go

@@ -0,0 +1,49 @@
+// Copyright 2016 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 gitutil
+
+import (
+	"html/template"
+	"testing"
+
+	dmp "github.com/sergi/go-diff/diffmatchpatch"
+	"github.com/stretchr/testify/assert"
+
+	"github.com/gogs/git-module"
+)
+
+func Test_diffsToHTML(t *testing.T) {
+	tests := []struct {
+		diffs    []dmp.Diff
+		lineType git.DiffLineType
+		expHTML  template.HTML
+	}{
+		{
+			diffs: []dmp.Diff{
+				{Type: dmp.DiffEqual, Text: "foo "},
+				{Type: dmp.DiffInsert, Text: "bar"},
+				{Type: dmp.DiffDelete, Text: " baz"},
+				{Type: dmp.DiffEqual, Text: " biz"},
+			},
+			lineType: git.DiffLineAdd,
+			expHTML:  template.HTML(`+ foo <span class="added-code">bar</span> biz`),
+		},
+		{
+			diffs: []dmp.Diff{
+				{Type: dmp.DiffEqual, Text: "foo "},
+				{Type: dmp.DiffDelete, Text: "bar"},
+				{Type: dmp.DiffInsert, Text: " baz"},
+				{Type: dmp.DiffEqual, Text: " biz"},
+			},
+			lineType: git.DiffLineDelete,
+			expHTML:  template.HTML(`- foo <span class="removed-code">bar</span> biz`),
+		},
+	}
+	for _, test := range tests {
+		t.Run("", func(t *testing.T) {
+			assert.Equal(t, test.expHTML, diffsToHTML(test.diffs, test.lineType))
+		})
+	}
+}

+ 19 - 0
internal/gitutil/error.go

@@ -0,0 +1,19 @@
+// Copyright 2020 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 gitutil
+
+import (
+	"github.com/gogs/git-module"
+)
+
+// IsErrRevisionNotExist returns true if the error is git.ErrRevisionNotExist.
+func IsErrRevisionNotExist(err error) bool {
+	return err == git.ErrRevisionNotExist
+}
+
+// IsErrNoMergeBase returns true if the error is git.ErrNoMergeBase.
+func IsErrNoMergeBase(err error) bool {
+	return err == git.ErrNoMergeBase
+}

+ 23 - 0
internal/gitutil/error_test.go

@@ -0,0 +1,23 @@
+// Copyright 2020 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 gitutil
+
+import (
+	"os"
+	"testing"
+
+	"github.com/gogs/git-module"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestIsErrRevisionNotExist(t *testing.T) {
+	assert.True(t, IsErrRevisionNotExist(git.ErrRevisionNotExist))
+	assert.False(t, IsErrRevisionNotExist(os.ErrNotExist))
+}
+
+func TestIsErrNoMergeBase(t *testing.T) {
+	assert.True(t, IsErrNoMergeBase(git.ErrNoMergeBase))
+	assert.False(t, IsErrNoMergeBase(os.ErrNotExist))
+}

+ 23 - 0
internal/gitutil/mock.go

@@ -0,0 +1,23 @@
+// Copyright 2020 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 gitutil
+
+import (
+	"github.com/gogs/git-module"
+)
+
+type MockModuleStore struct {
+	RepoAddRemote    func(repoPath, name, url string, opts ...git.AddRemoteOptions) error
+	RepoDiffNameOnly func(repoPath, base, head string, opts ...git.DiffNameOnlyOptions) ([]string, error)
+	RepoLog          func(repoPath, rev string, opts ...git.LogOptions) ([]*git.Commit, error)
+	RepoMergeBase    func(repoPath, base, head string, opts ...git.MergeBaseOptions) (string, error)
+	RepoRemoveRemote func(repoPath, name string, opts ...git.RemoveRemoteOptions) error
+	RepoTags         func(repoPath string, opts ...git.TagsOptions) ([]string, error)
+}
+
+// MockModule holds mock implementation of each method for Modulers interface.
+// When the field is non-nil, it is considered mocked. Otherwise, the real
+// implementation will be executed.
+var MockModule MockModuleStore

+ 90 - 0
internal/gitutil/module.go

@@ -0,0 +1,90 @@
+// Copyright 2020 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 gitutil
+
+import (
+	"github.com/gogs/git-module"
+)
+
+// Moduler is the interface for Git operations.
+//
+// NOTE: All methods are sorted in alphabetically.
+type Moduler interface {
+	// AddRemote adds a new remote to the repository in given path.
+	RepoAddRemote(repoPath, name, url string, opts ...git.AddRemoteOptions) error
+	// RepoDiffNameOnly returns a list of changed files between base and head revisions
+	// of the repository in given path.
+	RepoDiffNameOnly(repoPath, base, head string, opts ...git.DiffNameOnlyOptions) ([]string, error)
+	// RepoLog returns a list of commits in the state of given revision of the repository
+	// in given path. The returned list is in reverse chronological order.
+	RepoLog(repoPath, rev string, opts ...git.LogOptions) ([]*git.Commit, error)
+	// RepoMergeBase returns merge base between base and head revisions of the repository
+	// in given path.
+	RepoMergeBase(repoPath, base, head string, opts ...git.MergeBaseOptions) (string, error)
+	// RepoRemoveRemote removes a remote from the repository in given path.
+	RepoRemoveRemote(repoPath, name string, opts ...git.RemoveRemoteOptions) error
+	// RepoTags returns a list of tags of the repository in given path.
+	RepoTags(repoPath string, opts ...git.TagsOptions) ([]string, error)
+
+	Utiler
+}
+
+// Utiler is the interface for utility helpers implemented in this package.
+//
+// NOTE: All methods are sorted in alphabetically.
+type Utiler interface {
+	// GetPullRequestMeta gathers pull request metadata based on given head and base information.
+	PullRequestMeta(headPath, basePath, headBranch, baseBranch string) (*PullRequestMeta, error)
+	// ListTagsAfter returns a list of tags "after" (exlusive) given tag.
+	ListTagsAfter(repoPath, after string, limit int) (*TagsPage, error)
+}
+
+// moduler is holds real implementation.
+type moduler struct{}
+
+func (moduler) RepoAddRemote(repoPath, name, url string, opts ...git.AddRemoteOptions) error {
+	if MockModule.RepoAddRemote != nil {
+		return MockModule.RepoAddRemote(repoPath, name, url, opts...)
+	}
+	return git.RepoAddRemote(repoPath, name, url, opts...)
+}
+
+func (moduler) RepoDiffNameOnly(repoPath, base, head string, opts ...git.DiffNameOnlyOptions) ([]string, error) {
+	if MockModule.RepoDiffNameOnly != nil {
+		return MockModule.RepoDiffNameOnly(repoPath, base, head, opts...)
+	}
+	return git.RepoDiffNameOnly(repoPath, base, head, opts...)
+}
+
+func (moduler) RepoLog(repoPath, rev string, opts ...git.LogOptions) ([]*git.Commit, error) {
+	if MockModule.RepoLog != nil {
+		return MockModule.RepoLog(repoPath, rev, opts...)
+	}
+	return git.RepoLog(repoPath, rev, opts...)
+}
+
+func (moduler) RepoMergeBase(repoPath, base, head string, opts ...git.MergeBaseOptions) (string, error) {
+	if MockModule.RepoMergeBase != nil {
+		return MockModule.RepoMergeBase(repoPath, base, head, opts...)
+	}
+	return git.RepoMergeBase(repoPath, base, head, opts...)
+}
+
+func (moduler) RepoRemoveRemote(repoPath, name string, opts ...git.RemoveRemoteOptions) error {
+	if MockModule.RepoRemoveRemote != nil {
+		return MockModule.RepoRemoveRemote(repoPath, name, opts...)
+	}
+	return git.RepoRemoveRemote(repoPath, name, opts...)
+}
+
+func (moduler) RepoTags(repoPath string, opts ...git.TagsOptions) ([]string, error) {
+	if MockModule.RepoTags != nil {
+		return MockModule.RepoTags(repoPath, opts...)
+	}
+	return git.RepoTags(repoPath, opts...)
+}
+
+// Module is a mockable interface for Git operations.
+var Module Moduler = moduler{}

+ 69 - 0
internal/gitutil/pull_request.go

@@ -0,0 +1,69 @@
+// Copyright 2020 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 gitutil
+
+import (
+	"fmt"
+	"strconv"
+	"time"
+
+	"github.com/gogs/git-module"
+	"github.com/pkg/errors"
+	log "unknwon.dev/clog/v2"
+)
+
+// PullRequestMeta contains metadata for a pull request.
+type PullRequestMeta struct {
+	// The merge base of the pull request.
+	MergeBase string
+	// The commits that are requested to be merged.
+	Commits []*git.Commit
+	// The number of files changed.
+	NumFiles int
+}
+
+func (moduler) PullRequestMeta(headPath, basePath, headBranch, baseBranch string) (*PullRequestMeta, error) {
+	tmpRemoteBranch := baseBranch
+
+	// We need to create a temporary remote when the pull request is sent from a forked repository.
+	if headPath != basePath {
+		tmpRemote := strconv.FormatInt(time.Now().UnixNano(), 10)
+		err := Module.RepoAddRemote(headPath, tmpRemote, basePath, git.AddRemoteOptions{Fetch: true})
+		if err != nil {
+			return nil, fmt.Errorf("add remote: %v", err)
+		}
+		defer func() {
+			err := Module.RepoRemoveRemote(headPath, tmpRemote)
+			if err != nil {
+				log.Error("Failed to remove remote %q [path: %s]: %v", tmpRemote, headPath, err)
+				return
+			}
+		}()
+
+		tmpRemoteBranch = "remotes/" + tmpRemote + "/" + baseBranch
+	}
+
+	mergeBase, err := Module.RepoMergeBase(headPath, tmpRemoteBranch, headBranch)
+	if err != nil {
+		return nil, errors.Wrap(err, "get merge base")
+	}
+
+	commits, err := Module.RepoLog(headPath, mergeBase+"..."+headBranch)
+	if err != nil {
+		return nil, errors.Wrap(err, "get commits")
+	}
+
+	// Count number of changed files
+	names, err := Module.RepoDiffNameOnly(headPath, tmpRemoteBranch, headBranch, git.DiffNameOnlyOptions{NeedsMergeBase: true})
+	if err != nil {
+		return nil, errors.Wrap(err, "get changed files")
+	}
+
+	return &PullRequestMeta{
+		MergeBase: mergeBase,
+		Commits:   commits,
+		NumFiles:  len(names),
+	}, nil
+}

+ 108 - 0
internal/gitutil/pull_request_test.go

@@ -0,0 +1,108 @@
+// Copyright 2020 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 gitutil
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/gogs/git-module"
+	"github.com/pkg/errors"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestModuler_PullRequestMeta(t *testing.T) {
+	headPath := "/head/path"
+	basePath := "/base/path"
+	headBranch := "head_branch"
+	baseBranch := "base_branch"
+	mergeBase := "MERGE-BASE"
+	changedFiles := []string{"a.go", "b.txt"}
+	commits := []*git.Commit{
+		{ID: git.MustIDFromString("adfd6da3c0a3fb038393144becbf37f14f780087")},
+	}
+
+	MockModule.RepoAddRemote = func(repoPath, name, url string, opts ...git.AddRemoteOptions) error {
+		if repoPath != headPath {
+			return fmt.Errorf("repoPath: want %q but got %q", headPath, repoPath)
+		} else if name == "" {
+			return errors.New("empty name")
+		} else if url != basePath {
+			return fmt.Errorf("url: want %q but got %q", basePath, url)
+		}
+
+		if len(opts) == 0 {
+			return errors.New("no options")
+		} else if !opts[0].Fetch {
+			return fmt.Errorf("opts.Fetch: want %v but got %v", true, opts[0].Fetch)
+		}
+
+		return nil
+	}
+	MockModule.RepoMergeBase = func(repoPath, base, head string, opts ...git.MergeBaseOptions) (string, error) {
+		if repoPath != headPath {
+			return "", fmt.Errorf("repoPath: want %q but got %q", headPath, repoPath)
+		} else if base == "" {
+			return "", errors.New("empty base")
+		} else if head != headBranch {
+			return "", fmt.Errorf("head: want %q but got %q", headBranch, head)
+		}
+
+		return mergeBase, nil
+	}
+	MockModule.RepoLog = func(repoPath, rev string, opts ...git.LogOptions) ([]*git.Commit, error) {
+		if repoPath != headPath {
+			return nil, fmt.Errorf("repoPath: want %q but got %q", headPath, repoPath)
+		}
+
+		expRev := mergeBase + "..." + headBranch
+		if rev != expRev {
+			return nil, fmt.Errorf("rev: want %q but got %q", expRev, rev)
+		}
+
+		return commits, nil
+	}
+	MockModule.RepoDiffNameOnly = func(repoPath, base, head string, opts ...git.DiffNameOnlyOptions) ([]string, error) {
+		if repoPath != headPath {
+			return nil, fmt.Errorf("repoPath: want %q but got %q", headPath, repoPath)
+		} else if base == "" {
+			return nil, errors.New("empty base")
+		} else if head != headBranch {
+			return nil, fmt.Errorf("head: want %q but got %q", headBranch, head)
+		}
+
+		if len(opts) == 0 {
+			return nil, errors.New("no options")
+		} else if !opts[0].NeedsMergeBase {
+			return nil, fmt.Errorf("opts.NeedsMergeBase: want %v but got %v", true, opts[0].NeedsMergeBase)
+		}
+
+		return changedFiles, nil
+	}
+	MockModule.RepoRemoveRemote = func(repoPath, name string, opts ...git.RemoveRemoteOptions) error {
+		if repoPath != headPath {
+			return fmt.Errorf("repoPath: want %q but got %q", headPath, repoPath)
+		} else if name == "" {
+			return errors.New("empty name")
+		}
+
+		return nil
+	}
+	defer func() {
+		MockModule = MockModuleStore{}
+	}()
+
+	meta, err := Module.PullRequestMeta(headPath, basePath, headBranch, baseBranch)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	expMeta := &PullRequestMeta{
+		MergeBase: mergeBase,
+		Commits:   commits,
+		NumFiles:  2,
+	}
+	assert.Equal(t, expMeta, meta)
+}

+ 48 - 0
internal/gitutil/submodule.go

@@ -0,0 +1,48 @@
+// Copyright 2020 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 gitutil
+
+import (
+	"fmt"
+	"net/url"
+	"strings"
+
+	"github.com/gogs/git-module"
+
+	"github.com/G-Node/gogs/internal/lazyregexp"
+)
+
+var scpSyntax = lazyregexp.New(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):(.*)$`)
+
+// InferSubmoduleURL returns the inferred external URL of the submodule at best effort.
+func InferSubmoduleURL(mod *git.Submodule) string {
+	raw := strings.TrimSuffix(mod.URL, "/")
+	raw = strings.TrimSuffix(raw, ".git")
+
+	parsed, err := url.Parse(raw)
+	if err != nil {
+		// Try parse as SCP syntax again
+		match := scpSyntax.FindAllStringSubmatch(raw, -1)
+		if len(match) == 0 {
+			return mod.URL
+		}
+		parsed = &url.URL{
+			Scheme: "http",
+			Host:   match[0][2],
+			Path:   match[0][3],
+		}
+	}
+
+	switch parsed.Scheme {
+	case "http", "https":
+		raw = parsed.String()
+	case "ssh":
+		raw = fmt.Sprintf("http://%s%s", parsed.Host, parsed.Path)
+	default:
+		return raw
+	}
+
+	return fmt.Sprintf("%s/commit/%s", raw, mod.Commit)
+}

+ 58 - 0
internal/gitutil/submodule_test.go

@@ -0,0 +1,58 @@
+// Copyright 2020 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 gitutil
+
+import (
+	"testing"
+
+	"github.com/gogs/git-module"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestInferSubmoduleURL(t *testing.T) {
+	tests := []struct {
+		name      string
+		submodule *git.Submodule
+		expURL    string
+	}{
+		{
+			name: "HTTPS URL",
+			submodule: &git.Submodule{
+				URL:    "https://github.com/gogs/docs-api.git",
+				Commit: "6b08f76a5313fa3d26859515b30aa17a5faa2807",
+			},
+			expURL: "https://github.com/gogs/docs-api/commit/6b08f76a5313fa3d26859515b30aa17a5faa2807",
+		},
+		{
+			name: "SSH URL with port",
+			submodule: &git.Submodule{
+				URL:    "ssh://user@github.com:22/gogs/docs-api.git",
+				Commit: "6b08f76a5313fa3d26859515b30aa17a5faa2807",
+			},
+			expURL: "http://github.com:22/gogs/docs-api/commit/6b08f76a5313fa3d26859515b30aa17a5faa2807",
+		},
+		{
+			name: "SSH URL in SCP syntax",
+			submodule: &git.Submodule{
+				URL:    "git@github.com:gogs/docs-api.git",
+				Commit: "6b08f76a5313fa3d26859515b30aa17a5faa2807",
+			},
+			expURL: "http://github.com/gogs/docs-api/commit/6b08f76a5313fa3d26859515b30aa17a5faa2807",
+		},
+		{
+			name: "bad URL",
+			submodule: &git.Submodule{
+				URL:    "ftp://example.com",
+				Commit: "6b08f76a5313fa3d26859515b30aa17a5faa2807",
+			},
+			expURL: "ftp://example.com",
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			assert.Equal(t, test.expURL, InferSubmoduleURL(test.submodule))
+		})
+	}
+}

+ 95 - 0
internal/gitutil/tag.go

@@ -0,0 +1,95 @@
+// Copyright 2020 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 gitutil
+
+import (
+	"github.com/pkg/errors"
+)
+
+// TagsPage contains a list of tags and pagination information.
+type TagsPage struct {
+	// List of tags in the current page.
+	Tags []string
+	// Whether the results include the latest tag.
+	HasLatest bool
+	// When results do not include the latest tag, an indicator of 'after' to go back.
+	PreviousAfter string
+	// Whether there are more tags in the next page.
+	HasNext bool
+}
+
+func (moduler) ListTagsAfter(repoPath, after string, limit int) (*TagsPage, error) {
+	all, err := Module.RepoTags(repoPath)
+	if err != nil {
+		return nil, errors.Wrap(err, "get tags")
+	}
+	total := len(all)
+
+	if limit < 0 {
+		limit = 0
+	}
+
+	// Returns everything when no filter and no limit
+	if after == "" && limit == 0 {
+		return &TagsPage{
+			Tags:      all,
+			HasLatest: true,
+		}, nil
+	}
+
+	// No filter but has a limit, returns first X tags
+	if after == "" && limit > 0 {
+		endIdx := limit
+		if limit > total {
+			endIdx = total
+		}
+		return &TagsPage{
+			Tags:      all[:endIdx],
+			HasLatest: true,
+			HasNext:   limit < total,
+		}, nil
+	}
+
+	// Loop over all tags see if we can find the filter
+	previousAfter := ""
+	found := false
+	tags := make([]string, 0, len(all))
+	for i := range all {
+		if all[i] != after {
+			continue
+		}
+
+		found = true
+		if limit > 0 && i-limit >= 0 {
+			previousAfter = all[i-limit]
+		}
+
+		// In case filter is the oldest one
+		if i+1 < total {
+			tags = all[i+1:]
+		}
+		break
+	}
+
+	if !found {
+		tags = all
+	}
+
+	// If all tags after match is equal to the limit, it reaches the oldest tag as well.
+	if limit == 0 || len(tags) <= limit {
+		return &TagsPage{
+			Tags:          tags,
+			HasLatest:     !found,
+			PreviousAfter: previousAfter,
+		}, nil
+	}
+
+	return &TagsPage{
+		Tags:          tags[:limit],
+		HasLatest:     !found,
+		PreviousAfter: previousAfter,
+		HasNext:       true,
+	}, nil
+}

+ 109 - 0
internal/gitutil/tag_test.go

@@ -0,0 +1,109 @@
+// Copyright 2020 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 gitutil
+
+import (
+	"testing"
+
+	"github.com/gogs/git-module"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestModuler_ListTagsAfter(t *testing.T) {
+	MockModule.RepoTags = func(string, ...git.TagsOptions) ([]string, error) {
+		return []string{
+			"v2.3.0", "v2.2.1", "v2.1.0",
+			"v1.3.0", "v1.2.0", "v1.1.0",
+			"v0.8.0", "v0.5.0", "v0.1.0",
+		}, nil
+	}
+	defer func() {
+		MockModule = MockModuleStore{}
+	}()
+
+	tests := []struct {
+		name        string
+		after       string
+		expTagsPage *TagsPage
+	}{
+		{
+			name: "first page",
+			expTagsPage: &TagsPage{
+				Tags: []string{
+					"v2.3.0", "v2.2.1", "v2.1.0",
+				},
+				HasLatest: true,
+				HasNext:   true,
+			},
+		},
+		{
+			name:  "second page",
+			after: "v2.1.0",
+			expTagsPage: &TagsPage{
+				Tags: []string{
+					"v1.3.0", "v1.2.0", "v1.1.0",
+				},
+				HasLatest: false,
+				HasNext:   true,
+			},
+		},
+		{
+			name:  "last page",
+			after: "v1.1.0",
+			expTagsPage: &TagsPage{
+				Tags: []string{
+					"v0.8.0", "v0.5.0", "v0.1.0",
+				},
+				HasLatest:     false,
+				PreviousAfter: "v2.1.0",
+				HasNext:       false,
+			},
+		},
+
+		{
+			name:  "arbitrary after",
+			after: "v1.2.0",
+			expTagsPage: &TagsPage{
+				Tags: []string{
+					"v1.1.0", "v0.8.0", "v0.5.0",
+				},
+				HasLatest:     false,
+				PreviousAfter: "v2.2.1",
+				HasNext:       true,
+			},
+		},
+		{
+			name:  "after the oldest one",
+			after: "v0.1.0",
+			expTagsPage: &TagsPage{
+				Tags:          []string{},
+				HasLatest:     false,
+				PreviousAfter: "v1.1.0",
+				HasNext:       false,
+			},
+		},
+		{
+			name:  "after does not exist",
+			after: "v2.2.9",
+			expTagsPage: &TagsPage{
+				Tags: []string{
+					"v2.3.0", "v2.2.1", "v2.1.0",
+				},
+				HasLatest: true,
+				HasNext:   true,
+			},
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			tagsPage, err := Module.ListTagsAfter("", test.after, 3)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			assert.Equal(t, test.expTagsPage, tagsPage)
+		})
+	}
+}

+ 2 - 2
internal/route/api/v1/convert/convert.go

@@ -9,7 +9,7 @@ import (
 
 	"github.com/unknwon/com"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 	api "github.com/gogs/go-gogs-client"
 
 	"github.com/G-Node/gogs/internal/db"
@@ -43,7 +43,7 @@ func ToCommit(c *git.Commit) *api.PayloadCommit {
 	}
 	return &api.PayloadCommit{
 		ID:      c.ID.String(),
-		Message: c.Message(),
+		Message: c.Message,
 		URL:     "Not implemented",
 		Author: &api.PayloadUser{
 			Name:     c.Author.Name,

+ 20 - 19
internal/route/api/v1/repo/commits.go

@@ -9,13 +9,14 @@ import (
 	"strings"
 	"time"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 	api "github.com/gogs/go-gogs-client"
 
 	"github.com/G-Node/gogs/internal/conf"
 	"github.com/G-Node/gogs/internal/context"
 	"github.com/G-Node/gogs/internal/db"
 	"github.com/G-Node/gogs/internal/db/errors"
+	"github.com/G-Node/gogs/internal/gitutil"
 )
 
 func GetSingleCommit(c *context.APIContext) {
@@ -25,14 +26,14 @@ func GetSingleCommit(c *context.APIContext) {
 		return
 	}
 
-	gitRepo, err := git.OpenRepository(c.Repo.Repository.RepoPath())
+	gitRepo, err := git.Open(c.Repo.Repository.RepoPath())
 	if err != nil {
-		c.ServerError("OpenRepository", err)
+		c.ServerError("open repository", err)
 		return
 	}
-	commit, err := gitRepo.GetCommit(c.Params(":sha"))
+	commit, err := gitRepo.CatFileCommit(c.Params(":sha"))
 	if err != nil {
-		c.NotFoundOrServerError("GetCommit", git.IsErrNotExist, err)
+		c.NotFoundOrServerError("get commit", gitutil.IsErrRevisionNotExist, err)
 		return
 	}
 
@@ -59,8 +60,8 @@ func GetSingleCommit(c *context.APIContext) {
 	}
 
 	// Retrieve parent(s) of the commit
-	apiParents := make([]*api.CommitMeta, commit.ParentCount())
-	for i := 0; i < commit.ParentCount(); i++ {
+	apiParents := make([]*api.CommitMeta, commit.ParentsCount())
+	for i := 0; i < commit.ParentsCount(); i++ {
 		sha, _ := commit.ParentID(i)
 		apiParents[i] = &api.CommitMeta{
 			URL: c.BaseURL + "/repos/" + c.Repo.Repository.FullName() + "/commits/" + sha.String(),
@@ -99,24 +100,24 @@ func GetSingleCommit(c *context.APIContext) {
 }
 
 func GetReferenceSHA(c *context.APIContext) {
-	gitRepo, err := git.OpenRepository(c.Repo.Repository.RepoPath())
+	gitRepo, err := git.Open(c.Repo.Repository.RepoPath())
 	if err != nil {
-		c.ServerError("OpenRepository", err)
+		c.ServerError("open repository", err)
 		return
 	}
 
 	ref := c.Params("*")
-	refType := 0 // 0-undetermined, 1-branch, 2-tag
-	if strings.HasPrefix(ref, git.BRANCH_PREFIX) {
-		ref = strings.TrimPrefix(ref, git.BRANCH_PREFIX)
+	refType := 0 // 0-unknown, 1-branch, 2-tag
+	if strings.HasPrefix(ref, git.RefsHeads) {
+		ref = strings.TrimPrefix(ref, git.RefsHeads)
 		refType = 1
-	} else if strings.HasPrefix(ref, git.TAG_PREFIX) {
-		ref = strings.TrimPrefix(ref, git.TAG_PREFIX)
+	} else if strings.HasPrefix(ref, git.RefsTags) {
+		ref = strings.TrimPrefix(ref, git.RefsTags)
 		refType = 2
 	} else {
-		if gitRepo.IsBranchExist(ref) {
+		if gitRepo.HasBranch(ref) {
 			refType = 1
-		} else if gitRepo.IsTagExist(ref) {
+		} else if gitRepo.HasTag(ref) {
 			refType = 2
 		} else {
 			c.NotFound()
@@ -126,12 +127,12 @@ func GetReferenceSHA(c *context.APIContext) {
 
 	var sha string
 	if refType == 1 {
-		sha, err = gitRepo.GetBranchCommitID(ref)
+		sha, err = gitRepo.BranchCommitID(ref)
 	} else if refType == 2 {
-		sha, err = gitRepo.GetTagCommitID(ref)
+		sha, err = gitRepo.TagCommitID(ref)
 	}
 	if err != nil {
-		c.NotFoundOrServerError("get reference commit ID", git.IsErrNotExist, err)
+		c.NotFoundOrServerError("get reference commit ID", gitutil.IsErrRevisionNotExist, err)
 		return
 	}
 	c.PlainText(http.StatusOK, []byte(sha))

+ 30 - 45
internal/route/api/v1/repo/contents.go

@@ -7,11 +7,9 @@ package repo
 import (
 	"encoding/base64"
 	"fmt"
-	"io/ioutil"
-
-	"github.com/G-Node/git-module"
 
 	"github.com/G-Node/gogs/internal/context"
+	"github.com/G-Node/gogs/internal/gitutil"
 )
 
 type repoContent struct {
@@ -38,9 +36,9 @@ type Links struct {
 }
 
 func GetContents(c *context.APIContext) {
-	treeEntry, err := c.Repo.Commit.GetTreeEntryByPath(c.Repo.TreePath)
+	treeEntry, err := c.Repo.Commit.TreeEntry(c.Repo.TreePath)
 	if err != nil {
-		c.NotFoundOrServerError("GetTreeEntryByPath", git.IsErrNotExist, err)
+		c.NotFoundOrServerError("get tree entry", gitutil.IsErrRevisionNotExist, err)
 		return
 	}
 	username := c.Params(":username")
@@ -56,8 +54,8 @@ func GetContents(c *context.APIContext) {
 	// :baseurl/repos/:username/:project/tree/:sha
 	templateHTMLLLink := "%s/repos/%s/%s/tree/%s"
 
-	gitURL := fmt.Sprintf(templateGitURLLink, c.BaseURL, username, reponame, treeEntry.ID.String())
-	htmlURL := fmt.Sprintf(templateHTMLLLink, c.BaseURL, username, reponame, treeEntry.ID.String())
+	gitURL := fmt.Sprintf(templateGitURLLink, c.BaseURL, username, reponame, treeEntry.ID().String())
+	htmlURL := fmt.Sprintf(templateHTMLLLink, c.BaseURL, username, reponame, treeEntry.ID().String())
 	selfURL := fmt.Sprintf(templateSelfLink, c.BaseURL, username, reponame, c.Repo.TreePath)
 
 	// TODO(unknwon): Make a treeEntryToRepoContent helper.
@@ -65,7 +63,7 @@ func GetContents(c *context.APIContext) {
 		Size:        treeEntry.Size(),
 		Name:        treeEntry.Name(),
 		Path:        c.Repo.TreePath,
-		Sha:         treeEntry.ID.String(),
+		Sha:         treeEntry.ID().String(),
 		URL:         selfURL,
 		GitURL:      gitURL,
 		HTMLURL:     htmlURL,
@@ -82,65 +80,57 @@ func GetContents(c *context.APIContext) {
 	//   2. SubModule
 	//   3. SymLink
 	//   4. Blob (file)
-	if treeEntry.IsSubModule() {
+	if treeEntry.IsCommit() {
 		// TODO(unknwon): submoduleURL is not set as current git-module doesn't handle it properly
 		contents.Type = "submodule"
 		c.JSONSuccess(contents)
 		return
 
-	} else if treeEntry.IsLink() {
+	} else if treeEntry.IsSymlink() {
 		contents.Type = "symlink"
-		blob, err := c.Repo.Commit.GetBlobByPath(c.Repo.TreePath)
+		blob, err := c.Repo.Commit.Blob(c.Repo.TreePath)
 		if err != nil {
 			c.ServerError("GetBlobByPath", err)
 			return
 		}
-		b, err := blob.Data()
+		p, err := blob.Bytes()
 		if err != nil {
 			c.ServerError("Data", err)
 			return
 		}
-		buf, err := ioutil.ReadAll(b)
-		if err != nil {
-			c.ServerError("ReadAll", err)
-			return
-		}
-		contents.Target = string(buf)
+		contents.Target = string(p)
 		c.JSONSuccess(contents)
 		return
 
-	} else if treeEntry.Type == "blob" {
-		blob, err := c.Repo.Commit.GetBlobByPath(c.Repo.TreePath)
+	} else if treeEntry.IsBlob() {
+		blob, err := c.Repo.Commit.Blob(c.Repo.TreePath)
 		if err != nil {
 			c.ServerError("GetBlobByPath", err)
 			return
 		}
-		b, err := blob.Data()
+		p, err := blob.Bytes()
 		if err != nil {
 			c.ServerError("Data", err)
 			return
 		}
-		buf, err := ioutil.ReadAll(b)
-		if err != nil {
-			c.ServerError("ReadAll", err)
-			return
-		}
-		contents.Content = base64.StdEncoding.EncodeToString(buf)
+		contents.Content = base64.StdEncoding.EncodeToString(p)
 		contents.Type = "file"
 		c.JSONSuccess(contents)
 		return
 	}
 
+	// TODO: treeEntry.IsExec()
+
 	// treeEntry is a directory
-	dirTree, err := c.Repo.GitRepo.GetTree(treeEntry.ID.String())
+	dirTree, err := c.Repo.GitRepo.LsTree(treeEntry.ID().String())
 	if err != nil {
-		c.NotFoundOrServerError("GetTree", git.IsErrNotExist, err)
+		c.NotFoundOrServerError("get tree", gitutil.IsErrRevisionNotExist, err)
 		return
 	}
 
-	entries, err := dirTree.ListEntries()
+	entries, err := dirTree.Entries()
 	if err != nil {
-		c.NotFoundOrServerError("ListEntries", git.IsErrNotExist, err)
+		c.NotFoundOrServerError("list entries", gitutil.IsErrRevisionNotExist, err)
 		return
 	}
 
@@ -151,33 +141,28 @@ func GetContents(c *context.APIContext) {
 
 	var results = make([]*repoContent, 0, len(entries))
 	for _, entry := range entries {
-		gitURL := fmt.Sprintf(templateGitURLLink, c.BaseURL, username, reponame, entry.ID.String())
-		htmlURL := fmt.Sprintf(templateHTMLLLink, c.BaseURL, username, reponame, entry.ID.String())
+		gitURL := fmt.Sprintf(templateGitURLLink, c.BaseURL, username, reponame, entry.ID().String())
+		htmlURL := fmt.Sprintf(templateHTMLLLink, c.BaseURL, username, reponame, entry.ID().String())
 		selfURL := fmt.Sprintf(templateSelfLink, c.BaseURL, username, reponame, c.Repo.TreePath)
 		var contentType string
-		if entry.IsDir() {
+		if entry.IsTree() {
 			contentType = "dir"
-		} else if entry.IsSubModule() {
+		} else if entry.IsCommit() {
 			// TODO(unknwon): submoduleURL is not set as current git-module doesn't handle it properly
 			contentType = "submodule"
-		} else if entry.IsLink() {
+		} else if entry.IsSymlink() {
 			contentType = "symlink"
-			blob, err := c.Repo.Commit.GetBlobByPath(c.Repo.TreePath)
+			blob, err := c.Repo.Commit.Blob(c.Repo.TreePath)
 			if err != nil {
 				c.ServerError("GetBlobByPath", err)
 				return
 			}
-			b, err := blob.Data()
+			p, err := blob.Bytes()
 			if err != nil {
 				c.ServerError("Data", err)
 				return
 			}
-			buf, err := ioutil.ReadAll(b)
-			if err != nil {
-				c.ServerError("ReadAll", err)
-				return
-			}
-			contents.Target = string(buf)
+			contents.Target = string(p)
 		} else {
 			contentType = "file"
 		}
@@ -187,7 +172,7 @@ func GetContents(c *context.APIContext) {
 			Size:        entry.Size(),
 			Name:        entry.Name(),
 			Path:        c.Repo.TreePath,
-			Sha:         entry.ID.String(),
+			Sha:         entry.ID().String(),
 			URL:         selfURL,
 			GitURL:      gitURL,
 			HTMLURL:     htmlURL,

+ 10 - 11
internal/route/api/v1/repo/file.go

@@ -5,11 +5,11 @@
 package repo
 
 import (
-	"github.com/G-Node/git-module"
-	"github.com/go-macaron/captcha"
+	"github.com/gogs/git-module"
 
 	"github.com/G-Node/gogs/internal/context"
 	"github.com/G-Node/gogs/internal/db"
+	"github.com/G-Node/gogs/internal/gitutil"
 	"github.com/G-Node/gogs/internal/route/repo"
 )
 
@@ -24,22 +24,21 @@ func GetRawFile(c *context.APIContext) {
 		return
 	}
 
-	blob, err := c.Repo.Commit.GetBlobByPath(c.Repo.TreePath)
+	blob, err := c.Repo.Commit.Blob(c.Repo.TreePath)
 	if err != nil {
-		c.NotFoundOrServerError("GetBlobByPath", git.IsErrNotExist, err)
+		c.NotFoundOrServerError("get blob", gitutil.IsErrRevisionNotExist, err)
 		return
 	}
-	cp := captcha.NewCaptcha(captcha.Options{})
-	if err = repo.ServeBlob(c.Context, blob, cp); err != nil {
+	if err = repo.ServeBlob(c.Context, blob); err != nil {
 		c.ServerError("ServeBlob", err)
 	}
 }
 
 func GetArchive(c *context.APIContext) {
 	repoPath := db.RepoPath(c.Params(":username"), c.Params(":reponame"))
-	gitRepo, err := git.OpenRepository(repoPath)
+	gitRepo, err := git.Open(repoPath)
 	if err != nil {
-		c.ServerError("OpenRepository", err)
+		c.ServerError("open repository", err)
 		return
 	}
 	c.Repo.GitRepo = gitRepo
@@ -48,16 +47,16 @@ func GetArchive(c *context.APIContext) {
 }
 
 func GetEditorconfig(c *context.APIContext) {
-	ec, err := c.Repo.GetEditorconfig()
+	ec, err := c.Repo.Editorconfig()
 	if err != nil {
-		c.NotFoundOrServerError("GetEditorconfig", git.IsErrNotExist, err)
+		c.NotFoundOrServerError("get .editorconfig", gitutil.IsErrRevisionNotExist, err)
 		return
 	}
 
 	fileName := c.Params("filename")
 	def, err := ec.GetDefinitionForFilename(fileName)
 	if err != nil {
-		c.ServerError("GetDefinitionForFilename", err)
+		c.ServerError("get definition for filename", err)
 		return
 	}
 	if def == nil {

+ 14 - 9
internal/route/api/v1/repo/tree.go

@@ -1,11 +1,16 @@
+// Copyright 2020 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 (
 	"fmt"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 
 	"github.com/G-Node/gogs/internal/context"
+	"github.com/G-Node/gogs/internal/gitutil"
 )
 
 type repoGitTree struct {
@@ -24,14 +29,14 @@ type repoGitTreeEntry struct {
 }
 
 func GetRepoGitTree(c *context.APIContext) {
-	gitTree, err := c.Repo.GitRepo.GetTree(c.Params(":sha"))
+	gitTree, err := c.Repo.GitRepo.LsTree(c.Params(":sha"))
 	if err != nil {
-		c.NotFoundOrServerError("GetRepoGitTree", git.IsErrNotExist, err)
+		c.NotFoundOrServerError("get tree", gitutil.IsErrRevisionNotExist, err)
 		return
 	}
-	entries, err := gitTree.ListEntries()
+	entries, err := gitTree.Entries()
 	if err != nil {
-		c.ServerError("GetRepoGitTree", err)
+		c.ServerError("list entries", err)
 		return
 	}
 
@@ -48,7 +53,7 @@ func GetRepoGitTree(c *context.APIContext) {
 	children := make([]*repoGitTreeEntry, 0, len(entries))
 	for _, entry := range entries {
 		var mode string
-		switch entry.Type {
+		switch entry.Type() {
 		case git.ObjectCommit:
 			mode = "160000"
 		case git.ObjectTree:
@@ -63,10 +68,10 @@ func GetRepoGitTree(c *context.APIContext) {
 		children = append(children, &repoGitTreeEntry{
 			Path: entry.Name(),
 			Mode: mode,
-			Type: string(entry.Type),
+			Type: string(entry.Type()),
 			Size: entry.Size(),
-			Sha:  entry.ID.String(),
-			URL:  fmt.Sprintf(templateURL+"/%s", entry.ID.String()),
+			Sha:  entry.ID().String(),
+			URL:  fmt.Sprintf(templateURL+"/%s", entry.ID().String()),
 		})
 	}
 	c.JSONSuccess(&repoGitTree{

+ 3 - 3
internal/route/install.go

@@ -18,7 +18,7 @@ import (
 	log "unknwon.dev/clog/v2"
 	"xorm.io/xorm"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 
 	"github.com/G-Node/gogs/internal/conf"
 	"github.com/G-Node/gogs/internal/context"
@@ -41,9 +41,9 @@ func checkRunMode() {
 	if conf.IsProdMode() {
 		macaron.Env = macaron.PROD
 		macaron.ColorLog = false
-		git.Debug = false
+		git.SetOutput(nil)
 	} else {
-		git.Debug = true
+		git.SetOutput(os.Stdout)
 	}
 	log.Info("Run mode: %s", strings.Title(macaron.Env))
 }

+ 3 - 3
internal/route/repo/branch.go

@@ -9,7 +9,7 @@ import (
 
 	log "unknwon.dev/clog/v2"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 	api "github.com/gogs/go-gogs-client"
 
 	"github.com/G-Node/gogs/internal/context"
@@ -119,11 +119,11 @@ func DeleteBranchPost(c *context.Context) {
 		c.Redirect(redirectTo)
 	}()
 
-	if !c.Repo.GitRepo.IsBranchExist(branchName) {
+	if !c.Repo.GitRepo.HasBranch(branchName) {
 		return
 	}
 	if len(commitID) > 0 {
-		branchCommitID, err := c.Repo.GitRepo.GetBranchCommitID(branchName)
+		branchCommitID, err := c.Repo.GitRepo.BranchCommitID(branchName)
 		if err != nil {
 			log.Error("Failed to get commit ID of branch %q: %v", branchName, err)
 			return

+ 37 - 48
internal/route/repo/commit.go

@@ -5,14 +5,14 @@
 package repo
 
 import (
-	"container/list"
 	"path"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 
 	"github.com/G-Node/gogs/internal/conf"
 	"github.com/G-Node/gogs/internal/context"
 	"github.com/G-Node/gogs/internal/db"
+	"github.com/G-Node/gogs/internal/gitutil"
 	"github.com/G-Node/gogs/internal/tool"
 )
 
@@ -33,13 +33,9 @@ func RefCommits(c *context.Context) {
 	}
 }
 
-func RenderIssueLinks(oldCommits *list.List, repoLink string) *list.List {
-	newCommits := list.New()
-	for e := oldCommits.Front(); e != nil; e = e.Next() {
-		c := e.Value.(*git.Commit)
-		newCommits.PushBack(c)
-	}
-	return newCommits
+// TODO(unknwon)
+func RenderIssueLinks(oldCommits []*git.Commit, repoLink string) []*git.Commit {
+	return oldCommits
 }
 
 func renderCommits(c *context.Context, filename string) {
@@ -53,30 +49,23 @@ func renderCommits(c *context.Context, filename string) {
 	}
 	pageSize := c.QueryInt("pageSize")
 	if pageSize < 1 {
-		pageSize = git.DefaultCommitsPageSize
+		pageSize = conf.UI.User.CommitsPagingNum
 	}
 
-	// Both 'git log branchName' and 'git log commitID' work.
-	var err error
-	var commits *list.List
-	if len(filename) == 0 {
-		commits, err = c.Repo.Commit.CommitsByRangeSize(page, pageSize)
-	} else {
-		commits, err = c.Repo.GitRepo.CommitsByFileAndRangeSize(c.Repo.BranchName, filename, page, pageSize)
-	}
+	commits, err := c.Repo.Commit.CommitsByPage(page, pageSize, git.CommitsByPageOptions{Path: filename})
 	if err != nil {
-		c.Handle(500, "CommitsByRangeSize/CommitsByFileAndRangeSize", err)
+		c.ServerError("paging commits", err)
 		return
 	}
+
 	commits = RenderIssueLinks(commits, c.Repo.RepoLink)
-	commits = db.ValidateCommitsWithEmails(commits)
-	c.Data["Commits"] = commits
+	c.Data["Commits"] = db.ValidateCommitsWithEmails(commits)
 
 	if page > 1 {
 		c.Data["HasPrevious"] = true
 		c.Data["PreviousPage"] = page - 1
 	}
-	if commits.Len() == pageSize {
+	if len(commits) == pageSize {
 		c.Data["HasNext"] = true
 		c.Data["NextPage"] = page + 1
 	}
@@ -102,12 +91,12 @@ func SearchCommits(c *context.Context) {
 
 	commits, err := c.Repo.Commit.SearchCommits(keyword)
 	if err != nil {
-		c.Handle(500, "SearchCommits", err)
+		c.ServerError("SearchCommits", err)
 		return
 	}
+
 	commits = RenderIssueLinks(commits, c.Repo.RepoLink)
-	commits = db.ValidateCommitsWithEmails(commits)
-	c.Data["Commits"] = commits
+	c.Data["Commits"] = db.ValidateCommitsWithEmails(commits)
 
 	c.Data["Keyword"] = keyword
 	c.Data["Username"] = c.Repo.Owner.Name
@@ -128,22 +117,23 @@ func Diff(c *context.Context) {
 	repoName := c.Repo.Repository.Name
 	commitID := c.Params(":sha")
 
-	commit, err := c.Repo.GitRepo.GetCommit(commitID)
+	commit, err := c.Repo.GitRepo.CatFileCommit(commitID)
 	if err != nil {
-		c.NotFoundOrServerError("get commit by ID", git.IsErrNotExist, err)
+		// TODO: Move checker to gitutil package
+		c.NotFoundOrServerError("get commit by ID", gitutil.IsErrRevisionNotExist, err)
 		return
 	}
 
-	diff, err := db.GetDiffCommit(db.RepoPath(userName, repoName),
-		commitID, conf.Git.MaxGitDiffLines,
-		conf.Git.MaxGitDiffLineCharacters, conf.Git.MaxGitDiffFiles)
+	diff, err := gitutil.RepoDiff(c.Repo.GitRepo,
+		commitID, conf.Git.MaxDiffFiles, conf.Git.MaxDiffLines, conf.Git.MaxDiffLineChars,
+	)
 	if err != nil {
-		c.NotFoundOrServerError("get diff commit", git.IsErrNotExist, err)
+		c.NotFoundOrServerError("get diff", gitutil.IsErrRevisionNotExist, err)
 		return
 	}
 
-	parents := make([]string, commit.ParentCount())
-	for i := 0; i < commit.ParentCount(); i++ {
+	parents := make([]string, commit.ParentsCount())
+	for i := 0; i < commit.ParentsCount(); i++ {
 		sha, err := commit.ParentID(i)
 		parents[i] = sha.String()
 		if err != nil {
@@ -169,7 +159,7 @@ func Diff(c *context.Context) {
 	c.Data["Parents"] = parents
 	c.Data["DiffNotAvailable"] = diff.NumFiles() == 0
 	c.Data["SourcePath"] = conf.Server.Subpath + "/" + path.Join(userName, repoName, "src", commitID)
-	if commit.ParentCount() > 0 {
+	if commit.ParentsCount() > 0 {
 		c.Data["BeforeSourcePath"] = conf.Server.Subpath + "/" + path.Join(userName, repoName, "src", parents[0])
 	}
 	c.Data["RawPath"] = conf.Server.Subpath + "/" + path.Join(userName, repoName, "raw", commitID)
@@ -177,13 +167,12 @@ func Diff(c *context.Context) {
 }
 
 func RawDiff(c *context.Context) {
-	if err := git.GetRawDiff(
-		db.RepoPath(c.Repo.Owner.Name, c.Repo.Repository.Name),
+	if err := c.Repo.GitRepo.RawDiff(
 		c.Params(":sha"),
-		git.RawDiffType(c.Params(":ext")),
+		git.RawDiffFormat(c.Params(":ext")),
 		c.Resp,
 	); err != nil {
-		c.NotFoundOrServerError("GetRawDiff", git.IsErrNotExist, err)
+		c.NotFoundOrServerError("get raw diff", gitutil.IsErrRevisionNotExist, err)
 		return
 	}
 }
@@ -195,31 +184,31 @@ func CompareDiff(c *context.Context) {
 	beforeCommitID := c.Params(":before")
 	afterCommitID := c.Params(":after")
 
-	commit, err := c.Repo.GitRepo.GetCommit(afterCommitID)
+	commit, err := c.Repo.GitRepo.CatFileCommit(afterCommitID)
 	if err != nil {
 		c.Handle(404, "GetCommit", err)
 		return
 	}
 
-	diff, err := db.GetDiffRange(db.RepoPath(userName, repoName), beforeCommitID,
-		afterCommitID, conf.Git.MaxGitDiffLines,
-		conf.Git.MaxGitDiffLineCharacters, conf.Git.MaxGitDiffFiles)
+	diff, err := gitutil.RepoDiff(c.Repo.GitRepo,
+		afterCommitID, conf.Git.MaxDiffFiles, conf.Git.MaxDiffLines, conf.Git.MaxDiffLineChars,
+		git.DiffOptions{Base: beforeCommitID},
+	)
 	if err != nil {
-		c.Handle(404, "GetDiffRange", err)
+		c.ServerError("get diff", err)
 		return
 	}
 
-	commits, err := commit.CommitsBeforeUntil(beforeCommitID)
+	commits, err := commit.CommitsAfter(beforeCommitID)
 	if err != nil {
-		c.Handle(500, "CommitsBeforeUntil", err)
+		c.ServerError("get commits after", err)
 		return
 	}
-	commits = db.ValidateCommitsWithEmails(commits)
 
 	c.Data["IsSplitStyle"] = c.Query("style") == "split"
 	c.Data["CommitRepoLink"] = c.Repo.RepoLink
-	c.Data["Commits"] = commits
-	c.Data["CommitsCount"] = commits.Len()
+	c.Data["Commits"] = db.ValidateCommitsWithEmails(commits)
+	c.Data["CommitsCount"] = len(commits)
 	c.Data["BeforeCommitID"] = beforeCommitID
 	c.Data["AfterCommitID"] = afterCommitID
 	c.Data["Username"] = userName

+ 25 - 35
internal/route/repo/download.go

@@ -6,37 +6,30 @@ package repo
 
 import (
 	"fmt"
-	"io"
 	"net/http"
 	"path"
 
-	"github.com/G-Node/git-module"
-	"github.com/go-macaron/captcha"
+	"github.com/gogs/git-module"
 
 	"github.com/G-Node/gogs/internal/conf"
 	"github.com/G-Node/gogs/internal/context"
+	"github.com/G-Node/gogs/internal/gitutil"
 	"github.com/G-Node/gogs/internal/tool"
 )
 
-func serveData(c *context.Context, name string, r io.Reader, cpt *captcha.Captcha) error {
-	buf := make([]byte, 1024)
-	n, _ := r.Read(buf)
-	if n >= 0 {
-		buf = buf[:n]
-	}
-
-	commit, err := c.Repo.Commit.GetCommitByPath(c.Repo.TreePath)
+func serveData(c *context.Context, name string, data []byte) error {
+	commit, err := c.Repo.Commit.CommitByPath(git.CommitByRevisionOptions{Path: c.Repo.TreePath})
 	if err != nil {
-		return fmt.Errorf("GetCommitByPath: %v", err)
+		return fmt.Errorf("get commit by path %q: %v", c.Repo.TreePath, err)
 	}
 	c.Resp.Header().Set("Last-Modified", commit.Committer.When.Format(http.TimeFormat))
 
-	if tool.IsAnnexedFile(buf) {
-		return serveAnnexedData(c, name, cpt, buf)
+	if tool.IsAnnexedFile(data) {
+		return serveAnnexedData(c, name, data)
 	}
 
-	if !tool.IsTextFile(buf) {
-		if !tool.IsImageFile(buf) {
+	if !tool.IsTextFile(data) {
+		if !tool.IsImageFile(data) {
 			c.Resp.Header().Set("Content-Disposition", "attachment; filename=\""+name+"\"")
 			c.Resp.Header().Set("Content-Transfer-Encoding", "binary")
 		}
@@ -44,33 +37,30 @@ func serveData(c *context.Context, name string, r io.Reader, cpt *captcha.Captch
 		c.Resp.Header().Set("Content-Type", "text/plain; charset=utf-8")
 	}
 
-	if _, err := c.Resp.Write(buf); err != nil {
+	if _, err := c.Resp.Write(data); err != nil {
 		return fmt.Errorf("write buffer to response: %v", err)
 	}
-	_, err = io.Copy(c.Resp, r)
-	return err
+	return nil
 }
 
-func ServeBlob(c *context.Context, blob *git.Blob, cpt *captcha.Captcha) error {
-	r, w := io.Pipe()
-	defer r.Close()
-	defer w.Close()
-	go blob.DataPipeline(w, w)
-	return serveData(c, path.Base(c.Repo.TreePath), io.LimitReader(r, blob.Size()), cpt)
+func ServeBlob(c *context.Context, blob *git.Blob) error {
+	p, err := blob.Bytes()
+	if err != nil {
+		return err
+	}
+
+	return serveData(c, path.Base(c.Repo.TreePath), p)
 }
 
-func SingleDownload(c *context.Context, cpt *captcha.Captcha) {
-	blob, err := c.Repo.Commit.GetBlobByPath(c.Repo.TreePath)
+func SingleDownload(c *context.Context) {
+	blob, err := c.Repo.Commit.Blob(c.Repo.TreePath)
 	if err != nil {
-		if git.IsErrNotExist(err) {
-			c.Handle(404, "GetBlobByPath", nil)
-		} else {
-			c.Handle(500, "GetBlobByPath", err)
-		}
+		c.NotFoundOrServerError("get blob", gitutil.IsErrRevisionNotExist, err)
 		return
 	}
-	// reallow direct download independent of size
-	if err = ServeBlob(c, blob, nil); err != nil {
-		c.Handle(500, "ServeBlob", err)
+
+	if err = ServeBlob(c, blob); err != nil {
+		c.ServerError("serve blob", err)
+		return
 	}
 }

+ 24 - 31
internal/route/repo/editor.go

@@ -6,7 +6,6 @@ package repo
 
 import (
 	"fmt"
-	"io/ioutil"
 	"net/http"
 	"path"
 	"path/filepath"
@@ -14,12 +13,12 @@ import (
 
 	log "unknwon.dev/clog/v2"
 
-	"github.com/G-Node/git-module"
 	"github.com/G-Node/gogs/internal/conf"
 	"github.com/G-Node/gogs/internal/context"
 	"github.com/G-Node/gogs/internal/db"
 	"github.com/G-Node/gogs/internal/db/errors"
 	"github.com/G-Node/gogs/internal/form"
+	"github.com/G-Node/gogs/internal/gitutil"
 	"github.com/G-Node/gogs/internal/markup"
 	"github.com/G-Node/gogs/internal/pathutil"
 	"github.com/G-Node/gogs/internal/template"
@@ -57,20 +56,20 @@ func editFile(c *context.Context, isNewFile bool) {
 	treeNames, treePaths := getParentTreeFields(c.Repo.TreePath)
 
 	if !isNewFile {
-		entry, err := c.Repo.Commit.GetTreeEntryByPath(c.Repo.TreePath)
+		entry, err := c.Repo.Commit.TreeEntry(c.Repo.TreePath)
 		if err != nil {
-			c.NotFoundOrServerError("GetTreeEntryByPath", git.IsErrNotExist, err)
+			c.NotFoundOrServerError("get tree entry", gitutil.IsErrRevisionNotExist, err)
 			return
 		}
 
 		// No way to edit a directory online.
-		if entry.IsDir() {
+		if entry.IsTree() {
 			c.NotFound()
 			return
 		}
 
 		blob := entry.Blob()
-		dataRc, err := blob.Data()
+		p, err := blob.Bytes()
 		if err != nil {
 			c.ServerError("blob.Data", err)
 			return
@@ -81,25 +80,19 @@ func editFile(c *context.Context, isNewFile bool) {
 		c.Data["IsJSON"] = markup.IsJSON(blob.Name())
 		c.Data["IsYAML"] = markup.IsYAML(blob.Name())
 
-		buf := make([]byte, 1024)
-		n, _ := dataRc.Read(buf)
-		buf = buf[:n]
-
 		// Only text file are editable online.
-		if !tool.IsTextFile(buf) {
+		if !tool.IsTextFile(p) {
 			c.NotFound()
 			return
 		}
 
-		c.Data["IsODML"] = tool.IsODMLFile(buf)
+		c.Data["IsODML"] = tool.IsODMLFile(p)
 
-		d, _ := ioutil.ReadAll(dataRc)
-		buf = append(buf, d...)
-		if err, content := template.ToUTF8WithErr(buf); err != nil {
+		if err, content := template.ToUTF8WithErr(p); err != nil {
 			if err != nil {
 				log.Error("Failed to convert encoding to UTF-8: %v", err)
 			}
-			c.Data["FileContent"] = string(buf)
+			c.Data["FileContent"] = string(p)
 		} else {
 			c.Data["FileContent"] = content
 		}
@@ -188,9 +181,9 @@ func editFilePost(c *context.Context, f form.EditRepoFile, isNewFile bool) {
 	var newTreePath string
 	for index, part := range treeNames {
 		newTreePath = path.Join(newTreePath, part)
-		entry, err := c.Repo.Commit.GetTreeEntryByPath(newTreePath)
+		entry, err := c.Repo.Commit.TreeEntry(newTreePath)
 		if err != nil {
-			if git.IsErrNotExist(err) {
+			if gitutil.IsErrRevisionNotExist(err) {
 				// Means there is no item with that name, so we're good
 				break
 			}
@@ -199,17 +192,17 @@ func editFilePost(c *context.Context, f form.EditRepoFile, isNewFile bool) {
 			return
 		}
 		if index != len(treeNames)-1 {
-			if !entry.IsDir() {
+			if !entry.IsTree() {
 				c.FormErr("TreePath")
 				c.RenderWithErr(c.Tr("repo.editor.directory_is_a_file", part), EDIT_FILE, &f)
 				return
 			}
 		} else {
-			if entry.IsLink() {
+			if entry.IsSymlink() {
 				c.FormErr("TreePath")
 				c.RenderWithErr(c.Tr("repo.editor.file_is_a_symlink", part), EDIT_FILE, &f)
 				return
-			} else if entry.IsDir() {
+			} else if entry.IsTree() {
 				c.FormErr("TreePath")
 				c.RenderWithErr(c.Tr("repo.editor.filename_is_a_directory", part), EDIT_FILE, &f)
 				return
@@ -218,9 +211,9 @@ func editFilePost(c *context.Context, f form.EditRepoFile, isNewFile bool) {
 	}
 
 	if !isNewFile {
-		_, err := c.Repo.Commit.GetTreeEntryByPath(oldTreePath)
+		_, err := c.Repo.Commit.TreeEntry(oldTreePath)
 		if err != nil {
-			if git.IsErrNotExist(err) {
+			if gitutil.IsErrRevisionNotExist(err) {
 				c.FormErr("TreePath")
 				c.RenderWithErr(c.Tr("repo.editor.file_editing_no_longer_exists", oldTreePath), EDIT_FILE, &f)
 			} else {
@@ -229,7 +222,7 @@ func editFilePost(c *context.Context, f form.EditRepoFile, isNewFile bool) {
 			return
 		}
 		if lastCommit != c.Repo.CommitID {
-			files, err := c.Repo.Commit.GetFilesChangedSinceCommit(lastCommit)
+			files, err := c.Repo.Commit.FilesChangedAfter(lastCommit)
 			if err != nil {
 				c.ServerError("GetFilesChangedSinceCommit", err)
 				return
@@ -246,9 +239,9 @@ func editFilePost(c *context.Context, f form.EditRepoFile, isNewFile bool) {
 
 	if oldTreePath != f.TreePath {
 		// We have a new filename (rename or completely new file) so we need to make sure it doesn't already exist, can't clobber.
-		entry, err := c.Repo.Commit.GetTreeEntryByPath(f.TreePath)
+		entry, err := c.Repo.Commit.TreeEntry(f.TreePath)
 		if err != nil {
-			if !git.IsErrNotExist(err) {
+			if !gitutil.IsErrRevisionNotExist(err) {
 				c.ServerError("GetTreeEntryByPath", err)
 				return
 			}
@@ -308,11 +301,11 @@ func NewFilePost(c *context.Context, f form.EditRepoFile) {
 func DiffPreviewPost(c *context.Context, f form.EditPreviewDiff) {
 	treePath := c.Repo.TreePath
 
-	entry, err := c.Repo.Commit.GetTreeEntryByPath(treePath)
+	entry, err := c.Repo.Commit.TreeEntry(treePath)
 	if err != nil {
 		c.Error(500, "GetTreeEntryByPath: "+err.Error())
 		return
-	} else if entry.IsDir() {
+	} else if entry.IsTree() {
 		c.Error(422)
 		return
 	}
@@ -474,9 +467,9 @@ func UploadFilePost(c *context.Context, f form.UploadRepoFile) {
 	var newTreePath string
 	for _, part := range treeNames {
 		newTreePath = path.Join(newTreePath, part)
-		entry, err := c.Repo.Commit.GetTreeEntryByPath(newTreePath)
+		entry, err := c.Repo.Commit.TreeEntry(newTreePath)
 		if err != nil {
-			if git.IsErrNotExist(err) {
+			if gitutil.IsErrRevisionNotExist(err) {
 				// Means there is no item with that name, so we're good
 				break
 			}
@@ -486,7 +479,7 @@ func UploadFilePost(c *context.Context, f form.UploadRepoFile) {
 		}
 
 		// User can only upload files to a directory.
-		if !entry.IsDir() {
+		if !entry.IsTree() {
 			c.FormErr("TreePath")
 			c.RenderWithErr(c.Tr("repo.editor.directory_is_a_file", part), UPLOAD_FILE, &f)
 			return

+ 5 - 14
internal/route/repo/issue.go

@@ -6,8 +6,6 @@ package repo
 
 import (
 	"fmt"
-	"io"
-	"io/ioutil"
 	"net/http"
 	"net/url"
 	"strings"
@@ -303,30 +301,23 @@ func RetrieveRepoMetas(c *context.Context, repo *db.Repository) []*db.Label {
 }
 
 func getFileContentFromDefaultBranch(c *context.Context, filename string) (string, bool) {
-	var r io.Reader
-	var bytes []byte
-
 	if c.Repo.Commit == nil {
 		var err error
-		c.Repo.Commit, err = c.Repo.GitRepo.GetBranchCommit(c.Repo.Repository.DefaultBranch)
+		c.Repo.Commit, err = c.Repo.GitRepo.BranchCommit(c.Repo.Repository.DefaultBranch)
 		if err != nil {
 			return "", false
 		}
 	}
 
-	entry, err := c.Repo.Commit.GetTreeEntryByPath(filename)
-	if err != nil {
-		return "", false
-	}
-	r, err = entry.Blob().Data()
+	entry, err := c.Repo.Commit.TreeEntry(filename)
 	if err != nil {
 		return "", false
 	}
-	bytes, err = ioutil.ReadAll(r)
+	p, err := entry.Blob().Bytes()
 	if err != nil {
 		return "", false
 	}
-	return string(bytes), true
+	return string(p), true
 }
 
 func setTemplateIfExists(c *context.Context, ctxDataKey string, possibleFiles []string) {
@@ -656,7 +647,7 @@ func viewIssue(c *context.Context, isPullList bool) {
 		}
 
 		c.Data["IsPullBranchDeletable"] = pull.BaseRepoID == pull.HeadRepoID &&
-			c.Repo.IsWriter() && c.Repo.GitRepo.IsBranchExist(pull.HeadBranch) &&
+			c.Repo.IsWriter() && c.Repo.GitRepo.HasBranch(pull.HeadBranch) &&
 			!branchProtected
 
 		c.Data["DeleteBranchLink"] = c.Repo.MakeURL(url.URL{

+ 73 - 69
internal/route/repo/pull.go

@@ -5,20 +5,20 @@
 package repo
 
 import (
-	"container/list"
 	"path"
 	"strings"
 
 	"github.com/unknwon/com"
 	log "unknwon.dev/clog/v2"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 
 	"github.com/G-Node/gogs/internal/conf"
 	"github.com/G-Node/gogs/internal/context"
 	"github.com/G-Node/gogs/internal/db"
 	"github.com/G-Node/gogs/internal/db/errors"
 	"github.com/G-Node/gogs/internal/form"
+	"github.com/G-Node/gogs/internal/gitutil"
 	"github.com/G-Node/gogs/internal/tool"
 )
 
@@ -183,19 +183,21 @@ func PrepareMergedViewPullInfo(c *context.Context, issue *db.Issue) {
 	c.Data["BaseTarget"] = c.Repo.Owner.Name + "/" + pull.BaseBranch
 
 	var err error
-	c.Data["NumCommits"], err = c.Repo.GitRepo.CommitsCountBetween(pull.MergeBase, pull.MergedCommitID)
+	c.Data["NumCommits"], err = c.Repo.GitRepo.RevListCount([]string{pull.MergeBase + "..." + pull.MergedCommitID})
 	if err != nil {
 		c.ServerError("Repo.GitRepo.CommitsCountBetween", err)
 		return
 	}
-	c.Data["NumFiles"], err = c.Repo.GitRepo.FilesCountBetween(pull.MergeBase, pull.MergedCommitID)
+
+	names, err := c.Repo.GitRepo.DiffNameOnly(pull.MergeBase, pull.MergedCommitID, git.DiffNameOnlyOptions{NeedsMergeBase: true})
+	c.Data["NumFiles"] = len(names)
 	if err != nil {
 		c.ServerError("Repo.GitRepo.FilesCountBetween", err)
 		return
 	}
 }
 
-func PrepareViewPullInfo(c *context.Context, issue *db.Issue) *git.PullRequestInfo {
+func PrepareViewPullInfo(c *context.Context, issue *db.Issue) *gitutil.PullRequestMeta {
 	repo := c.Repo.Repository
 	pull := issue.PullRequest
 
@@ -208,14 +210,14 @@ func PrepareViewPullInfo(c *context.Context, issue *db.Issue) *git.PullRequestIn
 	)
 
 	if pull.HeadRepo != nil {
-		headGitRepo, err = git.OpenRepository(pull.HeadRepo.RepoPath())
+		headGitRepo, err = git.Open(pull.HeadRepo.RepoPath())
 		if err != nil {
-			c.ServerError("OpenRepository", err)
+			c.ServerError("open repository", err)
 			return nil
 		}
 	}
 
-	if pull.HeadRepo == nil || !headGitRepo.IsBranchExist(pull.HeadBranch) {
+	if pull.HeadRepo == nil || !headGitRepo.HasBranch(pull.HeadBranch) {
 		c.Data["IsPullReuqestBroken"] = true
 		c.Data["HeadTarget"] = "deleted"
 		c.Data["NumCommits"] = 0
@@ -223,8 +225,8 @@ func PrepareViewPullInfo(c *context.Context, issue *db.Issue) *git.PullRequestIn
 		return nil
 	}
 
-	prInfo, err := headGitRepo.GetPullRequestInfo(db.RepoPath(repo.Owner.Name, repo.Name),
-		pull.BaseBranch, pull.HeadBranch)
+	baseRepoPath := db.RepoPath(repo.Owner.Name, repo.Name)
+	prMeta, err := gitutil.Module.PullRequestMeta(headGitRepo.Path(), baseRepoPath, pull.HeadBranch, pull.BaseBranch)
 	if err != nil {
 		if strings.Contains(err.Error(), "fatal: Not a valid object name") {
 			c.Data["IsPullReuqestBroken"] = true
@@ -237,9 +239,9 @@ func PrepareViewPullInfo(c *context.Context, issue *db.Issue) *git.PullRequestIn
 		c.ServerError("GetPullRequestInfo", err)
 		return nil
 	}
-	c.Data["NumCommits"] = prInfo.Commits.Len()
-	c.Data["NumFiles"] = prInfo.NumFiles
-	return prInfo
+	c.Data["NumCommits"] = len(prMeta.Commits)
+	c.Data["NumFiles"] = prMeta.NumFiles
+	return prMeta
 }
 
 func ViewPullCommits(c *context.Context) {
@@ -257,25 +259,25 @@ func ViewPullCommits(c *context.Context) {
 		c.Data["Reponame"] = pull.HeadRepo.Name
 	}
 
-	var commits *list.List
+	var commits []*git.Commit
 	if pull.HasMerged {
 		PrepareMergedViewPullInfo(c, issue)
 		if c.Written() {
 			return
 		}
-		startCommit, err := c.Repo.GitRepo.GetCommit(pull.MergeBase)
+		startCommit, err := c.Repo.GitRepo.CatFileCommit(pull.MergeBase)
 		if err != nil {
-			c.ServerError("Repo.GitRepo.GetCommit", err)
+			c.ServerError("get commit of merge base", err)
 			return
 		}
-		endCommit, err := c.Repo.GitRepo.GetCommit(pull.MergedCommitID)
+		endCommit, err := c.Repo.GitRepo.CatFileCommit(pull.MergedCommitID)
 		if err != nil {
-			c.ServerError("Repo.GitRepo.GetCommit", err)
+			c.ServerError("get merged commit", err)
 			return
 		}
-		commits, err = c.Repo.GitRepo.CommitsBetween(endCommit, startCommit)
+		commits, err = c.Repo.GitRepo.RevList([]string{startCommit.ID.String() + "..." + endCommit.ID.String()})
 		if err != nil {
-			c.ServerError("Repo.GitRepo.CommitsBetween", err)
+			c.ServerError("list commits", err)
 			return
 		}
 
@@ -290,9 +292,8 @@ func ViewPullCommits(c *context.Context) {
 		commits = prInfo.Commits
 	}
 
-	commits = db.ValidateCommitsWithEmails(commits)
-	c.Data["Commits"] = commits
-	c.Data["CommitsCount"] = commits.Len()
+	c.Data["Commits"] = db.ValidateCommitsWithEmails(commits)
+	c.Data["CommitsCount"] = len(commits)
 
 	c.Success(PULL_COMMITS)
 }
@@ -308,7 +309,7 @@ func ViewPullFiles(c *context.Context) {
 	pull := issue.PullRequest
 
 	var (
-		diffRepoPath  string
+		diffGitRepo   *git.Repository
 		startCommitID string
 		endCommitID   string
 		gitRepo       *git.Repository
@@ -320,7 +321,7 @@ func ViewPullFiles(c *context.Context) {
 			return
 		}
 
-		diffRepoPath = c.Repo.GitRepo.Path
+		diffGitRepo = c.Repo.GitRepo
 		startCommitID = pull.MergeBase
 		endCommitID = pull.MergedCommitID
 		gitRepo = c.Repo.GitRepo
@@ -335,37 +336,38 @@ func ViewPullFiles(c *context.Context) {
 
 		headRepoPath := db.RepoPath(pull.HeadUserName, pull.HeadRepo.Name)
 
-		headGitRepo, err := git.OpenRepository(headRepoPath)
+		headGitRepo, err := git.Open(headRepoPath)
 		if err != nil {
-			c.ServerError("OpenRepository", err)
+			c.ServerError("open repository", err)
 			return
 		}
 
-		headCommitID, err := headGitRepo.GetBranchCommitID(pull.HeadBranch)
+		headCommitID, err := headGitRepo.BranchCommitID(pull.HeadBranch)
 		if err != nil {
-			c.ServerError("GetBranchCommitID", err)
+			c.ServerError("get head branch commit ID", err)
 			return
 		}
 
-		diffRepoPath = headRepoPath
+		diffGitRepo = headGitRepo
 		startCommitID = prInfo.MergeBase
 		endCommitID = headCommitID
 		gitRepo = headGitRepo
 	}
 
-	diff, err := db.GetDiffRange(diffRepoPath,
-		startCommitID, endCommitID, conf.Git.MaxGitDiffLines,
-		conf.Git.MaxGitDiffLineCharacters, conf.Git.MaxGitDiffFiles)
+	diff, err := gitutil.RepoDiff(diffGitRepo,
+		endCommitID, conf.Git.MaxDiffFiles, conf.Git.MaxDiffLines, conf.Git.MaxDiffLineChars,
+		git.DiffOptions{Base: startCommitID},
+	)
 	if err != nil {
-		c.ServerError("GetDiffRange", err)
+		c.ServerError("get diff", err)
 		return
 	}
 	c.Data["Diff"] = diff
 	c.Data["DiffNotAvailable"] = diff.NumFiles() == 0
 
-	commit, err := gitRepo.GetCommit(endCommitID)
+	commit, err := gitRepo.CatFileCommit(endCommitID)
 	if err != nil {
-		c.ServerError("GetCommit", err)
+		c.ServerError("get commit", err)
 		return
 	}
 
@@ -424,7 +426,7 @@ func MergePullRequest(c *context.Context) {
 	c.Redirect(c.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index))
 }
 
-func ParseCompareInfo(c *context.Context) (*db.User, *db.Repository, *git.Repository, *git.PullRequestInfo, string, string) {
+func ParseCompareInfo(c *context.Context) (*db.User, *db.Repository, *git.Repository, *gitutil.PullRequestMeta, string, string) {
 	baseRepo := c.Repo.Repository
 
 	// Get compared branches information
@@ -473,7 +475,7 @@ func ParseCompareInfo(c *context.Context) (*db.User, *db.Repository, *git.Reposi
 	c.Repo.PullRequest.SameRepo = isSameRepo
 
 	// Check if base branch is valid.
-	if !c.Repo.GitRepo.IsBranchExist(baseBranch) {
+	if !c.Repo.GitRepo.HasBranch(baseBranch) {
 		c.NotFound()
 		return nil, nil, nil, nil, "", ""
 	}
@@ -497,9 +499,9 @@ func ParseCompareInfo(c *context.Context) (*db.User, *db.Repository, *git.Reposi
 			return nil, nil, nil, nil, "", ""
 		}
 
-		headGitRepo, err = git.OpenRepository(db.RepoPath(headUser.Name, headRepo.Name))
+		headGitRepo, err = git.Open(db.RepoPath(headUser.Name, headRepo.Name))
 		if err != nil {
-			c.ServerError("OpenRepository", err)
+			c.ServerError("open repository", err)
 			return nil, nil, nil, nil, "", ""
 		}
 	} else {
@@ -514,31 +516,32 @@ func ParseCompareInfo(c *context.Context) (*db.User, *db.Repository, *git.Reposi
 	}
 
 	// Check if head branch is valid.
-	if !headGitRepo.IsBranchExist(headBranch) {
+	if !headGitRepo.HasBranch(headBranch) {
 		c.NotFound()
 		return nil, nil, nil, nil, "", ""
 	}
 
-	headBranches, err := headGitRepo.GetBranches()
+	headBranches, err := headGitRepo.Branches()
 	if err != nil {
-		c.ServerError("GetBranches", err)
+		c.ServerError("get branches", err)
 		return nil, nil, nil, nil, "", ""
 	}
 	c.Data["HeadBranches"] = headBranches
 
-	prInfo, err := headGitRepo.GetPullRequestInfo(db.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranch, headBranch)
+	baseRepoPath := db.RepoPath(baseRepo.Owner.Name, baseRepo.Name)
+	meta, err := gitutil.Module.PullRequestMeta(headGitRepo.Path(), baseRepoPath, headBranch, baseBranch)
 	if err != nil {
-		if git.IsErrNoMergeBase(err) {
+		if gitutil.IsErrNoMergeBase(err) {
 			c.Data["IsNoMergeBase"] = true
 			c.Success(COMPARE_PULL)
 		} else {
-			c.ServerError("GetPullRequestInfo", err)
+			c.ServerError("get pull request meta", err)
 		}
 		return nil, nil, nil, nil, "", ""
 	}
-	c.Data["BeforeCommitID"] = prInfo.MergeBase
+	c.Data["BeforeCommitID"] = meta.MergeBase
 
-	return headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch
+	return headUser, headRepo, headGitRepo, meta, baseBranch, headBranch
 }
 
 func PrepareCompareDiff(
@@ -546,8 +549,9 @@ func PrepareCompareDiff(
 	headUser *db.User,
 	headRepo *db.Repository,
 	headGitRepo *git.Repository,
-	prInfo *git.PullRequestInfo,
-	baseBranch, headBranch string) bool {
+	meta *gitutil.PullRequestMeta,
+	headBranch string,
+) bool {
 
 	var (
 		repo = c.Repo.Repository
@@ -557,44 +561,44 @@ func PrepareCompareDiff(
 	// Get diff information.
 	c.Data["CommitRepoLink"] = headRepo.Link()
 
-	headCommitID, err := headGitRepo.GetBranchCommitID(headBranch)
+	headCommitID, err := headGitRepo.BranchCommitID(headBranch)
 	if err != nil {
-		c.ServerError("GetBranchCommitID", err)
+		c.ServerError("get head branch commit ID", err)
 		return false
 	}
 	c.Data["AfterCommitID"] = headCommitID
 
-	if headCommitID == prInfo.MergeBase {
+	if headCommitID == meta.MergeBase {
 		c.Data["IsNothingToCompare"] = true
 		return true
 	}
 
-	diff, err := db.GetDiffRange(db.RepoPath(headUser.Name, headRepo.Name),
-		prInfo.MergeBase, headCommitID, conf.Git.MaxGitDiffLines,
-		conf.Git.MaxGitDiffLineCharacters, conf.Git.MaxGitDiffFiles)
+	diff, err := gitutil.RepoDiff(headGitRepo,
+		headCommitID, conf.Git.MaxDiffFiles, conf.Git.MaxDiffLines, conf.Git.MaxDiffLineChars,
+		git.DiffOptions{Base: meta.MergeBase},
+	)
 	if err != nil {
-		c.ServerError("GetDiffRange", err)
+		c.ServerError("get repository diff", err)
 		return false
 	}
 	c.Data["Diff"] = diff
 	c.Data["DiffNotAvailable"] = diff.NumFiles() == 0
 
-	headCommit, err := headGitRepo.GetCommit(headCommitID)
+	headCommit, err := headGitRepo.CatFileCommit(headCommitID)
 	if err != nil {
-		c.ServerError("GetCommit", err)
+		c.ServerError("get head commit", err)
 		return false
 	}
 
-	prInfo.Commits = db.ValidateCommitsWithEmails(prInfo.Commits)
-	c.Data["Commits"] = prInfo.Commits
-	c.Data["CommitCount"] = prInfo.Commits.Len()
+	c.Data["Commits"] = db.ValidateCommitsWithEmails(meta.Commits)
+	c.Data["CommitCount"] = len(meta.Commits)
 	c.Data["Username"] = headUser.Name
 	c.Data["Reponame"] = headRepo.Name
 	c.Data["IsImageFile"] = headCommit.IsImageFile
 
 	headTarget := path.Join(headUser.Name, repo.Name)
 	c.Data["SourcePath"] = conf.Server.Subpath + "/" + path.Join(headTarget, "src", headCommitID)
-	c.Data["BeforeSourcePath"] = conf.Server.Subpath + "/" + path.Join(headTarget, "src", prInfo.MergeBase)
+	c.Data["BeforeSourcePath"] = conf.Server.Subpath + "/" + path.Join(headTarget, "src", meta.MergeBase)
 	c.Data["RawPath"] = conf.Server.Subpath + "/" + path.Join(headTarget, "raw", headCommitID)
 	return false
 }
@@ -625,7 +629,7 @@ func CompareAndPullRequest(c *context.Context) {
 		return
 	}
 
-	nothingToCompare := PrepareCompareDiff(c, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch)
+	nothingToCompare := PrepareCompareDiff(c, headUser, headRepo, headGitRepo, prInfo, headBranch)
 	if c.Written() {
 		return
 	}
@@ -667,7 +671,7 @@ func CompareAndPullRequestPost(c *context.Context, f form.NewIssue) {
 		attachments []string
 	)
 
-	headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch := ParseCompareInfo(c)
+	headUser, headRepo, headGitRepo, meta, baseBranch, headBranch := ParseCompareInfo(c)
 	if c.Written() {
 		return
 	}
@@ -686,7 +690,7 @@ func CompareAndPullRequestPost(c *context.Context, f form.NewIssue) {
 
 		// This stage is already stop creating new pull request, so it does not matter if it has
 		// something to compare or not.
-		PrepareCompareDiff(c, headUser, headRepo, headGitRepo, prInfo, baseBranch, headBranch)
+		PrepareCompareDiff(c, headUser, headRepo, headGitRepo, meta, headBranch)
 		if c.Written() {
 			return
 		}
@@ -695,9 +699,9 @@ func CompareAndPullRequestPost(c *context.Context, f form.NewIssue) {
 		return
 	}
 
-	patch, err := headGitRepo.GetPatch(prInfo.MergeBase, headBranch)
+	patch, err := headGitRepo.DiffBinary(meta.MergeBase, headBranch)
 	if err != nil {
-		c.ServerError("GetPatch", err)
+		c.ServerError("get patch", err)
 		return
 	}
 
@@ -720,7 +724,7 @@ func CompareAndPullRequestPost(c *context.Context, f form.NewIssue) {
 		BaseBranch:   baseBranch,
 		HeadRepo:     headRepo,
 		BaseRepo:     repo,
-		MergeBase:    prInfo.MergeBase,
+		MergeBase:    meta.MergeBase,
 		Type:         db.PULL_REQUEST_GOGS,
 	}
 	// FIXME: check error in the case two people send pull request at almost same time, give nice error prompt

+ 23 - 21
internal/route/repo/release.go

@@ -8,12 +8,14 @@ import (
 	"fmt"
 	"strings"
 
+	"github.com/gogs/git-module"
 	log "unknwon.dev/clog/v2"
 
 	"github.com/G-Node/gogs/internal/conf"
 	"github.com/G-Node/gogs/internal/context"
 	"github.com/G-Node/gogs/internal/db"
 	"github.com/G-Node/gogs/internal/form"
+	"github.com/G-Node/gogs/internal/gitutil"
 	"github.com/G-Node/gogs/internal/markup"
 )
 
@@ -26,14 +28,14 @@ const (
 func calReleaseNumCommitsBehind(repoCtx *context.Repository, release *db.Release, countCache map[string]int64) error {
 	// Get count if not exists
 	if _, ok := countCache[release.Target]; !ok {
-		if repoCtx.GitRepo.IsBranchExist(release.Target) {
-			commit, err := repoCtx.GitRepo.GetBranchCommit(release.Target)
+		if repoCtx.GitRepo.HasBranch(release.Target) {
+			commit, err := repoCtx.GitRepo.BranchCommit(release.Target)
 			if err != nil {
-				return fmt.Errorf("GetBranchCommit: %v", err)
+				return fmt.Errorf("get branch commit: %v", err)
 			}
 			countCache[release.Target], err = commit.CommitsCount()
 			if err != nil {
-				return fmt.Errorf("CommitsCount: %v", err)
+				return fmt.Errorf("count commits: %v", err)
 			}
 		} else {
 			// Use NumCommits of the newest release on that target
@@ -49,13 +51,13 @@ func Releases(c *context.Context) {
 	c.Data["PageIsViewFiles"] = true
 	c.Data["PageIsReleaseList"] = true
 
-	tagsResult, err := c.Repo.GitRepo.GetTagsAfter(c.Query("after"), 10)
+	tagsPage, err := gitutil.Module.ListTagsAfter(c.Repo.GitRepo.Path(), c.Query("after"), 10)
 	if err != nil {
-		c.Handle(500, fmt.Sprintf("GetTags '%s'", c.Repo.Repository.RepoPath()), err)
+		c.ServerError("get tags", err)
 		return
 	}
 
-	releases, err := db.GetPublishedReleasesByRepoID(c.Repo.Repository.ID, tagsResult.Tags...)
+	releases, err := db.GetPublishedReleasesByRepoID(c.Repo.Repository.ID, tagsPage.Tags...)
 	if err != nil {
 		c.Handle(500, "GetPublishedReleasesByRepoID", err)
 		return
@@ -64,8 +66,8 @@ func Releases(c *context.Context) {
 	// Temproray cache commits count of used branches to speed up.
 	countCache := make(map[string]int64)
 
-	results := make([]*db.Release, len(tagsResult.Tags))
-	for i, rawTag := range tagsResult.Tags {
+	results := make([]*db.Release, len(tagsPage.Tags))
+	for i, rawTag := range tagsPage.Tags {
 		for j, r := range releases {
 			if r == nil || r.TagName != rawTag {
 				continue
@@ -89,9 +91,9 @@ func Releases(c *context.Context) {
 
 		// No published release matches this tag
 		if results[i] == nil {
-			commit, err := c.Repo.GitRepo.GetTagCommit(rawTag)
+			commit, err := c.Repo.GitRepo.TagCommit(rawTag)
 			if err != nil {
-				c.Handle(500, "GetTagCommit", err)
+				c.Handle(500, "get tag commit", err)
 				return
 			}
 
@@ -103,7 +105,7 @@ func Releases(c *context.Context) {
 
 			results[i].NumCommits, err = commit.CommitsCount()
 			if err != nil {
-				c.Handle(500, "CommitsCount", err)
+				c.ServerError("count commits", err)
 				return
 			}
 			results[i].NumCommitsBehind = c.Repo.CommitsCount - results[i].NumCommits
@@ -113,7 +115,7 @@ func Releases(c *context.Context) {
 
 	// Only show drafts if user is viewing the latest page
 	var drafts []*db.Release
-	if tagsResult.HasLatest {
+	if tagsPage.HasLatest {
 		drafts, err = db.GetDraftReleasesByRepoID(c.Repo.Repository.ID)
 		if err != nil {
 			c.Handle(500, "GetDraftReleasesByRepoID", err)
@@ -140,9 +142,9 @@ func Releases(c *context.Context) {
 	}
 
 	c.Data["Releases"] = results
-	c.Data["HasPrevious"] = !tagsResult.HasLatest
-	c.Data["ReachEnd"] = tagsResult.ReachEnd
-	c.Data["PreviousAfter"] = tagsResult.PreviousAfter
+	c.Data["HasPrevious"] = !tagsPage.HasLatest
+	c.Data["ReachEnd"] = !tagsPage.HasNext
+	c.Data["PreviousAfter"] = tagsPage.PreviousAfter
 	if len(results) > 0 {
 		c.Data["NextAfter"] = results[len(results)-1].TagName
 	}
@@ -175,14 +177,14 @@ func NewReleasePost(c *context.Context, f form.NewRelease) {
 		return
 	}
 
-	if !c.Repo.GitRepo.IsBranchExist(f.Target) {
+	if !c.Repo.GitRepo.HasBranch(f.Target) {
 		c.RenderWithErr(c.Tr("form.target_branch_not_exist"), RELEASE_NEW, &f)
 		return
 	}
 
 	// Use current time if tag not yet exist, otherwise get time from Git
 	var tagCreatedUnix int64
-	tag, err := c.Repo.GitRepo.GetTag(f.TagName)
+	tag, err := c.Repo.GitRepo.Tag(git.RefsTags + f.TagName)
 	if err == nil {
 		commit, err := tag.Commit()
 		if err == nil {
@@ -190,15 +192,15 @@ func NewReleasePost(c *context.Context, f form.NewRelease) {
 		}
 	}
 
-	commit, err := c.Repo.GitRepo.GetBranchCommit(f.Target)
+	commit, err := c.Repo.GitRepo.BranchCommit(f.Target)
 	if err != nil {
-		c.Handle(500, "GetBranchCommit", err)
+		c.ServerError("get branch commit", err)
 		return
 	}
 
 	commitsCount, err := commit.CommitsCount()
 	if err != nil {
-		c.Handle(500, "CommitsCount", err)
+		c.ServerError("count commits", err)
 		return
 	}
 

+ 20 - 24
internal/route/repo/repo.go

@@ -8,12 +8,13 @@ import (
 	"fmt"
 	"os"
 	"path"
+	"path/filepath"
 	"strings"
 
 	"github.com/unknwon/com"
 	log "unknwon.dev/clog/v2"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 
 	"github.com/G-Node/gogs/internal/conf"
 	"github.com/G-Node/gogs/internal/context"
@@ -272,27 +273,22 @@ func Action(c *context.Context) {
 
 func Download(c *context.Context) {
 	var (
-		uri         = c.Params("*")
-		refName     string
-		ext         string
-		archivePath string
-		archiveType git.ArchiveType
+		uri           = c.Params("*")
+		refName       string
+		ext           string
+		archivePath   string
+		archiveFormat git.ArchiveFormat
 	)
 
 	switch {
-	case strings.HasSuffix(uri, ".gin.zip"):
-		ext = ".gin.zip"
-		archivePath = path.Join(c.Repo.GitRepo.Path, "archives/gin")
-		archiveType = git.ArchiveGIN
 	case strings.HasSuffix(uri, ".zip"):
 		ext = ".zip"
-		archivePath = path.Join(c.Repo.GitRepo.Path, "archives/zip")
-		archiveType = git.ArchiveZip
+		archivePath = filepath.Join(c.Repo.GitRepo.Path(), "archives", "zip")
+		archiveFormat = git.ArchiveZip
 	case strings.HasSuffix(uri, ".tar.gz"):
 		ext = ".tar.gz"
-		archivePath = path.Join(c.Repo.GitRepo.Path, "archives/targz")
-		archiveType = git.ArchiveTarGz
-
+		archivePath = filepath.Join(c.Repo.GitRepo.Path(), "archives", "targz")
+		archiveFormat = git.ArchiveTarGz
 	default:
 		log.Trace("Unknown format: %s", uri)
 		c.Error(404)
@@ -313,20 +309,20 @@ func Download(c *context.Context) {
 		err    error
 	)
 	gitRepo := c.Repo.GitRepo
-	if gitRepo.IsBranchExist(refName) {
-		commit, err = gitRepo.GetBranchCommit(refName)
+	if gitRepo.HasBranch(refName) {
+		commit, err = gitRepo.BranchCommit(refName)
 		if err != nil {
-			c.Handle(500, "GetBranchCommit", err)
+			c.ServerError("get branch commit", err)
 			return
 		}
-	} else if gitRepo.IsTagExist(refName) {
-		commit, err = gitRepo.GetTagCommit(refName)
+	} else if gitRepo.HasTag(refName) {
+		commit, err = gitRepo.TagCommit(refName)
 		if err != nil {
-			c.Handle(500, "GetTagCommit", err)
+			c.ServerError("get tag commit", err)
 			return
 		}
 	} else if len(refName) >= 7 && len(refName) <= 40 {
-		commit, err = gitRepo.GetCommit(refName)
+		commit, err = gitRepo.CatFileCommit(refName)
 		if err != nil {
 			c.NotFound()
 			return
@@ -338,8 +334,8 @@ func Download(c *context.Context) {
 
 	archivePath = path.Join(archivePath, tool.ShortSHA1(commit.ID.String())+ext)
 	if !com.IsFile(archivePath) {
-		if err := commit.CreateArchive(archivePath, archiveType, c.Repo.Repository.CloneLink().SSH); err != nil {
-			c.Handle(500, "Download -> CreateArchive "+archivePath, err)
+		if err := commit.CreateArchive(archiveFormat, archivePath); err != nil {
+			c.ServerError("creates archive", err)
 			return
 		}
 	}

+ 14 - 22
internal/route/repo/repo_gin.go

@@ -5,24 +5,22 @@ import (
 	"bytes"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"os"
 	"path"
 	"path/filepath"
 	"strings"
 
-	"github.com/G-Node/git-module"
 	"github.com/G-Node/gogs/internal/conf"
 	"github.com/G-Node/gogs/internal/context"
 	"github.com/G-Node/gogs/internal/tool"
 	"github.com/G-Node/libgin/libgin"
-	"github.com/go-macaron/captcha"
+	"github.com/gogs/git-module"
 	log "gopkg.in/clog.v1"
 	"gopkg.in/yaml.v2"
 )
 
-func serveAnnexedData(ctx *context.Context, name string, cpt *captcha.Captcha, buf []byte) error {
+func serveAnnexedData(ctx *context.Context, name string, buf []byte) error {
 	keyparts := strings.Split(strings.TrimSpace(string(buf)), "/")
 	key := keyparts[len(keyparts)-1]
 	contentPath, err := git.NewCommand("annex", "contentlocation", key).RunInDir(ctx.Repo.Repository.RepoPath())
@@ -31,8 +29,8 @@ func serveAnnexedData(ctx *context.Context, name string, cpt *captcha.Captcha, b
 		return err
 	}
 	// always trim space from output for git command
-	contentPath = strings.TrimSpace(contentPath)
-	return serveAnnexedKey(ctx, name, contentPath)
+	contentPath = bytes.TrimSpace(contentPath)
+	return serveAnnexedKey(ctx, name, string(contentPath))
 }
 
 func serveAnnexedKey(ctx *context.Context, name string, contentPath string) error {
@@ -75,19 +73,13 @@ func serveAnnexedKey(ctx *context.Context, name string, contentPath string) erro
 
 func readDataciteFile(c *context.Context) {
 	log.Trace("Reading datacite.yml file")
-	entry, err := c.Repo.Commit.GetBlobByPath("/datacite.yml")
+	entry, err := c.Repo.Commit.Blob("/datacite.yml")
 	if err != nil || entry == nil {
 		log.Error(2, "datacite.yml blob could not be retrieved: %v", err)
 		c.Data["HasDataCite"] = false
 		return
 	}
-	doiData, err := entry.Data()
-	if err != nil {
-		log.Error(2, "datacite.yml blob could not be read: %v", err)
-		c.Data["HasDataCite"] = false
-		return
-	}
-	buf, err := ioutil.ReadAll(doiData)
+	buf, err := entry.Bytes()
 	if err != nil {
 		log.Error(2, "datacite.yml data could not be read: %v", err)
 		c.Data["HasDataCite"] = false
@@ -111,10 +103,10 @@ func readDataciteFile(c *context.Context) {
 // the two variables without modifications.
 // Any errors that occur during processing are stored in the provided context.
 // The FileSize of the annexed content is also saved in the context (c.Data["FileSize"]).
-func resolveAnnexedContent(c *context.Context, buf []byte, dataRc io.Reader) ([]byte, io.Reader, error) {
+func resolveAnnexedContent(c *context.Context, buf []byte) ([]byte, error) {
 	if !tool.IsAnnexedFile(buf) {
 		// not an annex pointer file; return as is
-		return buf, dataRc, nil
+		return buf, nil
 	}
 	log.Trace("Annexed file requested: Resolving content for %q", bytes.TrimSpace(buf))
 
@@ -124,21 +116,21 @@ func resolveAnnexedContent(c *context.Context, buf []byte, dataRc io.Reader) ([]
 	if err != nil {
 		log.Error(2, "Failed to find content location for key %q", key)
 		c.Data["IsAnnexedFile"] = true
-		return buf, dataRc, err
+		return buf, err
 	}
 	// always trim space from output for git command
-	contentPath = strings.TrimSpace(contentPath)
-	afp, err := os.Open(filepath.Join(c.Repo.Repository.RepoPath(), contentPath))
+	contentPath = bytes.TrimSpace(contentPath)
+	afp, err := os.Open(filepath.Join(c.Repo.Repository.RepoPath(), string(contentPath)))
 	if err != nil {
 		log.Trace("Could not open annex file: %v", err)
 		c.Data["IsAnnexedFile"] = true
-		return buf, dataRc, err
+		return buf, err
 	}
 	info, err := afp.Stat()
 	if err != nil {
 		log.Trace("Could not stat annex file: %v", err)
 		c.Data["IsAnnexedFile"] = true
-		return buf, dataRc, err
+		return buf, err
 	}
 	annexDataReader := bufio.NewReader(afp)
 	annexBuf := make([]byte, 1024)
@@ -146,7 +138,7 @@ func resolveAnnexedContent(c *context.Context, buf []byte, dataRc io.Reader) ([]
 	annexBuf = annexBuf[:n]
 	c.Data["FileSize"] = info.Size()
 	log.Trace("Annexed file size: %d B", info.Size())
-	return annexBuf, annexDataReader, nil
+	return annexBuf, nil
 }
 
 func GitConfig(c *context.Context) {

+ 15 - 18
internal/route/repo/setting.go

@@ -7,10 +7,11 @@ package repo
 import (
 	"fmt"
 	"io/ioutil"
+	"os"
 	"strings"
 	"time"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 	"github.com/unknwon/com"
 	log "unknwon.dev/clog/v2"
 
@@ -486,7 +487,7 @@ func SettingsBranches(c *context.Context) {
 	// Filter out deleted branches
 	branches := make([]string, 0, len(protectBranches))
 	for i := range protectBranches {
-		if c.Repo.GitRepo.IsBranchExist(protectBranches[i].Name) {
+		if c.Repo.GitRepo.HasBranch(protectBranches[i].Name) {
 			branches = append(branches, protectBranches[i].Name)
 		}
 	}
@@ -497,15 +498,12 @@ func SettingsBranches(c *context.Context) {
 
 func UpdateDefaultBranch(c *context.Context) {
 	branch := c.Query("branch")
-	if c.Repo.GitRepo.IsBranchExist(branch) &&
+	if c.Repo.GitRepo.HasBranch(branch) &&
 		c.Repo.Repository.DefaultBranch != branch {
 		c.Repo.Repository.DefaultBranch = branch
-		if err := c.Repo.GitRepo.SetDefaultBranch(branch); err != nil {
-			if !git.IsErrUnsupportedVersion(err) {
-				c.Handle(500, "SetDefaultBranch", err)
-				return
-			}
-
+		if _, err := c.Repo.GitRepo.SymbolicRef(git.SymbolicRefOptions{
+			Ref: git.RefsHeads + branch,
+		}); err != nil {
 			c.Flash.Warning(c.Tr("repo.settings.update_default_branch_unsupported"))
 			c.Redirect(c.Repo.RepoLink + "/settings/branches")
 			return
@@ -523,7 +521,7 @@ func UpdateDefaultBranch(c *context.Context) {
 
 func SettingsProtectedBranch(c *context.Context) {
 	branch := c.Params("*")
-	if !c.Repo.GitRepo.IsBranchExist(branch) {
+	if !c.Repo.GitRepo.HasBranch(branch) {
 		c.NotFound()
 		return
 	}
@@ -568,7 +566,7 @@ func SettingsProtectedBranch(c *context.Context) {
 
 func SettingsProtectedBranchPost(c *context.Context, f form.ProtectBranch) {
 	branch := c.Params("*")
-	if !c.Repo.GitRepo.IsBranchExist(branch) {
+	if !c.Repo.GitRepo.HasBranch(branch) {
 		c.NotFound()
 		return
 	}
@@ -608,7 +606,7 @@ func SettingsGitHooks(c *context.Context) {
 	c.Data["Title"] = c.Tr("repo.settings.githooks")
 	c.Data["PageIsSettingsGitHooks"] = true
 
-	hooks, err := c.Repo.GitRepo.Hooks()
+	hooks, err := c.Repo.GitRepo.Hooks("custom_hooks")
 	if err != nil {
 		c.Handle(500, "Hooks", err)
 		return
@@ -624,9 +622,9 @@ func SettingsGitHooksEdit(c *context.Context) {
 	c.Data["RequireSimpleMDE"] = true
 
 	name := c.Params(":name")
-	hook, err := c.Repo.GitRepo.GetHook(name)
+	hook, err := c.Repo.GitRepo.Hook("custom_hooks", git.HookName(name))
 	if err != nil {
-		if err == git.ErrNotValidHook {
+		if err == os.ErrNotExist {
 			c.Handle(404, "GetHook", err)
 		} else {
 			c.Handle(500, "GetHook", err)
@@ -639,17 +637,16 @@ func SettingsGitHooksEdit(c *context.Context) {
 
 func SettingsGitHooksEditPost(c *context.Context) {
 	name := c.Params(":name")
-	hook, err := c.Repo.GitRepo.GetHook(name)
+	hook, err := c.Repo.GitRepo.Hook("custom_hooks", git.HookName(name))
 	if err != nil {
-		if err == git.ErrNotValidHook {
+		if err == os.ErrNotExist {
 			c.Handle(404, "GetHook", err)
 		} else {
 			c.Handle(500, "GetHook", err)
 		}
 		return
 	}
-	hook.Content = c.Query("content")
-	if err = hook.Update(); err != nil {
+	if err = hook.Update(c.Query("content")); err != nil {
 		c.Handle(500, "hook.Update", err)
 		return
 	}

+ 54 - 65
internal/route/repo/view.go

@@ -8,20 +8,20 @@ import (
 	"bytes"
 	"fmt"
 	gotemplate "html/template"
-	"io/ioutil"
 	"path"
 	"strings"
+	"time"
 
-	"github.com/go-macaron/captcha"
+	"github.com/G-Node/libgin/libgin/annex"
+	"github.com/gogs/git-module"
+	"github.com/pkg/errors"
 	"github.com/unknwon/paginater"
 	log "unknwon.dev/clog/v2"
 
-	"github.com/G-Node/git-module"
-	"github.com/G-Node/libgin/libgin/annex"
-
 	"github.com/G-Node/gogs/internal/conf"
 	"github.com/G-Node/gogs/internal/context"
 	"github.com/G-Node/gogs/internal/db"
+	"github.com/G-Node/gogs/internal/gitutil"
 	"github.com/G-Node/gogs/internal/markup"
 	"github.com/G-Node/gogs/internal/template"
 	"github.com/G-Node/gogs/internal/template/highlight"
@@ -36,22 +36,26 @@ const (
 )
 
 func renderDirectory(c *context.Context, treeLink string) {
-	tree, err := c.Repo.Commit.SubTree(c.Repo.TreePath)
+	tree, err := c.Repo.Commit.Subtree(c.Repo.TreePath)
 	if err != nil {
-		c.NotFoundOrServerError("Repo.Commit.SubTree", git.IsErrNotExist, err)
+		c.NotFoundOrServerError("get subtree", gitutil.IsErrRevisionNotExist, err)
 		return
 	}
 
-	entries, err := tree.ListEntries()
+	entries, err := tree.Entries()
 	if err != nil {
-		c.ServerError("ListEntries", err)
+		c.ServerError("list entries", err)
 		return
 	}
 	entries.Sort()
 
-	c.Data["Files"], err = entries.GetCommitsInfoWithCustomConcurrency(c.Repo.Commit, c.Repo.TreePath, conf.Repository.CommitsFetchConcurrency)
+	c.Data["Files"], err = entries.CommitsInfo(c.Repo.Commit, git.CommitsInfoOptions{
+		Path:           c.Repo.TreePath,
+		MaxConcurrency: conf.Repository.CommitsFetchConcurrency,
+		Timeout:        5 * time.Minute,
+	})
 	if err != nil {
-		c.ServerError("GetCommitsInfoWithCustomConcurrency", err)
+		c.ServerError("get commits info", err)
 		return
 	}
 	if c.Data["HasDataCite"].(bool) {
@@ -59,11 +63,11 @@ func renderDirectory(c *context.Context, treeLink string) {
 	}
 	var readmeFile *git.Blob
 	for _, entry := range entries {
-		if entry.IsDir() || !markup.IsReadmeFile(entry.Name()) {
+		if entry.IsTree() || !markup.IsReadmeFile(entry.Name()) {
 			continue
 		}
 
-		// TODO: collect all possible README files and show with priority.
+		// TODO(unknwon): collect all possible README files and show with priority.
 		readmeFile = entry.Blob()
 		break
 	}
@@ -73,43 +77,36 @@ func renderDirectory(c *context.Context, treeLink string) {
 		c.Data["ReadmeInList"] = true
 		c.Data["ReadmeExist"] = true
 
-		dataRc, err := readmeFile.Data()
+		p, err := readmeFile.Bytes()
 		if err != nil {
 			c.ServerError("readmeFile.Data", err)
 			return
 		}
 
-		buf := make([]byte, 1024)
-		n, _ := dataRc.Read(buf)
-		buf = buf[:n]
-
-		// GIN mod: Replace existing buf and reader with annexed content buf and reader
-		buf, dataRc, err = resolveAnnexedContent(c, buf, dataRc)
+		// GIN mod: Replace existing buffer p and reader with annexed content buffer
+		p, err = resolveAnnexedContent(c, p)
 		if err != nil {
 			return
 		}
 
-		isTextFile := tool.IsTextFile(buf)
+		isTextFile := tool.IsTextFile(p)
 		c.Data["IsTextFile"] = isTextFile
 		c.Data["FileName"] = readmeFile.Name()
 		if isTextFile {
-			d, _ := ioutil.ReadAll(dataRc)
-			buf = append(buf, d...)
-
 			switch markup.Detect(readmeFile.Name()) {
 			case markup.MARKDOWN:
 				c.Data["IsMarkdown"] = true
-				buf = markup.Markdown(buf, treeLink, c.Repo.Repository.ComposeMetas())
+				p = markup.Markdown(p, treeLink, c.Repo.Repository.ComposeMetas())
 			case markup.ORG_MODE:
 				c.Data["IsMarkdown"] = true
-				buf = markup.OrgMode(buf, treeLink, c.Repo.Repository.ComposeMetas())
+				p = markup.OrgMode(p, treeLink, c.Repo.Repository.ComposeMetas())
 			case markup.IPYTHON_NOTEBOOK:
 				c.Data["IsIPythonNotebook"] = true
 				c.Data["RawFileLink"] = c.Repo.RepoLink + "/raw/" + path.Join(c.Repo.BranchName, c.Repo.TreePath, readmeFile.Name())
 			default:
-				buf = bytes.Replace(buf, []byte("\n"), []byte(`<br>`), -1)
+				p = bytes.Replace(p, []byte("\n"), []byte(`<br>`), -1)
 			}
-			c.Data["FileContent"] = string(buf)
+			c.Data["FileContent"] = string(p)
 		}
 	}
 
@@ -117,9 +114,9 @@ func renderDirectory(c *context.Context, treeLink string) {
 	// or of directory if not in root directory.
 	latestCommit := c.Repo.Commit
 	if len(c.Repo.TreePath) > 0 {
-		latestCommit, err = c.Repo.Commit.GetCommitByPath(c.Repo.TreePath)
+		latestCommit, err = c.Repo.Commit.CommitByPath(git.CommitByRevisionOptions{Path: c.Repo.TreePath})
 		if err != nil {
-			c.ServerError("GetCommitByPath", err)
+			c.ServerError("get commit by path", err)
 			return
 		}
 	}
@@ -132,11 +129,11 @@ func renderDirectory(c *context.Context, treeLink string) {
 	}
 }
 
-func renderFile(c *context.Context, entry *git.TreeEntry, treeLink, rawLink string, cpt *captcha.Captcha) {
+func renderFile(c *context.Context, entry *git.TreeEntry, treeLink, rawLink string) {
 	c.Data["IsViewFile"] = true
 
 	blob := entry.Blob()
-	dataRc, err := blob.Data()
+	p, err := blob.Bytes()
 	if err != nil {
 		c.Handle(500, "Data", err)
 		return
@@ -147,18 +144,13 @@ func renderFile(c *context.Context, entry *git.TreeEntry, treeLink, rawLink stri
 	c.Data["HighlightClass"] = highlight.FileNameToHighlightClass(blob.Name())
 	c.Data["RawFileLink"] = rawLink + "/" + c.Repo.TreePath
 
-	buf := make([]byte, 1024)
-	n, _ := dataRc.Read(buf)
-	buf = buf[:n]
-
-	// GIN mod: Replace existing buf and reader with annexed content buf and
-	// reader (only if it's an annexed ptr file)
-	buf, dataRc, err = resolveAnnexedContent(c, buf, dataRc)
+	// GIN mod: Replace existing buffer p with annexed content buffer (only if
+	// it's an annexed ptr file)
+	p, err = resolveAnnexedContent(c, p)
 	if err != nil {
 		return
 	}
-
-	isTextFile := tool.IsTextFile(buf)
+	isTextFile := tool.IsTextFile(p)
 	c.Data["IsTextFile"] = isTextFile
 
 	// Assume file is not editable first.
@@ -178,44 +170,41 @@ func renderFile(c *context.Context, entry *git.TreeEntry, treeLink, rawLink stri
 
 		c.Data["ReadmeExist"] = markup.IsReadmeFile(blob.Name())
 
-		d, _ := ioutil.ReadAll(dataRc)
-		buf = append(buf, d...)
-
 		switch markup.Detect(blob.Name()) {
 		case markup.MARKDOWN:
 			c.Data["IsMarkdown"] = true
-			c.Data["FileContent"] = string(markup.Markdown(buf, path.Dir(treeLink), c.Repo.Repository.ComposeMetas()))
+			c.Data["FileContent"] = string(markup.Markdown(p, path.Dir(treeLink), c.Repo.Repository.ComposeMetas()))
 		case markup.ORG_MODE:
 			c.Data["IsMarkdown"] = true
-			c.Data["FileContent"] = string(markup.OrgMode(buf, path.Dir(treeLink), c.Repo.Repository.ComposeMetas()))
+			c.Data["FileContent"] = string(markup.OrgMode(p, path.Dir(treeLink), c.Repo.Repository.ComposeMetas()))
 		case markup.IPYTHON_NOTEBOOK:
 			c.Data["IsIPythonNotebook"] = true
 			// GIN mod: JSON, YAML, and odML render support with jsTree
 		case markup.JSON:
 			c.Data["IsJSON"] = true
-			c.Data["RawFileContent"] = string(buf)
+			c.Data["RawFileContent"] = string(p)
 			fallthrough
 		case markup.YAML:
 			c.Data["IsYAML"] = true
-			c.Data["RawFileContent"] = string(buf)
+			c.Data["RawFileContent"] = string(p)
 			fallthrough
 		case markup.XML:
 			// pass XML down to ODML checker
 			fallthrough
 		case markup.ODML:
-			if tool.IsODMLFile(buf) {
+			if tool.IsODMLFile(p) {
 				c.Data["IsODML"] = true
-				c.Data["ODML"] = string(markup.MarshalODML(buf))
+				c.Data["ODML"] = string(markup.MarshalODML(p))
 			}
 			fallthrough
 		default:
 			// Building code view blocks with line number on server side.
 			var fileContent string
-			if err, content := template.ToUTF8WithErr(buf); err != nil {
+			if err, content := template.ToUTF8WithErr(p); err != nil {
 				if err != nil {
 					log.Error("ToUTF8WithErr: %s", err)
 				}
-				fileContent = string(buf)
+				fileContent = string(p)
 			} else {
 				fileContent = content
 			}
@@ -243,7 +232,7 @@ func renderFile(c *context.Context, entry *git.TreeEntry, treeLink, rawLink stri
 			c.Data["LineNums"] = gotemplate.HTML(output.String())
 		}
 
-		isannex := tool.IsAnnexedFile(buf)
+		isannex := tool.IsAnnexedFile(p)
 		if canEnableEditor && !isannex {
 			c.Data["CanEditFile"] = true
 			c.Data["EditFileTooltip"] = c.Tr("repo.editor.edit_this_file")
@@ -253,16 +242,16 @@ func renderFile(c *context.Context, entry *git.TreeEntry, treeLink, rawLink stri
 			c.Data["EditFileTooltip"] = c.Tr("repo.editor.fork_before_edit")
 		}
 
-	case tool.IsPDFFile(buf) && (c.Data["FileSize"].(int64) < conf.Repository.RawCaptchaMinFileSize*annex.MEGABYTE ||
+	case tool.IsPDFFile(p) && (c.Data["FileSize"].(int64) < conf.Repository.RawCaptchaMinFileSize*annex.MEGABYTE ||
 		c.IsLogged):
 		c.Data["IsPDFFile"] = true
-	case tool.IsVideoFile(buf) && (c.Data["FileSize"].(int64) < conf.Repository.RawCaptchaMinFileSize*annex.MEGABYTE ||
+	case tool.IsVideoFile(p) && (c.Data["FileSize"].(int64) < conf.Repository.RawCaptchaMinFileSize*annex.MEGABYTE ||
 		c.IsLogged):
 		c.Data["IsVideoFile"] = true
-	case tool.IsImageFile(buf) && (c.Data["FileSize"].(int64) < conf.Repository.RawCaptchaMinFileSize*annex.MEGABYTE ||
+	case tool.IsImageFile(p) && (c.Data["FileSize"].(int64) < conf.Repository.RawCaptchaMinFileSize*annex.MEGABYTE ||
 		c.IsLogged):
 		c.Data["IsImageFile"] = true
-	case tool.IsAnnexedFile(buf) && (c.Data["FileSize"].(int64) < conf.Repository.RawCaptchaMinFileSize*annex.MEGABYTE ||
+	case tool.IsAnnexedFile(p) && (c.Data["FileSize"].(int64) < conf.Repository.RawCaptchaMinFileSize*annex.MEGABYTE ||
 		c.IsLogged):
 		c.Data["IsAnnexedFile"] = true
 	}
@@ -278,15 +267,15 @@ func renderFile(c *context.Context, entry *git.TreeEntry, treeLink, rawLink stri
 }
 
 func setEditorconfigIfExists(c *context.Context) {
-	ec, err := c.Repo.GetEditorconfig()
-	if err != nil && !git.IsErrNotExist(err) {
-		log.Trace("setEditorconfigIfExists.GetEditorconfig [%d]: %v", c.Repo.Repository.ID, err)
+	ec, err := c.Repo.Editorconfig()
+	if err != nil && !gitutil.IsErrRevisionNotExist(errors.Cause(err)) {
+		log.Warn("setEditorconfigIfExists.Editorconfig [repo_id: %d]: %v", c.Repo.Repository.ID, err)
 		return
 	}
 	c.Data["Editorconfig"] = ec
 }
 
-func Home(c *context.Context, cpt *captcha.Captcha) {
+func Home(c *context.Context) {
 	c.Data["PageIsViewFiles"] = true
 
 	if c.Repo.Repository.IsBare {
@@ -326,16 +315,16 @@ func Home(c *context.Context, cpt *captcha.Captcha) {
 	c.Data["PageIsRepoHome"] = isRootDir
 
 	// Get current entry user currently looking at.
-	entry, err := c.Repo.Commit.GetTreeEntryByPath(c.Repo.TreePath)
+	entry, err := c.Repo.Commit.TreeEntry(c.Repo.TreePath)
 	if err != nil {
-		c.NotFoundOrServerError("Repo.Commit.GetTreeEntryByPath", git.IsErrNotExist, err)
+		c.NotFoundOrServerError("get tree entry", gitutil.IsErrRevisionNotExist, err)
 		return
 	}
 
-	if entry.IsDir() {
+	if entry.IsTree() {
 		renderDirectory(c, treeLink)
 	} else {
-		renderFile(c, entry, treeLink, rawLink, cpt)
+		renderFile(c, entry, treeLink, rawLink)
 	}
 	if c.Written() {
 		return

+ 45 - 31
internal/route/repo/webhook.go

@@ -11,7 +11,7 @@ import (
 	jsoniter "github.com/json-iterator/go"
 	"github.com/unknwon/com"
 
-	git "github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 	api "github.com/gogs/go-gogs-client"
 
 	"github.com/G-Node/gogs/internal/conf"
@@ -517,23 +517,37 @@ func DingtalkHooksEditPost(c *context.Context, f form.NewDingtalkHook) {
 }
 
 func TestWebhook(c *context.Context) {
-	var authorUsername, committerUsername string
+
+	var (
+		commitID          string
+		commitMessage     string
+		author            *git.Signature
+		committer         *git.Signature
+		authorUsername    string
+		committerUsername string
+		nameStatus        *git.NameStatus
+	)
 
 	// Grab latest commit or fake one if it's empty repository.
-	commit := c.Repo.Commit
-	if commit == nil {
+
+	if c.Repo.Commit == nil {
+		commitID = git.EmptyID
+		commitMessage = "This is a fake commit"
 		ghost := db.NewGhostUser()
-		commit = &git.Commit{
-			ID:            git.MustIDFromString(git.EMPTY_SHA),
-			Author:        ghost.NewGitSig(),
-			Committer:     ghost.NewGitSig(),
-			CommitMessage: "This is a fake commit",
-		}
+		author = ghost.NewGitSig()
+		committer = ghost.NewGitSig()
 		authorUsername = ghost.Name
 		committerUsername = ghost.Name
+		nameStatus = &git.NameStatus{}
+
 	} else {
+		commitID = c.Repo.Commit.ID.String()
+		commitMessage = c.Repo.Commit.Message
+		author = c.Repo.Commit.Author
+		committer = c.Repo.Commit.Committer
+
 		// Try to match email with a real user.
-		author, err := db.GetUserByEmail(commit.Author.Email)
+		author, err := db.GetUserByEmail(c.Repo.Commit.Author.Email)
 		if err == nil {
 			authorUsername = author.Name
 		} else if !errors.IsUserNotExist(err) {
@@ -541,44 +555,44 @@ func TestWebhook(c *context.Context) {
 			return
 		}
 
-		committer, err := db.GetUserByEmail(commit.Committer.Email)
+		user, err := db.GetUserByEmail(c.Repo.Commit.Committer.Email)
 		if err == nil {
-			committerUsername = committer.Name
+			committerUsername = user.Name
 		} else if !errors.IsUserNotExist(err) {
 			c.Handle(500, "GetUserByEmail.(committer)", err)
 			return
 		}
-	}
 
-	fileStatus, err := commit.FileStatus()
-	if err != nil {
-		c.Handle(500, "FileStatus", err)
-		return
+		nameStatus, err = c.Repo.Commit.ShowNameStatus()
+		if err != nil {
+			c.Handle(500, "FileStatus", err)
+			return
+		}
 	}
 
 	apiUser := c.User.APIFormat()
 	p := &api.PushPayload{
-		Ref:    git.BRANCH_PREFIX + c.Repo.Repository.DefaultBranch,
-		Before: commit.ID.String(),
-		After:  commit.ID.String(),
+		Ref:    git.RefsHeads + c.Repo.Repository.DefaultBranch,
+		Before: commitID,
+		After:  commitID,
 		Commits: []*api.PayloadCommit{
 			{
-				ID:      commit.ID.String(),
-				Message: commit.Message(),
-				URL:     c.Repo.Repository.HTMLURL() + "/commit/" + commit.ID.String(),
+				ID:      commitID,
+				Message: commitMessage,
+				URL:     c.Repo.Repository.HTMLURL() + "/commit/" + commitID,
 				Author: &api.PayloadUser{
-					Name:     commit.Author.Name,
-					Email:    commit.Author.Email,
+					Name:     author.Name,
+					Email:    author.Email,
 					UserName: authorUsername,
 				},
 				Committer: &api.PayloadUser{
-					Name:     commit.Committer.Name,
-					Email:    commit.Committer.Email,
+					Name:     committer.Name,
+					Email:    committer.Email,
 					UserName: committerUsername,
 				},
-				Added:    fileStatus.Added,
-				Removed:  fileStatus.Removed,
-				Modified: fileStatus.Modified,
+				Added:    nameStatus.Added,
+				Removed:  nameStatus.Removed,
+				Modified: nameStatus.Modified,
 			},
 		},
 		Repo:   c.Repo.Repository.APIFormat(nil),

+ 32 - 37
internal/route/repo/wiki.go

@@ -5,15 +5,15 @@
 package repo
 
 import (
-	"io/ioutil"
 	"strings"
 	"time"
 
-	"github.com/G-Node/git-module"
+	"github.com/gogs/git-module"
 
 	"github.com/G-Node/gogs/internal/context"
 	"github.com/G-Node/gogs/internal/db"
 	"github.com/G-Node/gogs/internal/form"
+	"github.com/G-Node/gogs/internal/gitutil"
 	"github.com/G-Node/gogs/internal/markup"
 )
 
@@ -43,27 +43,27 @@ type PageMeta struct {
 }
 
 func renderWikiPage(c *context.Context, isViewPage bool) (*git.Repository, string) {
-	wikiRepo, err := git.OpenRepository(c.Repo.Repository.WikiPath())
+	wikiRepo, err := git.Open(c.Repo.Repository.WikiPath())
 	if err != nil {
-		c.Handle(500, "OpenRepository", err)
+		c.ServerError("open repository", err)
 		return nil, ""
 	}
-	commit, err := wikiRepo.GetBranchCommit("master")
+	commit, err := wikiRepo.BranchCommit("master")
 	if err != nil {
-		c.Handle(500, "GetBranchCommit", err)
+		c.ServerError("get branch commit", err)
 		return nil, ""
 	}
 
 	// Get page list.
 	if isViewPage {
-		entries, err := commit.ListEntries()
+		entries, err := commit.Entries()
 		if err != nil {
-			c.Handle(500, "ListEntries", err)
+			c.ServerError("list entries", err)
 			return nil, ""
 		}
 		pages := make([]PageMeta, 0, len(entries))
 		for i := range entries {
-			if entries[i].Type == git.OBJECT_BLOB && strings.HasSuffix(entries[i].Name(), ".md") {
+			if entries[i].Type() == git.ObjectBlob && strings.HasSuffix(entries[i].Name(), ".md") {
 				name := strings.TrimSuffix(entries[i].Name(), ".md")
 				pages = append(pages, PageMeta{
 					Name: name,
@@ -86,29 +86,24 @@ func renderWikiPage(c *context.Context, isViewPage bool) (*git.Repository, strin
 	c.Data["title"] = pageName
 	c.Data["RequireHighlightJS"] = true
 
-	blob, err := commit.GetBlobByPath(pageName + ".md")
+	blob, err := commit.Blob(pageName + ".md")
 	if err != nil {
-		if git.IsErrNotExist(err) {
+		if gitutil.IsErrRevisionNotExist(err) {
 			c.Redirect(c.Repo.RepoLink + "/wiki/_pages")
 		} else {
-			c.Handle(500, "GetBlobByPath", err)
+			c.ServerError("GetBlobByPath", err)
 		}
 		return nil, ""
 	}
-	r, err := blob.Data()
+	p, err := blob.Bytes()
 	if err != nil {
-		c.Handle(500, "Data", err)
-		return nil, ""
-	}
-	data, err := ioutil.ReadAll(r)
-	if err != nil {
-		c.Handle(500, "ReadAll", err)
+		c.ServerError("Data", err)
 		return nil, ""
 	}
 	if isViewPage {
-		c.Data["content"] = string(markup.Markdown(data, c.Repo.RepoLink, c.Repo.Repository.ComposeMetas()))
+		c.Data["content"] = string(markup.Markdown(p, c.Repo.RepoLink, c.Repo.Repository.ComposeMetas()))
 	} else {
-		c.Data["content"] = string(data)
+		c.Data["content"] = string(p)
 	}
 
 	return wikiRepo, pageName
@@ -129,12 +124,12 @@ func Wiki(c *context.Context) {
 	}
 
 	// Get last change information.
-	lastCommit, err := wikiRepo.GetCommitByPath(pageName + ".md")
+	commits, err := wikiRepo.Log(git.RefsHeads+"master", git.LogOptions{Path: pageName + ".md"})
 	if err != nil {
-		c.Handle(500, "GetCommitByPath", err)
+		c.ServerError("get commits by path", err)
 		return
 	}
-	c.Data["Author"] = lastCommit.Author
+	c.Data["Author"] = commits[0].Author
 
 	c.HTML(200, WIKI_VIEW)
 }
@@ -148,35 +143,35 @@ func WikiPages(c *context.Context) {
 		return
 	}
 
-	wikiRepo, err := git.OpenRepository(c.Repo.Repository.WikiPath())
+	wikiRepo, err := git.Open(c.Repo.Repository.WikiPath())
 	if err != nil {
-		c.Handle(500, "OpenRepository", err)
+		c.ServerError("open repository", err)
 		return
 	}
-	commit, err := wikiRepo.GetBranchCommit("master")
+	commit, err := wikiRepo.BranchCommit("master")
 	if err != nil {
-		c.Handle(500, "GetBranchCommit", err)
+		c.ServerError("get branch commit", err)
 		return
 	}
 
-	entries, err := commit.ListEntries()
+	entries, err := commit.Entries()
 	if err != nil {
-		c.Handle(500, "ListEntries", err)
+		c.ServerError("list entries", err)
 		return
 	}
 	pages := make([]PageMeta, 0, len(entries))
 	for i := range entries {
-		if entries[i].Type == git.OBJECT_BLOB && strings.HasSuffix(entries[i].Name(), ".md") {
-			commit, err := wikiRepo.GetCommitByPath(entries[i].Name())
+		if entries[i].Type() == git.ObjectBlob && strings.HasSuffix(entries[i].Name(), ".md") {
+			commits, err := wikiRepo.Log(git.RefsHeads+"master", git.LogOptions{Path: entries[i].Name()})
 			if err != nil {
-				c.ServerError("GetCommitByPath", err)
+				c.ServerError("get commits by path", err)
 				return
 			}
 			name := strings.TrimSuffix(entries[i].Name(), ".md")
 			pages = append(pages, PageMeta{
 				Name:    name,
 				URL:     db.ToWikiPageURL(name),
-				Updated: commit.Author.When,
+				Updated: commits[0].Author.When,
 			})
 		}
 	}
@@ -212,7 +207,7 @@ func NewWikiPost(c *context.Context, f form.NewWiki) {
 			c.Data["Err_Title"] = true
 			c.RenderWithErr(c.Tr("repo.wiki.page_already_exists"), WIKI_NEW, &f)
 		} else {
-			c.Handle(500, "AddWikiPage", err)
+			c.ServerError("AddWikiPage", err)
 		}
 		return
 	}
@@ -249,7 +244,7 @@ func EditWikiPost(c *context.Context, f form.NewWiki) {
 	}
 
 	if err := c.Repo.Repository.EditWikiPage(c.User, f.OldTitle, f.Title, f.Content, f.Message); err != nil {
-		c.Handle(500, "EditWikiPage", err)
+		c.ServerError("EditWikiPage", err)
 		return
 	}
 
@@ -264,7 +259,7 @@ func DeleteWikiPagePost(c *context.Context) {
 
 	pageName := db.ToWikiPageName(pageURL)
 	if err := c.Repo.Repository.DeleteWikiPage(c.User, pageName); err != nil {
-		c.Handle(500, "DeleteWikiPage", err)
+		c.ServerError("DeleteWikiPage", err)
 		return
 	}
 

+ 19 - 13
internal/template/template.go

@@ -15,6 +15,7 @@ import (
 	"time"
 
 	"github.com/editorconfig/editorconfig-core-go/v2"
+	"github.com/gogs/git-module"
 	jsoniter "github.com/json-iterator/go"
 	"github.com/microcosm-cc/bluemonday"
 	"golang.org/x/net/html/charset"
@@ -23,6 +24,7 @@ import (
 
 	"github.com/G-Node/gogs/internal/conf"
 	"github.com/G-Node/gogs/internal/db"
+	"github.com/G-Node/gogs/internal/gitutil"
 	"github.com/G-Node/gogs/internal/markup"
 	"github.com/G-Node/gogs/internal/tool"
 )
@@ -36,6 +38,9 @@ var (
 func FuncMap() []template.FuncMap {
 	funcMapOnce.Do(func() {
 		funcMap = []template.FuncMap{map[string]interface{}{
+			"BuildCommit": func() string {
+				return conf.BuildCommit
+			},
 			"Year": func() int {
 				return time.Now().Year()
 			},
@@ -87,7 +92,6 @@ func FuncMap() []template.FuncMap {
 			"DateFmtShort": func(t time.Time) string {
 				return t.Format("Jan 02, 2006")
 			},
-			"List": List,
 			"SubStr": func(str string, start, length int) string {
 				if len(str) == 0 {
 					return ""
@@ -103,11 +107,10 @@ func FuncMap() []template.FuncMap {
 			},
 			"Join":                  strings.Join,
 			"EllipsisString":        tool.EllipsisString,
-			"DiffTypeToStr":         DiffTypeToStr,
+			"DiffFileTypeToStr":     DiffFileTypeToStr,
 			"DiffLineTypeToStr":     DiffLineTypeToStr,
 			"Sha1":                  Sha1,
 			"ShortSHA1":             tool.ShortSHA1,
-			"MD5":                   tool.MD5,
 			"ActionContent2Commits": ActionContent2Commits,
 			"EscapePound":           EscapePound,
 			"RenderCommitMessage":   RenderCommitMessage,
@@ -127,6 +130,7 @@ func FuncMap() []template.FuncMap {
 				}
 				return "tab-size-8"
 			},
+			"InferSubmoduleURL": gitutil.InferSubmoduleURL,
 		}}
 	})
 	return funcMap
@@ -274,20 +278,22 @@ func EscapePound(str string) string {
 	return strings.NewReplacer("%", "%25", "#", "%23", " ", "%20", "?", "%3F").Replace(str)
 }
 
-func DiffTypeToStr(diffType int) string {
-	diffTypes := map[int]string{
-		1: "add", 2: "modify", 3: "del", 4: "rename",
-	}
-	return diffTypes[diffType]
+func DiffFileTypeToStr(typ git.DiffFileType) string {
+	return map[git.DiffFileType]string{
+		git.DiffFileAdd:    "add",
+		git.DiffFileChange: "modify",
+		git.DiffFileDelete: "del",
+		git.DiffFileRename: "rename",
+	}[typ]
 }
 
-func DiffLineTypeToStr(diffType int) string {
-	switch diffType {
-	case 2:
+func DiffLineTypeToStr(typ git.DiffLineType) string {
+	switch typ {
+	case git.DiffLineAdd:
 		return "add"
-	case 3:
+	case git.DiffLineDelete:
 		return "del"
-	case 4:
+	case git.DiffLineSection:
 		return "tag"
 	}
 	return "same"

+ 56 - 62
public/css/gogs.css

@@ -5,7 +5,7 @@
   background-size: contain;
 }
 body:not(.full-width) {
-  font-family: "PingFang SC", "Helvetica Neue", "Microsoft YaHei", Arial, Helvetica, sans-serif !important;
+  font-family: "Helvetica Neue", "Microsoft YaHei", Arial, Helvetica, sans-serif !important;
   background-color: #fff;
   overflow-y: scroll;
   overflow-x: auto;
@@ -23,14 +23,14 @@ h5,
 .ui.menu,
 .ui.input input,
 .ui.button:not(.label) {
-  font-family: "PingFang SC", 'Hiragino Sans GB', "Helvetica Neue", "Microsoft YaHei", Arial, Helvetica, sans-serif !important;
+  font-family: "Helvetica Neue", "Microsoft YaHei", Arial, Helvetica, sans-serif !important;
 }
 img {
   border-radius: 3px;
 }
 pre,
 code {
-  font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace;
+  font-family: Consolas, Liberation Mono, Menlo, monospace;
 }
 pre.raw,
 code.raw {
@@ -75,7 +75,7 @@ code.wrap {
 }
 .following.bar.light {
   background-color: white;
-  border-bottom: 1px solid #DDDDDD;
+  border-bottom: 1px solid #dddddd;
   box-shadow: 0 2px 3px rgba(0, 0, 0, 0.04);
 }
 .following.bar .column .menu {
@@ -156,7 +156,7 @@ code.wrap {
   color: #d95c5c !important;
 }
 .ui .text.red a:hover {
-  color: #E67777 !important;
+  color: #e67777 !important;
 }
 .ui .text.blue {
   color: #428bca !important;
@@ -192,7 +192,7 @@ code.wrap {
   color: #6e5494 !important;
 }
 .ui .text.yellow {
-  color: #FBBD08 !important;
+  color: #fbbd08 !important;
 }
 .ui .text.gold {
   color: #a1882b !important;
@@ -235,11 +235,11 @@ code.wrap {
   vertical-align: middle;
 }
 .ui .warning.header {
-  background-color: #F9EDBE !important;
-  border-color: #F0C36D;
+  background-color: #f9edbe !important;
+  border-color: #f0c36d;
 }
 .ui .warning.segment {
-  border-color: #F0C36D;
+  border-color: #f0c36d;
 }
 .ui .info.segment {
   border: 1px solid #c5d5dd;
@@ -270,7 +270,7 @@ code.wrap {
   margin-left: 25px;
 }
 .ui .sha.label {
-  font-family: Consolas, Menlo, Monaco, "Lucida Console", monospace;
+  font-family: Consolas, Liberation Mono, Menlo, monospace;
   font-size: 13px;
   padding: 6px 10px 4px 10px;
   font-weight: normal;
@@ -1185,7 +1185,7 @@ footer .ui.language .menu {
   overflow-x: auto;
 }
 .repository .metas .ui.list .hide {
-  display: none!important;
+  display: none !important;
 }
 .repository .metas .ui.list .item {
   padding: 0px;
@@ -1204,7 +1204,7 @@ footer .ui.language .menu {
   color: #000;
 }
 .repository .header-wrapper {
-  background-color: #FAFAFA;
+  background-color: #fafafa;
   margin-top: -15px;
   padding-top: 15px;
 }
@@ -1228,8 +1228,8 @@ footer .ui.language .menu {
 .repository .filter.menu .menu {
   max-height: 300px;
   overflow-x: auto;
-  right: 0!important;
-  left: auto!important;
+  right: 0 !important;
+  left: auto !important;
 }
 .repository .filter.menu .dropdown.item {
   margin: 1px;
@@ -1268,8 +1268,8 @@ footer .ui.language .menu {
   padding: 0 10px;
 }
 .repository #clone-panel .dropdown .menu {
-  right: 0!important;
-  left: auto!important;
+  right: 0 !important;
+  left: auto !important;
 }
 .repository.branches:not(.settings) .ui.list {
   padding: 0;
@@ -1279,7 +1279,7 @@ footer .ui.language .menu {
   line-height: 31px;
 }
 .repository.branches:not(.settings) .ui.list > .item:not(:last-child) {
-  border-bottom: 1px solid #DDD;
+  border-bottom: 1px solid #ddd;
 }
 .repository.branches:not(.settings) .ui.list > .item .column {
   padding: 5px 15px;
@@ -1355,7 +1355,7 @@ footer .ui.language .menu {
   padding-bottom: 8px;
 }
 .repository.file.list #repo-files-table tr:hover {
-  background-color: #ffffEE;
+  background-color: #ffffee;
 }
 .repository.file.list #file-content .header .octicon {
   padding-right: 5px;
@@ -1414,7 +1414,7 @@ footer .ui.language .menu {
   padding: 0.1em 0.5em;
 }
 .repository.file.list #file-content #ipython-notebook .nb-stderr {
-  background-color: #FAA;
+  background-color: #faa;
 }
 .repository.file.list #file-content #ipython-notebook .nb-cell + .nb-cell {
   margin-top: 0.5em;
@@ -1431,7 +1431,7 @@ footer .ui.language .menu {
 .repository.file.list #file-content #ipython-notebook .nb-raw-cell {
   white-space: pre-wrap;
   background-color: #f5f2f0;
-  font-family: Consolas, Monaco, 'Andale Mono', monospace;
+  font-family: Consolas, Liberation Mono, Menlo, monospace;
   padding: 1em;
   margin: 0.5em 0;
 }
@@ -1497,7 +1497,7 @@ footer .ui.language .menu {
 }
 .repository.file.list #file-content .code-view * {
   font-size: 12px;
-  font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
+  font-family: Consolas, Liberation Mono, Menlo, monospace;
   line-height: 20px;
 }
 .repository.file.list #file-content .code-view table {
@@ -1542,6 +1542,7 @@ footer .ui.language .menu {
 .repository.file.list #file-content .code-view .lines-code .hljs li {
   display: inline-block;
   width: 100%;
+  padding-left: 5px;
 }
 .repository.file.list #file-content .code-view .lines-num pre li.active,
 .repository.file.list #file-content .code-view .lines-code pre li.active,
@@ -1551,14 +1552,6 @@ footer .ui.language .menu {
 .repository.file.list #file-content .code-view .lines-code .hljs li.active {
   background: #ffffdd;
 }
-.repository.file.list #file-content .code-view .lines-num pre li:before,
-.repository.file.list #file-content .code-view .lines-code pre li:before,
-.repository.file.list #file-content .code-view .lines-num ol li:before,
-.repository.file.list #file-content .code-view .lines-code ol li:before,
-.repository.file.list #file-content .code-view .lines-num .hljs li:before,
-.repository.file.list #file-content .code-view .lines-code .hljs li:before {
-  content: ' ';
-}
 .repository.file.list .sidebar {
   padding-left: 0;
 }
@@ -1606,7 +1599,7 @@ footer .ui.language .menu {
   pointer-events: none;
 }
 .repository.file.editor .commit-form-wrapper .commit-form:before {
-  border-right-color: #D4D4D5;
+  border-right-color: #d4d4d5;
   border-width: 9px;
   margin-top: -9px;
 }
@@ -1621,7 +1614,7 @@ footer .ui.language .menu {
 .repository.file.editor .commit-form-wrapper .commit-form .quick-pull-choice .branch-name {
   display: inline-block;
   padding: 3px 6px;
-  font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace;
+  font: 12px Consolas, Liberation Mono, Menlo, monospace;
   color: rgba(0, 0, 0, 0.65);
   background-color: rgba(209, 227, 237, 0.45);
   border-radius: 3px;
@@ -1641,7 +1634,7 @@ footer .ui.language .menu {
   color: #b0c4ce;
 }
 .repository.options #interval {
-  width: 100px!important;
+  width: 100px !important;
   min-width: 100px;
 }
 .repository.options .danger .item {
@@ -1668,7 +1661,7 @@ footer .ui.language .menu {
   pointer-events: none;
 }
 .repository.new.issue .comment.form .content:before {
-  border-right-color: #D4D4D5;
+  border-right-color: #d4d4d5;
   border-width: 9px;
   margin-top: -9px;
 }
@@ -1691,7 +1684,7 @@ footer .ui.language .menu {
   overflow-x: auto;
 }
 .repository.view.issue .title {
-  padding-bottom: 0!important;
+  padding-bottom: 0 !important;
 }
 .repository.view.issue .title h1 {
   font-weight: 300;
@@ -1720,7 +1713,7 @@ footer .ui.language .menu {
   margin-top: 10px;
 }
 .repository.view.issue .pull-desc code {
-  color: #0166E6;
+  color: #0166e6;
 }
 .repository.view.issue .pull.tabular.menu {
   margin-bottom: 10px;
@@ -1801,7 +1794,7 @@ footer .ui.language .menu {
   pointer-events: none;
 }
 .repository.view.issue .comment-list .comment .content .header:before {
-  border-right-color: #D4D4D5;
+  border-right-color: #d4d4d5;
   border-width: 9px;
   margin-top: -9px;
 }
@@ -1827,7 +1820,7 @@ footer .ui.language .menu {
 }
 .repository.view.issue .comment-list .comment .content > .bottom.segment .ui.images::after {
   clear: both;
-  content: ' ';
+  content: " ";
   display: block;
 }
 .repository.view.issue .comment-list .comment .content > .bottom.segment a {
@@ -1842,7 +1835,7 @@ footer .ui.language .menu {
   background-color: #fff;
 }
 .repository.view.issue .comment-list .comment .content > .bottom.segment a:before {
-  content: ' ';
+  content: " ";
   display: inline-block;
   height: 100%;
   vertical-align: middle;
@@ -1870,7 +1863,7 @@ footer .ui.language .menu {
 }
 .repository.view.issue .comment-list .comment .ui.form textarea {
   height: 200px;
-  font-family: "Consolas", monospace;
+  font-family: Consolas, Liberation Mono, Menlo, monospace;
 }
 .repository.view.issue .comment-list .comment .edit.buttons {
   margin-top: 10px;
@@ -1937,7 +1930,7 @@ footer .ui.language .menu {
   pointer-events: none;
 }
 .repository .comment.form .content .form:before {
-  border-right-color: #D4D4D5;
+  border-right-color: #d4d4d5;
   border-width: 9px;
   margin-top: -9px;
 }
@@ -1956,7 +1949,7 @@ footer .ui.language .menu {
 }
 .repository .comment.form .content textarea {
   height: 200px;
-  font-family: "Consolas", monospace;
+  font-family: Consolas, Liberation Mono, Menlo, monospace;
 }
 .repository .label.list {
   list-style: none;
@@ -1965,7 +1958,7 @@ footer .ui.language .menu {
 .repository .label.list > .item {
   padding-top: 10px;
   padding-bottom: 10px;
-  border-bottom: 1px dashed #AAA;
+  border-bottom: 1px dashed #aaa;
 }
 .repository .label.list > .item a {
   font-size: 15px;
@@ -1989,7 +1982,7 @@ footer .ui.language .menu {
 .repository .milestone.list > .item {
   padding-top: 10px;
   padding-bottom: 10px;
-  border-bottom: 1px dashed #AAA;
+  border-bottom: 1px dashed #aaa;
 }
 .repository .milestone.list > .item > a {
   padding-top: 5px;
@@ -2054,7 +2047,7 @@ footer .ui.language .menu {
   pointer-events: none;
 }
 .repository.compare.pull .comment.form .content:before {
-  border-right-color: #D4D4D5;
+  border-right-color: #d4d4d5;
   border-width: 9px;
   margin-top: -9px;
 }
@@ -2067,7 +2060,7 @@ footer .ui.language .menu {
   border-right-color: #fff;
 }
 .repository .filter.dropdown .menu {
-  margin-top: 1px!important;
+  margin-top: 1px !important;
 }
 .repository.diff .commit-message pre {
   white-space: pre-wrap;
@@ -2100,7 +2093,7 @@ footer .ui.language .menu {
   list-style: none;
   padding-bottom: 4px;
   margin-bottom: 4px;
-  border-bottom: 1px dashed #DDD;
+  border-bottom: 1px dashed #ddd;
   padding-left: 6px;
 }
 .repository .diff-detail-box span.status {
@@ -2146,7 +2139,7 @@ footer .ui.language .menu {
 }
 .repository .diff-file-box .file-body.file-code .lines-num {
   text-align: right;
-  color: #A7A7A7;
+  color: #a7a7a7;
   background: #fafafa;
   width: 1%;
 }
@@ -2155,7 +2148,7 @@ footer .ui.language .menu {
   text-align: center;
 }
 .repository .diff-file-box .file-body.file-code .lines-num-old {
-  border-right: 1px solid #DDD;
+  border-right: 1px solid #ddd;
 }
 .repository .diff-file-box .code-diff {
   font-size: 12px;
@@ -2175,6 +2168,7 @@ footer .ui.language .menu {
 }
 .repository .diff-file-box .code-diff .lines-num::before {
   content: attr(data-line-number);
+  font: Consolas, Liberation Mono, Menlo, monospace;
 }
 .repository .diff-file-box .code-diff .lines-num.lines-num-old,
 .repository .diff-file-box .code-diff .lines-num.lines-num-new {
@@ -2185,8 +2179,8 @@ footer .ui.language .menu {
   color: #383636;
 }
 .repository .diff-file-box .code-diff tbody tr.tag-code td {
-  background-color: #F0F0F0 !important;
-  border-color: #D2CECE !important;
+  background-color: #f0f0f0 !important;
+  border-color: #d2cece !important;
   padding-top: 4px;
   padding-bottom: 4px;
 }
@@ -2261,7 +2255,7 @@ footer .ui.language .menu {
   font-size: 1.2em;
 }
 .repository.release #release-list {
-  border-top: 1px solid #DDD;
+  border-top: 1px solid #ddd;
   margin-top: 20px;
   padding-top: 15px;
 }
@@ -2286,7 +2280,7 @@ footer .ui.language .menu {
   margin-top: 6px;
 }
 .repository.release #release-list > li .detail {
-  border-left: 1px solid #DDD;
+  border-left: 1px solid #ddd;
 }
 .repository.release #release-list > li .detail .author img {
   margin-bottom: -3px;
@@ -2319,7 +2313,7 @@ footer .ui.language .menu {
   left: -5px;
   top: 40px;
   border-radius: 6px;
-  border: 1px solid #FFF;
+  border: 1px solid #fff;
 }
 .repository.new.release .target {
   min-width: 500px;
@@ -2348,7 +2342,7 @@ footer .ui.language .menu {
 .repository.forks .list .item {
   padding-top: 10px;
   padding-bottom: 10px;
-  border-bottom: 1px solid #DDD;
+  border-bottom: 1px solid #ddd;
 }
 .repository.forks .list .item .ui.avatar {
   float: left;
@@ -2365,7 +2359,7 @@ footer .ui.language .menu {
   font-size: 48px;
 }
 .repository.wiki.new .CodeMirror .CodeMirror-code {
-  font-family: "Consolas", monospace;
+  font-family: Consolas, Liberation Mono, Menlo, monospace;
 }
 .repository.wiki.new .CodeMirror .CodeMirror-code .cm-comment {
   background: inherit;
@@ -2399,7 +2393,7 @@ footer .ui.language .menu {
   line-height: 2em;
 }
 .repository.settings.collaboration .collaborator.list > .item:not(:last-child) {
-  border-bottom: 1px solid #DDD;
+  border-bottom: 1px solid #ddd;
 }
 .repository.settings.collaboration #repo-collab-form #search-user-box .results {
   left: 7px;
@@ -2491,7 +2485,7 @@ footer .ui.language .menu {
 #search-repo-box .results .item,
 #search-user-box .results .item {
   padding: 10px 15px;
-  border-bottom: 1px solid #DDD;
+  border-bottom: 1px solid #ddd;
   cursor: pointer;
 }
 #search-repo-box .results .item:hover,
@@ -2510,7 +2504,7 @@ footer .ui.language .menu {
 .issue.list > .item {
   padding-top: 15px;
   padding-bottom: 10px;
-  border-bottom: 1px dashed #AAA;
+  border-bottom: 1px dashed #aaa;
 }
 .issue.list > .item .title {
   color: #444;
@@ -2546,8 +2540,8 @@ footer .ui.language .menu {
 .ui.form .dropzone {
   width: 100%;
   margin-bottom: 10px;
-  border: 2px dashed #0087F7;
-  box-shadow: none!important;
+  border: 2px dashed #0087f7;
+  box-shadow: none !important;
 }
 .ui.form .dropzone .dz-error-message {
   top: 140px;
@@ -2688,7 +2682,7 @@ footer .ui.language .menu {
   pointer-events: none;
 }
 #avatar-arrow:before {
-  border-right-color: #D4D4D5;
+  border-right-color: #d4d4d5;
   border-width: 9px;
   margin-top: -9px;
 }
@@ -2699,7 +2693,7 @@ footer .ui.language .menu {
 }
 #transfer-repo-modal .ui.message,
 #delete-repo-modal .ui.message {
-  width: 100%!important;
+  width: 100% !important;
 }
 .tab-size-1 {
   tab-size: 1 !important;

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 0
public/css/gogs.css.map


+ 377 - 371
public/less/_base.less

@@ -1,41 +1,46 @@
 @footer-margin: 40px;
 
 body:not(.full-width) {
-	font-family: "PingFang SC", "Helvetica Neue", "Microsoft YaHei", Arial, Helvetica, sans-serif !important;
-	background-color: #fff;
-	overflow-y: scroll;
-	overflow-x: auto;
-	min-width: 1020px;
+  font-family: "Helvetica Neue", "Microsoft YaHei", Arial, Helvetica, sans-serif !important;
+  background-color: #fff;
+  overflow-y: scroll;
+  overflow-x: auto;
+  min-width: 1020px;
 }
 .ui.container:not(.fluid) {
-	width: 980px !important;
+  width: 980px !important;
 }
-h1, h2, h3, h4, h5,
+h1,
+h2,
+h3,
+h4,
+h5,
 .ui.header,
 .ui.menu,
 .ui.input input,
 .ui.button:not(.label) {
-	font-family: "PingFang SC", 'Hiragino Sans GB', "Helvetica Neue", "Microsoft YaHei", Arial, Helvetica, sans-serif !important;
+  font-family: "Helvetica Neue", "Microsoft YaHei", Arial, Helvetica, sans-serif !important;
 }
 img {
-	border-radius: 3px;
+  border-radius: 3px;
 }
-pre, code {
-	font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace;
-	&.raw {
-		padding: 7px 12px;
-		margin: 10px 0;
-		background-color: #f8f8f8;
-		border: 1px solid #ddd;
-		border-radius: 3px;
-		font-size: 13px;
-		line-height: 1.5;
-		overflow: auto;
-	}
-	&.wrap {
-		white-space: pre-wrap;
-		word-break: break-word;
-	}
+pre,
+code {
+  font-family: Consolas, Liberation Mono, Menlo, monospace;
+  &.raw {
+    padding: 7px 12px;
+    margin: 10px 0;
+    background-color: #f8f8f8;
+    border: 1px solid #ddd;
+    border-radius: 3px;
+    font-size: 13px;
+    line-height: 1.5;
+    overflow: auto;
+  }
+  &.wrap {
+    white-space: pre-wrap;
+    word-break: break-word;
+  }
 }
 .dont-break-out {
   /* These are technically the same, but use both */
@@ -55,396 +60,397 @@ pre, code {
   hyphens: auto;
 }
 .full.height {
-	padding: 0;
-	margin: 0 0 -@footer-margin*2 0;
-	min-height: 100%;
+  padding: 0;
+  margin: 0 0 -@footer-margin*2 0;
+  min-height: 100%;
 }
 .following.bar {
-	z-index: 900;
-	left: 0;
-	width: 100%;
-	&.light {
-		background-color: white;
-		border-bottom: 1px solid #DDDDDD;
-		box-shadow: 0 2px 3px rgba(0, 0, 0, 0.04);
-	}
-	.column .menu {
-		margin-top: 0;
-	}
-	.top.menu a.item.brand {
-		padding-left: 0;
+  z-index: 900;
+  left: 0;
+  width: 100%;
+  &.light {
+    background-color: white;
+    border-bottom: 1px solid #dddddd;
+    box-shadow: 0 2px 3px rgba(0, 0, 0, 0.04);
+  }
+  .column .menu {
+    margin-top: 0;
+  }
+  .top.menu a.item.brand {
+    padding-left: 0;
     padding-right: 0;
-	}
-	.brand .ui.mini.image {
-		width: 30px;
-	}
-	.top.menu a.item:hover,
-	.top.menu .dropdown.item:hover,
-	.top.menu .dropdown.item.active	{
-		background-color: transparent;
-	}
-	.top.menu a.item:hover {
-		color: rgba(0,0,0,.45);
-	}
-	.top.menu .menu {
-		z-index: 900;
-	}
-	.icon,
-	.octicon {
-		margin-right: 5px !important;
-	}
-	.head.link.item {
-		padding-right: 0 !important;
-	}
-	.avatar > .ui.image {
-		margin-right: 0;
-	}
-	.avatar .octicon-triangle-down {
-		margin-top: 6.5px;
-	}
-	.searchbox {
-		background-color: rgb(244, 244, 244) !important;
-		&:focus {
-			background-color: rgb(233, 233, 233) !important;
-		}
-	}
-	.text .octicon {
-		width: 16px;
-		text-align: center;
-	}
-	.right.menu {
-		.menu {
-			left: auto;
-			right: 0;
-		}
-		.dropdown .menu {
-			margin-top: 0;
-		}
-	}
+  }
+  .brand .ui.mini.image {
+    width: 30px;
+  }
+  .top.menu a.item:hover,
+  .top.menu .dropdown.item:hover,
+  .top.menu .dropdown.item.active {
+    background-color: transparent;
+  }
+  .top.menu a.item:hover {
+    color: rgba(0, 0, 0, 0.45);
+  }
+  .top.menu .menu {
+    z-index: 900;
+  }
+  .icon,
+  .octicon {
+    margin-right: 5px !important;
+  }
+  .head.link.item {
+    padding-right: 0 !important;
+  }
+  .avatar > .ui.image {
+    margin-right: 0;
+  }
+  .avatar .octicon-triangle-down {
+    margin-top: 6.5px;
+  }
+  .searchbox {
+    background-color: rgb(244, 244, 244) !important;
+    &:focus {
+      background-color: rgb(233, 233, 233) !important;
+    }
+  }
+  .text .octicon {
+    width: 16px;
+    text-align: center;
+  }
+  .right.menu {
+    .menu {
+      left: auto;
+      right: 0;
+    }
+    .dropdown .menu {
+      margin-top: 0;
+    }
+  }
 }
 
 .ui {
-	&.left {
-		float: left;
-	}
-	&.right {
-		float: right;
-	}
-
-	&.container {
-		&.fluid {
-			&.padded {
-				padding: 0 10px 0 10px;
-			}
-		}
-	}
-
-	&.form {
-		.ui.button {
-			font-weight: normal;
-		}
-		.box.field {
-			padding-left: 27px;
-		}
-	}
-
-	&.menu,
-	&.vertical.menu,
-	&.segment {
-		box-shadow: none;
-	}
-
-	.text {
-		&.red {
-			color: #d95c5c !important;
-			a {
-				color: #d95c5c !important;
-				&:hover {
-					color: #E67777 !important;
-				}
-			}
-		}
-		&.blue {
-			color: #428bca !important;
-			a {
-				color: #15c !important;
-				&:hover {
-					color: #428bca !important;
-				}
-			}
-		}
-		&.black {
-			color: #444;
-			&:hover {
-				color: #000;
-			}
-		}
-		&.grey {
-			color: #767676 !important;
-			a {
-				color: #444 !important;
-				&:hover {
-					color: #000 !important;
-				}
-			}
-		}
-		&.light.grey {
-			color: #888 !important;
-		}
-		&.green {
-			color: #6cc644 !important;
-		}
-		&.purple {
-			color: #6e5494 !important;
-		}
-		&.yellow {
-			color: #FBBD08 !important;
-		}
-		&.gold {
-			color: #a1882b !important;
-		}
-
-		&.left {
-			text-align: left !important;
-		}
-		&.right {
-			text-align: right !important;
-		}
-		&.small {
-			font-size: 0.75em;
-		}
-		&.normal {
-			font-weight: normal;
-		}
-		&.bold {
-			font-weight: bold;
-		}
-		&.italic {
-			font-style: italic;
-		}
-
-		&.truncate {
-			overflow: hidden;
-			text-overflow: ellipsis;
-			white-space: nowrap;
-			display: inline-block;
-		}
-
-		&.thin {
-			font-weight: normal;
-		}
-
-		&.middle {
-			vertical-align: middle;
-		}
-	}
-
-	.message {
-		text-align: center;
-	}
-
-	.header > i + .content {
-		padding-left: 0.75rem;
-		vertical-align: middle;
-	}
-	.warning {
-		&.header {
-			background-color: #F9EDBE !important;
-			border-color: #F0C36D;
-		}
-		&.segment {
-			border-color: #F0C36D;
-		}
-	}
-	.info {
-		&.segment {
-			border: 1px solid #c5d5dd;
-			&.top {
-				background-color: #e6f1f6 !important;
-				h3, h4 {
-					margin-top: 0;
-				}
-				h3:last-child {
-					margin-top: 4px;
-				}
-				> :last-child {
-					margin-bottom: 0;
-				}
-			}
-		}
-	}
-
-	.normal.header {
-		font-weight: normal;
-	}
-
-	.avatar.image {
-		border-radius: 3px;
-	}
-
-	.form {
-		.fake {
-			display: none !important;
-		}
-
-		.sub.field {
-			margin-left: 25px;
-		}
-	}
-
-	.sha.label {
-		font-family: Consolas, Menlo, Monaco, "Lucida Console", monospace;
-		font-size: 13px;
-		padding: 6px 10px 4px 10px;
-		font-weight: normal;
-		margin: 0 6px;
-	}
-
-	&.status.buttons {
-		.octicon {
-			margin-right: 4px;
-		}
-	}
-
-	&.inline.delete-button {
-		padding: 8px 15px;
-		font-weight: normal;
-	}
+  &.left {
+    float: left;
+  }
+  &.right {
+    float: right;
+  }
+
+  &.container {
+    &.fluid {
+      &.padded {
+        padding: 0 10px 0 10px;
+      }
+    }
+  }
+
+  &.form {
+    .ui.button {
+      font-weight: normal;
+    }
+    .box.field {
+      padding-left: 27px;
+    }
+  }
+
+  &.menu,
+  &.vertical.menu,
+  &.segment {
+    box-shadow: none;
+  }
+
+  .text {
+    &.red {
+      color: #d95c5c !important;
+      a {
+        color: #d95c5c !important;
+        &:hover {
+          color: #e67777 !important;
+        }
+      }
+    }
+    &.blue {
+      color: #428bca !important;
+      a {
+        color: #15c !important;
+        &:hover {
+          color: #428bca !important;
+        }
+      }
+    }
+    &.black {
+      color: #444;
+      &:hover {
+        color: #000;
+      }
+    }
+    &.grey {
+      color: #767676 !important;
+      a {
+        color: #444 !important;
+        &:hover {
+          color: #000 !important;
+        }
+      }
+    }
+    &.light.grey {
+      color: #888 !important;
+    }
+    &.green {
+      color: #6cc644 !important;
+    }
+    &.purple {
+      color: #6e5494 !important;
+    }
+    &.yellow {
+      color: #fbbd08 !important;
+    }
+    &.gold {
+      color: #a1882b !important;
+    }
+
+    &.left {
+      text-align: left !important;
+    }
+    &.right {
+      text-align: right !important;
+    }
+    &.small {
+      font-size: 0.75em;
+    }
+    &.normal {
+      font-weight: normal;
+    }
+    &.bold {
+      font-weight: bold;
+    }
+    &.italic {
+      font-style: italic;
+    }
+
+    &.truncate {
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      display: inline-block;
+    }
+
+    &.thin {
+      font-weight: normal;
+    }
+
+    &.middle {
+      vertical-align: middle;
+    }
+  }
+
+  .message {
+    text-align: center;
+  }
+
+  .header > i + .content {
+    padding-left: 0.75rem;
+    vertical-align: middle;
+  }
+  .warning {
+    &.header {
+      background-color: #f9edbe !important;
+      border-color: #f0c36d;
+    }
+    &.segment {
+      border-color: #f0c36d;
+    }
+  }
+  .info {
+    &.segment {
+      border: 1px solid #c5d5dd;
+      &.top {
+        background-color: #e6f1f6 !important;
+        h3,
+        h4 {
+          margin-top: 0;
+        }
+        h3:last-child {
+          margin-top: 4px;
+        }
+        > :last-child {
+          margin-bottom: 0;
+        }
+      }
+    }
+  }
+
+  .normal.header {
+    font-weight: normal;
+  }
+
+  .avatar.image {
+    border-radius: 3px;
+  }
+
+  .form {
+    .fake {
+      display: none !important;
+    }
+
+    .sub.field {
+      margin-left: 25px;
+    }
+  }
+
+  .sha.label {
+    font-family: Consolas, Liberation Mono, Menlo, monospace;
+    font-size: 13px;
+    padding: 6px 10px 4px 10px;
+    font-weight: normal;
+    margin: 0 6px;
+  }
+
+  &.status.buttons {
+    .octicon {
+      margin-right: 4px;
+    }
+  }
+
+  &.inline.delete-button {
+    padding: 8px 15px;
+    font-weight: normal;
+  }
 }
 
 .overflow.menu {
-	.items {
-		max-height: 300px;
-		overflow-y: auto;
-		.item {
-			position: relative;
-			cursor: pointer;
-			display: block;
-			border: none;
-			height: auto;
-			border-top: none;
-			line-height: 1em;
-			color: rgba(0,0,0,.8);
-			padding: .71428571em 1.14285714em !important;
-			font-size: 1rem;
-			text-transform: none;
-			font-weight: 400;
-			box-shadow: none;
-			-webkit-touch-callout: none;
-			&.active {
-				font-weight: 700;
-			}
-			&:hover {
-				background: rgba(0,0,0,.05);
-				color: rgba(0,0,0,.8);
-				z-index: 13;
-			}
-		}
-	}
+  .items {
+    max-height: 300px;
+    overflow-y: auto;
+    .item {
+      position: relative;
+      cursor: pointer;
+      display: block;
+      border: none;
+      height: auto;
+      border-top: none;
+      line-height: 1em;
+      color: rgba(0, 0, 0, 0.8);
+      padding: 0.71428571em 1.14285714em !important;
+      font-size: 1rem;
+      text-transform: none;
+      font-weight: 400;
+      box-shadow: none;
+      -webkit-touch-callout: none;
+      &.active {
+        font-weight: 700;
+      }
+      &:hover {
+        background: rgba(0, 0, 0, 0.05);
+        color: rgba(0, 0, 0, 0.8);
+        z-index: 13;
+      }
+    }
+  }
 }
 
 .scrolling.menu {
-	.item.selected {
-		font-weight: 700 !important;
-	}
+  .item.selected {
+    font-weight: 700 !important;
+  }
 }
 
 footer {
-	margin-top: @footer-margin+14px !important;
-	height: @footer-margin;
-	background-color: white;
-	border-top: 1px solid #d6d6d6;
-	clear: both;
-	width: 100%;
-	color: #888888;
-	.container {
-		padding-top: 10px;
-		.fa {
-			width: 16px;
-			text-align: center;
-			color: #428bca;
-		}
-		.links >* {
-			border-left: 1px solid #d6d6d6;
-			padding-left: 8px;
-			margin-left: 5px;
-			&:first-child {
-				border-left: none;
-			}
-		}
-	}
-
-	.ui.language .menu {
-		max-height: 500px;
-		overflow-y: auto;
-		margin-bottom: 7px;
-	}
+  margin-top: @footer-margin+14px !important;
+  height: @footer-margin;
+  background-color: white;
+  border-top: 1px solid #d6d6d6;
+  clear: both;
+  width: 100%;
+  color: #888888;
+  .container {
+    padding-top: 10px;
+    .fa {
+      width: 16px;
+      text-align: center;
+      color: #428bca;
+    }
+    .links > * {
+      border-left: 1px solid #d6d6d6;
+      padding-left: 8px;
+      margin-left: 5px;
+      &:first-child {
+        border-left: none;
+      }
+    }
+  }
+
+  .ui.language .menu {
+    max-height: 500px;
+    overflow-y: auto;
+    margin-bottom: 7px;
+  }
 }
 
 .hide {
-	display: none;
+  display: none;
 }
 .display {
-	&.inline {
-		display: inline;
-	}
+  &.inline {
+    display: inline;
+  }
 }
 .center {
-	text-align: center;
+  text-align: center;
 }
 
 .no-padding-left {
-	padding-left: 0 !important;
+  padding-left: 0 !important;
 }
 
 .generate-img(16);
 .generate-img(@n, @i: 1) when (@i =< @n) {
-	.img-@{i} {
-		width: (2px * @i) !important;
-		height: (2px * @i) !important;
-	}
-	.generate-img(@n, (@i + 1));
+  .img-@{i} {
+    width: (2px * @i) !important;
+    height: (2px * @i) !important;
+  }
+  .generate-img(@n, (@i + 1));
 }
 
 // Accessibility
 .sr-only {
-	position: absolute;
-	width: 1px;
-	height: 1px;
-	padding: 0;
-	margin: -1px;
-	overflow: hidden;
-	clip: rect(0, 0, 0, 0);
-	border: 0;
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  padding: 0;
+  margin: -1px;
+  overflow: hidden;
+  clip: rect(0, 0, 0, 0);
+  border: 0;
 }
 .sr-only-focusable:active,
 .sr-only-focusable:focus {
-	position: static;
-	width: auto;
-	height: auto;
-	margin: 0;
-	overflow: visible;
-	clip: auto;
+  position: static;
+  width: auto;
+  height: auto;
+  margin: 0;
+  overflow: visible;
+  clip: auto;
 }
 
 @media only screen and (max-width: 991px) and (min-width: 768px) {
-	.ui.container {
-		width: 95%;
-	}
+  .ui.container {
+    width: 95%;
+  }
 }
 
 /* Overrides some styles of the Highlight.js plugin */
 .hljs {
-	background: inherit !important;
-	padding: 0 !important;
+  background: inherit !important;
+  padding: 0 !important;
 }
 
 // Reset CSS to prevent UI breaks
-.ui.dropdown .menu>.item>.image,
-.ui.dropdown .menu>.item>img,
-.ui.dropdown>.text>.image,
-.ui.dropdown>.text>img {
-	vertical-align: middle;
-	margin-top: 0;
-    margin-bottom: 0;
+.ui.dropdown .menu > .item > .image,
+.ui.dropdown .menu > .item > img,
+.ui.dropdown > .text > .image,
+.ui.dropdown > .text > img {
+  vertical-align: middle;
+  margin-top: 0;
+  margin-bottom: 0;
 }

Diferenças do arquivo suprimidas por serem muito extensas
+ 948 - 949
public/less/_repository.less


+ 3 - 3
templates/admin/config.tmpl

@@ -458,11 +458,11 @@
 						<dt>{{.i18n.Tr "admin.config.git.disable_diff_highlight"}}</dt>
 						<dd><i class="fa fa{{if .Git.DisableDiffHighlight}}-check{{end}}-square-o"></i></dd>
 						<dt>{{.i18n.Tr "admin.config.git.max_diff_lines"}}</dt>
-						<dd>{{.Git.MaxGitDiffLines}}</dd>
+						<dd>{{.Git.MaxDiffLines}}</dd>
 						<dt>{{.i18n.Tr "admin.config.git.max_diff_line_characters"}}</dt>
-						<dd>{{.Git.MaxGitDiffLineCharacters}}</dd>
+						<dd>{{.Git.MaxDiffLineChars}}</dd>
 						<dt>{{.i18n.Tr "admin.config.git.max_diff_files"}}</dt>
-						<dd>{{.Git.MaxGitDiffFiles}}</dd>
+						<dd>{{.Git.MaxDiffFiles}}</dd>
 						<dt>{{.i18n.Tr "admin.config.git.gc_args"}}</dt>
 						<dd><code>{{.Git.GCArgs}}</code></dd>
 

+ 2 - 2
templates/base/head.tmpl

@@ -60,7 +60,7 @@
 
 	<!-- Stylesheet -->
 	<link rel="stylesheet" href="{{AppSubURL}}/css/semantic-2.4.2.min.css">
-	<link rel="stylesheet" href="{{AppSubURL}}/css/gogs.css?v={{MD5 AppVer}}">
+	<link rel="stylesheet" href="{{AppSubURL}}/css/gogs.css?v={{BuildCommit}}">
 	<noscript>
 		<style>
 			.dropdown:hover > .menu { display: block; }
@@ -70,7 +70,7 @@
 
 	<!-- JavaScript -->
 	<script src="{{AppSubURL}}/js/semantic-2.4.2.min.js"></script>
-	<script src="{{AppSubURL}}/js/gogs.js?v={{MD5 AppVer}}"></script>
+	<script src="{{AppSubURL}}/js/gogs.js?v={{BuildCommit}}"></script>
 
 	<title>{{if .Title}}{{.Title}} - {{end}}{{AppName}}</title>
 

+ 2 - 3
templates/repo/commits_table.tmpl

@@ -29,8 +29,7 @@
 				</tr>
 			</thead>
 			<tbody>
-				{{ $r:= List .Commits}}
-				{{range $r}}
+				{{range .Commits}}
 					<tr>
 						<td class="author">
 							{{if .User}}
@@ -47,7 +46,7 @@
 							{{else}}
 								<a rel="nofollow" class="ui sha label" href="{{AppSubURL}}/{{$.Username}}/{{$.Reponame}}/commit/{{.ID}}">{{ShortSHA1 .ID.String}}</a>
 							{{end}}
-							<span class="{{if gt .ParentCount 1}}grey text {{end}} has-emoji">{{RenderCommitMessage false .Summary $.RepoLink $.Repository.ComposeMetas | Str2HTML}}</span>
+							<span class="{{if gt .ParentsCount 1}}grey text {{end}} has-emoji">{{RenderCommitMessage false .Summary $.RepoLink $.Repository.ComposeMetas | Str2HTML}}</span>
 						</td>
 						<td class="grey text right aligned">{{TimeSince .Author.When $.Lang}}</td>
 					</tr>

+ 17 - 17
templates/repo/diff/box.tmpl

@@ -4,7 +4,7 @@
 	<div class="diff-detail-box diff-box">
 		<div>
 			<i class="fa fa-retweet"></i>
-			{{.i18n.Tr "repo.diff.stats_desc" .Diff.NumFiles .Diff.TotalAddition .Diff.TotalDeletion | Str2HTML}}
+			{{.i18n.Tr "repo.diff.stats_desc" .Diff.NumFiles .Diff.TotalAdditions .Diff.TotalDeletions | Str2HTML}}
 			<div class="ui right">
 				<a class="ui tiny basic toggle button" href="?style={{if .IsSplitStyle}}unified{{else}}split{{end}}">{{ if .IsSplitStyle }}{{.i18n.Tr "repo.diff.show_unified_view"}}{{else}}{{.i18n.Tr "repo.diff.show_split_view"}}{{end}}</a>
 				<a class="ui tiny basic toggle button" data-target="#diff-files">{{.i18n.Tr "repo.diff.show_diff_stats"}}</a>
@@ -14,19 +14,19 @@
 			{{range .Diff.Files}}
 				<li>
 					<div class="diff-counter count pull-right">
-						{{if not .IsBin}}
-							<span class="add" data-line="{{.Addition}}">{{.Addition}}</span>
+						{{if not .IsBinary}}
+							<span class="add" data-line="{{.NumAdditions}}">{{.NumAdditions}}</span>
 							<span class="bar">
 								<span class="pull-left add"></span>
 								<span class="pull-left del"></span>
 							</span>
-							<span class="del" data-line="{{.Deletion}}">{{.Deletion}}</span>
+							<span class="del" data-line="{{.NumDeletions}}">{{.NumDeletions}}</span>
 						{{else}}
 							<span>{{$.i18n.Tr "repo.diff.bin"}}</span>
 						{{end}}
 					</div>
 					<!-- todo finish all file status, now modify, add, delete and rename -->
-					<span class="status {{DiffTypeToStr .GetType}} poping up" data-content="{{DiffTypeToStr .GetType}}" data-variation="inverted tiny" data-position="right center">&nbsp;</span>
+					<span class="status {{DiffFileTypeToStr .Type}} poping up" data-content="{{DiffFileTypeToStr .Type}}" data-variation="inverted tiny" data-position="right center">&nbsp;</span>
 					<a class="file" href="#diff-{{.Index}}">{{.Name}}</a>
 				</li>
 			{{end}}
@@ -40,12 +40,12 @@
 					{{$.i18n.Tr "repo.diff.file_suppressed"}}
 					<div class="diff-counter count ui left">
 						{{if not $file.IsRenamed}}
-							<span class="add" data-line="{{.Addition}}">+ {{.Addition}}</span>
+							<span class="add" data-line="{{.NumAdditions}}">+ {{.NumAdditions}}</span>
 							<span class="bar">
 								<span class="pull-left add"></span>
 								<span class="pull-left del"></span>
 							</span>
-							<span class="del" data-line="{{.Deletion}}">- {{.Deletion}}</span>
+							<span class="del" data-line="{{.NumDeletions}}">- {{.NumDeletions}}</span>
 						{{end}}
 					</div>
 					<span class="file">{{$file.Name}}</span>
@@ -55,15 +55,15 @@
 			<div class="diff-file-box diff-box file-content {{TabSizeClass $.Editorconfig $file.Name}}" id="diff-{{.Index}}">
 				<h4 class="ui top attached normal header">
 					<div class="diff-counter count ui left">
-						{{if $file.IsBin}}
+						{{if $file.IsBinary}}
 							{{$.i18n.Tr "repo.diff.bin"}}
 						{{else if not $file.IsRenamed}}
-							<span class="add" data-line="{{.Addition}}">+ {{.Addition}}</span>
+							<span class="add" data-line="{{.NumAdditions}}">+ {{.NumAdditions}}</span>
 							<span class="bar">
 								<span class="pull-left add"></span>
 								<span class="pull-left del"></span>
 							</span>
-							<span class="del" data-line="{{.Deletion}}">- {{.Deletion}}</span>
+							<span class="del" data-line="{{.NumDeletions}}">- {{.NumDeletions}}</span>
 						{{end}}
 					</div>
 					<span class="file">{{if $file.IsRenamed}}{{$file.OldName}} &rarr; {{end}}{{$file.Name}}</span>
@@ -80,7 +80,7 @@
 				<div class="ui unstackable attached table segment">
 					{{if not $file.IsRenamed}}
 						{{$isImage := (call $.IsImageFile $file.Name)}}
-						{{if and $isImage}}
+						{{if $isImage}}
 							<div class="center">
 								<img src="{{$.RawPath}}/{{EscapePound .Name}}">
 							</div>
@@ -92,22 +92,22 @@
 											{{$highlightClass := $file.HighlightClass}}
 											{{range $j, $section := $file.Sections}}
 												{{range $k, $line := $section.Lines}}
-													<tr class="{{DiffLineTypeToStr .GetType}}-code nl-{{$k}} ol-{{$k}}">
-														{{if eq .GetType 4}}
+													<tr class="{{DiffLineTypeToStr .Type}}-code nl-{{$k}} ol-{{$k}}">
+														{{if eq .Type 4}}
 															<td class="lines-num"></td>
 															<td colspan="3"  class="lines-code">
 																<pre><code class="{{if $highlightClass}}language-{{$highlightClass}}{{else}}nohighlight{{end}}">{{$section.ComputedInlineDiffFor $line}}</code></pre>
 															</td>
 														{{else}}
-															<td class="lines-num lines-num-old" {{if $line.LeftIdx}} id="diff-{{Sha1 $file.Index}}L{{$line.LeftIdx}}" data-line-number="{{$line.LeftIdx}}"{{end}}>
+															<td class="lines-num lines-num-old" {{if $line.LeftLine}} id="diff-{{Sha1 $file.Index}}L{{$line.LeftLine}}" data-line-number="{{$line.LeftLine}}"{{end}}>
 															</td>
 															<td class="lines-code halfwidth">
-																<pre><code class="wrap {{if $highlightClass}}language-{{$highlightClass}}{{else}}nohighlight{{end}}">{{if $line.LeftIdx}}{{$section.ComputedInlineDiffFor $line}}{{end}}</code></pre>
+																<pre><code class="wrap {{if $highlightClass}}language-{{$highlightClass}}{{else}}nohighlight{{end}}">{{if $line.LeftLine}}{{$section.ComputedInlineDiffFor $line}}{{end}}</code></pre>
 															</td>
-															<td class="lines-num lines-num-new" {{if $line.RightIdx}} id="diff-{{Sha1 $file.Index}}R{{$line.RightIdx}}" data-line-number="{{$line.RightIdx}}"{{end}}>
+															<td class="lines-num lines-num-new" {{if $line.RightLine}} id="diff-{{Sha1 $file.Index}}R{{$line.RightLine}}" data-line-number="{{$line.RightLine}}"{{end}}>
 															</td>
 															<td class="lines-code halfwidth">
-																<pre><code class="wrap {{if $highlightClass}}language-{{$highlightClass}}{{else}}nohighlight{{end}}">{{if $line.RightIdx}}{{$section.ComputedInlineDiffFor $line}}{{end}}</code></pre>
+																<pre><code class="wrap {{if $highlightClass}}language-{{$highlightClass}}{{else}}nohighlight{{end}}">{{if $line.RightLine}}{{$section.ComputedInlineDiffFor $line}}{{end}}</code></pre>
 															</td>
 														{{end}}
 													</tr>

+ 5 - 5
templates/repo/diff/section_unified.tmpl

@@ -2,18 +2,18 @@
 {{$highlightClass := $file.HighlightClass}}
 {{range $j, $section := $file.Sections}}
 	{{range $k, $line := $section.Lines}}
-		<tr class="{{DiffLineTypeToStr .GetType}}-code nl-{{$k}} ol-{{$k}}">
-			{{if eq .GetType 4}}
+		<tr class="{{DiffLineTypeToStr .Type}}-code nl-{{$k}} ol-{{$k}}">
+			{{if eq .Type 4}}
 				<td colspan="2" class="lines-num">
 					{{/* {{if gt $j 0}}<span class="fold octicon octicon-fold"></span>{{end}} */}}
 				</td>
 			{{else}}
-				<td class="lines-num lines-num-old" {{if $line.LeftIdx}} id="diff-{{$file.Index}}L{{$line.LeftIdx}}" data-line-number="{{$line.LeftIdx}}"{{end}}></td>
-				<td class="lines-num lines-num-new" {{if $line.RightIdx}} id="diff-{{$file.Index}}R{{$line.RightIdx}}" data-line-number="{{$line.RightIdx}}"{{end}}></td>
+				<td class="lines-num lines-num-old" {{if $line.LeftLine}} id="diff-{{$file.Index}}L{{$line.LeftLine}}" data-line-number="{{$line.LeftLine}}"{{end}}></td>
+				<td class="lines-num lines-num-new" {{if $line.RightLine}} id="diff-{{$file.Index}}R{{$line.RightLine}}" data-line-number="{{$line.RightLine}}"{{end}}></td>
 			{{end}}
 			<td class="lines-code">
 				<pre><code class="{{if $highlightClass}}language-{{$highlightClass}}{{else}}nohighlight{{end}}">{{$section.ComputedInlineDiffFor $line}}</code></pre>
 			</td>
 		</tr>
 	{{end}}
-{{end}}
+{{end}}

+ 1 - 1
templates/repo/settings/githook_edit.tmpl

@@ -20,7 +20,7 @@
 							</div>
 							<div class="field">
 								<label for="content">{{$.i18n.Tr "repo.settings.githook_content"}}</label>
-								<textarea id="content" name="content" wrap="off" autofocus>{{if .IsActive}}{{.Content}}{{else}}{{.Sample}}{{end}}</textarea>
+								<textarea id="content" name="content" wrap="off" autofocus>{{.Content}}</textarea>
 							</div>
 
 							<div class="inline field">

+ 1 - 1
templates/repo/settings/githooks.tmpl

@@ -16,7 +16,7 @@
 						</div>
 						{{range .Hooks}}
 							<div class="item">
-								<span class="text {{if .IsActive}}green{{else}}grey{{end}}"><i class="octicon octicon-primitive-dot"></i></span>
+								<span class="text {{if not .IsSample}}green{{else}}grey{{end}}"><i class="octicon octicon-primitive-dot"></i></span>
 								<span>{{.Name}}</span>
 								<a class="text blue ui right" href="{{$.RepoLink}}/settings/hooks/git/{{.Name}}"><i class="fa fa-pencil"></i></a>
 							</div>

+ 1 - 1
templates/repo/settings/options.tmpl

@@ -86,7 +86,7 @@
 							</div>
 							<div class="field">
 								<label for="mirror_address">{{.i18n.Tr "repo.mirror_address"}}</label>
-								<input id="mirror_address" name="mirror_address" value="{{.Mirror.FullAddress}}" required>
+								<input id="mirror_address" name="mirror_address" value="{{.Mirror.RawAddress}}" required>
 								<p class="help">{{.i18n.Tr "repo.mirror_address_desc"}}</p>
 							</div>
 

+ 9 - 16
templates/repo/view_list.tmpl

@@ -23,35 +23,28 @@
 				<td colspan="3"><i class="octicon octicon-mail-reply"></i><a href="{{EscapePound .BranchLink}}{{.ParentPath}}">..</a></td>
 			</tr>
 		{{end}}
-		{{range $item := .Files}}
-			{{$entry := index $item 0}}
-			{{$commit := index $item 1}}
+		{{range .Files}}
 			<tr>
-				{{if $entry.IsSubModule}}
+				{{if .Submodule}}
 					<td>
 						<span class="octicon octicon-file-submodule"></span>
-						{{$refURL := $commit.RefURL AppURL $.BranchLink}}
-						{{if $refURL}}
-							<a href="{{$refURL}}">{{$entry.Name}}</a> @ <a href="{{$refURL}}/commit/{{$commit.RefID}}">{{ShortSHA1 $commit.RefID}}</a>
-						{{else}}
-							{{$entry.Name}} @ {{ShortSHA1 $commit.RefID}}
-						{{end}}
+						<a href="{{InferSubmoduleURL .Submodule}}">{{.Entry.Name}} @ {{ShortSHA1 .Submodule.Commit}}</a>
 					</td>
 				{{else}}
 					<td class="name">
-						{{if $entry.IsLink}}
+						{{if .Entry.IsSymlink}}
 							<span class="octicon octicon-file-symlink-file"></span>
 						{{else}}
-							<span class="octicon octicon-file-{{if or $entry.IsDir}}directory{{else}}text{{end}}"></span>
+							<span class="octicon octicon-file-{{if or .Entry.IsTree}}directory{{else}}text{{end}}"></span>
 						{{end}}
-						<a href="{{EscapePound $.TreeLink}}/{{EscapePound $entry.Name}}">{{$entry.Name}}</a>
+						<a href="{{EscapePound $.TreeLink}}/{{EscapePound .Entry.Name}}">{{.Entry.Name}}</a>
 					</td>
 				{{end}}
 				<td class="message collapsing has-emoji">
-					<a rel="nofollow" class="ui sha label" href="{{$.RepoLink}}/commit/{{$commit.ID}}">{{ShortSHA1 $commit.ID.String}}</a>
-					{{RenderCommitMessage false $commit.Summary $.RepoLink $.Repository.ComposeMetas | Str2HTML}}
+					<a rel="nofollow" class="ui sha label" href="{{$.RepoLink}}/commit/{{.Commit.ID}}">{{ShortSHA1 .Commit.ID.String}}</a>
+					{{RenderCommitMessage false .Commit.Summary $.RepoLink $.Repository.ComposeMetas | Str2HTML}}
 				</td>
-				<td class="text grey right age">{{TimeSince $commit.Committer.When $.Lang}}</td>
+				<td class="text grey right age">{{TimeSince .Commit.Committer.When $.Lang}}</td>
 			</tr>
 		{{end}}
 	</tbody>

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff