瀏覽代碼

Merge tag 'v0.11.91' into rebased

Achilleas Koutsou 5 年之前
父節點
當前提交
4b2ba84154
共有 100 個文件被更改,包括 3856 次插入1771 次删除
  1. 8 5
      .pkgr.yml
  2. 1 0
      .travis.yml
  3. 2 1
      Dockerfile
  4. 3 2
      Dockerfile.aarch64
  5. 1 1
      Dockerfile.aarch64hub
  6. 1 2
      Dockerfile.rpi
  7. 3 2
      Dockerfile.rpihub
  8. 2 1
      README_ZH.md
  9. 2 2
      cmd/serv.go
  10. 1 0
      conf/locale/TRANSLATORS
  11. 1 1
      conf/locale/locale_de-DE.ini
  12. 33 32
      conf/locale/locale_gl-ES.ini
  13. 43 43
      conf/locale/locale_id-ID.ini
  14. 47 43
      conf/locale/locale_ja-JP.ini
  15. 2 2
      conf/locale/locale_ko-KR.ini
  16. 1 1
      conf/locale/locale_ru-RU.ini
  17. 6 6
      conf/locale/locale_sr-SP.ini
  18. 64 64
      conf/locale/locale_tr-TR.ini
  19. 2 2
      docker/s6/gogs/setup
  20. 3 3
      gogs.go
  21. 5 6
      models/access.go
  22. 1 0
      models/action.go
  23. 1 1
      models/repo.go
  24. 4 4
      pkg/bindata/bindata.go
  25. 3 3
      pkg/context/api.go
  26. 1 3
      pkg/context/context.go
  27. 62 0
      pkg/context/notice.go
  28. 2 2
      pkg/context/repo.go
  29. 1 1
      pkg/dav/middle.go
  30. 3 0
      pkg/template/template.go
  31. 0 1
      routes/api/v1/admin/org.go
  32. 5 9
      routes/api/v1/admin/org_repo.go
  33. 9 7
      routes/api/v1/admin/org_team.go
  34. 0 1
      routes/api/v1/admin/repo.go
  35. 16 18
      routes/api/v1/admin/user.go
  36. 147 91
      routes/api/v1/api.go
  37. 4 4
      routes/api/v1/misc/markdown.go
  38. 10 13
      routes/api/v1/org/org.go
  39. 8 18
      routes/api/v1/repo/file.go
  40. 23 30
      routes/api/v1/repo/issue.go
  41. 21 28
      routes/api/v1/repo/issue_comment.go
  42. 22 60
      routes/api/v1/repo/issue_label.go
  43. 13 34
      routes/api/v1/repo/label.go
  44. 13 20
      routes/api/v1/repo/milestone.go
  45. 41 52
      routes/api/v1/repo/repo.go
  46. 6 6
      routes/api/v1/user/app.go
  47. 11 12
      routes/api/v1/user/email.go
  48. 9 15
      routes/api/v1/user/follower.go
  49. 10 22
      routes/api/v1/user/key.go
  50. 7 9
      routes/api/v1/user/user.go
  51. 1 0
      routes/repo/http.go
  52. 1 1
      templates/.VERSION
  53. 1 1
      templates/base/footer.tmpl
  54. 2 5
      templates/base/head.tmpl
  55. 1 0
      vendor/github.com/gogs/go-gogs-client/repo_hook.go
  56. 62 53
      vendor/github.com/jtolds/gls/context.go
  57. 11 3
      vendor/github.com/jtolds/gls/gen_sym.go
  58. 25 0
      vendor/github.com/jtolds/gls/gid.go
  59. 126 22
      vendor/github.com/jtolds/gls/stack_tags.go
  60. 51 77
      vendor/github.com/jtolds/gls/stack_tags_js.go
  61. 16 47
      vendor/github.com/jtolds/gls/stack_tags_main.go
  62. 12 0
      vendor/github.com/smartystreets/assertions/CONTRIBUTING.md
  63. 1 1
      vendor/github.com/smartystreets/assertions/LICENSE.md
  64. 11 0
      vendor/github.com/smartystreets/assertions/Makefile
  65. 50 4
      vendor/github.com/smartystreets/assertions/README.md
  66. 0 3
      vendor/github.com/smartystreets/assertions/assertions.goconvey
  67. 2 2
      vendor/github.com/smartystreets/assertions/collections.go
  68. 5 1
      vendor/github.com/smartystreets/assertions/doc.go
  69. 75 0
      vendor/github.com/smartystreets/assertions/equal_method.go
  70. 77 26
      vendor/github.com/smartystreets/assertions/equality.go
  71. 37 0
      vendor/github.com/smartystreets/assertions/equality_diff.go
  72. 9 1
      vendor/github.com/smartystreets/assertions/filter.go
  73. 3 0
      vendor/github.com/smartystreets/assertions/go.mod
  74. 20 0
      vendor/github.com/smartystreets/assertions/internal/go-diff/LICENSE
  75. 1345 0
      vendor/github.com/smartystreets/assertions/internal/go-diff/diffmatchpatch/diff.go
  76. 46 0
      vendor/github.com/smartystreets/assertions/internal/go-diff/diffmatchpatch/diffmatchpatch.go
  77. 160 0
      vendor/github.com/smartystreets/assertions/internal/go-diff/diffmatchpatch/match.go
  78. 23 0
      vendor/github.com/smartystreets/assertions/internal/go-diff/diffmatchpatch/mathutil.go
  79. 17 0
      vendor/github.com/smartystreets/assertions/internal/go-diff/diffmatchpatch/operation_string.go
  80. 556 0
      vendor/github.com/smartystreets/assertions/internal/go-diff/diffmatchpatch/patch.go
  81. 88 0
      vendor/github.com/smartystreets/assertions/internal/go-diff/diffmatchpatch/stringutil.go
  82. 208 54
      vendor/github.com/smartystreets/assertions/internal/go-render/render/render.go
  83. 26 0
      vendor/github.com/smartystreets/assertions/internal/go-render/render/render_time.go
  84. 0 70
      vendor/github.com/smartystreets/assertions/internal/oglematchers/all_of.go
  85. 0 32
      vendor/github.com/smartystreets/assertions/internal/oglematchers/any.go
  86. 1 1
      vendor/github.com/smartystreets/assertions/internal/oglematchers/contains.go
  87. 0 91
      vendor/github.com/smartystreets/assertions/internal/oglematchers/elements_are.go
  88. 0 51
      vendor/github.com/smartystreets/assertions/internal/oglematchers/error.go
  89. 0 37
      vendor/github.com/smartystreets/assertions/internal/oglematchers/has_same_type_as.go
  90. 0 46
      vendor/github.com/smartystreets/assertions/internal/oglematchers/has_substr.go
  91. 0 134
      vendor/github.com/smartystreets/assertions/internal/oglematchers/identical_to.go
  92. 0 69
      vendor/github.com/smartystreets/assertions/internal/oglematchers/matches_regexp.go
  93. 0 43
      vendor/github.com/smartystreets/assertions/internal/oglematchers/new_matcher.go
  94. 0 74
      vendor/github.com/smartystreets/assertions/internal/oglematchers/panics.go
  95. 0 65
      vendor/github.com/smartystreets/assertions/internal/oglematchers/pointee.go
  96. 1 1
      vendor/github.com/smartystreets/assertions/internal/oglematchers/transform_description.go
  97. 73 61
      vendor/github.com/smartystreets/assertions/messages.go
  98. 1 1
      vendor/github.com/smartystreets/assertions/quantity.go
  99. 11 10
      vendor/github.com/smartystreets/assertions/serializer.go
  100. 17 1
      vendor/github.com/smartystreets/assertions/time.go

+ 8 - 5
.pkgr.yml

@@ -1,15 +1,13 @@
 targets:
-  debian-7: &debian
+  debian-8: &debian
     build_dependencies:
       - libpam0g-dev
     dependencies:
       - libpam0g
       - git
-  debian-8:
-    <<: *debian
   debian-9:
     <<: *debian
-  ubuntu-12.04:
+  debian-10:
     <<: *debian
   ubuntu-14.04:
     <<: *debian
@@ -18,6 +16,8 @@ targets:
     build_dependencies:
       - bzr
       - mercurial
+  ubuntu-18.04:
+    <<: *debian
   centos-6: &el
     build_dependencies:
       - pam-devel
@@ -33,4 +33,7 @@ before:
 after:
   - mv bin/gogs gogs
 after_install: ./packager/hooks/postinst
-buildpack: https://github.com/heroku/heroku-buildpack-go.git#v62
+# Can be updated after CentOS 6 support is dropped, otherwise fails with
+# `fatal: bad config file line 2 in /home/pkgr/.gitconfig` because of
+# https://github.com/heroku/heroku-buildpack-go/blob/f96ebebfa7605fd3916521e42ab050c81c9b947a/lib/common.sh#L238
+buildpack: https://github.com/heroku/heroku-buildpack-go.git#v76

+ 1 - 0
.travis.yml

@@ -4,6 +4,7 @@ go:
   - 1.9.x
   - 1.10.x
   - 1.11.x
+  - 1.12.x
 
 before_install:
   - sudo apt-get update -qq

+ 2 - 1
Dockerfile

@@ -22,7 +22,8 @@ RUN chmod +x /usr/sbin/gosu \
     socat \
     tzdata \
     python \
-    py-pip
+    py-pip \
+    rsync
 
 RUN pip install supervisor pyyaml
 RUN mkdir /git-annex

+ 3 - 2
Dockerfile.aarch64

@@ -1,4 +1,4 @@
-FROM aarch64/alpine:3.5
+FROM arm64v8/alpine:latest
 
 # Install system utils & Gogs runtime dependencies
 ADD https://github.com/tianon/gosu/releases/download/1.9/gosu-arm64 /usr/sbin/gosu
@@ -14,7 +14,8 @@ RUN chmod +x /usr/sbin/gosu \
     s6 \
     shadow \
     socat \
-    tzdata
+    tzdata \
+    rsync
 
 ENV GOGS_CUSTOM /data/gogs
 

+ 1 - 1
Dockerfile.aarch64hub

@@ -1,4 +1,4 @@
-FROM arm64v8/alpine:3.8
+FROM arm64v8/alpine:latest
 
 ENV GOGS_CUSTOM /data/gogs
 ENV QEMU_EXECVE 1

+ 1 - 2
Dockerfile.rpi

@@ -8,12 +8,11 @@ RUN apt-get update &&                                   \
                        gcc g++ libc6-dev make golang    \
                        git git-annex openssh-server     \
                        python-pip python-setuptools     \
-                       socat tzdata patch    \
+                       socat tzdata patch rsync \
 
     && rm -rf /var/lib/apt/lists/*
 
 RUN pip install supervisor pyyaml
-
 ENV GOGS_CUSTOM /data/gogs
 
 # Configure LibC Name Service

+ 3 - 2
Dockerfile.rpihub

@@ -1,4 +1,4 @@
-FROM armhf/alpine:3.5
+FROM arm32v7/alpine:latest
 
 ENV GOGS_CUSTOM /data/gogs
 ENV QEMU_EXECVE 1
@@ -30,7 +30,8 @@ RUN chmod +x /usr/sbin/gosu \
     s6 \
     shadow \
     socat \
-    tzdata
+    tzdata \
+    rsync
 
 # Configure LibC Name Service
 COPY docker/nsswitch.conf /etc/nsswitch.conf

+ 2 - 1
README_ZH.md

@@ -96,8 +96,9 @@ Gogs 是一款极易搭建的自助 Git 服务。
 
 - 感谢 [Egon Elbre](https://twitter.com/egonelbre) 设计的 Logo。
 - 感谢 [Crowdin](https://crowdin.com/project/gogs) 提供免费的开源项目本地化支持。
-- 感谢 [DigitalOcean](https://www.digitalocean.com)、[VPSServer](https://www.vpsserver.com/) 和 [Hosted.nl](https://www.hosted.nl/) 提供服务器赞助。
+- 感谢 [DigitalOcean](https://www.digitalocean.com)、[VPSServer](https://www.vpsserver.com/)、[Hosted.nl](https://www.hosted.nl/) 和 [MonoVM](https://monovm.com) 提供服务器赞助。
 - 感谢 [KeyCDN](https://www.keycdn.com/) 提供 CDN 服务赞助。
+- 感谢 [Buildkite](https://buildkite.com) 提供免费的开源项目 CI/CD 支持。
 
 ## 贡献成员
 

+ 2 - 2
cmd/serv.go

@@ -213,7 +213,7 @@ func runServ(c *cli.Context) error {
 				fail("Internal error", "Fail to get user by key ID '%d': %v", key.ID, err)
 			}
 
-			mode, err := models.AccessLevel(user.ID, repo)
+			mode, err := models.UserAccessMode(user.ID, repo)
 			if err != nil {
 				fail("Internal error", "Fail to check access: %v", err)
 			}
@@ -324,7 +324,7 @@ func secureGitAnnex(path string, user *models.User, repo *models.Repository) err
 	}
 	mode := models.ACCESS_MODE_NONE
 	if user != nil {
-		mode, err = models.AccessLevel(user.ID, repo)
+		mode, err = models.UserAccessMode(user.ID, repo)
 		if err != nil {
 			fail("Internal error", "Fail to check access: %v", err)
 		}

+ 1 - 0
conf/locale/TRANSLATORS

@@ -56,6 +56,7 @@ Luc Stepniewski <luc AT stepniewski DOT fr>
 Łukasz Jan Niemier <lukasz AT niemier DOT pl>
 Marc Schiller <marc AT schiller DOT im>
 Marvin Menzerath <github AT marvin-menzerath DOT de>
+Mathias Rangel Wulff <m AT rawu DOT dk>
 Michael Härtl <haertl DOT mike AT gmail DOT com>
 Miguel de la Cruz <miguel AT mcrx DOT me>
 Mikhail Burdin <xdshot9000 AT gmail DOT com>

+ 1 - 1
conf/locale/locale_de-DE.ini

@@ -324,7 +324,7 @@ add_key=Schlüssel hinzufügen
 ssh_desc=Dies ist eine Liste aller SSH-Schlüssel, die Ihrem Konto zugeordnet sind. Bitte entfernen Sie alle Schlüssel, die Ihnen nicht bekannt sind.
 ssh_helper=<strong>Brauchen Sie Hilfe?</strong> Hier ist eine Anleitung zum <a href="%s">Erzeugen von SSH-Schlüsseln</a> oder <a href="%s">Lösen einfacher SSH-Probleme</a>.
 add_new_key=SSH-Schlüssel hinzufügen
-ssh_key_been_used=Inhalt des öffentlichen Schlüssels wurde verwendet.
+ssh_key_been_used=Inhalt des öffentlichen Schlüssels wurde bereits verwendet.
 ssh_key_name_used=Ein öffentlicher Schlüssel mit diesem Namen existiert bereits.
 key_name=Schlüsselname
 key_content=Inhalt

+ 33 - 32
conf/locale/locale_gl-ES.ini

@@ -58,8 +58,8 @@ db_name=Nome da base de datos
 db_helper=Por favor, empregue o motor INNODB coa configuración de caracteres utf8_general_ci para MySQL.
 ssl_mode=Modo SSL
 path=Ruta
-sqlite_helper=The file path of SQLite3 database. <br>Please use absolute path when you start as service.
-err_empty_db_path=SQLite3 database path cannot be empty.
+sqlite_helper=A ruta do ficheiro da base de datos SQLite3. <br> Utilice a ruta absoluta cando arrique o servicio.
+err_empty_db_path=A ruta da base de datos SQLite3 non pode estar baleira.
 no_admin_and_disable_registration=Non pode deshabilitar o rexistro sen crear unha conta de administrador.
 err_empty_admin_password=O contrasinal de administrador non pode estar baleiro.
 
@@ -74,16 +74,16 @@ domain=Dominio
 domain_helper=Isto afecta ás URLs para clonar por SSH.
 ssh_port=Porto SSH
 ssh_port_helper=Número de porto do seu servidor SSH, déixeo en branco para desactivar SSH.
-use_builtin_ssh_server=Use Builtin SSH Server
-use_builtin_ssh_server_popup=Start builtin SSH server for Git operations to distinguish from system SSH daemon.
+use_builtin_ssh_server=Utilizar Builin en Sevidor SSH
+use_builtin_ssh_server_popup=Inicia o servidor SSH integrado para que as operacións de Git sexan distintas do demonio SSH do sistema.
 http_port=Porto HTTP
 http_port_helper=Porto no que escoitará a aplicación.
 app_url=URL da aplicación
 app_url_helper=Isto afecta ás URLs para clonar por HTTP/HTTPS e a algúns correos electrónicos.
 log_root_path=Ruta do rexistro
 log_root_path_helper=Directorio onde almacenar os rexistros.
-enable_console_mode=Enable Console Mode
-enable_console_mode_popup=In addition to file mode, also print logs to console.
+enable_console_mode=Habilitar Modo Consola
+enable_console_mode_popup=Ademáis do modo de ficheiro, tamén imprime os rexistros para a consola.
 
 optional_title=Configuración opcional
 email_title=Configuración do servizo de correo
@@ -119,7 +119,8 @@ sqlite3_not_available=A túa versión non soporta SQLite3, por favor, descarga o
 invalid_db_setting=A configuración da base de datos non é correcta: %v
 invalid_repo_path=A ruta da raíz do repositorio é inválida: %v
 run_user_not_match=A persoa usuaria que está executando a aplicación non é a persoa usuaria actual: %s -> %s
-smtp_host_missing_port=SMTP Host is missing port in address.
+smtp_host_missing_port=Falta o porto do Host SMTP
+
 invalid_smtp_from=O campo From do SMTP non é valido: %v
 save_config_failed=Erro ao gardar a configuración: %v
 invalid_admin_setting=A configuración da conta de administración é inválida: %v
@@ -151,8 +152,8 @@ register_hepler_msg=Xa tes unha conta? Inicia sesión!
 social_register_hepler_msg=Xa tes unha conta? Enlázaa!
 disable_register_prompt=Sentímolo, o rexistro está deshabilitado. Por favor, contacta co administrador do sitio.
 disable_register_mail=Sentímolo. Os correos de confirmación de rexistro están deshabilitados.
-auth_source=Authentication Source
-local=Local
+auth_source=Fonte de Autenticación
+local=Configuración rexional
 remember_me=Recórdame
 forgot_password=Esquecín o meu contrasinal
 forget_password=Esqueciches o teu contrasinal?
@@ -171,13 +172,13 @@ reset_password_helper=Prema aquí para restablecer o seu contrasinal
 password_too_short=A lonxitude do contrasinal non pode ser menor de 6.
 non_local_account=Contas que non son locais non poden cambiar os contrasinais a través de GIN.
 
-login_two_factor=Two-factor Authentication
-login_two_factor_passcode=Authentication Passcode
-login_two_factor_enter_recovery_code=Enter a two-factor recovery code
-login_two_factor_recovery=Two-factor Recovery
-login_two_factor_recovery_code=Recovery Code
-login_two_factor_enter_passcode=Enter a two-factor passcode
-login_two_factor_invalid_recovery_code=Recovery code has been used or does not valid.
+login_two_factor=Autenticación en dous pasos
+login_two_factor_passcode=Código de Autenticación
+login_two_factor_enter_recovery_code=Introduza o código de recuperación da verificación en dous pasos
+login_two_factor_recovery=Recuperación en dous pasos
+login_two_factor_recovery_code=Codigo de Recuperación
+login_two_factor_enter_passcode=Introduza o código de acceso en dous pasos
+login_two_factor_invalid_recovery_code=O código de recuperación foi usado ou non é válido.
 
 [mail]
 activate_account=Por favor, activa a túa conta
@@ -214,7 +215,7 @@ Content=Contido
 require_error=` non pode estar baleiro.`
 alpha_dash_error=` os caracteres deben ser alfanuméricos ou dash(-_).`
 alpha_dash_dot_error=` debe ser un carácter alfanumérivo válido, un guión alto ou baixo (-_) ou un signo de puntuación.`
-alpha_dash_dot_slash_error=` must be valid alpha or numeric or dash(-_) or dot characters or slashes.`
+alpha_dash_dot_slash_error=` debe ser un carácter válido : numérico, alfabético, guión(-_)  puntos ou barras.`
 size_error=` debe ser de tamaño %s.`
 min_size_error=` debe conter polo menos %s caracteres.`
 max_size_error=` debe conter como máximo %s caracteres.`
@@ -231,7 +232,7 @@ org_name_been_taken=Xa existe unha organización con este nome.
 team_name_been_taken=Xa existe un equipo con este nome.
 email_been_used=Este enderezo de correo electrónico xa está en uso.
 username_password_incorrect=Nome de usuario ou contrasinal incorrectos.
-auth_source_mismatch=The authentication source selected is not associated with the user.
+auth_source_mismatch=A fonte de autenticación seleccionada non está asociada co usuario.
 enterred_invalid_repo_name=Por favor, asegúrate de que introduciches correctamente o nome do repositorio.
 enterred_invalid_owner_name=Por favor, asegúrate de que introduciches correctamente o nome do propietario.
 enterred_invalid_password=Por favor, asegúrate de que introduciches correctamente o teu contrasinal.
@@ -267,8 +268,8 @@ profile=Perfil
 password=Contrasinal
 avatar=Avatar
 ssh_keys=Claves SSH
-security=Security
-repos=Repositories
+security=Seguridade
+repos=Repositorios
 orgs=Organizacións
 applications=Aplicacións
 delete=Eliminar conta
@@ -337,29 +338,29 @@ no_activity=Non hai actividade recente
 key_state_desc=Esta clave foi usada nos últimos 7 días
 token_state_desc=Token usado nos últimos 7 días
 
-two_factor=Two-factor Authentication
-two_factor_status=Status:
-two_factor_on=On
-two_factor_off=Off
-two_factor_enable=Enable
-two_factor_disable=Disable
+two_factor=Autenticación en Dous Pasos
+two_factor_status=Estado:
+two_factor_on=Si
+two_factor_off=Non
+two_factor_enable=Activar
+two_factor_disable=Desactivar
 two_factor_view_recovery_codes=View and save <a href="%s%s">your recovery codes</a> in a safe place. You can use them as passcode if you lose access to your authentication application.
 two_factor_http=For HTTP/HTTPS operations, you are no longer able to use plain username and password. Please create and use <a href="%[1]s%[2]s">Personal Access Token</a> as your credential, e.g. <code>%[3]s</code>.
 two_factor_enable_title=Enable Two-factor Authentication
 two_factor_scan_qr=Please use your authentication application to scan the image:
 two_factor_or_enter_secret=Or enter the secret:
-two_factor_then_enter_passcode=Then enter passcode:
-two_factor_verify=Verify
+two_factor_then_enter_passcode=A continuación, introduza o código:
+two_factor_verify=Verificar
 two_factor_invalid_passcode=The passcode you entered is not valid, please try again!
 two_factor_reused_passcode=The passcode you entered has already been used, please try another one!
 two_factor_enable_error=Enable Two-factor authentication failed: %v
 two_factor_enable_success=Two-factor authentication has enabled for your account successfully!
 two_factor_recovery_codes_title=Two-factor Authentication Recovery Codes
 two_factor_recovery_codes_desc=Recovery codes are used when you temporarily lose access to your authentication application. Each recovery code can only be used once, <b>please keep these codes in a safe place</b>.
-two_factor_regenerate_recovery_codes=Regenerate Recovery Codes
+two_factor_regenerate_recovery_codes=Rexenerar Códigos de Recuperación
 two_factor_regenerate_recovery_codes_error=Regenerate recovery codes failed: %v
 two_factor_regenerate_recovery_codes_success=New recovery codes has been generated successfully!
-two_factor_disable_title=Disable Two-factor Authentication
+two_factor_disable_title=Desactivar a verificación en dous pasos
 two_factor_disable_desc=Your account security level will decrease after disabled two-factor authentication. Do you want to continue?
 two_factor_disable_success=Two-factor authentication has disabled successfully!
 
@@ -379,8 +380,8 @@ orgs.none=Non es membro de nengunha organización.
 orgs.leave_title=Deixar unha organización
 orgs.leave_desc=Deixarás de ter aceso ao tódolos repositorios e equipos despois de deixar a organización. Desexas abandonala?
 
-repos.leave=Leave
-repos.leave_title=Leave repository
+repos.leave=Abandoar
+repos.leave_title=Deixar repositorio
 repos.leave_desc=You will lose access to the repository after you left. Do you want to continue?
 repos.leave_success=You have left repository '%s' successfully!
 

+ 43 - 43
conf/locale/locale_id-ID.ini

@@ -151,8 +151,8 @@ register_hepler_msg=Sudah memiliki account? Sign in sekarang!
 social_register_hepler_msg=Sudah memiliki account? Ikat sekarang!
 disable_register_prompt=Maaf, pendaftaran telah dinonaktifkan. Hubungi administrator situs.
 disable_register_mail=Maaf, konfirmasi pendaftaran melalui email telah dinonaktifkan.
-auth_source=Authentication Source
-local=Local
+auth_source=Sumber Autentikasi
+local=Lokal
 remember_me=Ingat saya
 forgot_password=Lupa Sandi
 forget_password=Lupa sandi?
@@ -160,7 +160,7 @@ sign_up_now=Membutuhkan akun? Daftar sekarang.
 confirmation_mail_sent_prompt=Email konfirmasi baru telah terkirim ke <b>%s</b>, silakan cek inbox Anda dalam waktu %d jam untuk menyelesaikan proses pendaftaran.
 active_your_account=Aktifkan Akun Anda
 prohibit_login=Login tidak diperbolehkan
-prohibit_login_desc=Account Anda tidak diperbolehkan untuk login, silahkan hubungi admin situs.
+prohibit_login_desc=Akun Anda tidak diperbolehkan untuk masuk, silakan hubungi admin situs.
 resent_limit_prompt=Maaf, Anda telah meminta email aktivasi beberapa saat lalu. Silakan tunggu 3 menit kemudian untuk dapat mencoba lagi.
 has_unconfirmed_mail=Hi %s, Anda memiliki alamat email yang belum terkonfirmasi (<b>%s</b>). Jika Anda belum menerima email konfirmasi atau perlu untuk mengirim ulang yang baru, silakan klik pada tombol di bawah ini.
 resend_mail=Klik di sini untuk mengirim kembali email aktivasi
@@ -171,10 +171,10 @@ reset_password_helper=Klik di sini untuk menyetel ulang sandi
 password_too_short=Panjang sandi tidak bisa kurang dari 6 huruf.
 non_local_account=Akun non-lokal tidak dapat mengganti password lewat Gogs.
 
-login_two_factor=Dua faktor otentikasi
-login_two_factor_passcode=Kode otentikasi
+login_two_factor=Autentikasi Dua Faktor
+login_two_factor_passcode=Kode Akses Autentikasi
 login_two_factor_enter_recovery_code=Masukkan kode pemulihan dua faktor
-login_two_factor_recovery=Dua faktor pemulihan
+login_two_factor_recovery=Pemulihan Dua Faktor
 login_two_factor_recovery_code=Kode pemulihan
 login_two_factor_enter_passcode=Masukkan kode akses dua faktor
 login_two_factor_invalid_recovery_code=Kode pemulihan telah digunakan atau tidak sesuai.
@@ -231,7 +231,7 @@ org_name_been_taken=Nama organisasi telah diambil.
 team_name_been_taken=Nama tim telah diambil.
 email_been_used=Alamat email telah digunakan.
 username_password_incorrect=Nama pengguna atau sandi tidak benar.
-auth_source_mismatch=The authentication source selected is not associated with the user.
+auth_source_mismatch=Sumber autentikasi yang dipilih tidak terkait dengan pengguna.
 enterred_invalid_repo_name=Pastikan bahwa nama repositori yang Anda masukkan benar.
 enterred_invalid_owner_name=Pastikan bahwa nama pemilik yang Anda masukkan benar.
 enterred_invalid_password=Harap pastikan bahwa sandi yang Anda masukkan benar.
@@ -240,7 +240,7 @@ last_org_owner=Menghapus pengguna terakhir dari tim pemilik tidak diperbolehkan,
 
 invalid_ssh_key=Maaf, kami tidak dapat memverifikasi kunci SSH Anda: %s
 unable_verify_ssh_key=Gogs tidak dapat memverifikasi kunci SSH Anda, tetapi kita menganggap bahwa itu sah, silakan periksa kembali.
-auth_failed=Otentikasi gagal: %v
+auth_failed=Autentikasi gagal: %v
 
 still_own_repo=Akun Anda masih memiliki kepemilikan atas setidaknya satu repositori, Anda harus menghapus atau mentransfernya terlebih dahulu.
 still_has_org=Akun Anda masih memiliki keanggotaan dalam setidaknya satu organisasi, Anda harus meninggalkan atau menghapus keanggotaan Anda terlebih dahulu.
@@ -274,7 +274,7 @@ applications=Aplikasi
 delete=Menghapus akun
 
 public_profile=Profil publik
-profile_desc=Alamat email Anda bersifat publik dan akan digunakan untuk setiap akun pemberitahuan terkait, dan setiap operasi berbasis web yang dilakukan melalui situs.
+profile_desc=Alamat email Anda bersifat publik dan akan digunakan untuk setiap akun yang terkait pemberitahuan dan setiap operasi berbasis web yang dilakukan melalui situs.
 password_username_disabled=Pengguna jenis bebas-lokal tidak diperbolehkan untuk mengubah nama pengguna mereka.
 full_name=Nama lengkap
 website=Situs web
@@ -288,7 +288,7 @@ cancel=Batal
 
 lookup_avatar_by_mail=Cari avatar dari email
 federated_avatar_lookup=Aktifkan Pencarian Avatar Representasi
-enable_custom_avatar=Mengaktifkan avatar kustom
+enable_custom_avatar=Gunakan Avatar Kostum
 choose_new_avatar=Pilih avatar baru
 update_avatar=Ubah pengaturan avatar
 delete_current_avatar=Hapus avatar terkini
@@ -320,7 +320,7 @@ add_email_success=Alamat email barumu telah berhasil ditambahkan.
 manage_ssh_keys=Kelola Kunci SSH
 add_key=Tambah kunci
 ssh_desc=Ini adalah daftar kunci SSH yang terkait dengan akun Anda. Karena kunci ini memungkinkan seseorang menggunakannya untuk mendapatkan akses ke repositori Anda, sangat penting bagi Anda untuk memastikan Anda mengenalinya.
-ssh_helper=<strong>Nggak tau gimana?</strong> Cek arahannya GitHub buat<a href="%s">bikin kunci SSH-mu sendiri</a> atau benerin <a href="%s">masalah yang umum</a> yang mungkin sedang kamu alami dengan SSH-mu.
+ssh_helper=<strong>Tidak tahu caranya?</strong> Kunjungi petunjuk GitHub untuk <a href="%s">membuat kunci SSH Anda sendiri</a> atau perbaiki <a href="%s">masalah umum</a> yang mungkin sedang Anda alami dengan SSH.
 add_new_key=Tambah kunci SSH
 ssh_key_been_used=Public key telah digunakan.
 ssh_key_name_used=Public key dengan nama itu udah pernah ada.
@@ -337,7 +337,7 @@ no_activity=Tidak ada aktifitas terakhir
 key_state_desc=Kunci ini digunakan dalam 7 hari terakhir
 token_state_desc=Token ini digunakan dalam 7 hari terakhir
 
-two_factor=Otentikasi dua faktor
+two_factor=Autentikasi dua faktor
 two_factor_status=Status:
 two_factor_on=Nyala
 two_factor_off=Mati
@@ -345,21 +345,21 @@ two_factor_enable=Aktifkan
 two_factor_disable=Nonaktifkan
 two_factor_view_recovery_codes=Lihat dan simpan <a href="%s%s"> kode pemulihan anda</a> di tempat yang aman. Anda dapat menggunakannya sebagai kode akses jika Anda kehilangan akses otentikasi aplikasi.
 two_factor_http=Untuk operasi HTTP/HTTPS, Anda tidak lagi mampu menggunakan nama pengguna dan kata sandi. Silakan membuat dan menggunakan <a href="%[1]s%[2]s"> Token akses pribadi</a> sebagai kredensial Anda, misalnya <code>%[3]s</code>.
-two_factor_enable_title=Aktifkan Otentikasi Dua-faktor
+two_factor_enable_title=Aktifkan Autentikasi Dua Faktor
 two_factor_scan_qr=Silakan gunakan aplikasi autentikasi Anda untuk memindai gambar:
 two_factor_or_enter_secret=Atau masukkan rahasianya:
-two_factor_then_enter_passcode=Masukan kode akses:
+two_factor_then_enter_passcode=Kemudian, masukkan kode akses:
 two_factor_verify=Verifikasi
-two_factor_invalid_passcode=Kode akses yang Anda masukan tidak sah, silahkan coba lagi!
-two_factor_reused_passcode=The passcode you entered has already been used, please try another one!
-two_factor_enable_error=Aktifkan Autentikasi dua faktor gagal: %v
-two_factor_enable_success=Autentikasi dua faktor telah memungkinkan akun Anda berhasil!
-two_factor_recovery_codes_title=Kode Pemulihan Otentikasi dua faktor
+two_factor_invalid_passcode=Kode akses yang Anda masukkan tidak sah, silakan coba lagi!
+two_factor_reused_passcode=Kode akses yang Anda masukkan sudah digunakan, coba yang lain!
+two_factor_enable_error=Gagal mengaktifkan autentikasi Dua Faktor: %v
+two_factor_enable_success=Autentikasi Dua faktor untuk akun Anda berhasil diaktifkan!
+two_factor_recovery_codes_title=Kode Pemulihan Autentikasi Dua Faktor
 two_factor_recovery_codes_desc=Kode pemulihan digunakan saat Anda sementara kehilangan akses ke aplikasi autentikasi Anda. Setiap kode pemulihan hanya dapat digunakan satu kali, <b> simpan kode ini di tempat yang aman </ b>.
 two_factor_regenerate_recovery_codes=Regenerate Recovery Codes
 two_factor_regenerate_recovery_codes_error=Regenerasi kode pemulihan gagal: %v
 two_factor_regenerate_recovery_codes_success=Kode pemulihan baru telah berhasil dibuat!
-two_factor_disable_title=Nonaktifkan Otentikasi Dua Faktor
+two_factor_disable_title=Nonaktifkan Autentikasi Dua Faktor
 two_factor_disable_desc=Tingkat keamanan akun Anda akan menurun setelah autentikasi dua faktor dinonaktifkan. Apakah Anda ingin melanjutkan?
 two_factor_disable_success=Autentikasi dua faktor telah berhasil dilakukan!
 
@@ -422,7 +422,7 @@ watchers=Watchers
 stargazers=Stargazers
 forks=Forks
 repo_description_helper=Description of repository. Maximum 512 characters length.
-repo_description_length=Available characters
+repo_description_length=Karakter tersedia
 
 form.reach_limit_of_creation=Pemiliknya telah mencapai batas pembuatan maksimum %d repositori.
 form.name_reserved=Nama repositori '%s' dicadangkan.
@@ -520,7 +520,7 @@ editor.file_changed_while_editing=Konten file telah berubah sejak Anda mulai men
 editor.file_already_exists=File dengan nama '%s' sudah ada di repositori ini.
 editor.no_changes_to_show=Tidak ada perubahan untuk ditunjukkan.
 editor.fail_to_update_file=Gagal memperbarui / membuat file '%s' dengan error: %v
-editor.fail_to_delete_file=Failed to delete file '%s' with error: %v
+editor.fail_to_delete_file=Gagal menghapus file '%s' dengan error: %v
 editor.add_subdir=Tambahkan subdirektori...
 editor.unable_to_upload_files=Gagal mengunggah file ke '%s' dengan kesalahan: %v
 editor.upload_files_to_dir=Upload file ke '%s'
@@ -641,7 +641,7 @@ pulls.cannot_auto_merge_desc=Permintaan tarik ini tidak bisa digabungkan secara
 pulls.cannot_auto_merge_helper=Silahkan bergabung secara manual untuk menyelesaikan konflik.
 pulls.create_merge_commit=Membuat komit penggabungan
 pulls.rebase_before_merging=Rebase sebelum penggabungan
-pulls.commit_description=Commit Description
+pulls.commit_description=Deskripsi Commit
 pulls.merge_pull_request=Permintaan tarik gabungan
 pulls.open_unmerged_pull_exists='Anda tidak dapat melakukan operasi membuka kembali karena sudah ada permintaan tarik terbuka (#%d) dari repositori yang sama dengan penggabungan informasi yang sama dan menunggu penggabungan. '
 pulls.delete_branch=Menghapus cabang
@@ -992,7 +992,7 @@ dashboard=Dasbor
 users=Pengguna
 organizations=Organisasi
 repositories=Repositori
-authentication=Otentikasi
+authentication=Autentikasi
 config=Konfigurasi
 notices=Pemberitahuan sistem
 monitor=Pemantauan
@@ -1064,16 +1064,16 @@ users.created=Dibuat
 users.send_register_notify=Kirim Pemberitahuan Pendaftaran ke Pengguna
 users.new_success=Akun baru '%s' telah berhasil dibuat.
 users.edit=Edit
-users.auth_source=Sumber Otentikasi
+users.auth_source=Sumber Autentikasi
 users.local=Lokal
-users.auth_login_name=Nama login otentikasi
+users.auth_login_name=Nama Masuk Autentikasi
 users.password_helper=Biarkan kosong agar tetap tidak berubah.
 users.update_profile_success=Profil akun telah berhasil diupdate.
 users.edit_account=Mengedit akun
 users.max_repo_creation=Maximum Repository Creation Limit
 users.max_repo_creation_desc=(Set -1 untuk menggunakan batas default global)
 users.is_activated=Akun ini diaktifkan
-users.prohibit_login=Akun ini dilarang masuk
+users.prohibit_login=Akun ini tidak diperbolekan masuk
 users.is_admin=Akun ini memiliki izin administrator
 users.allow_git_hook=Akun ini memiliki izin untuk membuat kait Git
 users.allow_import_local=Akun ini memiliki izin untuk mengimpor repositori lokal
@@ -1097,7 +1097,7 @@ repos.stars=Bintang
 repos.issues=Masalah
 repos.size=Ukuran
 
-auths.auth_sources=Authentication Sources
+auths.auth_sources=Sumber Autentikasi
 auths.new=Tambahkan Sumber Baru
 auths.name=Nama
 auths.type=Mengetik
@@ -1105,7 +1105,7 @@ auths.enabled=Diaktifkan
 auths.default=Default
 auths.updated=Diperbarui
 auths.auth_type=Jenis Autentikasi
-auths.auth_name=Nama Otentikasi
+auths.auth_name=Nama Autentikasi
 auths.security_protocol=Protokol Keamanan
 auths.domain=Domain
 auths.host=Tuan rumah
@@ -1130,7 +1130,7 @@ auths.attributes_in_bind=Ambil atribut dalam konteks Bind DN
 auths.filter=Pengguna saringan
 auths.admin_filter=Admin Filter
 auths.ms_ad_sa=Ms Ad SA
-auths.smtp_auth=SMTP Authentication Type
+auths.smtp_auth=Autentikasi tipe SMTP
 auths.smtphost=Host SMTP
 auths.smtpport=Port SMTP
 auths.allowed_domains=Domains di izinkan
@@ -1139,18 +1139,18 @@ auths.enable_tls=Enable TLS Encryption
 auths.skip_tls_verify=Skip TLS Verify
 auths.pam_service_name=Nama layanan PAM
 auths.enable_auto_register=Mengaktifkan pendaftaran otomatis
-auths.edit=Mengubah pengaturan pembuktian
-auths.activated=This authentication is activated
-auths.default_auth=This authentication is default login source
-auths.new_success=New authentication '%s' has been added successfully.
-auths.update_success=Authentication setting has been updated successfully.
-auths.update=Memperbarui pengetahuan otentikasi
-auths.delete=Hapus otentikasi ini
-auths.delete_auth_title=Authentication Deletion
-auths.delete_auth_desc=Otentikasi ini akan dihapus, apakah anda ingin melanjutkan?
-auths.still_in_used=This authentication is still used by some users, please delete or convert these users to another login type first.
-auths.deletion_success=Authentication has been deleted successfully!
-auths.login_source_exist=Login source '%s' already exists.
+auths.edit=Ubah Pengaturan Autentikasi
+auths.activated=Autentikasi ini diaktifkan
+auths.default_auth=Autentikasi ini adalah sumber masuk bawaan
+auths.new_success=Autentikasi baru %s berhasil ditambahkan.
+auths.update_success=Pengaturan autentikasi berhasil diperbarui.
+auths.update=Perbarui Pengaturan Autentikasi
+auths.delete=Hapus Autentikasi Ini
+auths.delete_auth_title=Penghapusan Autentikasi
+auths.delete_auth_desc=Autentikasi ini akan dihapus, apakah Anda ingin melanjutkan?
+auths.still_in_used=Autentikasi ini masih digunakan oleh pengguna lain, silakan hapus atau ubah pengguna tersebut ke tipe masuk lainnya.
+auths.deletion_success=Autentikasi berhasil dihapus!
+auths.login_source_exist=Sumber masuk %s sudah tersedia.
 
 config.not_set=(tidak diterapkan)
 config.server_config=Server Configuration
@@ -1165,7 +1165,7 @@ config.run_mode=Run Mode
 config.git_version=Git Version
 config.static_file_root_path=Static File Root Path
 config.log_file_root_path=Log File Root Path
-config.reverse_auth_user=Reverse Authentication User
+config.reverse_auth_user=Mengembalikan Pengguna Autentikasi
 
 config.ssh_config=SSH Configuration
 config.ssh_enabled=Diaktifkan

+ 47 - 43
conf/locale/locale_ja-JP.ini

@@ -14,10 +14,10 @@ page=ページ
 template=テンプレート
 language=言語
 create_new=作成...
-user_profile_and_more=ユーザープロファイルなど
+user_profile_and_more=ユーザープロフィールなど
 signed_in_as=サインイン済み
 
-username=ユーザ名
+username=ユーザ
 email=メールアドレス
 password=パスワード
 re_type=再入力
@@ -52,13 +52,13 @@ requite_db_desc=GIN は、MySQL、PostgreSQL、SQLite3 または TiDB が必要
 db_title=データベース設定
 db_type=データベースの種類
 host=ホスト
-user=ユーザ
+user=ユーザ
 password=パスワード
 db_name=データベース名
 db_helper=MySQLではエンジンがINNODB、文字セットがutf8_general_ciである必要があります。
 ssl_mode=SSL モード
 path=パス
-sqlite_helper=SQLite3データベースのファイルパスです。<br>serviceとして起動する場合は、絶対パスを使用してください。
+sqlite_helper=SQLite3かTiDBのデータベースのファイルパス。<br>サービスとして開始する際には絶対パスを利用してください。
 err_empty_db_path=SQLite3 データベースのPATHを空にすることはできません。
 no_admin_and_disable_registration=管理者アカウントを作成せずに登録を無効にすることはできません。
 err_empty_admin_password=管理者パスワードは空白にできません。
@@ -68,7 +68,7 @@ app_name=アプリケーション名
 app_name_helper=素晴らしい組織名を入れてください!
 repo_path=リポジトリのルートパス
 repo_path_helper=すべての Git リモート リポジトリはこのディレクトリに保存されます。
-run_user=実行ユーザ
+run_user=実行ユーザ
 run_user_helper=ユーザーはリポジトリのルートパスへのアクセス権限、及び Gogs の実行権限を持っている必要があります。
 domain=ドメイン
 domain_helper=これはSSH用クローンURLに影響します。
@@ -109,7 +109,7 @@ require_sign_in_view=サインインしたユーザのみページ閲覧を許
 require_sign_in_view_popup=サインインしたユーザのみがページを閲覧できます。ビジターはサインインもしくはサインアップページのみ見られます。
 admin_setting_desc=今管理者アカウントを作成する必要はありません。ID = 1のユーザ は自動的に管理者の権限を獲得します。
 admin_title=管理者アカウントの設定
-admin_name=ユーザ名
+admin_name=ユーザ
 admin_password=パスワード
 confirm_password=パスワード確認
 admin_email=管理者の電子メール
@@ -118,7 +118,7 @@ test_git_failed='Git' コマンドテストに失敗: %v
 sqlite3_not_available=このリリース バージョンは SQLite3 をサポートしていません。gobuild バージョンではない、公式のバイナリ バージョンを %s からダウンロードしてください。
 invalid_db_setting=データベースの設定が正しくありません: %v
 invalid_repo_path=リポジトリのルートパスが無効です: %v
-run_user_not_match=実行ユーザーは、現在のユーザーではない: %s-> %s
+run_user_not_match=実行ユーザーは現在のユーザーではありません: %s -> %s
 smtp_host_missing_port=SMTPホストのポートが見つかりません。
 invalid_smtp_from=SMTP From フィールドの値が有効ではありません: %v
 save_config_failed=構成の保存に失敗した: %v
@@ -129,7 +129,7 @@ invalid_log_root_path=ログのルートパスがむこうです: %v
 [home]
 uname_holder=ユーザー名またはEメール
 password_holder=パスワード
-switch_dashboard_context=ダッシュ ボードのコンテキストを切替
+switch_dashboard_context=ダッシュボードコンテキストの切替
 my_repos=自分のリポジトリ
 show_more_repos=リポジトリをさらに表示…
 collaborative_repos=共同リポジトリ
@@ -141,7 +141,7 @@ issues.in_your_repos=あなたのリポジトリ
 
 [explore]
 repos=リポジトリ
-users=ユーザ
+users=ユーザ
 organizations=組織
 search=検索
 
@@ -260,7 +260,7 @@ follow=フォロー
 unfollow=フォロー解除
 
 form.name_reserved=ユーザー名 '%s' は使用されています。
-form.name_pattern_not_allowed=ユーザ名のパターン '%s' は許可されていません。
+form.name_pattern_not_allowed=ユーザ名のパターン '%s' は許可されていません。
 
 [settings]
 profile=プロフィール
@@ -275,7 +275,7 @@ delete=アカウントを削除
 
 public_profile=パブリック プロフィール
 profile_desc=あなたのメールアドレスは公開され、任意のアカウント関連の通知に使用されます。また、Webベースの操作はサイトを介して行います。
-password_username_disabled=ローカルユーザ以外はユーザ名を変更できません。
+password_username_disabled=ローカルユーザ以外はユーザ名を変更できません。
 full_name=フルネーム
 website=WEBサイト
 location=ロケーション
@@ -301,7 +301,7 @@ new_password=新しいパスワード
 retype_new_password=新しいパスワードを再入力します。
 password_incorrect=現在のパスワードが正しくありません。
 change_password_success=パスワードが正常に変更されました。今すぐ新しいパスワードを使用してサインインすることができます。
-password_change_disabled=ローカルユーザ以外はパスワードを変更できません。
+password_change_disabled=ローカルユーザ以外はパスワードを変更できません。
 
 emails=メールアドレス
 manage_emails=メールアドレスを管理
@@ -351,7 +351,7 @@ two_factor_or_enter_secret=またはシークレットを入力:
 two_factor_then_enter_passcode=パスコードを入力してください:
 two_factor_verify=確認
 two_factor_invalid_passcode=入力されたパスコードは使用できません。もう一度お試しください。
-two_factor_reused_passcode=The passcode you entered has already been used, please try another one!
+two_factor_reused_passcode=入力したパスコードは既に使用されています。別のパスコードを試してください。
 two_factor_enable_error=2段階認証の有効化に失敗しました: %v
 two_factor_enable_success=2段階認証があなたのアカウントで有効化されました!
 two_factor_recovery_codes_title=2段階認証のリカバリーコード
@@ -416,13 +416,13 @@ mirror_prune=Prune
 mirror_prune_desc=リモートに存在しないリモート追跡参照を削除する
 mirror_interval=ミラー 間隔(時)
 mirror_address=ミラー アドレス
-mirror_address_desc=アドレスに必要なユーザーの資格情報を入力してください。
+mirror_address_desc=必要なユーザー資格情報をアドレスに含めてください。
 mirror_last_synced=最終同期
 watchers=ウォッチャー
 stargazers=スターゲイザー
 forks=フォーク
-repo_description_helper=Description of repository. Maximum 512 characters length.
-repo_description_length=Available characters
+repo_description_helper=リポジトリの説明 (512文字以内)
+repo_description_length=利用可能な文字
 
 form.reach_limit_of_creation=リポジトリの最大作成数 %d にすでに達しています。
 form.name_reserved=リポジトリ名 '%s' は使用されています。
@@ -445,11 +445,11 @@ copy_link=コピー
 copy_link_success=コピーされました!
 copy_link_error=⌘ C または Ctrl-C キーを押してコピー
 copied=コピー成功
-unwatch=Unwatch
-watch=Watch
-unstar=Unstar
-star=Star
-fork=Fork
+unwatch=ウォッチ解除
+watch=ウォッチ
+unstar=スターを外す
+star=スター
+fork=フォーク
 
 no_desc=説明なし
 quick_guide=クイック ガイド
@@ -520,7 +520,7 @@ editor.file_changed_while_editing=あなたが編集を開始してから、フ
 editor.file_already_exists=ファイル名 '%s' は、このリポジトリに既に存在します。
 editor.no_changes_to_show=表示する変更箇所はありません。
 editor.fail_to_update_file=ファイル '%s' の作成/更新に失敗しました: %v
-editor.fail_to_delete_file=Failed to delete file '%s' with error: %v
+editor.fail_to_delete_file=ファイル '%s' エラーを削除できませんでした: %v
 editor.add_subdir=サブディレクトリを追加...
 editor.unable_to_upload_files='%s' へのファイルアップロード中にエラーが発生し、失敗しました: %v
 editor.upload_files_to_dir='%s' にファイルをアップロード
@@ -633,7 +633,7 @@ pulls.tab_commits=コミット
 pulls.tab_files=変更されたファイル
 pulls.reopen_to_merge=マージ操作を実行するには、このプルリクエストを再び開いてください。
 pulls.merged=マージされた
-pulls.has_merged=このプルプルリクエストは正常にマージされました!
+pulls.has_merged=このプルリクエストは正常にマージされました!
 pulls.data_broken=フォーク情報の削除によってこのプルリクエストのデータは壊れています。
 pulls.is_checking=コンフリクトが発生していないかチェック中です、しばらく待ったのちページを更新してください。
 pulls.can_auto_merge_desc=このプルリクエストは自動的にマージできます。
@@ -641,7 +641,7 @@ pulls.cannot_auto_merge_desc=コンフリクトが発生しているため、こ
 pulls.cannot_auto_merge_helper=競合を解決するためには、手動でマージする必要があります。
 pulls.create_merge_commit=マージコミットを作成する
 pulls.rebase_before_merging=マージする前に再配置します。
-pulls.commit_description=Commit Description
+pulls.commit_description=コミットの説明
 pulls.merge_pull_request=プルリクエストをマージします。
 pulls.open_unmerged_pull_exists=`同じリポジトリに同じマージ情報持つ未解決のプルリクエスト (#%d) が存在するため再び開くことができません。`
 pulls.delete_branch=ブランチの削除
@@ -746,7 +746,7 @@ settings.tracker_issue_style=外部課題トラッキングシステムの命名
 settings.tracker_issue_style.numeric=数値
 settings.tracker_issue_style.alphanumeric=英数字
 settings.tracker_url_format_desc=ユーザー名、リポジトリ名、課題番号を埋め込むために <code>{user} {repo} {index}</code> が使用できます。
-settings.pulls_desc=Enable pull requests to accept contributions between repositories and branches
+settings.pulls_desc=プルリクエストを有効にし、リポジトリとブランチ間のコントリビューションを受け入れる
 settings.pulls.ignore_whitespace=空白の変更を無視する
 settings.pulls.allow_rebase_merge=コミットをマージするためのリベースの使用を許可する
 settings.danger_zone=危険地帯
@@ -758,7 +758,7 @@ settings.convert_notices_1=- この操作によりリポジトリはミラーリ
 settings.convert_confirm=変更を確認
 settings.convert_succeed=リポジトリは正常に通常リポジトリへ変更されました。
 settings.transfer=オーナー移転
-settings.transfer_desc=リポジトリをあなたが管理者権限を持っている別のユーザーまた組織に移譲します。
+settings.transfer_desc=このリポジトリを、別のユーザーまたはあなたが管理者権限を所持している組織に委譲します。
 settings.transfer_notices_1=-新しい所有者が個人ユーザーの場合、あなたがアクセスできなくなります。
 settings.transfer_notices_2=- 新しい所有者が組織で、あなたがその組織の所有者である場合はアクセス権が残ります。
 settings.transfer_form_title=操作を確認するために、以下の情報を入力してください。
@@ -809,8 +809,8 @@ settings.add_webhook_desc=私たちは、指定されたURLに購読されたイ
 settings.payload_url=ペイロードの URL
 settings.content_type=コンテンツ タイプ
 settings.secret=秘密
-settings.secret_desc=Secret will be sent as SHA256 HMAC hex digest of payload via <code>X-GIN-Signature</code> header.
-settings.slack_username=ユーザ名
+settings.secret_desc=秘密キーは、 <code>X-Gogs-Signature</code> ヘッダーを介してペイロードの HMAC SHA256 六角ダイジェストとして送信されます。
+settings.slack_username=ユーザ
 settings.slack_icon_url=アイコン URL
 settings.slack_color=カラー
 settings.event_desc=どのイベントをこの Webhook のトリガーにしますか?
@@ -860,8 +860,8 @@ settings.add_key_success=新しいデプロイキー '%s'が正常に追加さ
 settings.deploy_key_deletion=デプロイキーを削除
 settings.deploy_key_deletion_desc=このデプロイキーを削除すると、このリポジトリに関連するすべてのアクセス権も削除されます。続行しますか。
 settings.deploy_key_deletion_success=デプロイキーが正常に削除された!
-settings.description_desc=Description of repository. Maximum 512 characters length.
-settings.description_length=Available characters
+settings.description_desc=リポジトリの説明(512文字以内)
+settings.description_length=利用可能な文字
 
 diff.browse_source=ソースを参照
 diff.parent=親
@@ -989,7 +989,7 @@ teams.add_nonexistent_repo=追加しようとしているリポジトリは存
 
 [admin]
 dashboard=ダッシュボード
-users=ユーザ
+users=ユーザ
 organizations=組織
 repositories=リポジトリ
 authentication=認証
@@ -1003,7 +1003,11 @@ total=合計: %d
 dashboard.statistic=統計
 dashboard.operations=操作
 dashboard.system_status=システム モニターのステータス
+<<<<<<< HEAD
 dashboard.statistic_info=GIN データベースは <b>%d</b> ユーザー, <b>%d</b> 組織, <b>%d</b> 公開鍵, <b>%d</b> リポジトリ, <b>%d</b> ウォッチ, <b>%d</b> スター, <b>%d</b> 行動, <b>%d</b> アクセス, <b>%d</b> 課題, <b>%d</b> コメント, <b>%d</b> ソーシャルアカウント, <b>%d</b> フォロー, <b>%d</b> ミラー, <b>%d</b> リリース, <b>%d</b> ログイン元, <b>%d</b> webhook, <b>%d</b> マイルストーン, <b>%d</b> ラベル, <b>%d</b> フックタスク, <b>%d</b> チーム, <b>%d</b> アップデートタスク, <b>%d</b> 添付ファイル の情報を持っています。
+=======
+dashboard.statistic_info=Gogs データベースは <b>%d</b> 人のユーザー、<b>%d</b> 個の組織、<b>%d</b> 個の公開鍵、<b>%d</b> 個のリポジトリ、<b>%d</b> 個のウォッチ、<b>%d</b> 個のスター、<b>%d</b> 回のアクション、<b>%d</b> 回のアクセス、<b>%d</b> 個の課題、<b>%d</b> 個のコメント、<b>%d</b> 個のソーシャルアカウント、<b>%d</b> 個のフォロー、<b>%d</b> 個のミラー、<b>%d</b> 個のリリース、<b>%d</b> 個のログイン元、<b>%d</b> 個のwebフック、<b>%d</b> 個のマイルストーン、<b>%d</b> 個のラベル、<b>%d</b> 個のフックタスク、<b>%d</b> 個のチーム、<b>%d</b> 更新タスク、<b>%d</b> 個の添付ファイルの情報を保持しています。
+>>>>>>> v0.11.91
 dashboard.operation_name=操作の名前
 dashboard.operation_switch=スイッチ
 dashboard.operation_run=実行
@@ -1092,17 +1096,17 @@ repos.repo_manage_panel=リポジトリの管理パネル
 repos.owner=オーナー
 repos.name=名前
 repos.private=非公開
-repos.watches=Watches
-repos.stars=Stars
+repos.watches=ウォッチ
+repos.stars=スター
 repos.issues=課題
 repos.size=容量
 
-auths.auth_sources=Authentication Sources
+auths.auth_sources=認証ソース
 auths.new=新しいソースを追加
 auths.name=名前
 auths.type=タイプ
 auths.enabled=有効
-auths.default=Default
+auths.default=デフォルト
 auths.updated=更新しました
 auths.auth_type=認証タイプ
 auths.auth_name=認証名
@@ -1114,7 +1118,7 @@ auths.bind_dn=バインド DN
 auths.bind_dn_helper='%s'はユーザー名のプレースホルダとして使用できます。 例)DOM \%s
 auths.bind_password=バインド パスワード
 auths.bind_password_helper=警告: このパスワードは暗号化されずに格納されます。特権を持つアカウントに使用しないでください。
-auths.user_base=ユーザ検索ベース
+auths.user_base=ユーザ検索ベース
 auths.user_dn=User DN
 auths.attribute_username=ユーザー名属性
 auths.attribute_username_placeholder=ログインフォームの値を使う場合は空にしてください。
@@ -1127,7 +1131,7 @@ auths.group_filter=グループ フィルター
 auths.group_attribute_contain_user_list=ユーザーのリストを含むグループ属性
 auths.user_attribute_listed_in_group=グループのユーザー属性
 auths.attributes_in_bind=属性をバインドDNのコンテクストから取得する
-auths.filter=User フィルター
+auths.filter=ユーザーフィルター
 auths.admin_filter=Admin フィルター
 auths.ms_ad_sa=Ms Ad SA
 auths.smtp_auth=SMTP 認証の種類
@@ -1141,7 +1145,7 @@ auths.pam_service_name=PAMサービス名
 auths.enable_auto_register=自動登録を有効にする
 auths.edit=認証設定を編集
 auths.activated=認証の有効化
-auths.default_auth=This authentication is default login source
+auths.default_auth=この認証を、既定のログイン ソースとする
 auths.new_success=新しい認証 '%s' が正常に追加されました。
 auths.update_success=認証の設定が正常に更新されました。
 auths.update=認証設定を更新
@@ -1160,12 +1164,12 @@ config.app_url=アプリケーションの URL
 config.domain=ドメイン
 config.offline_mode=オフラインモード
 config.disable_router_log=ルーターのログを無効にする
-config.run_user=実行ユーザ
+config.run_user=実行ユーザ
 config.run_mode=実行モード
 config.git_version=Git バージョン
 config.static_file_root_path=静的ファイルのルートパス
 config.log_file_root_path=ログファイルのルートパス
-config.reverse_auth_user=リバース認証ユーザ
+config.reverse_auth_user=リバース認証ユーザ
 
 config.ssh_config=SSH設定
 config.ssh_enabled=有効
@@ -1197,7 +1201,7 @@ config.db_config=データベース設定
 config.db_type=タイプ
 config.db_host=ホスト
 config.db_name=名前
-config.db_user=ユーザ
+config.db_user=ユーザ
 config.db_ssl_mode=SSL モード
 config.db_ssl_mode_helper=(「postgres」のみ)
 config.db_path=パス
@@ -1224,7 +1228,7 @@ config.mailer_enabled=有効にした
 config.mailer_disable_helo=HELOコマンド無効
 config.mailer_subject_prefix=件名プレフィックス
 config.mailer_host=ホスト
-config.mailer_user=ユーザ
+config.mailer_user=ユーザ
 config.send_test_mail=テストメールの送信
 config.test_mail_failed='%s' 宛のテストメールの送信に失敗しました: %v
 config.test_mail_sent=テストメールが '%s' に送信されました。
@@ -1313,7 +1317,7 @@ push_tag=が <a href="%[1]s">%[3]s</a> にタグ <a href="%[1]s/src/%[2]s">%[2]s
 delete_tag=<a href="%[1]s">%[3]s</a> のタグ <code>%[2]s</code> を削除しました
 fork_repo=リポジトリを <a href="%s">%s</a> にフォークしました
 mirror_sync_push=synced commits to <a href="%[1]s/src/%[2]s">%[3]s</a> at <a href="%[1]s">%[4]s</a> from mirror
-mirror_sync_create=synced new reference <a href="%s/src/%s">%[2]s</a> to <a href="%[1]s">%[3]s</a> from mirror
+mirror_sync_create=新しい参照<a href="%s/src/%s">%[2]s</a>を<a href="%[1]s">%[3]s</a>にミラーから反映しました
 mirror_sync_delete=synced and deleted reference <code>%[2]s</code> at <a href="%[1]s">%[3]s</a> from mirror
 
 [tool]

+ 2 - 2
conf/locale/locale_ko-KR.ini

@@ -151,7 +151,7 @@ register_hepler_msg=이미 계정을 가지고 계신가요? 로그인하세요!
 social_register_hepler_msg=계정을 가지고 계신가요? 연결하세요!
 disable_register_prompt=죄송합니다, 가입이 비활성화 되어있습니다. 사이트 관리자에게 문의 해주세요.
 disable_register_mail=죄송합니다. 메일 등록이 비활성화 되었습니다.
-auth_source=Authentication Source
+auth_source=인증 소스 편집
 local=로컬
 remember_me=자동 로그인
 forgot_password=비밀번호 찾기
@@ -231,7 +231,7 @@ org_name_been_taken=이미 사용중인 조직 이름입니다.
 team_name_been_taken=이미 사용중인 팀 이름입니다.
 email_been_used=이미 사용중인 이메일 주소입니다.
 username_password_incorrect=사용자 이름이나 비밀번호가 올바르지 않습니다.
-auth_source_mismatch=The authentication source selected is not associated with the user.
+auth_source_mismatch=선택 인증 소스는 사용자와 연결 됩니다.
 enterred_invalid_repo_name=입력한 저장소 이름이 올바른지 확인하십시오.
 enterred_invalid_owner_name=입력한 사용자 이름이 올바른지 확인하십시오.
 enterred_invalid_password=입력한 비밀번호가 올바른지 확인하십시오.

+ 1 - 1
conf/locale/locale_ru-RU.ini

@@ -223,7 +223,7 @@ url_error=` не является допустимым URL-адресом.`
 include_error=` должен содержать '%s'.`
 unknown_error=Неизвестная ошибка:
 captcha_incorrect=Капча не пройдена.
-password_not_match=Пароли не совпадают.
+password_not_match=Пароль и подтверждение отличаются.
 
 username_been_taken=Имя пользователя занято.
 repo_name_been_taken=Имя репозитория занято.

+ 6 - 6
conf/locale/locale_sr-SP.ini

@@ -421,8 +421,8 @@ mirror_last_synced=Задње синхронизовано
 watchers=Посматрачи
 stargazers=Пратиоци
 forks=Огранци
-repo_description_helper=Description of repository. Maximum 512 characters length.
-repo_description_length=Available characters
+repo_description_helper=Опис спремишта. Максимум 512 карактера.
+repo_description_length=Доступни карактери
 
 form.reach_limit_of_creation=Власник има максимум број %d спремишта.
 form.name_reserved=Име спремишта '%s' је резервирано.
@@ -520,7 +520,7 @@ editor.file_changed_while_editing=Садржај датотеке је пром
 editor.file_already_exists=Датотека са именом '%s' већ постоји у овом спремишту.
 editor.no_changes_to_show=Нема никаквих промена.
 editor.fail_to_update_file=Промена над '%s' није успело са грешком: %v
-editor.fail_to_delete_file=Failed to delete file '%s' with error: %v
+editor.fail_to_delete_file=Фајл '%s' није успешно обрисан, разлог грешке: %v
 editor.add_subdir=Додај поддиректоријуми...
 editor.unable_to_upload_files=Учитање датотеке '%s' није успело са грешкном: %v
 editor.upload_files_to_dir=Пошаљи датотеке на '%s'
@@ -641,7 +641,7 @@ pulls.cannot_auto_merge_desc=Овај захтев за спајање не мо
 pulls.cannot_auto_merge_helper=Молимо вас, обавите спајање ручно да би сте разрешили сукобе.
 pulls.create_merge_commit=Направите спајање
 pulls.rebase_before_merging=Поврат пре обједињавања
-pulls.commit_description=Commit Description
+pulls.commit_description=Опис ревизије
 pulls.merge_pull_request=Обави спајање
 pulls.open_unmerged_pull_exists=`Неможете поново отворити јер већ постоји захтев за спајање (#%d) из истог спремишта са истим информацијама о спајању и чека спајање.`
 pulls.delete_branch=Избришите грану
@@ -860,8 +860,8 @@ settings.add_key_success=Нови кључ распоређивање '%s' је
 settings.deploy_key_deletion=Уклони кључ распоређивањa
 settings.deploy_key_deletion_desc=Брисање овог кључа за распоређивање ће довести до укидање приступ на овом спремишту. Да ли желите да наставите?
 settings.deploy_key_deletion_success=Кључ за распоређивање је успешно обрисан!
-settings.description_desc=Description of repository. Maximum 512 characters length.
-settings.description_length=Available characters
+settings.description_desc=Опис спремишта. Максимум 512 карактера.
+settings.description_length=Доступни карактери
 
 diff.browse_source=Преглед изворни кода
 diff.parent=родитељ

+ 64 - 64
conf/locale/locale_tr-TR.ini

@@ -151,8 +151,8 @@ register_hepler_msg=Bir hesabınız var mı? Şimdi giriş yapın!
 social_register_hepler_msg=Zaten bir hesabınız var mı? Şimdi bağlanın!
 disable_register_prompt=Üzgünüz, kaydolma devre dışı bırakıldı. Lütfen site yöneticisiyle irtibata geçin.
 disable_register_mail=Üzgünüz, kayıt doğrulama e-postası devre dışı bırakıldı.
-auth_source=Authentication Source
-local=Local
+auth_source=Yetkilendirme Kaynağı
+local=Yerel
 remember_me=Beni Hatırla
 forgot_password=Parolamı Unuttum
 forget_password=Parolanızı mı unuttunuz?
@@ -231,7 +231,7 @@ org_name_been_taken=Bu organizasyon adı zaten alınmış.
 team_name_been_taken=Bu takım adı zaten alınmış.
 email_been_used=Bu e-posta adresi zaten kullanımda.
 username_password_incorrect=Kullanıcı adı veya parola hatalı.
-auth_source_mismatch=The authentication source selected is not associated with the user.
+auth_source_mismatch=Seçilen kimlik doğrulama kaynağı kullanıcı ile ilişkili değil.
 enterred_invalid_repo_name=Lütfen girdiğiniz depo isminin doğru olduğundan emin olun.
 enterred_invalid_owner_name=Lütfen girdiğiniz depo sahibi isminin doğru olduğundan emin olun.
 enterred_invalid_password=Lütfen girdiğiniz parolanın doğru olduğundan emin olun.
@@ -351,7 +351,7 @@ two_factor_or_enter_secret=Veya parola girin:
 two_factor_then_enter_passcode=Daha sonra şifre kodunu girin:
 two_factor_verify=Doğrula
 two_factor_invalid_passcode=Girdiğiniz şifre kodu geçersiz,lütfen tekrar deneyin!
-two_factor_reused_passcode=The passcode you entered has already been used, please try another one!
+two_factor_reused_passcode=Girdiğiniz şifre zaten kullanılmış, lütfen başka bir tane deneyin!
 two_factor_enable_error=İki faktörlü kimlik doğrulama etkinleştirmesi başarısız :%v
 two_factor_enable_success=Hesabınız için iki faktörlü kimlik doğrulama başarıyla devre dışı bırakıldı!
 two_factor_recovery_codes_title=İki faktörlü Kimlik doğrulama Kurtarma Kodları
@@ -421,8 +421,8 @@ mirror_last_synced=Son Eşzamanlama
 watchers=İzleyenler
 stargazers=Yıldızlayanlar
 forks=Çatallamalar
-repo_description_helper=Description of repository. Maximum 512 characters length.
-repo_description_length=Available characters
+repo_description_helper=Depo açıklaması. Maksimum 512 karakter uzunluğu.
+repo_description_length=Mevcut karakterler
 
 form.reach_limit_of_creation=Sahip, maksimum %d depo oluşturma limitine ulaşmıştır.
 form.name_reserved=Depo ismi '%s' başkasına ayrılmış.
@@ -520,7 +520,7 @@ editor.file_changed_while_editing=Düzenlemeye başladıktan sonra dosya içeri
 editor.file_already_exists='% s ' adlı bir dosya mevcutta zaten var.
 editor.no_changes_to_show=Gösterilecek bir değişiklik mevcut değil.
 editor.fail_to_update_file='%s' dosyası güncellenemedi / oluşturulamadı : %v hatasıyla
-editor.fail_to_delete_file=Failed to delete file '%s' with error: %v
+editor.fail_to_delete_file='%s' dosyası hatalı bir şekilde silinemedi: %v
 editor.add_subdir=Alt dizin Ekle...
 editor.unable_to_upload_files='%s' dosyası yüklenemedi : %v hatasıyla
 editor.upload_files_to_dir=Dosyaları '%s' ye yükle
@@ -641,7 +641,7 @@ pulls.cannot_auto_merge_desc=Çakışmalardan dolayı bu değişiklik isteği ot
 pulls.cannot_auto_merge_helper=Çakışmaları çözmek için lütfen elle birleştirin.
 pulls.create_merge_commit=Birleştirme işlemi oluşturma
 pulls.rebase_before_merging=Birleştirmeden önce yenidenreferans al
-pulls.commit_description=Commit Description
+pulls.commit_description=Taahhüt Açıklaması
 pulls.merge_pull_request=Değişiklik İsteğini Birleştir
 pulls.open_unmerged_pull_exists=`Yeniden açma işlemini gerçekleştiremezsiniz. Çünkü zaten aynı depodan, aynı birleştirme bilgisiyle açık olan bir değişiklik isteği var (#%d) ve birleştirme bekliyor.`
 pulls.delete_branch=Şubeyi Sil
@@ -740,17 +740,17 @@ settings.use_internal_issue_tracker=Yerleşik hafif sorun izleyici kullanma
 settings.allow_public_issues_desc=Depo özel olduğunda toplulukların herkese açık olarak erişmesine izin ver
 settings.use_external_issue_tracker=Harici sorun takipçisi kullan
 settings.external_tracker_url=Harici Konu İzleyici URL'si
-settings.external_tracker_url_desc=Visitors will be redirected to URL when they click on the tab.
+settings.external_tracker_url_desc=Ziyaretçiler, sekmeye tıkladıklarında bağlantıya yönlendirilecektir.
 settings.tracker_url_format=Harici Sorun Takipçisi Bağlantı Formatı
 settings.tracker_issue_style=Harici Hata İzleyicisi Adlandırma Stili:
 settings.tracker_issue_style.numeric=Sayısal
 settings.tracker_issue_style.alphanumeric=Alfanumerik
 settings.tracker_url_format_desc=Kullanıcı adı, depo ismi ve hata indeksi için <code>{kullanıcı} {depo} {indeks}</code> tutucusunu kullanabilirsiniz.
-settings.pulls_desc=Enable pull requests to accept contributions between repositories and branches
-settings.pulls.ignore_whitespace=Ignore changes in whitespace
-settings.pulls.allow_rebase_merge=Allow use rebase to merge commits
+settings.pulls_desc=Depolar ve Şubeler arasındaki katkıları kabul etmek için çekme isteklerini etkinleştir
+settings.pulls.ignore_whitespace=Boşluktaki değişiklikleri yoksay
+settings.pulls.allow_rebase_merge=Taahhütleri birleştirmek için yeniden tabanın kullanmasına izin ver
 settings.danger_zone=Tehlike Alanı
-settings.cannot_fork_to_same_owner=You cannot fork a repository to its original owner.
+settings.cannot_fork_to_same_owner=Bir depoyu orijinal sahibine ayıramazsınız.
 settings.new_owner_has_same_repo=Yeni sahibin aynı isimde başka bir deposu var. Lütfen farklı bir isim seçin.
 settings.convert=Düzenli Depoya Dönüştür
 settings.convert_desc=Bu yansıyı düzenli bir depoya dönüştürebilirsiniz. Bu işlem geri alınamaz.
@@ -769,7 +769,7 @@ settings.wiki_deletion_success=Deponun Wiki verisi başarıyla silindi.
 settings.delete=Bu Depoyu Sil
 settings.delete_desc=Bir depoyu bir kez sildiğiniz taktirde geri getiremezsiniz. Lütfen emin olun.
 settings.delete_notices_1=- Bu işlem geri <strong>ALINAMAZ</strong>.
-settings.delete_notices_2=- This operation will permanently delete everything in this repository, including Git data, issues, comments and collaborator access.
+settings.delete_notices_2=- Bu işlem, git verileri, sorunlar, yorumlar ve işbirlikçi erişimi de dahil olmak üzere bu depodaki her şeyi kalıcı olarak siler.
 settings.delete_notices_fork_1=Silme işleminden sonra bütün çatallar bağımsız hale gelir.
 settings.deletion_success=Depo başarıyla silindi!
 settings.update_settings_success=Depo seçenekleri başarıyla güncellendi.
@@ -793,8 +793,8 @@ settings.webhook_deletion_success=Web isteği başarıyla silindi!
 settings.webhook.test_delivery=Test Dağıtımı
 settings.webhook.test_delivery_desc=Web isteği ayarlarınızı test etmek için sahte bir anlık olay gönderin
 settings.webhook.test_delivery_success=Test web isteği, dağıtım kuyruğuna eklendi. Bunun dağıtım geçmişinde görünmesi birkaç saniye sürebilir.
-settings.webhook.redelivery=Redelivery
-settings.webhook.redelivery_success=Hook task '%s' has been readded to delivery queue. It may take few seconds to update delivery status in history.
+settings.webhook.redelivery=Yeniden teslimat
+settings.webhook.redelivery_success=Kanca görev '%s' teslim kuyruğuna eklenmiştir. Bu tarihte teslim durumunu güncellemek birkaç saniye sürebilir.
 settings.webhook.request=İstekler
 settings.webhook.response=Cevaplar
 settings.webhook.headers=Başlıklar
@@ -809,7 +809,7 @@ settings.add_webhook_desc=GIN, meydana gelen olay ile birlikte belirttiğiniz ba
 settings.payload_url=Yük Bağlantısı
 settings.content_type=İçerik Türü
 settings.secret=Gizli
-settings.secret_desc=Secret will be sent as SHA256 HMAC hex digest of payload via <code>X-GIN-Signature</code> header.
+settings.secret_desc=Gizli, <code>X-Gogs-Signature</code> başlığı ile SHA256 hmac hexdigest yükü olarak gönderilecektir.
 settings.slack_username=Kullanıcı Adı
 settings.slack_icon_url=Simge Bağlantısı
 settings.slack_color=Renk
@@ -819,20 +819,20 @@ settings.event_send_everything=<strong>Her şeye</strong> ihtiyacım var.
 settings.event_choose=Neye ihtiyacım olduğunu seçtir.
 settings.event_create=Oluştur
 settings.event_create_desc=Dal veya biçim imi oluşturuldu
-settings.event_delete=Delete
-settings.event_delete_desc=Branch or tag deleted
-settings.event_fork=Fork
-settings.event_fork_desc=Repository forked
+settings.event_delete=Sil
+settings.event_delete_desc=Dal veya etiket silindi
+settings.event_fork=Çatalla
+settings.event_fork_desc=Depo çatallandı
 settings.event_push=Push
 settings.event_push_desc=Bir depoya git push
-settings.event_issues=Issues
-settings.event_issues_desc=Issue opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared, milestoned, or demilestoned.
-settings.event_pull_request=Pull Request
-settings.event_pull_request_desc=Pull request opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared, milestoned, demilestoned, or synchronized.
-settings.event_issue_comment=Issue Comment
-settings.event_issue_comment_desc=Issue comment created, edited, or deleted.
-settings.event_release=Release
-settings.event_release_desc=Release published in a repository.
+settings.event_issues=Sorunlar
+settings.event_issues_desc=Sorun açıldı, kapatıldı, yeniden açıldı, düzenlendi, atandı, atanmadı, etiket güncellendi, etiket silindi, dönüm noktası yapıldı ya da iptal edildi.
+settings.event_pull_request=İstek Çek
+settings.event_pull_request_desc=Çekme isteği açıldı, kapatıldı, yeniden açıldı, düzenlendi, atandı, atanmamış, etiket güncellendi, etiket silindi, kilometre taşları, dönüm noktası veya senkronize edildi.
+settings.event_issue_comment=Sorun Yorumu
+settings.event_issue_comment_desc=Konu yorumu eklendi, düzenlendi veya silindi.
+settings.event_release=Yayın
+settings.event_release_desc=Bir depoda yayınlanan sürüm.
 settings.active=Aktif
 settings.active_helper=Bu isteği tetikleyen olaya ilişkin detaylar da gönderilecektir.
 settings.add_hook_success=Yeni web isteği eklendi.
@@ -842,13 +842,13 @@ settings.delete_webhook=Web İsteğini Sil
 settings.recent_deliveries=Son Dağıtımlar
 settings.hook_type=İstek Türü
 settings.add_slack_hook_desc=Deponuza <a href="%s">Slack</a> entegrasyonunu ekleyin.
-settings.add_discord_hook_desc=Add <a href="%s">Discord</a> integration to your repository.
-settings.add_dingtalk_hook_desc=Add <a href="%s">Dingtalk</a> integration to your repository.
+settings.add_discord_hook_desc=Deponuza <a href="%s">Discord</a> entegrasyonu ekleyin.
+settings.add_dingtalk_hook_desc=Deponuza <a href="%s">Dingtalk</a> entegrasyonu ekleyin.
 settings.slack_token=Erişim Anahtarı
 settings.slack_domain=Alan Adı
 settings.slack_channel=Kanal
 settings.deploy_keys=Dağıtım Anahtarları
-settings.deploy_keys_helper=<b>Common Gotcha!</b> If you're looking for adding personal public keys, please add them in your <a href="%s%s">account settings</a>.
+settings.deploy_keys_helper=<b>Ortak Gotcha!</b> Kişisel ortak anahtarlar eklemek istiyorsanız lütfen bunları <a href="%s%s">hesap ayarlarınıza</a> ekleyin.
 settings.add_deploy_key=Dağıtım Anahtarı Ekle
 settings.deploy_key_desc=Dağıtım anahtarlarının yalnızca okuma izni vardır. Kişisel hesapların SSH anahtarlarıyla aynı değillerdir.
 settings.no_deploy_keys=Herhangi bir dağıtım anahtarı eklemediniz.
@@ -860,8 +860,8 @@ settings.add_key_success=Yeni dağıtım anahtarı '%s' başarıyla eklendi!
 settings.deploy_key_deletion=Dağıtım Anahtarını Sil
 settings.deploy_key_deletion_desc=Bu dağıtım anahtarını silerseniz bu depoya ilişkin tüm erişimler de kaldırılacaktır. Devam etmek istiyor musunuz?
 settings.deploy_key_deletion_success=Dağıtım anahtarı başarıyla silindi!
-settings.description_desc=Description of repository. Maximum 512 characters length.
-settings.description_length=Available characters
+settings.description_desc=Depo açıklaması. Maksimum 512 karakter uzunluğu.
+settings.description_length=Mevcut karakterler
 
 diff.browse_source=Kaynağa Gözat
 diff.parent=ebeveyn
@@ -928,7 +928,7 @@ team_permission_desc=Bu takım, ne gibi bir izin seviyesine sahiptir?
 
 form.name_reserved=Organizasyon adı '%s' başka birisine ayrılmış.
 form.name_pattern_not_allowed=Organizasyon adı modeli '%s' geçersiz.
-form.team_name_reserved=Team name '%s' is reserved.
+form.team_name_reserved='%s' takım ismi başka birine ayrılmış.
 
 settings=Ayarlar
 settings.options=Seçenekler
@@ -1019,8 +1019,8 @@ dashboard.git_gc_repos=Depolarda çöp toplama işlemini gerçekleştir
 dashboard.git_gc_repos_success=Tüm depolarda çöp toplama işlemi başarıyla gerçekleştirildi.
 dashboard.resync_all_sshkeys='.ssh/authorized_keys' dosyasını yeniden yaz (dikkat: GIN'un olmayan anahtarlar silinecektir)
 dashboard.resync_all_sshkeys_success=Tüm genel anahtarlar başarıyla yeniden yazıldı.
-dashboard.resync_all_hooks=Resync pre-receive, update and post-receive hooks of all repositories
-dashboard.resync_all_hooks_success=All repositories' pre-receive, update and post-receive hooks have been resynced successfully.
+dashboard.resync_all_hooks=Tüm depoların yeniden alımı, güncellemesi ve gönderi alım kancalarını yeniden senkronize et
+dashboard.resync_all_hooks_success=Tüm depoların önceden alımı, güncellemesi ve gönderi alımı kancaları başarıyla senkronize edildi.
 dashboard.reinit_missing_repos=Git dosyalarını kaybetmiş tüm depoları yeniden oluştur
 dashboard.reinit_missing_repos_success=Git dosyalarını kaybetmiş tüm depolar başarıyla yeniden oluşturuldu.
 
@@ -1095,14 +1095,14 @@ repos.private=Özel
 repos.watches=İzlemeler
 repos.stars=Yıldızlar
 repos.issues=Sorunlar
-repos.size=Size
+repos.size=Boyut
 
-auths.auth_sources=Authentication Sources
+auths.auth_sources=Yetkilendirme Kaynakları
 auths.new=Yeni Kaynak Ekle
 auths.name=İsim
 auths.type=Tür
 auths.enabled=Aktifleştirilmiş
-auths.default=Default
+auths.default=Varsayılan
 auths.updated=Güncellendi
 auths.auth_type=Yetki Türü
 auths.auth_name=Yetki İsmi
@@ -1111,21 +1111,21 @@ auths.domain=Alan Adı
 auths.host=Sunucu
 auths.port=Port
 auths.bind_dn=Bağlama DN'i
-auths.bind_dn_helper=You can use '%s' as placeholder for username, e.g. DOM\%s
+auths.bind_dn_helper=Kullanıcı adı için yer tutucu olarak '%s' kullanabilirsiniz, Örneğin DOM\%s
 auths.bind_password=Bağlama Parolası
 auths.bind_password_helper=Uyarı: Bu parola, ham halde bir metin dosyası içerisinde saklanacaktır. Yüksek izinli bir hesap kullanmayın.
 auths.user_base=Kullanıcı Arama Tabanı
 auths.user_dn=Kullanıcı DN'i
 auths.attribute_username=Kullanıcı özelliği
 auths.attribute_username_placeholder=Kullanıcı adı için giriş yapma form alanı kullanmak için boş bırakın.
-auths.attribute_name=First Name Attribute
+auths.attribute_name=İlk Ad Özelliği
 auths.attribute_surname=Soyad özelliği
 auths.attribute_mail=E-posta özelliği
-auths.verify_group_membership=Verify group membership
-auths.group_search_base_dn=Group Search Base DN
-auths.group_filter=Group Filter
-auths.group_attribute_contain_user_list=Group Attribute Containing List of Users
-auths.user_attribute_listed_in_group=User Attribute Listed in Group
+auths.verify_group_membership=Grup üyeliğini doğrula
+auths.group_search_base_dn=Grup Arama Tabanı DN
+auths.group_filter=Grup Filtresi
+auths.group_attribute_contain_user_list=Kullanıcı Listesi İçeren Grup Özelliği
+auths.user_attribute_listed_in_group=Grupta Listelenen Kullanıcı Özelliği
 auths.attributes_in_bind=Bağlı DN tabanındaki özellikleri çek
 auths.filter=Kullanıcı Filtresi
 auths.admin_filter=Yönetici Filtresi
@@ -1141,7 +1141,7 @@ auths.pam_service_name=PAM Servis Adı
 auths.enable_auto_register=Otomatik Kaydolmayı Aktifleştir
 auths.edit=Yetkilendirme Ayarlarını Düzenle
 auths.activated=Bu yetkilendirme aktif
-auths.default_auth=This authentication is default login source
+auths.default_auth=Bu kimlik doğrulama varsayılan giriş kaynağıdır
 auths.new_success=Yeni yetkilendirme '%s' başarıyla eklendi.
 auths.update_success=Yetkilendirme ayarları başarıyla güncellendi.
 auths.update=Yetkilendirme Ayarlarını Güncelle
@@ -1150,9 +1150,9 @@ auths.delete_auth_title=Yetkilendirme Silme
 auths.delete_auth_desc=Bu yetkilendirme silinecek. Devam etmek istiyor musunuz?
 auths.still_in_used=Bu yetkilendirme hala bazı kullanıcılar tarafından kullanılıyor. Lütfen öncelikle bunları silin ya da başka oturum açma türlerine çevirin.
 auths.deletion_success=Yetkilendirme başarıyla silindi!
-auths.login_source_exist=Login source '%s' already exists.
+auths.login_source_exist='%s' giriş kaynağı zaten mevcut.
 
-config.not_set=(not set)
+config.not_set=(ayarlı değil)
 config.server_config=Sunucu Yapılandırması
 config.app_name=Uygulama Adı
 config.app_ver=Uygulama Sürümü
@@ -1162,7 +1162,7 @@ config.offline_mode=Çevrim Dışı Modu
 config.disable_router_log=Yönlendirici Log'larını Devre Dışı Bırak
 config.run_user=Çalıştırma Kullanıcısı
 config.run_mode=Çalıştırma Modu
-config.git_version=Git Version
+config.git_version=Git Sürümü
 config.static_file_root_path=Sabit Dosya Kök Yolu
 config.log_file_root_path=Log Dosyası Kök Yolu
 config.reverse_auth_user=Tersine Yetkilendirme Kullanıcısı
@@ -1174,24 +1174,24 @@ config.ssh_domain=Alan Adı
 config.ssh_port=Port
 config.ssh_listen_port=Port'u Dinle
 config.ssh_root_path=Kök Yol
-config.ssh_rewrite_authorized_keys_at_start=Rewrite authorized_keys At Start
+config.ssh_rewrite_authorized_keys_at_start=Başlangıçta yetkili anahtarları yeniden yaz
 config.ssh_key_test_path=Anahtar Test Yolu
 config.ssh_keygen_path=Keygen ('ssh-keygen') Yolu
 config.ssh_minimum_key_size_check=Minimum Anahtar Uzunluğu Kontrolü
 config.ssh_minimum_key_sizes=Minimum Anahtar Uzunlukları
 
-config.repo_config=Repository Configuration
+config.repo_config=Depo Yapılandırması
 config.repo_root_path=Depo Kök Yolu
 config.script_type=Betik Türü
-config.repo_force_private=Force Private
-config.max_creation_limit=Max Creation Limit
-config.preferred_licenses=Preferred Licenses
-config.disable_http_git=Disable HTTP Git
-config.enable_local_path_migration=Enable Local Path Migration
-config.commits_fetch_concurrency=Commits Fetch Concurrency
+config.repo_force_private=Özel Kuvvet
+config.max_creation_limit=Maksimum Oluşturma Sınırı
+config.preferred_licenses=Tercih Edilen Lisanslar
+config.disable_http_git=HTTP Git'i devre dışı bırak
+config.enable_local_path_migration=Yerel Yol Geçişi Etkinleştir
+config.commits_fetch_concurrency=Eşzamanlılık Alma Taahhüdü
 
-config.http_config=HTTP Configuration
-config.http_access_control_allow_origin=Access Control Allow Origin
+config.http_config=HTTP Yapılandırması
+config.http_access_control_allow_origin=Erişim Kontrolü Kaynağına İzin Ver
 
 config.db_config=Veritabanı Yapılandırması
 config.db_type=Türü
@@ -1222,7 +1222,7 @@ config.skip_tls_verify=TLS Doğrulamasını Atla
 config.mailer_config=Mailer Yapılandırması
 config.mailer_enabled=Aktif
 config.mailer_disable_helo=HELO'yu Devre Dışı Bırak
-config.mailer_subject_prefix=Subject Prefix
+config.mailer_subject_prefix=Konu Öneki
 config.mailer_host=Sunucu
 config.mailer_user=Kullanıcı
 config.send_test_mail=Test E-Postası Gönder
@@ -1250,9 +1250,9 @@ config.cookie_life_time=Çerez Yaşam Zamanı
 config.picture_config=Resim Yapılandırması
 config.picture_service=Resim Servisi
 config.disable_gravatar=Gravatar Hizmet Dışı
-config.enable_federated_avatar=Enable Federated Avatars
+config.enable_federated_avatar=Birleştirilmiş Avatarları Etkinleştir
 
-config.git_config=Git Configuration
+config.git_config=Git Yapılandırması
 config.git_disable_diff_highlight=Diff İşlemi Sözdizimini Devre Dışı Bırak
 config.git_max_diff_lines=Maksimum Ayırma Hatları (tek bir dosya için)
 config.git_max_diff_line_characters=Maksimum Ayırma Karakterleri(tek bir hat için)

+ 2 - 2
docker/s6/gogs/setup

@@ -19,8 +19,8 @@ ln -sfn /data/gogs/data ./data
 # Backward Compatibility with Gogs Container v0.6.15
 ln -sfn /data/git /home/git
 
-# Only chown for the first time, '/data/gogs/conf/app.ini' must exist inside Docker after installation
-if ! test -f /data/gogs/conf/app.ini; then
+# Only chown for the first time, owner of '/data' is 'git' inside Docker after installation
+if [ $(stat -c '%U' /data) != 'git' ]; then
 	chown -R git:git /data /app/gogs ~git/
 fi
 chmod 0755 /data /data/gogs ~git/

+ 3 - 3
gogs.go

@@ -16,17 +16,17 @@ import (
 	"github.com/G-Node/gogs/pkg/setting"
 )
 
-const APP_VER = "0.11.87.0206.gin0001"
+const Version = "0.11.91.0811"
 
 func init() {
-	setting.AppVer = APP_VER
+	setting.AppVer = Version
 }
 
 func main() {
 	app := cli.NewApp()
 	app.Name = "GIN"
 	app.Usage = "Modern Research Data Management for Neuroscience"
-	app.Version = APP_VER
+	app.Version = Version
 	app.Commands = []cli.Command{
 		cmd.Web,
 		cmd.Serv,

+ 5 - 6
models/access.go

@@ -59,7 +59,7 @@ type Access struct {
 	Mode   AccessMode
 }
 
-func accessLevel(e Engine, userID int64, repo *Repository) (AccessMode, error) {
+func userAccessMode(e Engine, userID int64, repo *Repository) (AccessMode, error) {
 	mode := ACCESS_MODE_NONE
 	// Everyone has read access to public repository
 	if !repo.IsPrivate {
@@ -84,14 +84,13 @@ func accessLevel(e Engine, userID int64, repo *Repository) (AccessMode, error) {
 	return access.Mode, nil
 }
 
-// AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the
-// user does not have access.
-func AccessLevel(userID int64, repo *Repository) (AccessMode, error) {
-	return accessLevel(x, userID, repo)
+// UserAccessMode returns the access mode of given user to the repository.
+func UserAccessMode(userID int64, repo *Repository) (AccessMode, error) {
+	return userAccessMode(x, userID, repo)
 }
 
 func hasAccess(e Engine, userID int64, repo *Repository, testMode AccessMode) (bool, error) {
-	mode, err := accessLevel(e, userID, repo)
+	mode, err := userAccessMode(e, userID, repo)
 	return mode >= testMode, err
 }
 

+ 1 - 0
models/action.go

@@ -604,6 +604,7 @@ func CommitRepoAction(opts CommitRepoActionOptions) error {
 		if err = PrepareWebhooks(repo, HOOK_EVENT_CREATE, &api.CreatePayload{
 			Ref:           refName,
 			RefType:       "tag",
+			Sha:           opts.NewCommitID,
 			DefaultBranch: repo.DefaultBranch,
 			Repo:          apiRepo,
 			Sender:        apiPusher,

+ 1 - 1
models/repo.go

@@ -1927,7 +1927,7 @@ func GitFsck() {
 			repo := bean.(*Repository)
 			repoPath := repo.RepoPath()
 			if err := git.Fsck(repoPath, setting.Cron.RepoHealthCheck.Timeout, setting.Cron.RepoHealthCheck.Args...); err != nil {
-				desc := fmt.Sprintf("Fail to health check repository '%s': %v", repoPath, err)
+				desc := fmt.Sprintf("Failed to perform health check on repository '%s': %v", repoPath, err)
 				log.Warn(desc)
 				if err = CreateRepositoryNotice(desc); err != nil {
 					log.Error(3, "CreateRepositoryNotice: %v", err)

File diff suppressed because it is too large
+ 4 - 4
pkg/bindata/bindata.go


+ 3 - 3
pkg/context/api.go

@@ -25,8 +25,8 @@ type APIContext struct {
 	Org *APIOrganization
 }
 
-// FIXME: move to github.com/gogs/go-gogs-client
-const DOC_URL = "https://github.com/gogs/docs-api"
+// FIXME: move this constant to github.com/gogs/go-gogs-client
+const DocURL = "https://github.com/gogs/docs-api"
 
 // Error responses error message to client with given message.
 // If status is 500, also it prints error to log.
@@ -44,7 +44,7 @@ func (c *APIContext) Error(status int, title string, obj interface{}) {
 
 	c.JSON(status, map[string]string{
 		"message": message,
-		"url":     DOC_URL,
+		"url":     DocURL,
 	})
 }
 

+ 1 - 3
pkg/context/context.go

@@ -328,9 +328,7 @@ func Contexter() macaron.Handler {
 		c.Data["ShowFooterBranding"] = setting.ShowFooterBranding
 		c.Data["ShowFooterVersion"] = setting.ShowFooterVersion
 
-		c.Data["auto_init"] = true
-
-		readNotice(c) // GIN mod: Add notice if notice file exists
+		c.renderNoticeBanner()
 
 		ctx.Map(c)
 	}

+ 62 - 0
pkg/context/notice.go

@@ -0,0 +1,62 @@
+// Copyright 2019 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 context
+
+import (
+	"os"
+	"path"
+
+	"github.com/Unknwon/com"
+	log "gopkg.in/clog.v1"
+
+	"github.com/G-Node/gogs/pkg/markup"
+	"github.com/G-Node/gogs/pkg/setting"
+	"github.com/G-Node/gogs/pkg/tool"
+)
+
+// renderNoticeBanner checks if a notice banner file exists and loads the message to display
+// on all pages.
+func (c *Context) renderNoticeBanner() {
+	fpath := path.Join(setting.CustomPath, "notice", "banner.md")
+	if !com.IsExist(fpath) {
+		return
+	}
+
+	f, err := os.Open(fpath)
+	if err != nil {
+		log.Error(2, "Failed to open file %q: %v", fpath, err)
+		return
+	}
+	defer f.Close()
+
+	fi, err := f.Stat()
+	if err != nil {
+		log.Error(2, "Failed to stat file %q: %v", fpath, err)
+		return
+	}
+
+	// Limit size to prevent very large messages from breaking pages
+	var maxSize int64 = 1024
+
+	if fi.Size() > maxSize { // Refuse to print very long messages
+		log.Warn("Notice banner file %q size too large [%d > %d]: refusing to render", fpath, fi.Size(), maxSize)
+		return
+	}
+
+	buf := make([]byte, maxSize)
+	n, err := f.Read(buf)
+	if err != nil {
+		log.Error(2, "Failed to read file %q: %v", fpath, err)
+		return
+	}
+	buf = buf[:n]
+
+	if !tool.IsTextFile(buf) {
+		log.Warn("Notice banner file %q does not appear to be a text file: aborting", fpath)
+		return
+	}
+
+	c.Data["ServerNotice"] = string(markup.RawMarkdown(buf, ""))
+}

+ 2 - 2
pkg/context/repo.go

@@ -160,9 +160,9 @@ func RepoAssignment(pages ...bool) macaron.Handler {
 		if c.IsLogged && c.User.IsAdmin {
 			c.Repo.AccessMode = models.ACCESS_MODE_OWNER
 		} else {
-			mode, err := models.AccessLevel(c.UserID(), repo)
+			mode, err := models.UserAccessMode(c.UserID(), repo)
 			if err != nil {
-				c.ServerError("AccessLevel", err)
+				c.ServerError("UserAccessMode", err)
 				return
 			}
 			c.Repo.AccessMode = mode

+ 1 - 1
pkg/dav/middle.go

@@ -47,7 +47,7 @@ func DavMiddle() macaron.Handler {
 		if c.IsLogged && c.User.IsAdmin {
 			c.Repo.AccessMode = models.ACCESS_MODE_OWNER
 		} else {
-			mode, err := models.AccessLevel(c.UserID(), repo)
+			mode, err := models.UserAccessMode(c.UserID(), repo)
 			if err != nil {
 				c.WriteHeader(http.StatusInternalServerError)
 				return

+ 3 - 0
pkg/template/template.go

@@ -33,6 +33,9 @@ func NewFuncMap() []template.FuncMap {
 		"GoVer": func() string {
 			return strings.Title(runtime.Version())
 		},
+		"Year": func() int {
+			return time.Now().Year()
+		},
 		"UseHTTPS": func() bool {
 			return strings.HasPrefix(setting.AppURL, "https")
 		},

+ 0 - 1
routes/api/v1/admin/org.go

@@ -12,7 +12,6 @@ import (
 	"github.com/G-Node/gogs/routes/api/v1/user"
 )
 
-// https://github.com/gogs/go-gogs-client/wiki/Administration-Organizations#create-a-new-organization
 func CreateOrg(c *context.APIContext, form api.CreateOrgOption) {
 	org.CreateOrgForUser(c, form, user.GetUserByParams(c))
 }

+ 5 - 9
routes/api/v1/admin/org_repo.go

@@ -13,11 +13,7 @@ import (
 func GetRepositoryByParams(c *context.APIContext) *models.Repository {
 	repo, err := models.GetRepositoryByName(c.Org.Team.OrgID, c.Params(":reponame"))
 	if err != nil {
-		if errors.IsRepoNotExist(err) {
-			c.Status(404)
-		} else {
-			c.Error(500, "GetRepositoryByName", err)
-		}
+		c.NotFoundOrServerError("GetRepositoryByName", errors.IsRepoNotExist, err)
 		return nil
 	}
 	return repo
@@ -29,11 +25,11 @@ func AddTeamRepository(c *context.APIContext) {
 		return
 	}
 	if err := c.Org.Team.AddRepository(repo); err != nil {
-		c.Error(500, "AddRepository", err)
+		c.ServerError("AddRepository", err)
 		return
 	}
 
-	c.Status(204)
+	c.NoContent()
 }
 
 func RemoveTeamRepository(c *context.APIContext) {
@@ -42,9 +38,9 @@ func RemoveTeamRepository(c *context.APIContext) {
 		return
 	}
 	if err := c.Org.Team.RemoveRepository(repo.ID); err != nil {
-		c.Error(500, "RemoveRepository", err)
+		c.ServerError("RemoveRepository", err)
 		return
 	}
 
-	c.Status(204)
+	c.NoContent()
 }

+ 9 - 7
routes/api/v1/admin/org_team.go

@@ -5,6 +5,8 @@
 package admin
 
 import (
+	"net/http"
+
 	api "github.com/gogs/go-gogs-client"
 
 	"github.com/G-Node/gogs/models"
@@ -22,14 +24,14 @@ func CreateTeam(c *context.APIContext, form api.CreateTeamOption) {
 	}
 	if err := models.NewTeam(team); err != nil {
 		if models.IsErrTeamAlreadyExist(err) {
-			c.Error(422, "", err)
+			c.Error(http.StatusUnprocessableEntity, "", err)
 		} else {
-			c.Error(500, "NewTeam", err)
+			c.ServerError("NewTeam", err)
 		}
 		return
 	}
 
-	c.JSON(201, convert.ToTeam(team))
+	c.JSON(http.StatusCreated, convert.ToTeam(team))
 }
 
 func AddTeamMember(c *context.APIContext) {
@@ -38,11 +40,11 @@ func AddTeamMember(c *context.APIContext) {
 		return
 	}
 	if err := c.Org.Team.AddMember(u.ID); err != nil {
-		c.Error(500, "AddMember", err)
+		c.ServerError("AddMember", err)
 		return
 	}
 
-	c.Status(204)
+	c.NoContent()
 }
 
 func RemoveTeamMember(c *context.APIContext) {
@@ -52,9 +54,9 @@ func RemoveTeamMember(c *context.APIContext) {
 	}
 
 	if err := c.Org.Team.RemoveMember(u.ID); err != nil {
-		c.Error(500, "RemoveMember", err)
+		c.ServerError("RemoveMember", err)
 		return
 	}
 
-	c.Status(204)
+	c.NoContent()
 }

+ 0 - 1
routes/api/v1/admin/repo.go

@@ -12,7 +12,6 @@ import (
 	"github.com/G-Node/gogs/routes/api/v1/user"
 )
 
-// https://github.com/gogs/go-gogs-client/wiki/Administration-Repositories#create-a-new-repository
 func CreateRepo(c *context.APIContext, form api.CreateRepoOption) {
 	owner := user.GetUserByParams(c)
 	if c.Written() {

+ 16 - 18
routes/api/v1/admin/user.go

@@ -5,6 +5,8 @@
 package admin
 
 import (
+	"net/http"
+
 	log "gopkg.in/clog.v1"
 
 	api "github.com/gogs/go-gogs-client"
@@ -25,9 +27,9 @@ func parseLoginSource(c *context.APIContext, u *models.User, sourceID int64, log
 	source, err := models.GetLoginSourceByID(sourceID)
 	if err != nil {
 		if errors.IsLoginSourceNotExist(err) {
-			c.Error(422, "", err)
+			c.Error(http.StatusUnprocessableEntity, "", err)
 		} else {
-			c.Error(500, "GetLoginSourceByID", err)
+			c.ServerError("GetLoginSourceByID", err)
 		}
 		return
 	}
@@ -37,7 +39,6 @@ func parseLoginSource(c *context.APIContext, u *models.User, sourceID int64, log
 	u.LoginName = loginName
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Administration-Users#create-a-new-user
 func CreateUser(c *context.APIContext, form api.CreateUserOption) {
 	u := &models.User{
 		Name:      form.Username,
@@ -58,23 +59,22 @@ func CreateUser(c *context.APIContext, form api.CreateUserOption) {
 			models.IsErrEmailAlreadyUsed(err) ||
 			models.IsErrNameReserved(err) ||
 			models.IsErrNamePatternNotAllowed(err) {
-			c.Error(422, "", err)
+			c.Error(http.StatusUnprocessableEntity, "", err)
 		} else {
-			c.Error(500, "CreateUser", err)
+			c.ServerError("CreateUser", err)
 		}
 		return
 	}
-	log.Trace("Account created by admin (%s): %s", c.User.Name, u.Name)
+	log.Trace("Account created by admin %q: %s", c.User.Name, u.Name)
 
 	// Send email notification.
 	if form.SendNotify && setting.MailService != nil {
 		mailer.SendRegisterNotifyMail(c.Context.Context, models.NewMailerUser(u))
 	}
 
-	c.JSON(201, u.APIFormat())
+	c.JSON(http.StatusCreated, u.APIFormat())
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Administration-Users#edit-an-existing-user
 func EditUser(c *context.APIContext, form api.EditUserOption) {
 	u := user.GetUserByParams(c)
 	if c.Written() {
@@ -90,7 +90,7 @@ func EditUser(c *context.APIContext, form api.EditUserOption) {
 		u.Passwd = form.Password
 		var err error
 		if u.Salt, err = models.GetUserSalt(); err != nil {
-			c.Error(500, "UpdateUser", err)
+			c.ServerError("GetUserSalt", err)
 			return
 		}
 		u.EncodePasswd()
@@ -119,18 +119,17 @@ func EditUser(c *context.APIContext, form api.EditUserOption) {
 
 	if err := models.UpdateUser(u); err != nil {
 		if models.IsErrEmailAlreadyUsed(err) {
-			c.Error(422, "", err)
+			c.Error(http.StatusUnprocessableEntity, "", err)
 		} else {
-			c.Error(500, "UpdateUser", err)
+			c.ServerError("UpdateUser", err)
 		}
 		return
 	}
-	log.Trace("Account profile updated by admin (%s): %s", c.User.Name, u.Name)
+	log.Trace("Account profile updated by admin %q: %s", c.User.Name, u.Name)
 
-	c.JSON(200, u.APIFormat())
+	c.JSONSuccess(u.APIFormat())
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Administration-Users#delete-a-user
 func DeleteUser(c *context.APIContext) {
 	u := user.GetUserByParams(c)
 	if c.Written() {
@@ -140,18 +139,17 @@ func DeleteUser(c *context.APIContext) {
 	if err := models.DeleteUser(u); err != nil {
 		if models.IsErrUserOwnRepos(err) ||
 			models.IsErrUserHasOrgs(err) {
-			c.Error(422, "", err)
+			c.Error(http.StatusUnprocessableEntity, "", err)
 		} else {
-			c.Error(500, "DeleteUser", err)
+			c.ServerError("DeleteUser", err)
 		}
 		return
 	}
 	log.Trace("Account deleted by admin(%s): %s", c.User.Name, u.Name)
 
-	c.Status(204)
+	c.NoContent()
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Administration-Users#create-a-public-key-for-user
 func CreatePublicKey(c *context.APIContext, form api.CreateKeyOption) {
 	u := user.GetUserByParams(c)
 	if c.Written() {

+ 147 - 91
routes/api/v1/api.go

@@ -25,21 +25,21 @@ import (
 	"github.com/G-Node/gogs/routes/api/v1/user"
 )
 
+// repoAssignment extracts information from URL parameters to retrieve the repository,
+// and makes sure the context user has at least the read access to the repository.
 func repoAssignment() macaron.Handler {
 	return func(c *context.APIContext) {
-		userName := c.Params(":username")
-		repoName := c.Params(":reponame")
+		username := c.Params(":username")
+		reponame := c.Params(":reponame")
 
-		var (
-			owner *models.User
-			err   error
-		)
+		var err error
+		var owner *models.User
 
-		// Check if the user is the same as the repository owner.
-		if c.IsLogged && c.User.LowerName == strings.ToLower(userName) {
+		// Check if the context user is the repository owner.
+		if c.IsLogged && c.User.LowerName == strings.ToLower(username) {
 			owner = c.User
 		} else {
-			owner, err = models.GetUserByName(userName)
+			owner, err = models.GetUserByName(username)
 			if err != nil {
 				c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
 				return
@@ -47,11 +47,11 @@ func repoAssignment() macaron.Handler {
 		}
 		c.Repo.Owner = owner
 
-		repo, err := models.GetRepositoryByName(owner.ID, repoName)
+		r, err := models.GetRepositoryByName(owner.ID, reponame)
 		if err != nil {
 			c.NotFoundOrServerError("GetRepositoryByName", errors.IsRepoNotExist, err)
 			return
-		} else if err = repo.GetOwner(); err != nil {
+		} else if err = r.GetOwner(); err != nil {
 			c.ServerError("GetOwner", err)
 			return
 		}
@@ -59,9 +59,9 @@ func repoAssignment() macaron.Handler {
 		if c.IsTokenAuth && c.User.IsAdmin {
 			c.Repo.AccessMode = models.ACCESS_MODE_OWNER
 		} else {
-			mode, err := models.AccessLevel(c.UserID(), repo)
+			mode, err := models.UserAccessMode(c.UserID(), r)
 			if err != nil {
-				c.ServerError("AccessLevel", err)
+				c.ServerError("UserAccessMode", err)
 				return
 			}
 			c.Repo.AccessMode = mode
@@ -72,11 +72,45 @@ func repoAssignment() macaron.Handler {
 			return
 		}
 
-		c.Repo.Repository = repo
+		c.Repo.Repository = r
+	}
+}
+
+// orgAssignment extracts information from URL parameters to retrieve the organization or team.
+func orgAssignment(args ...bool) macaron.Handler {
+	var (
+		assignOrg  bool
+		assignTeam bool
+	)
+	if len(args) > 0 {
+		assignOrg = args[0]
+	}
+	if len(args) > 1 {
+		assignTeam = args[1]
+	}
+	return func(c *context.APIContext) {
+		c.Org = new(context.APIOrganization)
+
+		var err error
+		if assignOrg {
+			c.Org.Organization, err = models.GetUserByName(c.Params(":orgname"))
+			if err != nil {
+				c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
+				return
+			}
+		}
+
+		if assignTeam {
+			c.Org.Team, err = models.GetTeamByID(c.ParamsInt64(":teamid"))
+			if err != nil {
+				c.NotFoundOrServerError("GetTeamByID", errors.IsTeamNotExist, err)
+				return
+			}
+		}
 	}
 }
 
-// Contexter middleware already checks token for user sign in process.
+// reqToken makes sure the context user is authorized via access token.
 func reqToken() macaron.Handler {
 	return func(c *context.Context) {
 		if !c.IsTokenAuth {
@@ -86,6 +120,7 @@ func reqToken() macaron.Handler {
 	}
 }
 
+// reqBasicAuth makes sure the context user is authorized via HTTP Basic Auth.
 func reqBasicAuth() macaron.Handler {
 	return func(c *context.Context) {
 		if !c.IsBasicAuth {
@@ -95,6 +130,7 @@ func reqBasicAuth() macaron.Handler {
 	}
 }
 
+// reqAdmin makes sure the context user is a site admin.
 func reqAdmin() macaron.Handler {
 	return func(c *context.Context) {
 		if !c.IsLogged || !c.User.IsAdmin {
@@ -104,6 +140,7 @@ func reqAdmin() macaron.Handler {
 	}
 }
 
+// reqRepoWriter makes sure the context user has at least write access to the repository.
 func reqRepoWriter() macaron.Handler {
 	return func(c *context.Context) {
 		if !c.Repo.IsWriter() {
@@ -113,35 +150,12 @@ func reqRepoWriter() macaron.Handler {
 	}
 }
 
-func orgAssignment(args ...bool) macaron.Handler {
-	var (
-		assignOrg  bool
-		assignTeam bool
-	)
-	if len(args) > 0 {
-		assignOrg = args[0]
-	}
-	if len(args) > 1 {
-		assignTeam = args[1]
-	}
-	return func(c *context.APIContext) {
-		c.Org = new(context.APIOrganization)
-
-		var err error
-		if assignOrg {
-			c.Org.Organization, err = models.GetUserByName(c.Params(":orgname"))
-			if err != nil {
-				c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
-				return
-			}
-		}
-
-		if assignTeam {
-			c.Org.Team, err = models.GetTeamByID(c.ParamsInt64(":teamid"))
-			if err != nil {
-				c.NotFoundOrServerError("GetTeamByID", errors.IsTeamNotExist, err)
-				return
-			}
+// reqRepoWriter makes sure the context user has at least admin access to the repository.
+func reqRepoAdmin() macaron.Handler {
+	return func(c *context.Context) {
+		if !c.Repo.IsAdmin() {
+			c.Error(http.StatusForbidden)
+			return
 		}
 	}
 }
@@ -153,7 +167,7 @@ func mustEnableIssues(c *context.APIContext) {
 	}
 }
 
-// RegisterRoutes registers all v1 APIs routes to web application.
+// RegisterRoutes registers all routes in API v1 to the web application.
 // FIXME: custom form error response
 func RegisterRoutes(m *macaron.Macaron) {
 	bind := binding.Bind
@@ -177,7 +191,8 @@ func RegisterRoutes(m *macaron.Macaron) {
 				m.Get("", user.GetInfo)
 
 				m.Group("/tokens", func() {
-					m.Combo("").Get(user.ListAccessTokens).
+					m.Combo("").
+						Get(user.ListAccessTokens).
 						Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken)
 				}, reqBasicAuth())
 			})
@@ -197,30 +212,37 @@ func RegisterRoutes(m *macaron.Macaron) {
 
 		m.Group("/user", func() {
 			m.Get("", user.GetAuthenticatedUser)
-			m.Combo("/emails").Get(user.ListEmails).
+			m.Combo("/emails").
+				Get(user.ListEmails).
 				Post(bind(api.CreateEmailOption{}), user.AddEmail).
 				Delete(bind(api.CreateEmailOption{}), user.DeleteEmail)
 
 			m.Get("/followers", user.ListMyFollowers)
 			m.Group("/following", func() {
 				m.Get("", user.ListMyFollowing)
-				m.Combo("/:username").Get(user.CheckMyFollowing).Put(user.Follow).Delete(user.Unfollow)
+				m.Combo("/:username").
+					Get(user.CheckMyFollowing).
+					Put(user.Follow).
+					Delete(user.Unfollow)
 			})
 
 			m.Group("/keys", func() {
-				m.Combo("").Get(user.ListMyPublicKeys).
+				m.Combo("").
+					Get(user.ListMyPublicKeys).
 					Post(bind(api.CreateKeyOption{}), user.CreatePublicKey)
-				m.Combo("/:id").Get(user.GetPublicKey).
+				m.Combo("/:id").
+					Get(user.GetPublicKey).
 					Delete(user.DeletePublicKey)
 			})
 
-			m.Combo("/issues").Get(repo.ListUserIssues)
+			m.Get("/issues", repo.ListUserIssues)
 		}, reqToken())
 
 		// Repositories
 		m.Get("/users/:username/repos", reqToken(), repo.ListUserRepositories)
 		m.Get("/orgs/:org/repos", reqToken(), repo.ListOrgRepositories)
-		m.Combo("/user/repos", reqToken()).Get(repo.ListMyRepos).
+		m.Combo("/user/repos", reqToken()).
+			Get(repo.ListMyRepos).
 			Post(bind(api.CreateRepoOption{}), repo.Create)
 		m.Post("/org/:org/repos", reqToken(), bind(api.CreateRepoOption{}), repo.CreateOrgRepo)
 
@@ -237,16 +259,22 @@ func RegisterRoutes(m *macaron.Macaron) {
 
 			m.Group("/:username/:reponame", func() {
 				m.Group("/hooks", func() {
-					m.Combo("").Get(repo.ListHooks).
+					m.Combo("").
+						Get(repo.ListHooks).
 						Post(bind(api.CreateHookOption{}), repo.CreateHook)
-					m.Combo("/:id").Patch(bind(api.EditHookOption{}), repo.EditHook).
+					m.Combo("/:id").
+						Patch(bind(api.EditHookOption{}), repo.EditHook).
 						Delete(repo.DeleteHook)
-				})
+				}, reqRepoAdmin())
+
 				m.Group("/collaborators", func() {
 					m.Get("", repo.ListCollaborators)
-					m.Combo("/:collaborator").Get(repo.IsCollaborator).Put(bind(api.AddCollaboratorOption{}), repo.AddCollaborator).
+					m.Combo("/:collaborator").
+						Get(repo.IsCollaborator).
+						Put(bind(api.AddCollaboratorOption{}), repo.AddCollaborator).
 						Delete(repo.DeleteCollaborator)
-				})
+				}, reqRepoAdmin())
+
 				m.Get("/raw/*", context.RepoRef(), repo.GetRawFile)
 				m.Get("/archive/*", repo.GetArchive)
 				m.Get("/forks", repo.ListForks)
@@ -254,59 +282,77 @@ func RegisterRoutes(m *macaron.Macaron) {
 					m.Get("", repo.ListBranches)
 					m.Get("/*", repo.GetBranch)
 				})
-
 				m.Group("/commits", func() {
 					m.Get("/:sha", repo.GetSingleCommit)
 					m.Get("/*", repo.GetReferenceSHA)
 				})
 
 				m.Group("/keys", func() {
-					m.Combo("").Get(repo.ListDeployKeys).
+					m.Combo("").
+						Get(repo.ListDeployKeys).
 						Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey)
-					m.Combo("/:id").Get(repo.GetDeployKey).
+					m.Combo("/:id").
+						Get(repo.GetDeployKey).
 						Delete(repo.DeleteDeploykey)
-				})
+				}, reqRepoAdmin())
+
 				m.Group("/issues", func() {
-					m.Combo("").Get(repo.ListIssues).Post(bind(api.CreateIssueOption{}), repo.CreateIssue)
+					m.Combo("").
+						Get(repo.ListIssues).
+						Post(bind(api.CreateIssueOption{}), repo.CreateIssue)
 					m.Group("/comments", func() {
 						m.Get("", repo.ListRepoIssueComments)
-						m.Combo("/:id").Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueComment)
+						m.Patch("/:id", bind(api.EditIssueCommentOption{}), repo.EditIssueComment)
 					})
 					m.Group("/:index", func() {
-						m.Combo("").Get(repo.GetIssue).Patch(bind(api.EditIssueOption{}), repo.EditIssue)
+						m.Combo("").
+							Get(repo.GetIssue).
+							Patch(bind(api.EditIssueOption{}), repo.EditIssue)
 
 						m.Group("/comments", func() {
-							m.Combo("").Get(repo.ListIssueComments).Post(bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
-							m.Combo("/:id").Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueComment).
+							m.Combo("").
+								Get(repo.ListIssueComments).
+								Post(bind(api.CreateIssueCommentOption{}), repo.CreateIssueComment)
+							m.Combo("/:id").
+								Patch(bind(api.EditIssueCommentOption{}), repo.EditIssueComment).
 								Delete(repo.DeleteIssueComment)
 						})
 
+						m.Get("/labels", repo.ListIssueLabels)
 						m.Group("/labels", func() {
-							m.Combo("").Get(repo.ListIssueLabels).
+							m.Combo("").
 								Post(bind(api.IssueLabelsOption{}), repo.AddIssueLabels).
 								Put(bind(api.IssueLabelsOption{}), repo.ReplaceIssueLabels).
 								Delete(repo.ClearIssueLabels)
 							m.Delete("/:id", repo.DeleteIssueLabel)
-						})
-
+						}, reqRepoWriter())
 					})
 				}, mustEnableIssues)
+
 				m.Group("/labels", func() {
-					m.Combo("").Get(repo.ListLabels).
-						Post(bind(api.CreateLabelOption{}), repo.CreateLabel)
-					m.Combo("/:id").Get(repo.GetLabel).Patch(bind(api.EditLabelOption{}), repo.EditLabel).
-						Delete(repo.DeleteLabel)
+					m.Get("", repo.ListLabels)
+					m.Get("/:id", repo.GetLabel)
 				})
+				m.Group("/labels", func() {
+					m.Post("", bind(api.CreateLabelOption{}), repo.CreateLabel)
+					m.Combo("/:id").
+						Patch(bind(api.EditLabelOption{}), repo.EditLabel).
+						Delete(repo.DeleteLabel)
+				}, reqRepoWriter())
+
 				m.Group("/milestones", func() {
-					m.Combo("").Get(repo.ListMilestones).
-						Post(reqRepoWriter(), bind(api.CreateMilestoneOption{}), repo.CreateMilestone)
-					m.Combo("/:id").Get(repo.GetMilestone).
-						Patch(reqRepoWriter(), bind(api.EditMilestoneOption{}), repo.EditMilestone).
-						Delete(reqRepoWriter(), repo.DeleteMilestone)
+					m.Get("", repo.ListMilestones)
+					m.Get("/:id", repo.GetMilestone)
 				})
-
-				m.Patch("/issue-tracker", bind(api.EditIssueTrackerOption{}), repo.IssueTracker)
-				m.Post("/mirror-sync", repo.MirrorSync)
+				m.Group("/milestones", func() {
+					m.Post("", bind(api.CreateMilestoneOption{}), repo.CreateMilestone)
+					m.Combo("/:id").
+						Patch(bind(api.EditMilestoneOption{}), repo.EditMilestone).
+						Delete(repo.DeleteMilestone)
+				}, reqRepoWriter())
+
+				m.Patch("/issue-tracker", reqRepoWriter(), bind(api.EditIssueTrackerOption{}), repo.IssueTracker)
+				m.Post("/mirror-sync", reqRepoWriter(), repo.MirrorSync)
 				m.Get("/editorconfig/:filename", context.RepoRef(), repo.GetEditorconfig)
 			}, repoAssignment())
 		}, reqToken())
@@ -314,24 +360,25 @@ func RegisterRoutes(m *macaron.Macaron) {
 		m.Get("/issues", reqToken(), repo.ListUserIssues)
 
 		// Organizations
-		m.Combo("/user/orgs", reqToken()).Get(org.ListMyOrgs).Post(bind(api.CreateOrgOption{}), org.CreateMyOrg)
+		m.Combo("/user/orgs", reqToken()).
+			Get(org.ListMyOrgs).
+			Post(bind(api.CreateOrgOption{}), org.CreateMyOrg)
 
 		m.Get("/users/:username/orgs", org.ListUserOrgs)
 		m.Group("/orgs/:orgname", func() {
-			m.Combo("").Get(org.Get).Patch(bind(api.EditOrgOption{}), org.Edit)
-			m.Combo("/teams").Get(org.ListTeams)
+			m.Combo("").
+				Get(org.Get).
+				Patch(bind(api.EditOrgOption{}), org.Edit)
+			m.Get("/teams", org.ListTeams)
 		}, orgAssignment(true))
 
-		m.Any("/*", func(c *context.Context) {
-			c.NotFound()
-		})
-
 		m.Group("/admin", func() {
 			m.Group("/users", func() {
 				m.Post("", bind(api.CreateUserOption{}), admin.CreateUser)
 
 				m.Group("/:username", func() {
-					m.Combo("").Patch(bind(api.EditUserOption{}), admin.EditUser).
+					m.Combo("").
+						Patch(bind(api.EditUserOption{}), admin.EditUser).
 						Delete(admin.DeleteUser)
 					m.Post("/keys", bind(api.CreateKeyOption{}), admin.CreatePublicKey)
 					m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg)
@@ -344,12 +391,21 @@ func RegisterRoutes(m *macaron.Macaron) {
 					m.Post("", orgAssignment(true), bind(api.CreateTeamOption{}), admin.CreateTeam)
 				})
 			})
+
 			m.Group("/teams", func() {
 				m.Group("/:teamid", func() {
-					m.Combo("/members/:username").Put(admin.AddTeamMember).Delete(admin.RemoveTeamMember)
-					m.Combo("/repos/:reponame").Put(admin.AddTeamRepository).Delete(admin.RemoveTeamRepository)
+					m.Combo("/members/:username").
+						Put(admin.AddTeamMember).
+						Delete(admin.RemoveTeamMember)
+					m.Combo("/repos/:reponame").
+						Put(admin.AddTeamRepository).
+						Delete(admin.RemoveTeamRepository)
 				}, orgAssignment(false, true))
 			})
 		}, reqAdmin())
+
+		m.Any("/*", func(c *context.Context) {
+			c.NotFound()
+		})
 	}, context.APIContexter())
 }

+ 4 - 4
routes/api/v1/misc/markdown.go

@@ -5,16 +5,17 @@
 package misc
 
 import (
+	"net/http"
+
 	api "github.com/gogs/go-gogs-client"
 
 	"github.com/G-Node/gogs/pkg/context"
 	"github.com/G-Node/gogs/pkg/markup"
 )
 
-// https://github.com/gogs/go-gogs-client/wiki/Miscellaneous#render-an-arbitrary-markdown-document
 func Markdown(c *context.APIContext, form api.MarkdownOption) {
 	if c.HasApiError() {
-		c.Error(422, "", c.GetErrMsg())
+		c.Error(http.StatusUnprocessableEntity, "", c.GetErrMsg())
 		return
 	}
 
@@ -31,11 +32,10 @@ func Markdown(c *context.APIContext, form api.MarkdownOption) {
 	}
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Miscellaneous#render-a-markdown-document-in-raw-mode
 func MarkdownRaw(c *context.APIContext) {
 	body, err := c.Req.Body().Bytes()
 	if err != nil {
-		c.Error(422, "", err)
+		c.Error(http.StatusUnprocessableEntity, "", err)
 		return
 	}
 	c.Write(markup.RawMarkdown(body, ""))

+ 10 - 13
routes/api/v1/org/org.go

@@ -5,6 +5,8 @@
 package org
 
 import (
+	"net/http"
+
 	api "github.com/gogs/go-gogs-client"
 
 	"github.com/G-Node/gogs/models"
@@ -31,9 +33,9 @@ func CreateOrgForUser(c *context.APIContext, apiForm api.CreateOrgOption, user *
 		if models.IsErrUserAlreadyExist(err) ||
 			models.IsErrNameReserved(err) ||
 			models.IsErrNamePatternNotAllowed(err) {
-			c.Error(422, "", err)
+			c.Error(http.StatusUnprocessableEntity, "", err)
 		} else {
-			c.Error(500, "CreateOrganization", err)
+			c.ServerError("CreateOrganization", err)
 		}
 		return
 	}
@@ -43,7 +45,7 @@ func CreateOrgForUser(c *context.APIContext, apiForm api.CreateOrgOption, user *
 
 func listUserOrgs(c *context.APIContext, u *models.User, all bool) {
 	if err := u.GetOrganizations(all); err != nil {
-		c.Error(500, "GetOrganizations", err)
+		c.ServerError("GetOrganizations", err)
 		return
 	}
 
@@ -51,20 +53,17 @@ func listUserOrgs(c *context.APIContext, u *models.User, all bool) {
 	for i := range u.Orgs {
 		apiOrgs[i] = convert.ToOrganization(u.Orgs[i])
 	}
-	c.JSON(200, &apiOrgs)
+	c.JSONSuccess(&apiOrgs)
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Organizations#list-your-organizations
 func ListMyOrgs(c *context.APIContext) {
 	listUserOrgs(c, c.User, true)
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Organizations#create-your-organization
 func CreateMyOrg(c *context.APIContext, apiForm api.CreateOrgOption) {
 	CreateOrgForUser(c, apiForm, c.User)
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Organizations#list-user-organizations
 func ListUserOrgs(c *context.APIContext) {
 	u := user.GetUserByParams(c)
 	if c.Written() {
@@ -73,16 +72,14 @@ func ListUserOrgs(c *context.APIContext) {
 	listUserOrgs(c, u, false)
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Organizations#get-an-organization
 func Get(c *context.APIContext) {
-	c.JSON(200, convert.ToOrganization(c.Org.Organization))
+	c.JSONSuccess(convert.ToOrganization(c.Org.Organization))
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Organizations#edit-an-organization
 func Edit(c *context.APIContext, form api.EditOrgOption) {
 	org := c.Org.Organization
 	if !org.IsOwnedBy(c.User.ID) {
-		c.Status(403)
+		c.Status(http.StatusForbidden)
 		return
 	}
 
@@ -91,9 +88,9 @@ func Edit(c *context.APIContext, form api.EditOrgOption) {
 	org.Website = form.Website
 	org.Location = form.Location
 	if err := models.UpdateUser(org); err != nil {
-		c.Error(500, "UpdateUser", err)
+		c.ServerError("UpdateUser", err)
 		return
 	}
 
-	c.JSON(200, convert.ToOrganization(org))
+	c.JSONSuccess(convert.ToOrganization(org))
 }

+ 8 - 18
routes/api/v1/repo/file.go

@@ -13,39 +13,33 @@ import (
 	"github.com/go-macaron/captcha"
 )
 
-// https://github.com/gogs/go-gogs-client/wiki/Repositories-Contents#download-raw-content
 func GetRawFile(c *context.APIContext) {
 	if !c.Repo.HasAccess() {
-		c.Status(404)
+		c.NotFound()
 		return
 	}
 
 	if c.Repo.Repository.IsBare {
-		c.Status(404)
+		c.NotFound()
 		return
 	}
 
 	blob, err := c.Repo.Commit.GetBlobByPath(c.Repo.TreePath)
 	if err != nil {
-		if git.IsErrNotExist(err) {
-			c.Status(404)
-		} else {
-			c.Error(500, "GetBlobByPath", err)
-		}
+		c.NotFoundOrServerError("GetBlobByPath", git.IsErrNotExist, err)
 		return
 	}
 	cp := captcha.NewCaptcha(captcha.Options{})
 	if err = repo.ServeBlob(c.Context, blob, cp); err != nil {
-		c.Error(500, "ServeBlob", err)
+		c.ServerError("ServeBlob", err)
 	}
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Repositories-Contents#download-archive
 func GetArchive(c *context.APIContext) {
 	repoPath := models.RepoPath(c.Params(":username"), c.Params(":reponame"))
 	gitRepo, err := git.OpenRepository(repoPath)
 	if err != nil {
-		c.Error(500, "OpenRepository", err)
+		c.ServerError("OpenRepository", err)
 		return
 	}
 	c.Repo.GitRepo = gitRepo
@@ -56,19 +50,15 @@ func GetArchive(c *context.APIContext) {
 func GetEditorconfig(c *context.APIContext) {
 	ec, err := c.Repo.GetEditorconfig()
 	if err != nil {
-		if git.IsErrNotExist(err) {
-			c.Error(404, "GetEditorconfig", err)
-		} else {
-			c.Error(500, "GetEditorconfig", err)
-		}
+		c.NotFoundOrServerError("GetEditorconfig", git.IsErrNotExist, err)
 		return
 	}
 
 	fileName := c.Params("filename")
 	def := ec.GetDefinitionForFilename(fileName)
 	if def == nil {
-		c.Error(404, "GetDefinitionForFilename", err)
+		c.NotFound()
 		return
 	}
-	c.JSON(200, def)
+	c.JSONSuccess(def)
 }

+ 23 - 30
routes/api/v1/repo/issue.go

@@ -6,6 +6,7 @@ package repo
 
 import (
 	"fmt"
+	"net/http"
 	"strings"
 
 	api "github.com/gogs/go-gogs-client"
@@ -19,13 +20,13 @@ import (
 func listIssues(c *context.APIContext, opts *models.IssuesOptions) {
 	issues, err := models.Issues(opts)
 	if err != nil {
-		c.Error(500, "Issues", err)
+		c.ServerError("Issues", err)
 		return
 	}
 
 	count, err := models.IssuesCount(opts)
 	if err != nil {
-		c.Error(500, "IssuesCount", err)
+		c.ServerError("IssuesCount", err)
 		return
 	}
 
@@ -33,14 +34,14 @@ func listIssues(c *context.APIContext, opts *models.IssuesOptions) {
 	apiIssues := make([]*api.Issue, len(issues))
 	for i := range issues {
 		if err = issues[i].LoadAttributes(); err != nil {
-			c.Error(500, "LoadAttributes", err)
+			c.ServerError("LoadAttributes", err)
 			return
 		}
 		apiIssues[i] = issues[i].APIFormat()
 	}
 
 	c.SetLinkHeader(int(count), setting.UI.IssuePagingNum)
-	c.JSON(200, &apiIssues)
+	c.JSONSuccess(&apiIssues)
 }
 
 func ListUserIssues(c *context.APIContext) {
@@ -66,14 +67,10 @@ func ListIssues(c *context.APIContext) {
 func GetIssue(c *context.APIContext) {
 	issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
 	if err != nil {
-		if errors.IsIssueNotExist(err) {
-			c.Status(404)
-		} else {
-			c.Error(500, "GetIssueByIndex", err)
-		}
+		c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
 		return
 	}
-	c.JSON(200, issue.APIFormat())
+	c.JSONSuccess(issue.APIFormat())
 }
 
 func CreateIssue(c *context.APIContext, form api.CreateIssueOption) {
@@ -90,9 +87,9 @@ func CreateIssue(c *context.APIContext, form api.CreateIssueOption) {
 			assignee, err := models.GetUserByName(form.Assignee)
 			if err != nil {
 				if errors.IsUserNotExist(err) {
-					c.Error(422, "", fmt.Sprintf("Assignee does not exist: [name: %s]", form.Assignee))
+					c.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("assignee does not exist: [name: %s]", form.Assignee))
 				} else {
-					c.Error(500, "GetUserByName", err)
+					c.ServerError("GetUserByName", err)
 				}
 				return
 			}
@@ -104,13 +101,13 @@ func CreateIssue(c *context.APIContext, form api.CreateIssueOption) {
 	}
 
 	if err := models.NewIssue(c.Repo.Repository, issue, form.Labels, nil); err != nil {
-		c.Error(500, "NewIssue", err)
+		c.ServerError("NewIssue", err)
 		return
 	}
 
 	if form.Closed {
 		if err := issue.ChangeStatus(c.User, c.Repo.Repository, true); err != nil {
-			c.Error(500, "ChangeStatus", err)
+			c.ServerError("ChangeStatus", err)
 			return
 		}
 	}
@@ -119,25 +116,21 @@ func CreateIssue(c *context.APIContext, form api.CreateIssueOption) {
 	var err error
 	issue, err = models.GetIssueByID(issue.ID)
 	if err != nil {
-		c.Error(500, "GetIssueByID", err)
+		c.ServerError("GetIssueByID", err)
 		return
 	}
-	c.JSON(201, issue.APIFormat())
+	c.JSON(http.StatusCreated, issue.APIFormat())
 }
 
 func EditIssue(c *context.APIContext, form api.EditIssueOption) {
 	issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
 	if err != nil {
-		if errors.IsIssueNotExist(err) {
-			c.Status(404)
-		} else {
-			c.Error(500, "GetIssueByIndex", err)
-		}
+		c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
 		return
 	}
 
 	if !issue.IsPoster(c.User.ID) && !c.Repo.IsWriter() {
-		c.Status(403)
+		c.Status(http.StatusForbidden)
 		return
 	}
 
@@ -156,9 +149,9 @@ func EditIssue(c *context.APIContext, form api.EditIssueOption) {
 			assignee, err := models.GetUserByName(*form.Assignee)
 			if err != nil {
 				if errors.IsUserNotExist(err) {
-					c.Error(422, "", fmt.Sprintf("assignee does not exist: [name: %s]", *form.Assignee))
+					c.Error(http.StatusUnprocessableEntity, "", fmt.Sprintf("assignee does not exist: [name: %s]", *form.Assignee))
 				} else {
-					c.Error(500, "GetUserByName", err)
+					c.ServerError("GetUserByName", err)
 				}
 				return
 			}
@@ -166,7 +159,7 @@ func EditIssue(c *context.APIContext, form api.EditIssueOption) {
 		}
 
 		if err = models.UpdateIssueUserByAssignee(issue); err != nil {
-			c.Error(500, "UpdateIssueUserByAssignee", err)
+			c.ServerError("UpdateIssueUserByAssignee", err)
 			return
 		}
 	}
@@ -175,18 +168,18 @@ func EditIssue(c *context.APIContext, form api.EditIssueOption) {
 		oldMilestoneID := issue.MilestoneID
 		issue.MilestoneID = *form.Milestone
 		if err = models.ChangeMilestoneAssign(c.User, issue, oldMilestoneID); err != nil {
-			c.Error(500, "ChangeMilestoneAssign", err)
+			c.ServerError("ChangeMilestoneAssign", err)
 			return
 		}
 	}
 
 	if err = models.UpdateIssue(issue); err != nil {
-		c.Error(500, "UpdateIssue", err)
+		c.ServerError("UpdateIssue", err)
 		return
 	}
 	if form.State != nil {
 		if err = issue.ChangeStatus(c.User, c.Repo.Repository, api.STATE_CLOSED == api.StateType(*form.State)); err != nil {
-			c.Error(500, "ChangeStatus", err)
+			c.ServerError("ChangeStatus", err)
 			return
 		}
 	}
@@ -194,8 +187,8 @@ func EditIssue(c *context.APIContext, form api.EditIssueOption) {
 	// Refetch from database to assign some automatic values
 	issue, err = models.GetIssueByID(issue.ID)
 	if err != nil {
-		c.Error(500, "GetIssueByID", err)
+		c.ServerError("GetIssueByID", err)
 		return
 	}
-	c.JSON(201, issue.APIFormat())
+	c.JSON(http.StatusCreated, issue.APIFormat())
 }

+ 21 - 28
routes/api/v1/repo/issue_comment.go

@@ -4,6 +4,7 @@
 package repo
 
 import (
+	"net/http"
 	"time"
 
 	api "github.com/gogs/go-gogs-client"
@@ -18,7 +19,7 @@ func ListIssueComments(c *context.APIContext) {
 		var err error
 		since, err = time.Parse(time.RFC3339, c.Query("since"))
 		if err != nil {
-			c.Error(422, "", err)
+			c.Error(http.StatusUnprocessableEntity, "", err)
 			return
 		}
 	}
@@ -26,13 +27,13 @@ func ListIssueComments(c *context.APIContext) {
 	// comments,err:=models.GetCommentsByIssueIDSince(, since)
 	issue, err := models.GetRawIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
 	if err != nil {
-		c.Error(500, "GetRawIssueByIndex", err)
+		c.ServerError("GetRawIssueByIndex", err)
 		return
 	}
 
 	comments, err := models.GetCommentsByIssueIDSince(issue.ID, since.Unix())
 	if err != nil {
-		c.Error(500, "GetCommentsByIssueIDSince", err)
+		c.ServerError("GetCommentsByIssueIDSince", err)
 		return
 	}
 
@@ -40,7 +41,7 @@ func ListIssueComments(c *context.APIContext) {
 	for i := range comments {
 		apiComments[i] = comments[i].APIFormat()
 	}
-	c.JSON(200, &apiComments)
+	c.JSONSuccess(&apiComments)
 }
 
 func ListRepoIssueComments(c *context.APIContext) {
@@ -49,14 +50,14 @@ func ListRepoIssueComments(c *context.APIContext) {
 		var err error
 		since, err = time.Parse(time.RFC3339, c.Query("since"))
 		if err != nil {
-			c.Error(422, "", err)
+			c.Error(http.StatusUnprocessableEntity, "", err)
 			return
 		}
 	}
 
 	comments, err := models.GetCommentsByRepoIDSince(c.Repo.Repository.ID, since.Unix())
 	if err != nil {
-		c.Error(500, "GetCommentsByRepoIDSince", err)
+		c.ServerError("GetCommentsByRepoIDSince", err)
 		return
 	}
 
@@ -64,75 +65,67 @@ func ListRepoIssueComments(c *context.APIContext) {
 	for i := range comments {
 		apiComments[i] = comments[i].APIFormat()
 	}
-	c.JSON(200, &apiComments)
+	c.JSONSuccess(&apiComments)
 }
 
 func CreateIssueComment(c *context.APIContext, form api.CreateIssueCommentOption) {
 	issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
 	if err != nil {
-		c.Error(500, "GetIssueByIndex", err)
+		c.ServerError("GetIssueByIndex", err)
 		return
 	}
 
 	comment, err := models.CreateIssueComment(c.User, c.Repo.Repository, issue, form.Body, nil)
 	if err != nil {
-		c.Error(500, "CreateIssueComment", err)
+		c.ServerError("CreateIssueComment", err)
 		return
 	}
 
-	c.JSON(201, comment.APIFormat())
+	c.JSON(http.StatusCreated, comment.APIFormat())
 }
 
 func EditIssueComment(c *context.APIContext, form api.EditIssueCommentOption) {
 	comment, err := models.GetCommentByID(c.ParamsInt64(":id"))
 	if err != nil {
-		if models.IsErrCommentNotExist(err) {
-			c.Error(404, "GetCommentByID", err)
-		} else {
-			c.Error(500, "GetCommentByID", err)
-		}
+		c.NotFoundOrServerError("GetCommentByID", models.IsErrCommentNotExist, err)
 		return
 	}
 
 	if c.User.ID != comment.PosterID && !c.Repo.IsAdmin() {
-		c.Status(403)
+		c.Status(http.StatusForbidden)
 		return
 	} else if comment.Type != models.COMMENT_TYPE_COMMENT {
-		c.Status(204)
+		c.NoContent()
 		return
 	}
 
 	oldContent := comment.Content
 	comment.Content = form.Body
 	if err := models.UpdateComment(c.User, comment, oldContent); err != nil {
-		c.Error(500, "UpdateComment", err)
+		c.ServerError("UpdateComment", err)
 		return
 	}
-	c.JSON(200, comment.APIFormat())
+	c.JSONSuccess(comment.APIFormat())
 }
 
 func DeleteIssueComment(c *context.APIContext) {
 	comment, err := models.GetCommentByID(c.ParamsInt64(":id"))
 	if err != nil {
-		if models.IsErrCommentNotExist(err) {
-			c.Error(404, "GetCommentByID", err)
-		} else {
-			c.Error(500, "GetCommentByID", err)
-		}
+		c.NotFoundOrServerError("GetCommentByID", models.IsErrCommentNotExist, err)
 		return
 	}
 
 	if c.User.ID != comment.PosterID && !c.Repo.IsAdmin() {
-		c.Status(403)
+		c.Status(http.StatusForbidden)
 		return
 	} else if comment.Type != models.COMMENT_TYPE_COMMENT {
-		c.Status(204)
+		c.NoContent()
 		return
 	}
 
 	if err = models.DeleteCommentByID(c.User, comment.ID); err != nil {
-		c.Error(500, "DeleteCommentByID", err)
+		c.ServerError("DeleteCommentByID", err)
 		return
 	}
-	c.Status(204)
+	c.NoContent()
 }

+ 22 - 60
routes/api/v1/repo/issue_label.go

@@ -5,6 +5,8 @@
 package repo
 
 import (
+	"net/http"
+
 	api "github.com/gogs/go-gogs-client"
 
 	"github.com/G-Node/gogs/models"
@@ -15,11 +17,7 @@ import (
 func ListIssueLabels(c *context.APIContext) {
 	issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
 	if err != nil {
-		if errors.IsIssueNotExist(err) {
-			c.Status(404)
-		} else {
-			c.Error(500, "GetIssueByIndex", err)
-		}
+		c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
 		return
 	}
 
@@ -27,39 +25,30 @@ func ListIssueLabels(c *context.APIContext) {
 	for i := range issue.Labels {
 		apiLabels[i] = issue.Labels[i].APIFormat()
 	}
-	c.JSON(200, &apiLabels)
+	c.JSONSuccess(&apiLabels)
 }
 
 func AddIssueLabels(c *context.APIContext, form api.IssueLabelsOption) {
-	if !c.Repo.IsWriter() {
-		c.Status(403)
-		return
-	}
-
 	issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
 	if err != nil {
-		if errors.IsIssueNotExist(err) {
-			c.Status(404)
-		} else {
-			c.Error(500, "GetIssueByIndex", err)
-		}
+		c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
 		return
 	}
 
 	labels, err := models.GetLabelsInRepoByIDs(c.Repo.Repository.ID, form.Labels)
 	if err != nil {
-		c.Error(500, "GetLabelsInRepoByIDs", err)
+		c.ServerError("GetLabelsInRepoByIDs", err)
 		return
 	}
 
 	if err = issue.AddLabels(c.User, labels); err != nil {
-		c.Error(500, "AddLabels", err)
+		c.ServerError("AddLabels", err)
 		return
 	}
 
 	labels, err = models.GetLabelsByIssueID(issue.ID)
 	if err != nil {
-		c.Error(500, "GetLabelsByIssueID", err)
+		c.ServerError("GetLabelsByIssueID", err)
 		return
 	}
 
@@ -67,73 +56,55 @@ func AddIssueLabels(c *context.APIContext, form api.IssueLabelsOption) {
 	for i := range labels {
 		apiLabels[i] = issue.Labels[i].APIFormat()
 	}
-	c.JSON(200, &apiLabels)
+	c.JSONSuccess(&apiLabels)
 }
 
 func DeleteIssueLabel(c *context.APIContext) {
-	if !c.Repo.IsWriter() {
-		c.Status(403)
-		return
-	}
-
 	issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
 	if err != nil {
-		if errors.IsIssueNotExist(err) {
-			c.Status(404)
-		} else {
-			c.Error(500, "GetIssueByIndex", err)
-		}
+		c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
 		return
 	}
 
 	label, err := models.GetLabelOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
 	if err != nil {
 		if models.IsErrLabelNotExist(err) {
-			c.Error(422, "", err)
+			c.Error(http.StatusUnprocessableEntity, "", err)
 		} else {
-			c.Error(500, "GetLabelInRepoByID", err)
+			c.ServerError("GetLabelInRepoByID", err)
 		}
 		return
 	}
 
 	if err := models.DeleteIssueLabel(issue, label); err != nil {
-		c.Error(500, "DeleteIssueLabel", err)
+		c.ServerError("DeleteIssueLabel", err)
 		return
 	}
 
-	c.Status(204)
+	c.NoContent()
 }
 
 func ReplaceIssueLabels(c *context.APIContext, form api.IssueLabelsOption) {
-	if !c.Repo.IsWriter() {
-		c.Status(403)
-		return
-	}
-
 	issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
 	if err != nil {
-		if errors.IsIssueNotExist(err) {
-			c.Status(404)
-		} else {
-			c.Error(500, "GetIssueByIndex", err)
-		}
+		c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
 		return
 	}
 
 	labels, err := models.GetLabelsInRepoByIDs(c.Repo.Repository.ID, form.Labels)
 	if err != nil {
-		c.Error(500, "GetLabelsInRepoByIDs", err)
+		c.ServerError("GetLabelsInRepoByIDs", err)
 		return
 	}
 
 	if err := issue.ReplaceLabels(labels); err != nil {
-		c.Error(500, "ReplaceLabels", err)
+		c.ServerError("ReplaceLabels", err)
 		return
 	}
 
 	labels, err = models.GetLabelsByIssueID(issue.ID)
 	if err != nil {
-		c.Error(500, "GetLabelsByIssueID", err)
+		c.ServerError("GetLabelsByIssueID", err)
 		return
 	}
 
@@ -141,29 +112,20 @@ func ReplaceIssueLabels(c *context.APIContext, form api.IssueLabelsOption) {
 	for i := range labels {
 		apiLabels[i] = issue.Labels[i].APIFormat()
 	}
-	c.JSON(200, &apiLabels)
+	c.JSONSuccess(&apiLabels)
 }
 
 func ClearIssueLabels(c *context.APIContext) {
-	if !c.Repo.IsWriter() {
-		c.Status(403)
-		return
-	}
-
 	issue, err := models.GetIssueByIndex(c.Repo.Repository.ID, c.ParamsInt64(":index"))
 	if err != nil {
-		if errors.IsIssueNotExist(err) {
-			c.Status(404)
-		} else {
-			c.Error(500, "GetIssueByIndex", err)
-		}
+		c.NotFoundOrServerError("GetIssueByIndex", errors.IsIssueNotExist, err)
 		return
 	}
 
 	if err := issue.ClearLabels(c.User); err != nil {
-		c.Error(500, "ClearLabels", err)
+		c.ServerError("ClearLabels", err)
 		return
 	}
 
-	c.Status(204)
+	c.NoContent()
 }

+ 13 - 34
routes/api/v1/repo/label.go

@@ -5,6 +5,8 @@
 package repo
 
 import (
+	"net/http"
+
 	"github.com/Unknwon/com"
 
 	api "github.com/gogs/go-gogs-client"
@@ -16,7 +18,7 @@ import (
 func ListLabels(c *context.APIContext) {
 	labels, err := models.GetLabelsByRepoID(c.Repo.Repository.ID)
 	if err != nil {
-		c.Error(500, "GetLabelsByRepoID", err)
+		c.ServerError("GetLabelsByRepoID", err)
 		return
 	}
 
@@ -24,7 +26,7 @@ func ListLabels(c *context.APIContext) {
 	for i := range labels {
 		apiLabels[i] = labels[i].APIFormat()
 	}
-	c.JSON(200, &apiLabels)
+	c.JSONSuccess(&apiLabels)
 }
 
 func GetLabel(c *context.APIContext) {
@@ -37,48 +39,30 @@ func GetLabel(c *context.APIContext) {
 		label, err = models.GetLabelOfRepoByName(c.Repo.Repository.ID, idStr)
 	}
 	if err != nil {
-		if models.IsErrLabelNotExist(err) {
-			c.Status(404)
-		} else {
-			c.Error(500, "GetLabelByRepoID", err)
-		}
+		c.NotFoundOrServerError("GetLabel", models.IsErrLabelNotExist, err)
 		return
 	}
 
-	c.JSON(200, label.APIFormat())
+	c.JSONSuccess(label.APIFormat())
 }
 
 func CreateLabel(c *context.APIContext, form api.CreateLabelOption) {
-	if !c.Repo.IsWriter() {
-		c.Status(403)
-		return
-	}
-
 	label := &models.Label{
 		Name:   form.Name,
 		Color:  form.Color,
 		RepoID: c.Repo.Repository.ID,
 	}
 	if err := models.NewLabels(label); err != nil {
-		c.Error(500, "NewLabel", err)
+		c.ServerError("NewLabel", err)
 		return
 	}
-	c.JSON(201, label.APIFormat())
+	c.JSON(http.StatusCreated, label.APIFormat())
 }
 
 func EditLabel(c *context.APIContext, form api.EditLabelOption) {
-	if !c.Repo.IsWriter() {
-		c.Status(403)
-		return
-	}
-
 	label, err := models.GetLabelOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
 	if err != nil {
-		if models.IsErrLabelNotExist(err) {
-			c.Status(404)
-		} else {
-			c.Error(500, "GetLabelByRepoID", err)
-		}
+		c.NotFoundOrServerError("GetLabelOfRepoByID", models.IsErrLabelNotExist, err)
 		return
 	}
 
@@ -89,22 +73,17 @@ func EditLabel(c *context.APIContext, form api.EditLabelOption) {
 		label.Color = *form.Color
 	}
 	if err := models.UpdateLabel(label); err != nil {
-		c.Handle(500, "UpdateLabel", err)
+		c.ServerError("UpdateLabel", err)
 		return
 	}
-	c.JSON(200, label.APIFormat())
+	c.JSONSuccess(label.APIFormat())
 }
 
 func DeleteLabel(c *context.APIContext) {
-	if !c.Repo.IsWriter() {
-		c.Status(403)
-		return
-	}
-
 	if err := models.DeleteLabel(c.Repo.Repository.ID, c.ParamsInt64(":id")); err != nil {
-		c.Error(500, "DeleteLabel", err)
+		c.ServerError("DeleteLabel", err)
 		return
 	}
 
-	c.Status(204)
+	c.NoContent()
 }

+ 13 - 20
routes/api/v1/repo/milestone.go

@@ -5,6 +5,7 @@
 package repo
 
 import (
+	"net/http"
 	"time"
 
 	api "github.com/gogs/go-gogs-client"
@@ -16,7 +17,7 @@ import (
 func ListMilestones(c *context.APIContext) {
 	milestones, err := models.GetMilestonesByRepoID(c.Repo.Repository.ID)
 	if err != nil {
-		c.Error(500, "GetMilestonesByRepoID", err)
+		c.ServerError("GetMilestonesByRepoID", err)
 		return
 	}
 
@@ -24,20 +25,16 @@ func ListMilestones(c *context.APIContext) {
 	for i := range milestones {
 		apiMilestones[i] = milestones[i].APIFormat()
 	}
-	c.JSON(200, &apiMilestones)
+	c.JSONSuccess(&apiMilestones)
 }
 
 func GetMilestone(c *context.APIContext) {
 	milestone, err := models.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
 	if err != nil {
-		if models.IsErrMilestoneNotExist(err) {
-			c.Status(404)
-		} else {
-			c.Error(500, "GetMilestoneByRepoID", err)
-		}
+		c.NotFoundOrServerError("GetMilestoneByRepoID", models.IsErrMilestoneNotExist, err)
 		return
 	}
-	c.JSON(200, milestone.APIFormat())
+	c.JSONSuccess(milestone.APIFormat())
 }
 
 func CreateMilestone(c *context.APIContext, form api.CreateMilestoneOption) {
@@ -54,20 +51,16 @@ func CreateMilestone(c *context.APIContext, form api.CreateMilestoneOption) {
 	}
 
 	if err := models.NewMilestone(milestone); err != nil {
-		c.Error(500, "NewMilestone", err)
+		c.ServerError("NewMilestone", err)
 		return
 	}
-	c.JSON(201, milestone.APIFormat())
+	c.JSON(http.StatusCreated, milestone.APIFormat())
 }
 
 func EditMilestone(c *context.APIContext, form api.EditMilestoneOption) {
 	milestone, err := models.GetMilestoneByRepoID(c.Repo.Repository.ID, c.ParamsInt64(":id"))
 	if err != nil {
-		if models.IsErrMilestoneNotExist(err) {
-			c.Status(404)
-		} else {
-			c.Error(500, "GetMilestoneByRepoID", err)
-		}
+		c.NotFoundOrServerError("GetMilestoneByRepoID", models.IsErrMilestoneNotExist, err)
 		return
 	}
 
@@ -83,21 +76,21 @@ func EditMilestone(c *context.APIContext, form api.EditMilestoneOption) {
 
 	if form.State != nil {
 		if err = milestone.ChangeStatus(api.STATE_CLOSED == api.StateType(*form.State)); err != nil {
-			c.Error(500, "ChangeStatus", err)
+			c.ServerError("ChangeStatus", err)
 			return
 		}
 	} else if err = models.UpdateMilestone(milestone); err != nil {
-		c.Handle(500, "UpdateMilestone", err)
+		c.ServerError("UpdateMilestone", err)
 		return
 	}
 
-	c.JSON(200, milestone.APIFormat())
+	c.JSONSuccess(milestone.APIFormat())
 }
 
 func DeleteMilestone(c *context.APIContext) {
 	if err := models.DeleteMilestoneOfRepoByID(c.Repo.Repository.ID, c.ParamsInt64(":id")); err != nil {
-		c.Error(500, "DeleteMilestoneByRepoID", err)
+		c.ServerError("DeleteMilestoneByRepoID", err)
 		return
 	}
-	c.Status(204)
+	c.NoContent()
 }

+ 41 - 52
routes/api/v1/repo/repo.go

@@ -5,6 +5,8 @@
 package repo
 
 import (
+	"fmt"
+	"net/http"
 	"path"
 
 	log "gopkg.in/clog.v1"
@@ -19,7 +21,6 @@ import (
 	"github.com/G-Node/gogs/routes/api/v1/convert"
 )
 
-// https://github.com/gogs/go-gogs-client/wiki/Repositories#search-repositories
 func Search(c *context.APIContext) {
 	opts := &models.SearchRepoOptions{
 		Keyword:  path.Base(c.Query("q")),
@@ -39,7 +40,7 @@ func Search(c *context.APIContext) {
 		} else {
 			u, err := models.GetUserByID(opts.OwnerID)
 			if err != nil {
-				c.JSON(500, map[string]interface{}{
+				c.JSON(http.StatusInternalServerError, map[string]interface{}{
 					"ok":    false,
 					"error": err.Error(),
 				})
@@ -54,7 +55,7 @@ func Search(c *context.APIContext) {
 
 	repos, count, err := models.SearchRepositoryByName(opts)
 	if err != nil {
-		c.JSON(500, map[string]interface{}{
+		c.JSON(http.StatusInternalServerError, map[string]interface{}{
 			"ok":    false,
 			"error": err.Error(),
 		})
@@ -62,7 +63,7 @@ func Search(c *context.APIContext) {
 	}
 
 	if err = models.RepositoryList(repos).LoadAttributes(); err != nil {
-		c.JSON(500, map[string]interface{}{
+		c.JSON(http.StatusInternalServerError, map[string]interface{}{
 			"ok":    false,
 			"error": err.Error(),
 		})
@@ -79,7 +80,7 @@ func Search(c *context.APIContext) {
 	}
 
 	c.SetLinkHeader(int(count), opts.PageSize)
-	c.JSON(200, map[string]interface{}{
+	c.JSONSuccess(map[string]interface{}{
 		"ok":   true,
 		"data": results,
 	})
@@ -106,12 +107,12 @@ func listUserRepositories(c *context.APIContext, username string) {
 		})
 	}
 	if err != nil {
-		c.Error(500, "GetUserRepositories", err)
+		c.ServerError("GetUserRepositories", err)
 		return
 	}
 
 	if err = models.RepositoryList(ownRepos).LoadAttributes(); err != nil {
-		c.Error(500, "LoadAttributes(ownRepos)", err)
+		c.ServerError("LoadAttributes(ownRepos)", err)
 		return
 	}
 
@@ -121,13 +122,13 @@ func listUserRepositories(c *context.APIContext, username string) {
 		for i := range ownRepos {
 			repos[i] = ownRepos[i].APIFormat(&api.Permission{true, true, true})
 		}
-		c.JSON(200, &repos)
+		c.JSONSuccess(&repos)
 		return
 	}
 
 	accessibleRepos, err := user.GetRepositoryAccesses()
 	if err != nil {
-		c.Error(500, "GetRepositoryAccesses", err)
+		c.ServerError("GetRepositoryAccesses", err)
 		return
 	}
 
@@ -147,7 +148,7 @@ func listUserRepositories(c *context.APIContext, username string) {
 		i++
 	}
 
-	c.JSON(200, &repos)
+	c.JSONSuccess(&repos)
 }
 
 func ListMyRepos(c *context.APIContext) {
@@ -176,14 +177,14 @@ func CreateUserRepo(c *context.APIContext, owner *models.User, opt api.CreateRep
 		if models.IsErrRepoAlreadyExist(err) ||
 			models.IsErrNameReserved(err) ||
 			models.IsErrNamePatternNotAllowed(err) {
-			c.Error(422, "", err)
+			c.Error(http.StatusUnprocessableEntity, "", err)
 		} else {
 			if repo != nil {
 				if err = models.DeleteRepository(c.User.ID, repo.ID); err != nil {
 					log.Error(2, "DeleteRepository: %v", err)
 				}
 			}
-			c.Error(500, "CreateRepository", err)
+			c.ServerError("CreateRepository", err)
 		}
 		return
 	}
@@ -191,11 +192,10 @@ func CreateUserRepo(c *context.APIContext, owner *models.User, opt api.CreateRep
 	c.JSON(201, repo.APIFormat(&api.Permission{true, true, true}))
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Repositories#create
 func Create(c *context.APIContext, opt api.CreateRepoOption) {
 	// Shouldn't reach this condition, but just in case.
 	if c.User.IsOrganization() {
-		c.Error(422, "", "not allowed creating repository for organization")
+		c.Error(http.StatusUnprocessableEntity, "", "not allowed creating repository for organization")
 		return
 	}
 	CreateUserRepo(c, c.User, opt)
@@ -204,22 +204,17 @@ func Create(c *context.APIContext, opt api.CreateRepoOption) {
 func CreateOrgRepo(c *context.APIContext, opt api.CreateRepoOption) {
 	org, err := models.GetOrgByName(c.Params(":org"))
 	if err != nil {
-		if errors.IsUserNotExist(err) {
-			c.Error(422, "", err)
-		} else {
-			c.Error(500, "GetOrgByName", err)
-		}
+		c.NotFoundOrServerError("GetOrgByName", errors.IsUserNotExist, err)
 		return
 	}
 
 	if !org.IsOwnedBy(c.User.ID) {
-		c.Error(403, "", "Given user is not owner of organization.")
+		c.Error(http.StatusForbidden, "", "given user is not owner of organization")
 		return
 	}
 	CreateUserRepo(c, org, opt)
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Repositories#migrate
 func Migrate(c *context.APIContext, f form.MigrateRepo) {
 	ctxUser := c.User
 	// Not equal means context user is an organization,
@@ -228,27 +223,27 @@ func Migrate(c *context.APIContext, f form.MigrateRepo) {
 		org, err := models.GetUserByID(f.Uid)
 		if err != nil {
 			if errors.IsUserNotExist(err) {
-				c.Error(422, "", err)
+				c.Error(http.StatusUnprocessableEntity, "", err)
 			} else {
-				c.Error(500, "GetUserByID", err)
+				c.Error(http.StatusInternalServerError, "GetUserByID", err)
 			}
 			return
 		} else if !org.IsOrganization() && !c.User.IsAdmin {
-			c.Error(403, "", "Given user is not an organization")
+			c.Error(http.StatusForbidden, "", "given user is not an organization")
 			return
 		}
 		ctxUser = org
 	}
 
 	if c.HasError() {
-		c.Error(422, "", c.GetErrMsg())
+		c.Error(http.StatusUnprocessableEntity, "", c.GetErrMsg())
 		return
 	}
 
 	if ctxUser.IsOrganization() && !c.User.IsAdmin {
 		// Check ownership of organization.
 		if !ctxUser.IsOwnedBy(c.User.ID) {
-			c.Error(403, "", "Given user is not owner of organization")
+			c.Error(http.StatusForbidden, "", "Given user is not owner of organization")
 			return
 		}
 	}
@@ -259,16 +254,16 @@ func Migrate(c *context.APIContext, f form.MigrateRepo) {
 			addrErr := err.(models.ErrInvalidCloneAddr)
 			switch {
 			case addrErr.IsURLError:
-				c.Error(422, "", err)
+				c.Error(http.StatusUnprocessableEntity, "", err)
 			case addrErr.IsPermissionDenied:
-				c.Error(422, "", "You are not allowed to import local repositories")
+				c.Error(http.StatusUnprocessableEntity, "", "you are not allowed to import local repositories")
 			case addrErr.IsInvalidPath:
-				c.Error(422, "", "Invalid local path, it does not exist or not a directory")
+				c.Error(http.StatusUnprocessableEntity, "", "invalid local path, it does not exist or not a directory")
 			default:
-				c.Error(500, "ParseRemoteAddr", "Unknown error type (ErrInvalidCloneAddr): "+err.Error())
+				c.ServerError("ParseRemoteAddr", fmt.Errorf("unknown error type (ErrInvalidCloneAddr): %v", err))
 			}
 		} else {
-			c.Error(500, "ParseRemoteAddr", err)
+			c.ServerError("ParseRemoteAddr", err)
 		}
 		return
 	}
@@ -288,9 +283,9 @@ func Migrate(c *context.APIContext, f form.MigrateRepo) {
 		}
 
 		if errors.IsReachLimitOfRepo(err) {
-			c.Error(422, "", err)
+			c.Error(http.StatusUnprocessableEntity, "", err)
 		} else {
-			c.Error(500, "MigrateRepository", models.HandleMirrorCredentials(err.Error(), true))
+			c.ServerError("MigrateRepository", errors.New(models.HandleMirrorCredentials(err.Error(), true)))
 		}
 		return
 	}
@@ -299,46 +294,40 @@ func Migrate(c *context.APIContext, f form.MigrateRepo) {
 	c.JSON(201, repo.APIFormat(&api.Permission{true, true, true}))
 }
 
-// FIXME: Inject to *context.APIContext
+// FIXME: inject in the handler chain
 func parseOwnerAndRepo(c *context.APIContext) (*models.User, *models.Repository) {
 	owner, err := models.GetUserByName(c.Params(":username"))
 	if err != nil {
 		if errors.IsUserNotExist(err) {
-			c.Error(422, "", err)
+			c.Error(http.StatusUnprocessableEntity, "", err)
 		} else {
-			c.Error(500, "GetUserByName", err)
+			c.ServerError("GetUserByName", err)
 		}
 		return nil, nil
 	}
 
 	repo, err := models.GetRepositoryByName(owner.ID, c.Params(":reponame"))
 	if err != nil {
-		if errors.IsRepoNotExist(err) {
-			c.Status(404)
-		} else {
-			c.Error(500, "GetRepositoryByName", err)
-		}
+		c.NotFoundOrServerError("GetRepositoryByName", errors.IsRepoNotExist, err)
 		return nil, nil
 	}
 
 	return owner, repo
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Repositories#get
 func Get(c *context.APIContext) {
 	_, repo := parseOwnerAndRepo(c)
 	if c.Written() {
 		return
 	}
 
-	c.JSON(200, repo.APIFormat(&api.Permission{
+	c.JSONSuccess(repo.APIFormat(&api.Permission{
 		Admin: c.Repo.IsAdmin(),
 		Push:  c.Repo.IsWriter(),
 		Pull:  true,
 	}))
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Repositories#delete
 func Delete(c *context.APIContext) {
 	owner, repo := parseOwnerAndRepo(c)
 	if c.Written() {
@@ -346,30 +335,30 @@ func Delete(c *context.APIContext) {
 	}
 
 	if owner.IsOrganization() && !owner.IsOwnedBy(c.User.ID) {
-		c.Error(403, "", "Given user is not owner of organization.")
+		c.Error(http.StatusForbidden, "", "given user is not owner of organization")
 		return
 	}
 
 	if err := models.DeleteRepository(owner.ID, repo.ID); err != nil {
-		c.Error(500, "DeleteRepository", err)
+		c.ServerError("DeleteRepository", err)
 		return
 	}
 
 	log.Trace("Repository deleted: %s/%s", owner.Name, repo.Name)
-	c.Status(204)
+	c.NoContent()
 }
 
 func ListForks(c *context.APIContext) {
 	forks, err := c.Repo.Repository.GetForks()
 	if err != nil {
-		c.Error(500, "GetForks", err)
+		c.ServerError("GetForks", err)
 		return
 	}
 
 	apiForks := make([]*api.Repository, len(forks))
 	for i := range forks {
 		if err := forks[i].GetOwner(); err != nil {
-			c.Error(500, "GetOwner", err)
+			c.ServerError("GetOwner", err)
 			return
 		}
 		apiForks[i] = forks[i].APIFormat(&api.Permission{
@@ -379,7 +368,7 @@ func ListForks(c *context.APIContext) {
 		})
 	}
 
-	c.JSON(200, &apiForks)
+	c.JSONSuccess(&apiForks)
 }
 
 func IssueTracker(c *context.APIContext, form api.EditIssueTrackerOption) {
@@ -417,10 +406,10 @@ func MirrorSync(c *context.APIContext) {
 	if c.Written() {
 		return
 	} else if !repo.IsMirror {
-		c.Status(404)
+		c.NotFound()
 		return
 	}
 
 	go models.MirrorQueue.Add(repo.ID)
-	c.Status(202)
+	c.Status(http.StatusAccepted)
 }

+ 6 - 6
routes/api/v1/user/app.go

@@ -5,17 +5,18 @@
 package user
 
 import (
+	"net/http"
+
 	api "github.com/gogs/go-gogs-client"
 
 	"github.com/G-Node/gogs/models"
 	"github.com/G-Node/gogs/pkg/context"
 )
 
-// https://github.com/gogs/go-gogs-client/wiki/Users#list-access-tokens-for-a-user
 func ListAccessTokens(c *context.APIContext) {
 	tokens, err := models.ListAccessTokens(c.User.ID)
 	if err != nil {
-		c.Error(500, "ListAccessTokens", err)
+		c.ServerError("ListAccessTokens", err)
 		return
 	}
 
@@ -23,18 +24,17 @@ func ListAccessTokens(c *context.APIContext) {
 	for i := range tokens {
 		apiTokens[i] = &api.AccessToken{tokens[i].Name, tokens[i].Sha1}
 	}
-	c.JSON(200, &apiTokens)
+	c.JSONSuccess(&apiTokens)
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Users#create-a-access-token
 func CreateAccessToken(c *context.APIContext, form api.CreateAccessTokenOption) {
 	t := &models.AccessToken{
 		UID:  c.User.ID,
 		Name: form.Name,
 	}
 	if err := models.NewAccessToken(t); err != nil {
-		c.Error(500, "NewAccessToken", err)
+		c.ServerError("NewAccessToken", err)
 		return
 	}
-	c.JSON(201, &api.AccessToken{t.Name, t.Sha1})
+	c.JSON(http.StatusCreated, &api.AccessToken{t.Name, t.Sha1})
 }

+ 11 - 12
routes/api/v1/user/email.go

@@ -5,6 +5,8 @@
 package user
 
 import (
+	"net/http"
+
 	api "github.com/gogs/go-gogs-client"
 
 	"github.com/G-Node/gogs/models"
@@ -13,24 +15,22 @@ import (
 	"github.com/G-Node/gogs/routes/api/v1/convert"
 )
 
-// https://github.com/gogs/go-gogs-client/wiki/Users-Emails#list-email-addresses-for-a-user
 func ListEmails(c *context.APIContext) {
 	emails, err := models.GetEmailAddresses(c.User.ID)
 	if err != nil {
-		c.Error(500, "GetEmailAddresses", err)
+		c.ServerError("GetEmailAddresses", err)
 		return
 	}
 	apiEmails := make([]*api.Email, len(emails))
 	for i := range emails {
 		apiEmails[i] = convert.ToEmail(emails[i])
 	}
-	c.JSON(200, &apiEmails)
+	c.JSONSuccess(&apiEmails)
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Users-Emails#add-email-addresses
 func AddEmail(c *context.APIContext, form api.CreateEmailOption) {
 	if len(form.Emails) == 0 {
-		c.Status(422)
+		c.Status(http.StatusUnprocessableEntity)
 		return
 	}
 
@@ -45,9 +45,9 @@ func AddEmail(c *context.APIContext, form api.CreateEmailOption) {
 
 	if err := models.AddEmailAddresses(emails); err != nil {
 		if models.IsErrEmailAlreadyUsed(err) {
-			c.Error(422, "", "Email address has been used: "+err.(models.ErrEmailAlreadyUsed).Email)
+			c.Error(http.StatusUnprocessableEntity, "", "email address has been used: "+err.(models.ErrEmailAlreadyUsed).Email)
 		} else {
-			c.Error(500, "AddEmailAddresses", err)
+			c.Error(http.StatusInternalServerError, "AddEmailAddresses", err)
 		}
 		return
 	}
@@ -56,13 +56,12 @@ func AddEmail(c *context.APIContext, form api.CreateEmailOption) {
 	for i := range emails {
 		apiEmails[i] = convert.ToEmail(emails[i])
 	}
-	c.JSON(201, &apiEmails)
+	c.JSON(http.StatusCreated, &apiEmails)
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Users-Emails#delete-email-addresses
 func DeleteEmail(c *context.APIContext, form api.CreateEmailOption) {
 	if len(form.Emails) == 0 {
-		c.Status(204)
+		c.NoContent()
 		return
 	}
 
@@ -75,8 +74,8 @@ func DeleteEmail(c *context.APIContext, form api.CreateEmailOption) {
 	}
 
 	if err := models.DeleteEmailAddresses(emails); err != nil {
-		c.Error(500, "DeleteEmailAddresses", err)
+		c.Error(http.StatusInternalServerError, "DeleteEmailAddresses", err)
 		return
 	}
-	c.Status(204)
+	c.NoContent()
 }

+ 9 - 15
routes/api/v1/user/follower.go

@@ -16,13 +16,13 @@ func responseApiUsers(c *context.APIContext, users []*models.User) {
 	for i := range users {
 		apiUsers[i] = users[i].APIFormat()
 	}
-	c.JSON(200, &apiUsers)
+	c.JSONSuccess(&apiUsers)
 }
 
 func listUserFollowers(c *context.APIContext, u *models.User) {
 	users, err := u.GetFollowers(c.QueryInt("page"))
 	if err != nil {
-		c.Error(500, "GetUserFollowers", err)
+		c.ServerError("GetUserFollowers", err)
 		return
 	}
 	responseApiUsers(c, users)
@@ -32,7 +32,6 @@ func ListMyFollowers(c *context.APIContext) {
 	listUserFollowers(c, c.User)
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Users-Followers#list-followers-of-a-user
 func ListFollowers(c *context.APIContext) {
 	u := GetUserByParams(c)
 	if c.Written() {
@@ -44,7 +43,7 @@ func ListFollowers(c *context.APIContext) {
 func listUserFollowing(c *context.APIContext, u *models.User) {
 	users, err := u.GetFollowing(c.QueryInt("page"))
 	if err != nil {
-		c.Error(500, "GetFollowing", err)
+		c.ServerError("GetFollowing", err)
 		return
 	}
 	responseApiUsers(c, users)
@@ -54,7 +53,6 @@ func ListMyFollowing(c *context.APIContext) {
 	listUserFollowing(c, c.User)
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Users-Followers#list-users-followed-by-another-user
 func ListFollowing(c *context.APIContext) {
 	u := GetUserByParams(c)
 	if c.Written() {
@@ -65,13 +63,12 @@ func ListFollowing(c *context.APIContext) {
 
 func checkUserFollowing(c *context.APIContext, u *models.User, followID int64) {
 	if u.IsFollowing(followID) {
-		c.Status(204)
+		c.NotFound()
 	} else {
-		c.Status(404)
+		c.NotFound()
 	}
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Users-Followers#check-if-you-are-following-a-user
 func CheckMyFollowing(c *context.APIContext) {
 	target := GetUserByParams(c)
 	if c.Written() {
@@ -80,7 +77,6 @@ func CheckMyFollowing(c *context.APIContext) {
 	checkUserFollowing(c, c.User, target.ID)
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Users-Followers#check-if-one-user-follows-another
 func CheckFollowing(c *context.APIContext) {
 	u := GetUserByParams(c)
 	if c.Written() {
@@ -93,28 +89,26 @@ func CheckFollowing(c *context.APIContext) {
 	checkUserFollowing(c, u, target.ID)
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Users-Followers#follow-a-user
 func Follow(c *context.APIContext) {
 	target := GetUserByParams(c)
 	if c.Written() {
 		return
 	}
 	if err := models.FollowUser(c.User.ID, target.ID); err != nil {
-		c.Error(500, "FollowUser", err)
+		c.ServerError("FollowUser", err)
 		return
 	}
-	c.Status(204)
+	c.NoContent()
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Users-Followers#unfollow-a-user
 func Unfollow(c *context.APIContext) {
 	target := GetUserByParams(c)
 	if c.Written() {
 		return
 	}
 	if err := models.UnfollowUser(c.User.ID, target.ID); err != nil {
-		c.Error(500, "UnfollowUser", err)
+		c.ServerError("UnfollowUser", err)
 		return
 	}
-	c.Status(204)
+	c.NoContent()
 }

+ 10 - 22
routes/api/v1/user/key.go

@@ -6,6 +6,7 @@ package user
 
 import (
 	api "github.com/gogs/go-gogs-client"
+	"net/http"
 
 	"github.com/G-Node/gogs/models"
 	"github.com/G-Node/gogs/models/errors"
@@ -18,11 +19,7 @@ import (
 func GetUserByParamsName(c *context.APIContext, name string) *models.User {
 	user, err := models.GetUserByName(c.Params(name))
 	if err != nil {
-		if errors.IsUserNotExist(err) {
-			c.Status(404)
-		} else {
-			c.Error(500, "GetUserByName", err)
-		}
+		c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
 		return nil
 	}
 	return user
@@ -40,7 +37,7 @@ func composePublicKeysAPILink() string {
 func listPublicKeys(c *context.APIContext, uid int64) {
 	keys, err := models.ListPublicKeys(uid)
 	if err != nil {
-		c.Error(500, "ListPublicKeys", err)
+		c.ServerError("ListPublicKeys", err)
 		return
 	}
 
@@ -50,15 +47,13 @@ func listPublicKeys(c *context.APIContext, uid int64) {
 		apiKeys[i] = convert.ToPublicKey(apiLink, keys[i])
 	}
 
-	c.JSON(200, &apiKeys)
+	c.JSONSuccess(&apiKeys)
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Users-Public-Keys#list-your-public-keys
 func ListMyPublicKeys(c *context.APIContext) {
 	listPublicKeys(c, c.User.ID)
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Users-Public-Keys#list-public-keys-for-a-user
 func ListPublicKeys(c *context.APIContext) {
 	user := GetUserByParams(c)
 	if c.Written() {
@@ -67,20 +62,15 @@ func ListPublicKeys(c *context.APIContext) {
 	listPublicKeys(c, user.ID)
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Users-Public-Keys#get-a-single-public-key
 func GetPublicKey(c *context.APIContext) {
 	key, err := models.GetPublicKeyByID(c.ParamsInt64(":id"))
 	if err != nil {
-		if models.IsErrKeyNotExist(err) {
-			c.Status(404)
-		} else {
-			c.Error(500, "GetPublicKeyByID", err)
-		}
+		c.NotFoundOrServerError("GetPublicKeyByID", models.IsErrKeyNotExist, err)
 		return
 	}
 
 	apiLink := composePublicKeysAPILink()
-	c.JSON(200, convert.ToPublicKey(apiLink, key))
+	c.JSONSuccess(convert.ToPublicKey(apiLink, key))
 }
 
 // CreateUserPublicKey creates new public key to given user by ID.
@@ -97,24 +87,22 @@ func CreateUserPublicKey(c *context.APIContext, form api.CreateKeyOption, uid in
 		return
 	}
 	apiLink := composePublicKeysAPILink()
-	c.JSON(201, convert.ToPublicKey(apiLink, key))
+	c.JSON(http.StatusCreated, convert.ToPublicKey(apiLink, key))
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Users-Public-Keys#create-a-public-key
 func CreatePublicKey(c *context.APIContext, form api.CreateKeyOption) {
 	CreateUserPublicKey(c, form, c.User.ID)
 }
 
-// https://github.com/gogs/go-gogs-client/wiki/Users-Public-Keys#delete-a-public-key
 func DeletePublicKey(c *context.APIContext) {
 	if err := models.DeletePublicKey(c.User, c.ParamsInt64(":id")); err != nil {
 		if models.IsErrKeyAccessDenied(err) {
-			c.Error(403, "", "You do not have access to this key")
+			c.Error(http.StatusForbidden, "", "you do not have access to this key")
 		} else {
-			c.Error(500, "DeletePublicKey", err)
+			c.Error(http.StatusInternalServerError, "DeletePublicKey", err)
 		}
 		return
 	}
 
-	c.Status(204)
+	c.NoContent()
 }

+ 7 - 9
routes/api/v1/user/user.go

@@ -5,6 +5,8 @@
 package user
 
 import (
+	"net/http"
+
 	"github.com/Unknwon/com"
 
 	api "github.com/gogs/go-gogs-client"
@@ -27,7 +29,7 @@ func Search(c *context.APIContext) {
 
 	users, _, err := models.SearchUserByName(opts)
 	if err != nil {
-		c.JSON(500, map[string]interface{}{
+		c.JSON(http.StatusInternalServerError, map[string]interface{}{
 			"ok":    false,
 			"error": err.Error(),
 		})
@@ -47,7 +49,7 @@ func Search(c *context.APIContext) {
 		}
 	}
 
-	c.JSON(200, map[string]interface{}{
+	c.JSONSuccess(map[string]interface{}{
 		"ok":   true,
 		"data": results,
 	})
@@ -56,20 +58,16 @@ func Search(c *context.APIContext) {
 func GetInfo(c *context.APIContext) {
 	u, err := models.GetUserByName(c.Params(":username"))
 	if err != nil {
-		if errors.IsUserNotExist(err) {
-			c.Status(404)
-		} else {
-			c.Error(500, "GetUserByName", err)
-		}
+		c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
 		return
 	}
 
 	// Hide user e-mail
 	u.Email = ""
 
-	c.JSON(200, u.APIFormat())
+	c.JSONSuccess(u.APIFormat())
 }
 
 func GetAuthenticatedUser(c *context.APIContext) {
-	c.JSON(200, c.User.APIFormat())
+	c.JSONSuccess(c.User.APIFormat())
 }

+ 1 - 0
routes/repo/http.go

@@ -130,6 +130,7 @@ func HTTPContexter() macaron.Handler {
 				return
 			}
 			token.Updated = time.Now()
+			// TODO: verify or update token.Updated in database
 
 			authUser, err = models.GetUserByID(token.UID)
 			if err != nil {

+ 1 - 1
templates/.VERSION

@@ -1 +1 @@
-0.11.87.0206.gin0001
+0.11.91.0811

+ 1 - 1
templates/base/footer.tmpl

@@ -7,7 +7,7 @@
 	<footer>
 		<div class="ui container">
 			<div class="ui center links item brand footertext">
-				<a href="http://www.g-node.org"><img class="ui mini footericon" src="https://projects.g-node.org/assets/gnode-bootstrap-theme/1.2.0-snapshot/img/gnode-icon-50x50-transparent.png"/>© G-Node, 2016-2019</a>
+				<a href="http://www.g-node.org"><img class="ui mini footericon" src="https://projects.g-node.org/assets/gnode-bootstrap-theme/1.2.0-snapshot/img/gnode-icon-50x50-transparent.png"/>© 2016-{{Year}} G-Node</a>
 				<a href="/G-Node/Info/wiki/about">About</a>
 				<a href="/G-Node/Info/wiki/imprint">Imprint</a>
 				<a href="/G-Node/Info/wiki/contact">Contact</a>

+ 2 - 5
templates/base/head.tmpl

@@ -193,13 +193,10 @@
 			</div><!-- end bar -->
 		{{end}}
 
-		{{if .HasNotice}}
+		{{if .ServerNotice}}
 			<div class="ui container grid warning message">
 				<div class="content">
-					<div class="header">
-						{{.NoticeTitle}}
-					</div>
-					<p>{{.NoticeMessage | Str2HTML}}</p>
+					{{.ServerNotice | Str2HTML}}
 				</div>
 			</div>
 		{{end}}

+ 1 - 0
vendor/github.com/gogs/go-gogs-client/repo_hook.go

@@ -114,6 +114,7 @@ var (
 type CreatePayload struct {
 	Ref           string      `json:"ref"`
 	RefType       string      `json:"ref_type"`
+	Sha           string      `json:"sha"`
 	DefaultBranch string      `json:"default_branch"`
 	Repo          *Repository `json:"repository"`
 	Sender        *User       `json:"sender"`

+ 62 - 53
vendor/github.com/jtolds/gls/context.go

@@ -5,12 +5,7 @@ import (
 	"sync"
 )
 
-const (
-	maxCallers = 64
-)
-
 var (
-	stackTagPool   = &idPool{}
 	mgrRegistry    = make(map[*ContextManager]bool)
 	mgrRegistryMtx sync.RWMutex
 )
@@ -25,7 +20,7 @@ type Values map[interface{}]interface{}
 // class of context variables. You should use NewContextManager for
 // construction.
 type ContextManager struct {
-	mtx    sync.RWMutex
+	mtx    sync.Mutex
 	values map[uint]Values
 }
 
@@ -62,63 +57,77 @@ func (m *ContextManager) SetValues(new_values Values, context_call func()) {
 		return
 	}
 
-	tags := readStackTags(1)
+	mutated_keys := make([]interface{}, 0, len(new_values))
+	mutated_vals := make(Values, len(new_values))
 
-	m.mtx.Lock()
-	values := new_values
-	for _, tag := range tags {
-		if existing_values, ok := m.values[tag]; ok {
-			// oh, we found existing values, let's make a copy
-			values = make(Values, len(existing_values)+len(new_values))
-			for key, val := range existing_values {
-				values[key] = val
-			}
-			for key, val := range new_values {
-				values[key] = val
-			}
-			break
-		}
-	}
-	new_tag := stackTagPool.Acquire()
-	m.values[new_tag] = values
-	m.mtx.Unlock()
-	defer func() {
+	EnsureGoroutineId(func(gid uint) {
 		m.mtx.Lock()
-		delete(m.values, new_tag)
+		state, found := m.values[gid]
+		if !found {
+			state = make(Values, len(new_values))
+			m.values[gid] = state
+		}
 		m.mtx.Unlock()
-		stackTagPool.Release(new_tag)
-	}()
 
-	addStackTag(new_tag, context_call)
+		for key, new_val := range new_values {
+			mutated_keys = append(mutated_keys, key)
+			if old_val, ok := state[key]; ok {
+				mutated_vals[key] = old_val
+			}
+			state[key] = new_val
+		}
+
+		defer func() {
+			if !found {
+				m.mtx.Lock()
+				delete(m.values, gid)
+				m.mtx.Unlock()
+				return
+			}
+
+			for _, key := range mutated_keys {
+				if val, ok := mutated_vals[key]; ok {
+					state[key] = val
+				} else {
+					delete(state, key)
+				}
+			}
+		}()
+
+		context_call()
+	})
 }
 
 // GetValue will return a previously set value, provided that the value was set
 // by SetValues somewhere higher up the stack. If the value is not found, ok
 // will be false.
-func (m *ContextManager) GetValue(key interface{}) (value interface{}, ok bool) {
-
-	tags := readStackTags(1)
-	m.mtx.RLock()
-	defer m.mtx.RUnlock()
-	for _, tag := range tags {
-		if values, ok := m.values[tag]; ok {
-			value, ok := values[key]
-			return value, ok
-		}
+func (m *ContextManager) GetValue(key interface{}) (
+	value interface{}, ok bool) {
+	gid, ok := GetGoroutineId()
+	if !ok {
+		return nil, false
 	}
-	return "", false
+
+	m.mtx.Lock()
+	state, found := m.values[gid]
+	m.mtx.Unlock()
+
+	if !found {
+		return nil, false
+	}
+	value, ok = state[key]
+	return value, ok
 }
 
 func (m *ContextManager) getValues() Values {
-	tags := readStackTags(2)
-	m.mtx.RLock()
-	defer m.mtx.RUnlock()
-	for _, tag := range tags {
-		if values, ok := m.values[tag]; ok {
-			return values
-		}
+	gid, ok := GetGoroutineId()
+	if !ok {
+		return nil
 	}
-	return nil
+	m.mtx.Lock()
+	state, _ := m.values[gid]
+	m.mtx.Unlock()
+	return state
 }
 
 // Go preserves ContextManager values and Goroutine-local-storage across new
@@ -131,12 +140,12 @@ func Go(cb func()) {
 	mgrRegistryMtx.RLock()
 	defer mgrRegistryMtx.RUnlock()
 
-	for mgr, _ := range mgrRegistry {
+	for mgr := range mgrRegistry {
 		values := mgr.getValues()
 		if len(values) > 0 {
-			mgr_copy := mgr
-			cb_copy := cb
-			cb = func() { mgr_copy.SetValues(values, cb_copy) }
+			cb = func(mgr *ContextManager, cb func()) func() {
+				return func() { mgr.SetValues(values, cb) }
+			}(mgr, cb)
 		}
 	}
 

+ 11 - 3
vendor/github.com/jtolds/gls/gen_sym.go

@@ -1,13 +1,21 @@
 package gls
 
+import (
+	"sync"
+)
+
 var (
-	symPool = &idPool{}
+	keyMtx     sync.Mutex
+	keyCounter uint64
 )
 
 // ContextKey is a throwaway value you can use as a key to a ContextManager
-type ContextKey struct{ id uint }
+type ContextKey struct{ id uint64 }
 
 // GenSym will return a brand new, never-before-used ContextKey
 func GenSym() ContextKey {
-	return ContextKey{id: symPool.Acquire()}
+	keyMtx.Lock()
+	defer keyMtx.Unlock()
+	keyCounter += 1
+	return ContextKey{id: keyCounter}
 }

+ 25 - 0
vendor/github.com/jtolds/gls/gid.go

@@ -0,0 +1,25 @@
+package gls
+
+var (
+	stackTagPool = &idPool{}
+)
+
+// Will return this goroutine's identifier if set. If you always need a
+// goroutine identifier, you should use EnsureGoroutineId which will make one
+// if there isn't one already.
+func GetGoroutineId() (gid uint, ok bool) {
+	return readStackTag()
+}
+
+// Will call cb with the current goroutine identifier. If one hasn't already
+// been generated, one will be created and set first. The goroutine identifier
+// might be invalid after cb returns.
+func EnsureGoroutineId(cb func(gid uint)) {
+	if gid, ok := readStackTag(); ok {
+		cb(gid)
+		return
+	}
+	gid := stackTagPool.Acquire()
+	defer stackTagPool.Release(gid)
+	addStackTag(gid, func() { cb(gid) })
+}

+ 126 - 22
vendor/github.com/jtolds/gls/stack_tags.go

@@ -3,36 +3,105 @@ package gls
 // so, basically, we're going to encode integer tags in base-16 on the stack
 
 const (
-	bitWidth = 4
+	bitWidth       = 4
+	stackBatchSize = 16
 )
 
+var (
+	pc_lookup   = make(map[uintptr]int8, 17)
+	mark_lookup [16]func(uint, func())
+)
+
+func init() {
+	setEntries := func(f func(uint, func()), v int8) {
+		var ptr uintptr
+		f(0, func() {
+			ptr = findPtr()
+		})
+		pc_lookup[ptr] = v
+		if v >= 0 {
+			mark_lookup[v] = f
+		}
+	}
+	setEntries(github_com_jtolds_gls_markS, -0x1)
+	setEntries(github_com_jtolds_gls_mark0, 0x0)
+	setEntries(github_com_jtolds_gls_mark1, 0x1)
+	setEntries(github_com_jtolds_gls_mark2, 0x2)
+	setEntries(github_com_jtolds_gls_mark3, 0x3)
+	setEntries(github_com_jtolds_gls_mark4, 0x4)
+	setEntries(github_com_jtolds_gls_mark5, 0x5)
+	setEntries(github_com_jtolds_gls_mark6, 0x6)
+	setEntries(github_com_jtolds_gls_mark7, 0x7)
+	setEntries(github_com_jtolds_gls_mark8, 0x8)
+	setEntries(github_com_jtolds_gls_mark9, 0x9)
+	setEntries(github_com_jtolds_gls_markA, 0xa)
+	setEntries(github_com_jtolds_gls_markB, 0xb)
+	setEntries(github_com_jtolds_gls_markC, 0xc)
+	setEntries(github_com_jtolds_gls_markD, 0xd)
+	setEntries(github_com_jtolds_gls_markE, 0xe)
+	setEntries(github_com_jtolds_gls_markF, 0xf)
+}
+
 func addStackTag(tag uint, context_call func()) {
 	if context_call == nil {
 		return
 	}
-	markS(tag, context_call)
+	github_com_jtolds_gls_markS(tag, context_call)
 }
 
-func markS(tag uint, cb func()) { _m(tag, cb) }
-func mark0(tag uint, cb func()) { _m(tag, cb) }
-func mark1(tag uint, cb func()) { _m(tag, cb) }
-func mark2(tag uint, cb func()) { _m(tag, cb) }
-func mark3(tag uint, cb func()) { _m(tag, cb) }
-func mark4(tag uint, cb func()) { _m(tag, cb) }
-func mark5(tag uint, cb func()) { _m(tag, cb) }
-func mark6(tag uint, cb func()) { _m(tag, cb) }
-func mark7(tag uint, cb func()) { _m(tag, cb) }
-func mark8(tag uint, cb func()) { _m(tag, cb) }
-func mark9(tag uint, cb func()) { _m(tag, cb) }
-func markA(tag uint, cb func()) { _m(tag, cb) }
-func markB(tag uint, cb func()) { _m(tag, cb) }
-func markC(tag uint, cb func()) { _m(tag, cb) }
-func markD(tag uint, cb func()) { _m(tag, cb) }
-func markE(tag uint, cb func()) { _m(tag, cb) }
-func markF(tag uint, cb func()) { _m(tag, cb) }
-
-var pc_lookup = make(map[uintptr]int8, 17)
-var mark_lookup [16]func(uint, func())
+// these private methods are named this horrendous name so gopherjs support
+// is easier. it shouldn't add any runtime cost in non-js builds.
+
+//go:noinline
+func github_com_jtolds_gls_markS(tag uint, cb func()) { _m(tag, cb) }
+
+//go:noinline
+func github_com_jtolds_gls_mark0(tag uint, cb func()) { _m(tag, cb) }
+
+//go:noinline
+func github_com_jtolds_gls_mark1(tag uint, cb func()) { _m(tag, cb) }
+
+//go:noinline
+func github_com_jtolds_gls_mark2(tag uint, cb func()) { _m(tag, cb) }
+
+//go:noinline
+func github_com_jtolds_gls_mark3(tag uint, cb func()) { _m(tag, cb) }
+
+//go:noinline
+func github_com_jtolds_gls_mark4(tag uint, cb func()) { _m(tag, cb) }
+
+//go:noinline
+func github_com_jtolds_gls_mark5(tag uint, cb func()) { _m(tag, cb) }
+
+//go:noinline
+func github_com_jtolds_gls_mark6(tag uint, cb func()) { _m(tag, cb) }
+
+//go:noinline
+func github_com_jtolds_gls_mark7(tag uint, cb func()) { _m(tag, cb) }
+
+//go:noinline
+func github_com_jtolds_gls_mark8(tag uint, cb func()) { _m(tag, cb) }
+
+//go:noinline
+func github_com_jtolds_gls_mark9(tag uint, cb func()) { _m(tag, cb) }
+
+//go:noinline
+func github_com_jtolds_gls_markA(tag uint, cb func()) { _m(tag, cb) }
+
+//go:noinline
+func github_com_jtolds_gls_markB(tag uint, cb func()) { _m(tag, cb) }
+
+//go:noinline
+func github_com_jtolds_gls_markC(tag uint, cb func()) { _m(tag, cb) }
+
+//go:noinline
+func github_com_jtolds_gls_markD(tag uint, cb func()) { _m(tag, cb) }
+
+//go:noinline
+func github_com_jtolds_gls_markE(tag uint, cb func()) { _m(tag, cb) }
+
+//go:noinline
+func github_com_jtolds_gls_markF(tag uint, cb func()) { _m(tag, cb) }
 
 func _m(tag_remainder uint, cb func()) {
 	if tag_remainder == 0 {
@@ -41,3 +110,38 @@ func _m(tag_remainder uint, cb func()) {
 		mark_lookup[tag_remainder&0xf](tag_remainder>>bitWidth, cb)
 	}
 }
+
+func readStackTag() (tag uint, ok bool) {
+	var current_tag uint
+	offset := 0
+	for {
+		batch, next_offset := getStack(offset, stackBatchSize)
+		for _, pc := range batch {
+			val, ok := pc_lookup[pc]
+			if !ok {
+				continue
+			}
+			if val < 0 {
+				return current_tag, true
+			}
+			current_tag <<= bitWidth
+			current_tag += uint(val)
+		}
+		if next_offset == 0 {
+			break
+		}
+		offset = next_offset
+	}
+	return 0, false
+}
+
+func (m *ContextManager) preventInlining() {
+	// dunno if findPtr or getStack are likely to get inlined in a future release
+	// of go, but if they are inlined and their callers are inlined, that could
+	// hork some things. let's do our best to explain to the compiler that we
+	// really don't want those two functions inlined by saying they could change
+	// at any time. assumes preventInlining doesn't get compiled out.
+	// this whole thing is probably overkill.
+	findPtr = m.values[0][0].(func() uintptr)
+	getStack = m.values[0][1].(func(int, int) ([]uintptr, int))
+}

+ 51 - 77
vendor/github.com/jtolds/gls/stack_tags_js.go

@@ -2,100 +2,74 @@
 
 package gls
 
-// This file is used for GopherJS builds, which don't have normal runtime support
+// This file is used for GopherJS builds, which don't have normal runtime
+// stack trace support
 
 import (
-	"regexp"
 	"strconv"
 	"strings"
 
 	"github.com/gopherjs/gopherjs/js"
 )
 
-var stackRE = regexp.MustCompile("\\s+at (\\S*) \\([^:]+:(\\d+):(\\d+)")
+const (
+	jsFuncNamePrefix = "github_com_jtolds_gls_mark"
+)
 
-func findPtr() uintptr {
-	jsStack := js.Global.Get("Error").New().Get("stack").Call("split", "\n")
-	for i := 1; i < jsStack.Get("length").Int(); i++ {
-		item := jsStack.Index(i).String()
-		matches := stackRE.FindAllStringSubmatch(item, -1)
-		if matches == nil {
-			return 0
+func jsMarkStack() (f []uintptr) {
+	lines := strings.Split(
+		js.Global.Get("Error").New().Get("stack").String(), "\n")
+	f = make([]uintptr, 0, len(lines))
+	for i, line := range lines {
+		line = strings.TrimSpace(line)
+		if line == "" {
+			continue
 		}
-		pkgPath := matches[0][1]
-		if strings.HasPrefix(pkgPath, "$packages.github.com/jtolds/gls.mark") {
-			line, _ := strconv.Atoi(matches[0][2])
-			char, _ := strconv.Atoi(matches[0][3])
-			x := (uintptr(line) << 16) | uintptr(char)
-			return x
+		if i == 0 {
+			if line != "Error" {
+				panic("didn't understand js stack trace")
+			}
+			continue
+		}
+		fields := strings.Fields(line)
+		if len(fields) < 2 || fields[0] != "at" {
+			panic("didn't understand js stack trace")
 		}
-	}
-
-	return 0
-}
 
-func init() {
-	setEntries := func(f func(uint, func()), v int8) {
-		var ptr uintptr
-		f(0, func() {
-			ptr = findPtr()
-		})
-		pc_lookup[ptr] = v
-		if v >= 0 {
-			mark_lookup[v] = f
+		pos := strings.Index(fields[1], jsFuncNamePrefix)
+		if pos < 0 {
+			continue
+		}
+		pos += len(jsFuncNamePrefix)
+		if pos >= len(fields[1]) {
+			panic("didn't understand js stack trace")
+		}
+		char := string(fields[1][pos])
+		switch char {
+		case "S":
+			f = append(f, uintptr(0))
+		default:
+			val, err := strconv.ParseUint(char, 16, 8)
+			if err != nil {
+				panic("didn't understand js stack trace")
+			}
+			f = append(f, uintptr(val)+1)
 		}
 	}
-	setEntries(markS, -0x1)
-	setEntries(mark0, 0x0)
-	setEntries(mark1, 0x1)
-	setEntries(mark2, 0x2)
-	setEntries(mark3, 0x3)
-	setEntries(mark4, 0x4)
-	setEntries(mark5, 0x5)
-	setEntries(mark6, 0x6)
-	setEntries(mark7, 0x7)
-	setEntries(mark8, 0x8)
-	setEntries(mark9, 0x9)
-	setEntries(markA, 0xa)
-	setEntries(markB, 0xb)
-	setEntries(markC, 0xc)
-	setEntries(markD, 0xd)
-	setEntries(markE, 0xe)
-	setEntries(markF, 0xf)
+	return f
 }
 
-func currentStack(skip int) (stack []uintptr) {
-	jsStack := js.Global.Get("Error").New().Get("stack").Call("split", "\n")
-	for i := skip + 2; i < jsStack.Get("length").Int(); i++ {
-		item := jsStack.Index(i).String()
-		matches := stackRE.FindAllStringSubmatch(item, -1)
-		if matches == nil {
-			return stack
+// variables to prevent inlining
+var (
+	findPtr = func() uintptr {
+		funcs := jsMarkStack()
+		if len(funcs) == 0 {
+			panic("failed to find function pointer")
 		}
-		line, _ := strconv.Atoi(matches[0][2])
-		char, _ := strconv.Atoi(matches[0][3])
-		x := (uintptr(line) << 16) | uintptr(char)&0xffff
-		stack = append(stack, x)
+		return funcs[0]
 	}
 
-	return stack
-}
-
-func readStackTags(skip int) (tags []uint) {
-	stack := currentStack(skip)
-	var current_tag uint
-	for _, pc := range stack {
-		val, ok := pc_lookup[pc]
-		if !ok {
-			continue
-		}
-		if val < 0 {
-			tags = append(tags, current_tag)
-			current_tag = 0
-			continue
-		}
-		current_tag <<= bitWidth
-		current_tag += uint(val)
+	getStack = func(offset, amount int) (stack []uintptr, next_offset int) {
+		return jsMarkStack(), 0
 	}
-	return
-}
+)

+ 16 - 47
vendor/github.com/jtolds/gls/stack_tags_main.go

@@ -2,60 +2,29 @@
 
 package gls
 
-// This file is used for standard Go builds, which have the expected runtime support
+// This file is used for standard Go builds, which have the expected runtime
+// support
 
 import (
-	"reflect"
 	"runtime"
 )
 
-func init() {
-	setEntries := func(f func(uint, func()), v int8) {
-		pc_lookup[reflect.ValueOf(f).Pointer()] = v
-		if v >= 0 {
-			mark_lookup[v] = f
+var (
+	findPtr = func() uintptr {
+		var pc [1]uintptr
+		n := runtime.Callers(4, pc[:])
+		if n != 1 {
+			panic("failed to find function pointer")
 		}
+		return pc[0]
 	}
-	setEntries(markS, -0x1)
-	setEntries(mark0, 0x0)
-	setEntries(mark1, 0x1)
-	setEntries(mark2, 0x2)
-	setEntries(mark3, 0x3)
-	setEntries(mark4, 0x4)
-	setEntries(mark5, 0x5)
-	setEntries(mark6, 0x6)
-	setEntries(mark7, 0x7)
-	setEntries(mark8, 0x8)
-	setEntries(mark9, 0x9)
-	setEntries(markA, 0xa)
-	setEntries(markB, 0xb)
-	setEntries(markC, 0xc)
-	setEntries(markD, 0xd)
-	setEntries(markE, 0xe)
-	setEntries(markF, 0xf)
-}
 
-func currentStack(skip int) []uintptr {
-	stack := make([]uintptr, maxCallers)
-	return stack[:runtime.Callers(3+skip, stack)]
-}
-
-func readStackTags(skip int) (tags []uint) {
-	stack := currentStack(skip)
-	var current_tag uint
-	for _, pc := range stack {
-		pc = runtime.FuncForPC(pc).Entry()
-		val, ok := pc_lookup[pc]
-		if !ok {
-			continue
-		}
-		if val < 0 {
-			tags = append(tags, current_tag)
-			current_tag = 0
-			continue
+	getStack = func(offset, amount int) (stack []uintptr, next_offset int) {
+		stack = make([]uintptr, amount)
+		stack = stack[:runtime.Callers(offset, stack)]
+		if len(stack) < amount {
+			return stack, 0
 		}
-		current_tag <<= bitWidth
-		current_tag += uint(val)
+		return stack, offset + len(stack)
 	}
-	return
-}
+)

+ 12 - 0
vendor/github.com/smartystreets/assertions/CONTRIBUTING.md

@@ -0,0 +1,12 @@
+# Contributing
+
+In general, the code posted to the [SmartyStreets github organization](https://github.com/smartystreets) is created to solve specific problems at SmartyStreets that are ancillary to our core products in the address verification industry and may or may not be useful to other organizations or developers. Our reason for posting said code isn't necessarily to solicit feedback or contributions from the community but more as a showcase of some of the approaches to solving problems we have adopted.
+
+Having stated that, we do consider issues raised by other githubbers as well as contributions submitted via pull requests. When submitting such a pull request, please follow these guidelines:
+
+- _Look before you leap:_ If the changes you plan to make are significant, it's in everyone's best interest for you to discuss them with a SmartyStreets team member prior to opening a pull request.
+- _License and ownership:_ If modifying the `LICENSE.md` file, limit your changes to fixing typographical mistakes. Do NOT modify the actual terms in the license or the copyright by **SmartyStreets, LLC**. Code submitted to SmartyStreets projects becomes property of SmartyStreets and must be compatible with the associated license.
+- _Testing:_ If the code you are submitting resides in packages/modules covered by automated tests, be sure to add passing tests that cover your changes and assert expected behavior and state. Submit the additional test cases as part of your change set.
+- _Style:_ Match your approach to **naming** and **formatting** with the surrounding code. Basically, the code you submit shouldn't stand out.
+  - "Naming" refers to such constructs as variables, methods, functions, classes, structs, interfaces, packages, modules, directories, files, etc...
+  - "Formatting" refers to such constructs as whitespace, horizontal line length, vertical function length, vertical file length, indentation, curly braces, etc...

+ 1 - 1
vendor/github.com/smartystreets/assertions/LICENSE.md

@@ -1,4 +1,4 @@
-Copyright (c) 2015 SmartyStreets, LLC
+Copyright (c) 2016 SmartyStreets, LLC
 
 Permission is hereby granted, free of charge, to any person obtaining a copy 
 of this software and associated documentation files (the "Software"), to deal 

+ 11 - 0
vendor/github.com/smartystreets/assertions/Makefile

@@ -0,0 +1,11 @@
+#!/usr/bin/make -f
+
+test:
+	go test -timeout=1s -short ./...
+
+compile:
+	go build ./...
+
+build: test compile
+
+.PHONY: test compile build

+ 50 - 4
vendor/github.com/smartystreets/assertions/README.md

@@ -1,3 +1,5 @@
+[![Build Status](https://travis-ci.org/smartystreets/assertions.svg?branch=master)](https://travis-ci.org/smartystreets/assertions)
+
 # assertions
 --
     import "github.com/smartystreets/assertions"
@@ -8,6 +10,8 @@ referenced in goconvey's `convey` package
 (github.com/smartystreets/gunit) for use with the So(...) method. They can also
 be used in traditional Go test functions and even in applications.
 
+https://smartystreets.com
+
 Many of the assertions lean heavily on work done by Aaron Jacobs in his
 excellent oglematchers library. (https://github.com/jacobsa/oglematchers) The
 ShouldResemble assertion leans heavily on work done by Daniel Jacques in his
@@ -25,7 +29,7 @@ the assertions in this package from the convey package JSON results are very
 helpful and can be rendered in a DIFF view. In that case, this function will be
 called with a true value to enable the JSON serialization. By default, the
 assertions in this package will not serializer a JSON result, making standalone
-ussage more convenient.
+usage more convenient.
 
 #### func  ShouldAlmostEqual
 
@@ -67,7 +71,7 @@ to "".
 ```go
 func ShouldBeChronological(actual interface{}, expected ...interface{}) string
 ```
-ShouldBeChronological receives a []time.Time slice and asserts that the are in
+ShouldBeChronological receives a []time.Time slice and asserts that they are in
 chronological order starting with the first time.Time as the earliest.
 
 #### func  ShouldBeEmpty
@@ -79,6 +83,15 @@ ShouldBeEmpty receives a single parameter (actual) and determines whether or not
 calling len(actual) would return `0`. It obeys the rules specified by the len
 function for determining length: http://golang.org/pkg/builtin/#len
 
+#### func  ShouldBeError
+
+```go
+func ShouldBeError(actual interface{}, expected ...interface{}) string
+```
+ShouldBeError asserts that the first argument implements the error interface. It
+also compares the first argument against the second argument if provided (which
+must be an error message string or another error value).
+
 #### func  ShouldBeFalse
 
 ```go
@@ -187,7 +200,19 @@ ends with the second.
 ```go
 func ShouldEqual(actual interface{}, expected ...interface{}) string
 ```
-ShouldEqual receives exactly two parameters and does an equality check.
+ShouldEqual receives exactly two parameters and does an equality check using the
+following semantics: 1. If the expected and actual values implement an Equal
+method in the form `func (this T) Equal(that T) bool` then call the method. If
+true, they are equal. 2. The expected and actual values are judged equal or not
+by oglematchers.Equals.
+
+#### func  ShouldEqualJSON
+
+```go
+func ShouldEqualJSON(actual interface{}, expected ...interface{}) string
+```
+ShouldEqualJSON receives exactly two parameters and does an equality check by
+marshalling to JSON
 
 #### func  ShouldEqualTrimSpace
 
@@ -322,6 +347,14 @@ func ShouldNotBeBlank(actual interface{}, expected ...interface{}) string
 ShouldNotBeBlank receives exactly 1 string parameter and ensures that it is
 equal to "".
 
+#### func  ShouldNotBeChronological
+
+```go
+func ShouldNotBeChronological(actual interface{}, expected ...interface{}) string
+```
+ShouldNotBeChronological receives a []time.Time slice and asserts that they are
+NOT in chronological order.
+
 #### func  ShouldNotBeEmpty
 
 ```go
@@ -349,6 +382,14 @@ func ShouldNotBeNil(actual interface{}, expected ...interface{}) string
 ```
 ShouldNotBeNil receives a single parameter and ensures that it is not nil.
 
+#### func  ShouldNotBeZeroValue
+
+```go
+func ShouldNotBeZeroValue(actual interface{}, expected ...interface{}) string
+```
+ShouldBeZeroValue receives a single parameter and ensures that it is NOT the Go
+equivalent of the default value, or "zero" value.
+
 #### func  ShouldNotContain
 
 ```go
@@ -386,7 +427,8 @@ does not end with the second.
 ```go
 func ShouldNotEqual(actual interface{}, expected ...interface{}) string
 ```
-ShouldNotEqual receives exactly two parameters and does an inequality check.
+ShouldNotEqual receives exactly two parameters and does an inequality check. See
+ShouldEqual for details on how equality is determined.
 
 #### func  ShouldNotHappenOnOrBetween
 
@@ -521,6 +563,10 @@ Example:
          log.Println(message)
     }
 
+For an alternative implementation of So (that provides more flexible return
+options) see the `So` function in the package at
+github.com/smartystreets/assertions/assert.
+
 #### type Assertion
 
 ```go

+ 0 - 3
vendor/github.com/smartystreets/assertions/assertions.goconvey

@@ -1,3 +0,0 @@
-#ignore
--timeout=1s
--coverpkg=github.com/smartystreets/assertions,github.com/smartystreets/assertions/internal/oglematchers

+ 2 - 2
vendor/github.com/smartystreets/assertions/collections.go

@@ -227,7 +227,7 @@ func ShouldHaveLength(actual interface{}, expected ...interface{}) string {
 		if int64(value.Len()) == expectedLen {
 			return success
 		} else {
-			return fmt.Sprintf(shouldHaveHadLength, actual, value.Len(), expectedLen)
+			return fmt.Sprintf(shouldHaveHadLength, expectedLen, value.Len(), actual)
 		}
 	case reflect.Ptr:
 		elem := value.Elem()
@@ -236,7 +236,7 @@ func ShouldHaveLength(actual interface{}, expected ...interface{}) string {
 			if int64(elem.Len()) == expectedLen {
 				return success
 			} else {
-				return fmt.Sprintf(shouldHaveHadLength, actual, elem.Len(), expectedLen)
+				return fmt.Sprintf(shouldHaveHadLength, expectedLen, elem.Len(), actual)
 			}
 		}
 	}

+ 5 - 1
vendor/github.com/smartystreets/assertions/doc.go

@@ -5,6 +5,8 @@
 // They can also be used in traditional Go test functions and even in
 // applications.
 //
+// https://smartystreets.com
+//
 // Many of the assertions lean heavily on work done by Aaron Jacobs in his excellent oglematchers library.
 // (https://github.com/jacobsa/oglematchers)
 // The ShouldResemble assertion leans heavily on work done by Daniel Jacques in his very helpful go-render library.
@@ -26,7 +28,7 @@ var serializer Serializer = new(noopSerializer)
 // are very helpful and can be rendered in a DIFF view. In that case, this function
 // will be called with a true value to enable the JSON serialization. By default,
 // the assertions in this package will not serializer a JSON result, making
-// standalone ussage more convenient.
+// standalone usage more convenient.
 func GoConveyMode(yes bool) {
 	if yes {
 		serializer = newSerializer()
@@ -82,6 +84,8 @@ func (this *Assertion) So(actual interface{}, assert assertion, expected ...inte
 //        log.Println(message)
 //   }
 //
+// For an alternative implementation of So (that provides more flexible return options)
+// see the `So` function in the package at github.com/smartystreets/assertions/assert.
 func So(actual interface{}, assert assertion, expected ...interface{}) (bool, string) {
 	if result := so(actual, assert, expected...); len(result) == 0 {
 		return true, result

+ 75 - 0
vendor/github.com/smartystreets/assertions/equal_method.go

@@ -0,0 +1,75 @@
+package assertions
+
+import "reflect"
+
+type equalityMethodSpecification struct {
+	a interface{}
+	b interface{}
+
+	aType reflect.Type
+	bType reflect.Type
+
+	equalMethod reflect.Value
+}
+
+func newEqualityMethodSpecification(a, b interface{}) *equalityMethodSpecification {
+	return &equalityMethodSpecification{
+		a: a,
+		b: b,
+	}
+}
+
+func (this *equalityMethodSpecification) IsSatisfied() bool {
+	if !this.bothAreSameType() {
+		return false
+	}
+	if !this.typeHasEqualMethod() {
+		return false
+	}
+	if !this.equalMethodReceivesSameTypeForComparison() {
+		return false
+	}
+	if !this.equalMethodReturnsBool() {
+		return false
+	}
+	return true
+}
+
+func (this *equalityMethodSpecification) bothAreSameType() bool {
+	this.aType = reflect.TypeOf(this.a)
+	if this.aType == nil {
+		return false
+	}
+	if this.aType.Kind() == reflect.Ptr {
+		this.aType = this.aType.Elem()
+	}
+	this.bType = reflect.TypeOf(this.b)
+	return this.aType == this.bType
+}
+func (this *equalityMethodSpecification) typeHasEqualMethod() bool {
+	aInstance := reflect.ValueOf(this.a)
+	this.equalMethod = aInstance.MethodByName("Equal")
+	return this.equalMethod != reflect.Value{}
+}
+
+func (this *equalityMethodSpecification) equalMethodReceivesSameTypeForComparison() bool {
+	signature := this.equalMethod.Type()
+	return signature.NumIn() == 1 && signature.In(0) == this.aType
+}
+
+func (this *equalityMethodSpecification) equalMethodReturnsBool() bool {
+	signature := this.equalMethod.Type()
+	return signature.NumOut() == 1 && signature.Out(0) == reflect.TypeOf(true)
+}
+
+func (this *equalityMethodSpecification) AreEqual() bool {
+	a := reflect.ValueOf(this.a)
+	b := reflect.ValueOf(this.b)
+	return areEqual(a, b) && areEqual(b, a)
+}
+func areEqual(receiver reflect.Value, argument reflect.Value) bool {
+	equalMethod := receiver.MethodByName("Equal")
+	argumentList := []reflect.Value{argument}
+	result := equalMethod.Call(argumentList)
+	return result[0].Bool()
+}

+ 77 - 26
vendor/github.com/smartystreets/assertions/equality.go

@@ -1,20 +1,22 @@
 package assertions
 
 import (
+	"encoding/json"
 	"errors"
 	"fmt"
 	"math"
 	"reflect"
 	"strings"
 
-	"github.com/smartystreets/assertions/internal/oglematchers"
 	"github.com/smartystreets/assertions/internal/go-render/render"
+	"github.com/smartystreets/assertions/internal/oglematchers"
 )
 
-// default acceptable delta for ShouldAlmostEqual
-const defaultDelta = 0.0000000001
-
-// ShouldEqual receives exactly two parameters and does an equality check.
+// ShouldEqual receives exactly two parameters and does an equality check
+// using the following semantics:
+// 1. If the expected and actual values implement an Equal method in the form
+// `func (this T) Equal(that T) bool` then call the method. If true, they are equal.
+// 2. The expected and actual values are judged equal or not by oglematchers.Equals.
 func ShouldEqual(actual interface{}, expected ...interface{}) string {
 	if message := need(1, expected); message != success {
 		return message
@@ -24,27 +26,35 @@ func ShouldEqual(actual interface{}, expected ...interface{}) string {
 func shouldEqual(actual, expected interface{}) (message string) {
 	defer func() {
 		if r := recover(); r != nil {
-			message = serializer.serialize(expected, actual, fmt.Sprintf(shouldHaveBeenEqual, expected, actual))
-			return
+			message = serializer.serialize(expected, actual, composeEqualityMismatchMessage(expected, actual))
 		}
 	}()
 
-	if matchError := oglematchers.Equals(expected).Matches(actual); matchError != nil {
-		expectedSyntax := fmt.Sprintf("%v", expected)
-		actualSyntax := fmt.Sprintf("%v", actual)
-		if expectedSyntax == actualSyntax && reflect.TypeOf(expected) != reflect.TypeOf(actual) {
-			message = fmt.Sprintf(shouldHaveBeenEqualTypeMismatch, expected, expected, actual, actual)
-		} else {
-			message = fmt.Sprintf(shouldHaveBeenEqual, expected, actual)
-		}
-		message = serializer.serialize(expected, actual, message)
-		return
+	if spec := newEqualityMethodSpecification(expected, actual); spec.IsSatisfied() && spec.AreEqual() {
+		return success
+	} else if matchError := oglematchers.Equals(expected).Matches(actual); matchError == nil {
+		return success
 	}
 
-	return success
+	return serializer.serialize(expected, actual, composeEqualityMismatchMessage(expected, actual))
+}
+func composeEqualityMismatchMessage(expected, actual interface{}) string {
+	var (
+		renderedExpected = fmt.Sprintf("%v", expected)
+		renderedActual   = fmt.Sprintf("%v", actual)
+	)
+
+	if renderedExpected != renderedActual {
+		return fmt.Sprintf(shouldHaveBeenEqual+composePrettyDiff(renderedExpected, renderedActual), expected, actual)
+	} else if reflect.TypeOf(expected) != reflect.TypeOf(actual) {
+		return fmt.Sprintf(shouldHaveBeenEqualTypeMismatch, expected, expected, actual, actual)
+	} else {
+		return fmt.Sprintf(shouldHaveBeenEqualNoResemblance, renderedExpected)
+	}
 }
 
 // ShouldNotEqual receives exactly two parameters and does an inequality check.
+// See ShouldEqual for details on how equality is determined.
 func ShouldNotEqual(actual interface{}, expected ...interface{}) string {
 	if fail := need(1, expected); fail != success {
 		return fail
@@ -95,7 +105,7 @@ func cleanAlmostEqualInput(actual interface{}, expected ...interface{}) (float64
 		delta, err := getFloat(expected[1])
 
 		if err != nil {
-			return 0.0, 0.0, 0.0, "delta must be a numerical type"
+			return 0.0, 0.0, 0.0, "The delta value " + err.Error()
 		}
 
 		deltaFloat = delta
@@ -104,15 +114,13 @@ func cleanAlmostEqualInput(actual interface{}, expected ...interface{}) (float64
 	}
 
 	actualFloat, err := getFloat(actual)
-
 	if err != nil {
-		return 0.0, 0.0, 0.0, err.Error()
+		return 0.0, 0.0, 0.0, "The actual value " + err.Error()
 	}
 
 	expectedFloat, err := getFloat(expected[0])
-
 	if err != nil {
-		return 0.0, 0.0, 0.0, err.Error()
+		return 0.0, 0.0, 0.0, "The comparison value " + err.Error()
 	}
 
 	return actualFloat, expectedFloat, deltaFloat, ""
@@ -139,8 +147,36 @@ func getFloat(num interface{}) (float64, error) {
 		numKind == reflect.Float64 {
 		return numValue.Float(), nil
 	} else {
-		return 0.0, errors.New("must be a numerical type, but was " + numKind.String())
+		return 0.0, errors.New("must be a numerical type, but was: " + numKind.String())
+	}
+}
+
+// ShouldEqualJSON receives exactly two parameters and does an equality check by marshalling to JSON
+func ShouldEqualJSON(actual interface{}, expected ...interface{}) string {
+	if message := need(1, expected); message != success {
+		return message
 	}
+
+	expectedString, expectedErr := remarshal(expected[0].(string))
+	if expectedErr != nil {
+		return "Expected value not valid JSON: " + expectedErr.Error()
+	}
+
+	actualString, actualErr := remarshal(actual.(string))
+	if actualErr != nil {
+		return "Actual value not valid JSON: " + actualErr.Error()
+	}
+
+	return ShouldEqual(actualString, expectedString)
+}
+func remarshal(value string) (string, error) {
+	var structured interface{}
+	err := json.Unmarshal([]byte(value), &structured)
+	if err != nil {
+		return "", err
+	}
+	canonical, _ := json.Marshal(structured)
+	return string(canonical), nil
 }
 
 // ShouldResemble receives exactly two parameters and does a deep equal check (see reflect.DeepEqual)
@@ -150,8 +186,10 @@ func ShouldResemble(actual interface{}, expected ...interface{}) string {
 	}
 
 	if matchError := oglematchers.DeepEquals(expected[0]).Matches(actual); matchError != nil {
-		return serializer.serializeDetailed(expected[0], actual,
-			fmt.Sprintf(shouldHaveResembled, render.Render(expected[0]), render.Render(actual)))
+		renderedExpected, renderedActual := render.Render(expected[0]), render.Render(actual)
+		message := fmt.Sprintf(shouldHaveResembled, renderedExpected, renderedActual) +
+			composePrettyDiff(renderedExpected, renderedActual)
+		return serializer.serializeDetailed(expected[0], actual, message)
 	}
 
 	return success
@@ -278,3 +316,16 @@ func ShouldBeZeroValue(actual interface{}, expected ...interface{}) string {
 	}
 	return success
 }
+
+// ShouldBeZeroValue receives a single parameter and ensures that it is NOT
+// the Go equivalent of the default value, or "zero" value.
+func ShouldNotBeZeroValue(actual interface{}, expected ...interface{}) string {
+	if fail := need(0, expected); fail != success {
+		return fail
+	}
+	zeroVal := reflect.Zero(reflect.TypeOf(actual)).Interface()
+	if reflect.DeepEqual(zeroVal, actual) {
+		return serializer.serialize(zeroVal, actual, fmt.Sprintf(shouldNotHaveBeenZeroValue, actual))
+	}
+	return success
+}

+ 37 - 0
vendor/github.com/smartystreets/assertions/equality_diff.go

@@ -0,0 +1,37 @@
+package assertions
+
+import (
+	"fmt"
+
+	"github.com/smartystreets/assertions/internal/go-diff/diffmatchpatch"
+)
+
+func composePrettyDiff(expected, actual string) string {
+	diff := diffmatchpatch.New()
+	diffs := diff.DiffMain(expected, actual, false)
+	if prettyDiffIsLikelyToBeHelpful(diffs) {
+		return fmt.Sprintf("\nDiff:     '%s'", diff.DiffPrettyText(diffs))
+	}
+	return ""
+}
+
+// prettyDiffIsLikelyToBeHelpful returns true if the diff listing contains
+// more 'equal' segments than 'deleted'/'inserted' segments.
+func prettyDiffIsLikelyToBeHelpful(diffs []diffmatchpatch.Diff) bool {
+	equal, deleted, inserted := measureDiffTypeLengths(diffs)
+	return equal > deleted && equal > inserted
+}
+
+func measureDiffTypeLengths(diffs []diffmatchpatch.Diff) (equal, deleted, inserted int) {
+	for _, segment := range diffs {
+		switch segment.Type {
+		case diffmatchpatch.DiffEqual:
+			equal += len(segment.Text)
+		case diffmatchpatch.DiffDelete:
+			deleted += len(segment.Text)
+		case diffmatchpatch.DiffInsert:
+			inserted += len(segment.Text)
+		}
+	}
+	return equal, deleted, inserted
+}

+ 9 - 1
vendor/github.com/smartystreets/assertions/filter.go

@@ -6,6 +6,7 @@ const (
 	success                = ""
 	needExactValues        = "This assertion requires exactly %d comparison values (you provided %d)."
 	needNonEmptyCollection = "This assertion requires at least 1 comparison value (you provided 0)."
+	needFewerValues        = "This assertion allows %d or fewer comparison values (you provided %d)."
 )
 
 func need(needed int, expected []interface{}) string {
@@ -16,8 +17,15 @@ func need(needed int, expected []interface{}) string {
 }
 
 func atLeast(minimum int, expected []interface{}) string {
-	if len(expected) < 1 {
+	if len(expected) < minimum {
 		return needNonEmptyCollection
 	}
 	return success
 }
+
+func atMost(max int, expected []interface{}) string {
+	if len(expected) > max {
+		return fmt.Sprintf(needFewerValues, max, len(expected))
+	}
+	return success
+}

+ 3 - 0
vendor/github.com/smartystreets/assertions/go.mod

@@ -0,0 +1,3 @@
+module github.com/smartystreets/assertions
+
+go 1.12

+ 20 - 0
vendor/github.com/smartystreets/assertions/internal/go-diff/LICENSE

@@ -0,0 +1,20 @@
+Copyright (c) 2012-2016 The go-diff Authors. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+

+ 1345 - 0
vendor/github.com/smartystreets/assertions/internal/go-diff/diffmatchpatch/diff.go

@@ -0,0 +1,1345 @@
+// Copyright (c) 2012-2016 The go-diff authors. All rights reserved.
+// https://github.com/sergi/go-diff
+// See the included LICENSE file for license details.
+//
+// go-diff is a Go implementation of Google's Diff, Match, and Patch library
+// Original library is Copyright (c) 2006 Google Inc.
+// http://code.google.com/p/google-diff-match-patch/
+
+package diffmatchpatch
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"html"
+	"math"
+	"net/url"
+	"regexp"
+	"strconv"
+	"strings"
+	"time"
+	"unicode/utf8"
+)
+
+// Operation defines the operation of a diff item.
+type Operation int8
+
+//go:generate stringer -type=Operation -trimprefix=Diff
+
+const (
+	// DiffDelete item represents a delete diff.
+	DiffDelete Operation = -1
+	// DiffInsert item represents an insert diff.
+	DiffInsert Operation = 1
+	// DiffEqual item represents an equal diff.
+	DiffEqual Operation = 0
+)
+
+// Diff represents one diff operation
+type Diff struct {
+	Type Operation
+	Text string
+}
+
+// splice removes amount elements from slice at index index, replacing them with elements.
+func splice(slice []Diff, index int, amount int, elements ...Diff) []Diff {
+	if len(elements) == amount {
+		// Easy case: overwrite the relevant items.
+		copy(slice[index:], elements)
+		return slice
+	}
+	if len(elements) < amount {
+		// Fewer new items than old.
+		// Copy in the new items.
+		copy(slice[index:], elements)
+		// Shift the remaining items left.
+		copy(slice[index+len(elements):], slice[index+amount:])
+		// Calculate the new end of the slice.
+		end := len(slice) - amount + len(elements)
+		// Zero stranded elements at end so that they can be garbage collected.
+		tail := slice[end:]
+		for i := range tail {
+			tail[i] = Diff{}
+		}
+		return slice[:end]
+	}
+	// More new items than old.
+	// Make room in slice for new elements.
+	// There's probably an even more efficient way to do this,
+	// but this is simple and clear.
+	need := len(slice) - amount + len(elements)
+	for len(slice) < need {
+		slice = append(slice, Diff{})
+	}
+	// Shift slice elements right to make room for new elements.
+	copy(slice[index+len(elements):], slice[index+amount:])
+	// Copy in new elements.
+	copy(slice[index:], elements)
+	return slice
+}
+
+// DiffMain finds the differences between two texts.
+// If an invalid UTF-8 sequence is encountered, it will be replaced by the Unicode replacement character.
+func (dmp *DiffMatchPatch) DiffMain(text1, text2 string, checklines bool) []Diff {
+	return dmp.DiffMainRunes([]rune(text1), []rune(text2), checklines)
+}
+
+// DiffMainRunes finds the differences between two rune sequences.
+// If an invalid UTF-8 sequence is encountered, it will be replaced by the Unicode replacement character.
+func (dmp *DiffMatchPatch) DiffMainRunes(text1, text2 []rune, checklines bool) []Diff {
+	var deadline time.Time
+	if dmp.DiffTimeout > 0 {
+		deadline = time.Now().Add(dmp.DiffTimeout)
+	}
+	return dmp.diffMainRunes(text1, text2, checklines, deadline)
+}
+
+func (dmp *DiffMatchPatch) diffMainRunes(text1, text2 []rune, checklines bool, deadline time.Time) []Diff {
+	if runesEqual(text1, text2) {
+		var diffs []Diff
+		if len(text1) > 0 {
+			diffs = append(diffs, Diff{DiffEqual, string(text1)})
+		}
+		return diffs
+	}
+	// Trim off common prefix (speedup).
+	commonlength := commonPrefixLength(text1, text2)
+	commonprefix := text1[:commonlength]
+	text1 = text1[commonlength:]
+	text2 = text2[commonlength:]
+
+	// Trim off common suffix (speedup).
+	commonlength = commonSuffixLength(text1, text2)
+	commonsuffix := text1[len(text1)-commonlength:]
+	text1 = text1[:len(text1)-commonlength]
+	text2 = text2[:len(text2)-commonlength]
+
+	// Compute the diff on the middle block.
+	diffs := dmp.diffCompute(text1, text2, checklines, deadline)
+
+	// Restore the prefix and suffix.
+	if len(commonprefix) != 0 {
+		diffs = append([]Diff{Diff{DiffEqual, string(commonprefix)}}, diffs...)
+	}
+	if len(commonsuffix) != 0 {
+		diffs = append(diffs, Diff{DiffEqual, string(commonsuffix)})
+	}
+
+	return dmp.DiffCleanupMerge(diffs)
+}
+
+// diffCompute finds the differences between two rune slices.  Assumes that the texts do not have any common prefix or suffix.
+func (dmp *DiffMatchPatch) diffCompute(text1, text2 []rune, checklines bool, deadline time.Time) []Diff {
+	diffs := []Diff{}
+	if len(text1) == 0 {
+		// Just add some text (speedup).
+		return append(diffs, Diff{DiffInsert, string(text2)})
+	} else if len(text2) == 0 {
+		// Just delete some text (speedup).
+		return append(diffs, Diff{DiffDelete, string(text1)})
+	}
+
+	var longtext, shorttext []rune
+	if len(text1) > len(text2) {
+		longtext = text1
+		shorttext = text2
+	} else {
+		longtext = text2
+		shorttext = text1
+	}
+
+	if i := runesIndex(longtext, shorttext); i != -1 {
+		op := DiffInsert
+		// Swap insertions for deletions if diff is reversed.
+		if len(text1) > len(text2) {
+			op = DiffDelete
+		}
+		// Shorter text is inside the longer text (speedup).
+		return []Diff{
+			Diff{op, string(longtext[:i])},
+			Diff{DiffEqual, string(shorttext)},
+			Diff{op, string(longtext[i+len(shorttext):])},
+		}
+	} else if len(shorttext) == 1 {
+		// Single character string.
+		// After the previous speedup, the character can't be an equality.
+		return []Diff{
+			Diff{DiffDelete, string(text1)},
+			Diff{DiffInsert, string(text2)},
+		}
+		// Check to see if the problem can be split in two.
+	} else if hm := dmp.diffHalfMatch(text1, text2); hm != nil {
+		// A half-match was found, sort out the return data.
+		text1A := hm[0]
+		text1B := hm[1]
+		text2A := hm[2]
+		text2B := hm[3]
+		midCommon := hm[4]
+		// Send both pairs off for separate processing.
+		diffsA := dmp.diffMainRunes(text1A, text2A, checklines, deadline)
+		diffsB := dmp.diffMainRunes(text1B, text2B, checklines, deadline)
+		// Merge the results.
+		diffs := diffsA
+		diffs = append(diffs, Diff{DiffEqual, string(midCommon)})
+		diffs = append(diffs, diffsB...)
+		return diffs
+	} else if checklines && len(text1) > 100 && len(text2) > 100 {
+		return dmp.diffLineMode(text1, text2, deadline)
+	}
+	return dmp.diffBisect(text1, text2, deadline)
+}
+
+// diffLineMode does a quick line-level diff on both []runes, then rediff the parts for greater accuracy. This speedup can produce non-minimal diffs.
+func (dmp *DiffMatchPatch) diffLineMode(text1, text2 []rune, deadline time.Time) []Diff {
+	// Scan the text on a line-by-line basis first.
+	text1, text2, linearray := dmp.diffLinesToRunes(text1, text2)
+
+	diffs := dmp.diffMainRunes(text1, text2, false, deadline)
+
+	// Convert the diff back to original text.
+	diffs = dmp.DiffCharsToLines(diffs, linearray)
+	// Eliminate freak matches (e.g. blank lines)
+	diffs = dmp.DiffCleanupSemantic(diffs)
+
+	// Rediff any replacement blocks, this time character-by-character.
+	// Add a dummy entry at the end.
+	diffs = append(diffs, Diff{DiffEqual, ""})
+
+	pointer := 0
+	countDelete := 0
+	countInsert := 0
+
+	// NOTE: Rune slices are slower than using strings in this case.
+	textDelete := ""
+	textInsert := ""
+
+	for pointer < len(diffs) {
+		switch diffs[pointer].Type {
+		case DiffInsert:
+			countInsert++
+			textInsert += diffs[pointer].Text
+		case DiffDelete:
+			countDelete++
+			textDelete += diffs[pointer].Text
+		case DiffEqual:
+			// Upon reaching an equality, check for prior redundancies.
+			if countDelete >= 1 && countInsert >= 1 {
+				// Delete the offending records and add the merged ones.
+				diffs = splice(diffs, pointer-countDelete-countInsert,
+					countDelete+countInsert)
+
+				pointer = pointer - countDelete - countInsert
+				a := dmp.diffMainRunes([]rune(textDelete), []rune(textInsert), false, deadline)
+				for j := len(a) - 1; j >= 0; j-- {
+					diffs = splice(diffs, pointer, 0, a[j])
+				}
+				pointer = pointer + len(a)
+			}
+
+			countInsert = 0
+			countDelete = 0
+			textDelete = ""
+			textInsert = ""
+		}
+		pointer++
+	}
+
+	return diffs[:len(diffs)-1] // Remove the dummy entry at the end.
+}
+
+// DiffBisect finds the 'middle snake' of a diff, split the problem in two and return the recursively constructed diff.
+// If an invalid UTF-8 sequence is encountered, it will be replaced by the Unicode replacement character.
+// See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
+func (dmp *DiffMatchPatch) DiffBisect(text1, text2 string, deadline time.Time) []Diff {
+	// Unused in this code, but retained for interface compatibility.
+	return dmp.diffBisect([]rune(text1), []rune(text2), deadline)
+}
+
+// diffBisect finds the 'middle snake' of a diff, splits the problem in two and returns the recursively constructed diff.
+// See Myers's 1986 paper: An O(ND) Difference Algorithm and Its Variations.
+func (dmp *DiffMatchPatch) diffBisect(runes1, runes2 []rune, deadline time.Time) []Diff {
+	// Cache the text lengths to prevent multiple calls.
+	runes1Len, runes2Len := len(runes1), len(runes2)
+
+	maxD := (runes1Len + runes2Len + 1) / 2
+	vOffset := maxD
+	vLength := 2 * maxD
+
+	v1 := make([]int, vLength)
+	v2 := make([]int, vLength)
+	for i := range v1 {
+		v1[i] = -1
+		v2[i] = -1
+	}
+	v1[vOffset+1] = 0
+	v2[vOffset+1] = 0
+
+	delta := runes1Len - runes2Len
+	// If the total number of characters is odd, then the front path will collide with the reverse path.
+	front := (delta%2 != 0)
+	// Offsets for start and end of k loop. Prevents mapping of space beyond the grid.
+	k1start := 0
+	k1end := 0
+	k2start := 0
+	k2end := 0
+	for d := 0; d < maxD; d++ {
+		// Bail out if deadline is reached.
+		if !deadline.IsZero() && d%16 == 0 && time.Now().After(deadline) {
+			break
+		}
+
+		// Walk the front path one step.
+		for k1 := -d + k1start; k1 <= d-k1end; k1 += 2 {
+			k1Offset := vOffset + k1
+			var x1 int
+
+			if k1 == -d || (k1 != d && v1[k1Offset-1] < v1[k1Offset+1]) {
+				x1 = v1[k1Offset+1]
+			} else {
+				x1 = v1[k1Offset-1] + 1
+			}
+
+			y1 := x1 - k1
+			for x1 < runes1Len && y1 < runes2Len {
+				if runes1[x1] != runes2[y1] {
+					break
+				}
+				x1++
+				y1++
+			}
+			v1[k1Offset] = x1
+			if x1 > runes1Len {
+				// Ran off the right of the graph.
+				k1end += 2
+			} else if y1 > runes2Len {
+				// Ran off the bottom of the graph.
+				k1start += 2
+			} else if front {
+				k2Offset := vOffset + delta - k1
+				if k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] != -1 {
+					// Mirror x2 onto top-left coordinate system.
+					x2 := runes1Len - v2[k2Offset]
+					if x1 >= x2 {
+						// Overlap detected.
+						return dmp.diffBisectSplit(runes1, runes2, x1, y1, deadline)
+					}
+				}
+			}
+		}
+		// Walk the reverse path one step.
+		for k2 := -d + k2start; k2 <= d-k2end; k2 += 2 {
+			k2Offset := vOffset + k2
+			var x2 int
+			if k2 == -d || (k2 != d && v2[k2Offset-1] < v2[k2Offset+1]) {
+				x2 = v2[k2Offset+1]
+			} else {
+				x2 = v2[k2Offset-1] + 1
+			}
+			var y2 = x2 - k2
+			for x2 < runes1Len && y2 < runes2Len {
+				if runes1[runes1Len-x2-1] != runes2[runes2Len-y2-1] {
+					break
+				}
+				x2++
+				y2++
+			}
+			v2[k2Offset] = x2
+			if x2 > runes1Len {
+				// Ran off the left of the graph.
+				k2end += 2
+			} else if y2 > runes2Len {
+				// Ran off the top of the graph.
+				k2start += 2
+			} else if !front {
+				k1Offset := vOffset + delta - k2
+				if k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] != -1 {
+					x1 := v1[k1Offset]
+					y1 := vOffset + x1 - k1Offset
+					// Mirror x2 onto top-left coordinate system.
+					x2 = runes1Len - x2
+					if x1 >= x2 {
+						// Overlap detected.
+						return dmp.diffBisectSplit(runes1, runes2, x1, y1, deadline)
+					}
+				}
+			}
+		}
+	}
+	// Diff took too long and hit the deadline or number of diffs equals number of characters, no commonality at all.
+	return []Diff{
+		Diff{DiffDelete, string(runes1)},
+		Diff{DiffInsert, string(runes2)},
+	}
+}
+
+func (dmp *DiffMatchPatch) diffBisectSplit(runes1, runes2 []rune, x, y int,
+	deadline time.Time) []Diff {
+	runes1a := runes1[:x]
+	runes2a := runes2[:y]
+	runes1b := runes1[x:]
+	runes2b := runes2[y:]
+
+	// Compute both diffs serially.
+	diffs := dmp.diffMainRunes(runes1a, runes2a, false, deadline)
+	diffsb := dmp.diffMainRunes(runes1b, runes2b, false, deadline)
+
+	return append(diffs, diffsb...)
+}
+
+// DiffLinesToChars splits two texts into a list of strings, and educes the texts to a string of hashes where each Unicode character represents one line.
+// It's slightly faster to call DiffLinesToRunes first, followed by DiffMainRunes.
+func (dmp *DiffMatchPatch) DiffLinesToChars(text1, text2 string) (string, string, []string) {
+	chars1, chars2, lineArray := dmp.DiffLinesToRunes(text1, text2)
+	return string(chars1), string(chars2), lineArray
+}
+
+// DiffLinesToRunes splits two texts into a list of runes. Each rune represents one line.
+func (dmp *DiffMatchPatch) DiffLinesToRunes(text1, text2 string) ([]rune, []rune, []string) {
+	// '\x00' is a valid character, but various debuggers don't like it. So we'll insert a junk entry to avoid generating a null character.
+	lineArray := []string{""}    // e.g. lineArray[4] == 'Hello\n'
+	lineHash := map[string]int{} // e.g. lineHash['Hello\n'] == 4
+
+	chars1 := dmp.diffLinesToRunesMunge(text1, &lineArray, lineHash)
+	chars2 := dmp.diffLinesToRunesMunge(text2, &lineArray, lineHash)
+
+	return chars1, chars2, lineArray
+}
+
+func (dmp *DiffMatchPatch) diffLinesToRunes(text1, text2 []rune) ([]rune, []rune, []string) {
+	return dmp.DiffLinesToRunes(string(text1), string(text2))
+}
+
+// diffLinesToRunesMunge splits a text into an array of strings, and reduces the texts to a []rune where each Unicode character represents one line.
+// We use strings instead of []runes as input mainly because you can't use []rune as a map key.
+func (dmp *DiffMatchPatch) diffLinesToRunesMunge(text string, lineArray *[]string, lineHash map[string]int) []rune {
+	// Walk the text, pulling out a substring for each line. text.split('\n') would would temporarily double our memory footprint. Modifying text would create many large strings to garbage collect.
+	lineStart := 0
+	lineEnd := -1
+	runes := []rune{}
+
+	for lineEnd < len(text)-1 {
+		lineEnd = indexOf(text, "\n", lineStart)
+
+		if lineEnd == -1 {
+			lineEnd = len(text) - 1
+		}
+
+		line := text[lineStart : lineEnd+1]
+		lineStart = lineEnd + 1
+		lineValue, ok := lineHash[line]
+
+		if ok {
+			runes = append(runes, rune(lineValue))
+		} else {
+			*lineArray = append(*lineArray, line)
+			lineHash[line] = len(*lineArray) - 1
+			runes = append(runes, rune(len(*lineArray)-1))
+		}
+	}
+
+	return runes
+}
+
+// DiffCharsToLines rehydrates the text in a diff from a string of line hashes to real lines of text.
+func (dmp *DiffMatchPatch) DiffCharsToLines(diffs []Diff, lineArray []string) []Diff {
+	hydrated := make([]Diff, 0, len(diffs))
+	for _, aDiff := range diffs {
+		chars := aDiff.Text
+		text := make([]string, len(chars))
+
+		for i, r := range chars {
+			text[i] = lineArray[r]
+		}
+
+		aDiff.Text = strings.Join(text, "")
+		hydrated = append(hydrated, aDiff)
+	}
+	return hydrated
+}
+
+// DiffCommonPrefix determines the common prefix length of two strings.
+func (dmp *DiffMatchPatch) DiffCommonPrefix(text1, text2 string) int {
+	// Unused in this code, but retained for interface compatibility.
+	return commonPrefixLength([]rune(text1), []rune(text2))
+}
+
+// DiffCommonSuffix determines the common suffix length of two strings.
+func (dmp *DiffMatchPatch) DiffCommonSuffix(text1, text2 string) int {
+	// Unused in this code, but retained for interface compatibility.
+	return commonSuffixLength([]rune(text1), []rune(text2))
+}
+
+// commonPrefixLength returns the length of the common prefix of two rune slices.
+func commonPrefixLength(text1, text2 []rune) int {
+	// Linear search. See comment in commonSuffixLength.
+	n := 0
+	for ; n < len(text1) && n < len(text2); n++ {
+		if text1[n] != text2[n] {
+			return n
+		}
+	}
+	return n
+}
+
+// commonSuffixLength returns the length of the common suffix of two rune slices.
+func commonSuffixLength(text1, text2 []rune) int {
+	// Use linear search rather than the binary search discussed at https://neil.fraser.name/news/2007/10/09/.
+	// See discussion at https://github.com/sergi/go-diff/issues/54.
+	i1 := len(text1)
+	i2 := len(text2)
+	for n := 0; ; n++ {
+		i1--
+		i2--
+		if i1 < 0 || i2 < 0 || text1[i1] != text2[i2] {
+			return n
+		}
+	}
+}
+
+// DiffCommonOverlap determines if the suffix of one string is the prefix of another.
+func (dmp *DiffMatchPatch) DiffCommonOverlap(text1 string, text2 string) int {
+	// Cache the text lengths to prevent multiple calls.
+	text1Length := len(text1)
+	text2Length := len(text2)
+	// Eliminate the null case.
+	if text1Length == 0 || text2Length == 0 {
+		return 0
+	}
+	// Truncate the longer string.
+	if text1Length > text2Length {
+		text1 = text1[text1Length-text2Length:]
+	} else if text1Length < text2Length {
+		text2 = text2[0:text1Length]
+	}
+	textLength := int(math.Min(float64(text1Length), float64(text2Length)))
+	// Quick check for the worst case.
+	if text1 == text2 {
+		return textLength
+	}
+
+	// Start by looking for a single character match and increase length until no match is found. Performance analysis: http://neil.fraser.name/news/2010/11/04/
+	best := 0
+	length := 1
+	for {
+		pattern := text1[textLength-length:]
+		found := strings.Index(text2, pattern)
+		if found == -1 {
+			break
+		}
+		length += found
+		if found == 0 || text1[textLength-length:] == text2[0:length] {
+			best = length
+			length++
+		}
+	}
+
+	return best
+}
+
+// DiffHalfMatch checks whether the two texts share a substring which is at least half the length of the longer text. This speedup can produce non-minimal diffs.
+func (dmp *DiffMatchPatch) DiffHalfMatch(text1, text2 string) []string {
+	// Unused in this code, but retained for interface compatibility.
+	runeSlices := dmp.diffHalfMatch([]rune(text1), []rune(text2))
+	if runeSlices == nil {
+		return nil
+	}
+
+	result := make([]string, len(runeSlices))
+	for i, r := range runeSlices {
+		result[i] = string(r)
+	}
+	return result
+}
+
+func (dmp *DiffMatchPatch) diffHalfMatch(text1, text2 []rune) [][]rune {
+	if dmp.DiffTimeout <= 0 {
+		// Don't risk returning a non-optimal diff if we have unlimited time.
+		return nil
+	}
+
+	var longtext, shorttext []rune
+	if len(text1) > len(text2) {
+		longtext = text1
+		shorttext = text2
+	} else {
+		longtext = text2
+		shorttext = text1
+	}
+
+	if len(longtext) < 4 || len(shorttext)*2 < len(longtext) {
+		return nil // Pointless.
+	}
+
+	// First check if the second quarter is the seed for a half-match.
+	hm1 := dmp.diffHalfMatchI(longtext, shorttext, int(float64(len(longtext)+3)/4))
+
+	// Check again based on the third quarter.
+	hm2 := dmp.diffHalfMatchI(longtext, shorttext, int(float64(len(longtext)+1)/2))
+
+	hm := [][]rune{}
+	if hm1 == nil && hm2 == nil {
+		return nil
+	} else if hm2 == nil {
+		hm = hm1
+	} else if hm1 == nil {
+		hm = hm2
+	} else {
+		// Both matched.  Select the longest.
+		if len(hm1[4]) > len(hm2[4]) {
+			hm = hm1
+		} else {
+			hm = hm2
+		}
+	}
+
+	// A half-match was found, sort out the return data.
+	if len(text1) > len(text2) {
+		return hm
+	}
+
+	return [][]rune{hm[2], hm[3], hm[0], hm[1], hm[4]}
+}
+
+// diffHalfMatchI checks if a substring of shorttext exist within longtext such that the substring is at least half the length of longtext?
+// Returns a slice containing the prefix of longtext, the suffix of longtext, the prefix of shorttext, the suffix of shorttext and the common middle, or null if there was no match.
+func (dmp *DiffMatchPatch) diffHalfMatchI(l, s []rune, i int) [][]rune {
+	var bestCommonA []rune
+	var bestCommonB []rune
+	var bestCommonLen int
+	var bestLongtextA []rune
+	var bestLongtextB []rune
+	var bestShorttextA []rune
+	var bestShorttextB []rune
+
+	// Start with a 1/4 length substring at position i as a seed.
+	seed := l[i : i+len(l)/4]
+
+	for j := runesIndexOf(s, seed, 0); j != -1; j = runesIndexOf(s, seed, j+1) {
+		prefixLength := commonPrefixLength(l[i:], s[j:])
+		suffixLength := commonSuffixLength(l[:i], s[:j])
+
+		if bestCommonLen < suffixLength+prefixLength {
+			bestCommonA = s[j-suffixLength : j]
+			bestCommonB = s[j : j+prefixLength]
+			bestCommonLen = len(bestCommonA) + len(bestCommonB)
+			bestLongtextA = l[:i-suffixLength]
+			bestLongtextB = l[i+prefixLength:]
+			bestShorttextA = s[:j-suffixLength]
+			bestShorttextB = s[j+prefixLength:]
+		}
+	}
+
+	if bestCommonLen*2 < len(l) {
+		return nil
+	}
+
+	return [][]rune{
+		bestLongtextA,
+		bestLongtextB,
+		bestShorttextA,
+		bestShorttextB,
+		append(bestCommonA, bestCommonB...),
+	}
+}
+
+// DiffCleanupSemantic reduces the number of edits by eliminating semantically trivial equalities.
+func (dmp *DiffMatchPatch) DiffCleanupSemantic(diffs []Diff) []Diff {
+	changes := false
+	// Stack of indices where equalities are found.
+	equalities := make([]int, 0, len(diffs))
+
+	var lastequality string
+	// Always equal to diffs[equalities[equalitiesLength - 1]][1]
+	var pointer int // Index of current position.
+	// Number of characters that changed prior to the equality.
+	var lengthInsertions1, lengthDeletions1 int
+	// Number of characters that changed after the equality.
+	var lengthInsertions2, lengthDeletions2 int
+
+	for pointer < len(diffs) {
+		if diffs[pointer].Type == DiffEqual {
+			// Equality found.
+			equalities = append(equalities, pointer)
+			lengthInsertions1 = lengthInsertions2
+			lengthDeletions1 = lengthDeletions2
+			lengthInsertions2 = 0
+			lengthDeletions2 = 0
+			lastequality = diffs[pointer].Text
+		} else {
+			// An insertion or deletion.
+
+			if diffs[pointer].Type == DiffInsert {
+				lengthInsertions2 += len(diffs[pointer].Text)
+			} else {
+				lengthDeletions2 += len(diffs[pointer].Text)
+			}
+			// Eliminate an equality that is smaller or equal to the edits on both sides of it.
+			difference1 := int(math.Max(float64(lengthInsertions1), float64(lengthDeletions1)))
+			difference2 := int(math.Max(float64(lengthInsertions2), float64(lengthDeletions2)))
+			if len(lastequality) > 0 &&
+				(len(lastequality) <= difference1) &&
+				(len(lastequality) <= difference2) {
+				// Duplicate record.
+				insPoint := equalities[len(equalities)-1]
+				diffs = splice(diffs, insPoint, 0, Diff{DiffDelete, lastequality})
+
+				// Change second copy to insert.
+				diffs[insPoint+1].Type = DiffInsert
+				// Throw away the equality we just deleted.
+				equalities = equalities[:len(equalities)-1]
+
+				if len(equalities) > 0 {
+					equalities = equalities[:len(equalities)-1]
+				}
+				pointer = -1
+				if len(equalities) > 0 {
+					pointer = equalities[len(equalities)-1]
+				}
+
+				lengthInsertions1 = 0 // Reset the counters.
+				lengthDeletions1 = 0
+				lengthInsertions2 = 0
+				lengthDeletions2 = 0
+				lastequality = ""
+				changes = true
+			}
+		}
+		pointer++
+	}
+
+	// Normalize the diff.
+	if changes {
+		diffs = dmp.DiffCleanupMerge(diffs)
+	}
+	diffs = dmp.DiffCleanupSemanticLossless(diffs)
+	// Find any overlaps between deletions and insertions.
+	// e.g: <del>abcxxx</del><ins>xxxdef</ins>
+	//   -> <del>abc</del>xxx<ins>def</ins>
+	// e.g: <del>xxxabc</del><ins>defxxx</ins>
+	//   -> <ins>def</ins>xxx<del>abc</del>
+	// Only extract an overlap if it is as big as the edit ahead or behind it.
+	pointer = 1
+	for pointer < len(diffs) {
+		if diffs[pointer-1].Type == DiffDelete &&
+			diffs[pointer].Type == DiffInsert {
+			deletion := diffs[pointer-1].Text
+			insertion := diffs[pointer].Text
+			overlapLength1 := dmp.DiffCommonOverlap(deletion, insertion)
+			overlapLength2 := dmp.DiffCommonOverlap(insertion, deletion)
+			if overlapLength1 >= overlapLength2 {
+				if float64(overlapLength1) >= float64(len(deletion))/2 ||
+					float64(overlapLength1) >= float64(len(insertion))/2 {
+
+					// Overlap found. Insert an equality and trim the surrounding edits.
+					diffs = splice(diffs, pointer, 0, Diff{DiffEqual, insertion[:overlapLength1]})
+					diffs[pointer-1].Text =
+						deletion[0 : len(deletion)-overlapLength1]
+					diffs[pointer+1].Text = insertion[overlapLength1:]
+					pointer++
+				}
+			} else {
+				if float64(overlapLength2) >= float64(len(deletion))/2 ||
+					float64(overlapLength2) >= float64(len(insertion))/2 {
+					// Reverse overlap found. Insert an equality and swap and trim the surrounding edits.
+					overlap := Diff{DiffEqual, deletion[:overlapLength2]}
+					diffs = splice(diffs, pointer, 0, overlap)
+					diffs[pointer-1].Type = DiffInsert
+					diffs[pointer-1].Text = insertion[0 : len(insertion)-overlapLength2]
+					diffs[pointer+1].Type = DiffDelete
+					diffs[pointer+1].Text = deletion[overlapLength2:]
+					pointer++
+				}
+			}
+			pointer++
+		}
+		pointer++
+	}
+
+	return diffs
+}
+
+// Define some regex patterns for matching boundaries.
+var (
+	nonAlphaNumericRegex = regexp.MustCompile(`[^a-zA-Z0-9]`)
+	whitespaceRegex      = regexp.MustCompile(`\s`)
+	linebreakRegex       = regexp.MustCompile(`[\r\n]`)
+	blanklineEndRegex    = regexp.MustCompile(`\n\r?\n$`)
+	blanklineStartRegex  = regexp.MustCompile(`^\r?\n\r?\n`)
+)
+
+// diffCleanupSemanticScore computes a score representing whether the internal boundary falls on logical boundaries.
+// Scores range from 6 (best) to 0 (worst). Closure, but does not reference any external variables.
+func diffCleanupSemanticScore(one, two string) int {
+	if len(one) == 0 || len(two) == 0 {
+		// Edges are the best.
+		return 6
+	}
+
+	// Each port of this function behaves slightly differently due to subtle differences in each language's definition of things like 'whitespace'.  Since this function's purpose is largely cosmetic, the choice has been made to use each language's native features rather than force total conformity.
+	rune1, _ := utf8.DecodeLastRuneInString(one)
+	rune2, _ := utf8.DecodeRuneInString(two)
+	char1 := string(rune1)
+	char2 := string(rune2)
+
+	nonAlphaNumeric1 := nonAlphaNumericRegex.MatchString(char1)
+	nonAlphaNumeric2 := nonAlphaNumericRegex.MatchString(char2)
+	whitespace1 := nonAlphaNumeric1 && whitespaceRegex.MatchString(char1)
+	whitespace2 := nonAlphaNumeric2 && whitespaceRegex.MatchString(char2)
+	lineBreak1 := whitespace1 && linebreakRegex.MatchString(char1)
+	lineBreak2 := whitespace2 && linebreakRegex.MatchString(char2)
+	blankLine1 := lineBreak1 && blanklineEndRegex.MatchString(one)
+	blankLine2 := lineBreak2 && blanklineEndRegex.MatchString(two)
+
+	if blankLine1 || blankLine2 {
+		// Five points for blank lines.
+		return 5
+	} else if lineBreak1 || lineBreak2 {
+		// Four points for line breaks.
+		return 4
+	} else if nonAlphaNumeric1 && !whitespace1 && whitespace2 {
+		// Three points for end of sentences.
+		return 3
+	} else if whitespace1 || whitespace2 {
+		// Two points for whitespace.
+		return 2
+	} else if nonAlphaNumeric1 || nonAlphaNumeric2 {
+		// One point for non-alphanumeric.
+		return 1
+	}
+	return 0
+}
+
+// DiffCleanupSemanticLossless looks for single edits surrounded on both sides by equalities which can be shifted sideways to align the edit to a word boundary.
+// E.g: The c<ins>at c</ins>ame. -> The <ins>cat </ins>came.
+func (dmp *DiffMatchPatch) DiffCleanupSemanticLossless(diffs []Diff) []Diff {
+	pointer := 1
+
+	// Intentionally ignore the first and last element (don't need checking).
+	for pointer < len(diffs)-1 {
+		if diffs[pointer-1].Type == DiffEqual &&
+			diffs[pointer+1].Type == DiffEqual {
+
+			// This is a single edit surrounded by equalities.
+			equality1 := diffs[pointer-1].Text
+			edit := diffs[pointer].Text
+			equality2 := diffs[pointer+1].Text
+
+			// First, shift the edit as far left as possible.
+			commonOffset := dmp.DiffCommonSuffix(equality1, edit)
+			if commonOffset > 0 {
+				commonString := edit[len(edit)-commonOffset:]
+				equality1 = equality1[0 : len(equality1)-commonOffset]
+				edit = commonString + edit[:len(edit)-commonOffset]
+				equality2 = commonString + equality2
+			}
+
+			// Second, step character by character right, looking for the best fit.
+			bestEquality1 := equality1
+			bestEdit := edit
+			bestEquality2 := equality2
+			bestScore := diffCleanupSemanticScore(equality1, edit) +
+				diffCleanupSemanticScore(edit, equality2)
+
+			for len(edit) != 0 && len(equality2) != 0 {
+				_, sz := utf8.DecodeRuneInString(edit)
+				if len(equality2) < sz || edit[:sz] != equality2[:sz] {
+					break
+				}
+				equality1 += edit[:sz]
+				edit = edit[sz:] + equality2[:sz]
+				equality2 = equality2[sz:]
+				score := diffCleanupSemanticScore(equality1, edit) +
+					diffCleanupSemanticScore(edit, equality2)
+				// The >= encourages trailing rather than leading whitespace on edits.
+				if score >= bestScore {
+					bestScore = score
+					bestEquality1 = equality1
+					bestEdit = edit
+					bestEquality2 = equality2
+				}
+			}
+
+			if diffs[pointer-1].Text != bestEquality1 {
+				// We have an improvement, save it back to the diff.
+				if len(bestEquality1) != 0 {
+					diffs[pointer-1].Text = bestEquality1
+				} else {
+					diffs = splice(diffs, pointer-1, 1)
+					pointer--
+				}
+
+				diffs[pointer].Text = bestEdit
+				if len(bestEquality2) != 0 {
+					diffs[pointer+1].Text = bestEquality2
+				} else {
+					diffs = append(diffs[:pointer+1], diffs[pointer+2:]...)
+					pointer--
+				}
+			}
+		}
+		pointer++
+	}
+
+	return diffs
+}
+
+// DiffCleanupEfficiency reduces the number of edits by eliminating operationally trivial equalities.
+func (dmp *DiffMatchPatch) DiffCleanupEfficiency(diffs []Diff) []Diff {
+	changes := false
+	// Stack of indices where equalities are found.
+	type equality struct {
+		data int
+		next *equality
+	}
+	var equalities *equality
+	// Always equal to equalities[equalitiesLength-1][1]
+	lastequality := ""
+	pointer := 0 // Index of current position.
+	// Is there an insertion operation before the last equality.
+	preIns := false
+	// Is there a deletion operation before the last equality.
+	preDel := false
+	// Is there an insertion operation after the last equality.
+	postIns := false
+	// Is there a deletion operation after the last equality.
+	postDel := false
+	for pointer < len(diffs) {
+		if diffs[pointer].Type == DiffEqual { // Equality found.
+			if len(diffs[pointer].Text) < dmp.DiffEditCost &&
+				(postIns || postDel) {
+				// Candidate found.
+				equalities = &equality{
+					data: pointer,
+					next: equalities,
+				}
+				preIns = postIns
+				preDel = postDel
+				lastequality = diffs[pointer].Text
+			} else {
+				// Not a candidate, and can never become one.
+				equalities = nil
+				lastequality = ""
+			}
+			postIns = false
+			postDel = false
+		} else { // An insertion or deletion.
+			if diffs[pointer].Type == DiffDelete {
+				postDel = true
+			} else {
+				postIns = true
+			}
+
+			// Five types to be split:
+			// <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
+			// <ins>A</ins>X<ins>C</ins><del>D</del>
+			// <ins>A</ins><del>B</del>X<ins>C</ins>
+			// <ins>A</del>X<ins>C</ins><del>D</del>
+			// <ins>A</ins><del>B</del>X<del>C</del>
+			var sumPres int
+			if preIns {
+				sumPres++
+			}
+			if preDel {
+				sumPres++
+			}
+			if postIns {
+				sumPres++
+			}
+			if postDel {
+				sumPres++
+			}
+			if len(lastequality) > 0 &&
+				((preIns && preDel && postIns && postDel) ||
+					((len(lastequality) < dmp.DiffEditCost/2) && sumPres == 3)) {
+
+				insPoint := equalities.data
+
+				// Duplicate record.
+				diffs = splice(diffs, insPoint, 0, Diff{DiffDelete, lastequality})
+
+				// Change second copy to insert.
+				diffs[insPoint+1].Type = DiffInsert
+				// Throw away the equality we just deleted.
+				equalities = equalities.next
+				lastequality = ""
+
+				if preIns && preDel {
+					// No changes made which could affect previous entry, keep going.
+					postIns = true
+					postDel = true
+					equalities = nil
+				} else {
+					if equalities != nil {
+						equalities = equalities.next
+					}
+					if equalities != nil {
+						pointer = equalities.data
+					} else {
+						pointer = -1
+					}
+					postIns = false
+					postDel = false
+				}
+				changes = true
+			}
+		}
+		pointer++
+	}
+
+	if changes {
+		diffs = dmp.DiffCleanupMerge(diffs)
+	}
+
+	return diffs
+}
+
+// DiffCleanupMerge reorders and merges like edit sections. Merge equalities.
+// Any edit section can move as long as it doesn't cross an equality.
+func (dmp *DiffMatchPatch) DiffCleanupMerge(diffs []Diff) []Diff {
+	// Add a dummy entry at the end.
+	diffs = append(diffs, Diff{DiffEqual, ""})
+	pointer := 0
+	countDelete := 0
+	countInsert := 0
+	commonlength := 0
+	textDelete := []rune(nil)
+	textInsert := []rune(nil)
+
+	for pointer < len(diffs) {
+		switch diffs[pointer].Type {
+		case DiffInsert:
+			countInsert++
+			textInsert = append(textInsert, []rune(diffs[pointer].Text)...)
+			pointer++
+			break
+		case DiffDelete:
+			countDelete++
+			textDelete = append(textDelete, []rune(diffs[pointer].Text)...)
+			pointer++
+			break
+		case DiffEqual:
+			// Upon reaching an equality, check for prior redundancies.
+			if countDelete+countInsert > 1 {
+				if countDelete != 0 && countInsert != 0 {
+					// Factor out any common prefixies.
+					commonlength = commonPrefixLength(textInsert, textDelete)
+					if commonlength != 0 {
+						x := pointer - countDelete - countInsert
+						if x > 0 && diffs[x-1].Type == DiffEqual {
+							diffs[x-1].Text += string(textInsert[:commonlength])
+						} else {
+							diffs = append([]Diff{Diff{DiffEqual, string(textInsert[:commonlength])}}, diffs...)
+							pointer++
+						}
+						textInsert = textInsert[commonlength:]
+						textDelete = textDelete[commonlength:]
+					}
+					// Factor out any common suffixies.
+					commonlength = commonSuffixLength(textInsert, textDelete)
+					if commonlength != 0 {
+						insertIndex := len(textInsert) - commonlength
+						deleteIndex := len(textDelete) - commonlength
+						diffs[pointer].Text = string(textInsert[insertIndex:]) + diffs[pointer].Text
+						textInsert = textInsert[:insertIndex]
+						textDelete = textDelete[:deleteIndex]
+					}
+				}
+				// Delete the offending records and add the merged ones.
+				if countDelete == 0 {
+					diffs = splice(diffs, pointer-countInsert,
+						countDelete+countInsert,
+						Diff{DiffInsert, string(textInsert)})
+				} else if countInsert == 0 {
+					diffs = splice(diffs, pointer-countDelete,
+						countDelete+countInsert,
+						Diff{DiffDelete, string(textDelete)})
+				} else {
+					diffs = splice(diffs, pointer-countDelete-countInsert,
+						countDelete+countInsert,
+						Diff{DiffDelete, string(textDelete)},
+						Diff{DiffInsert, string(textInsert)})
+				}
+
+				pointer = pointer - countDelete - countInsert + 1
+				if countDelete != 0 {
+					pointer++
+				}
+				if countInsert != 0 {
+					pointer++
+				}
+			} else if pointer != 0 && diffs[pointer-1].Type == DiffEqual {
+				// Merge this equality with the previous one.
+				diffs[pointer-1].Text += diffs[pointer].Text
+				diffs = append(diffs[:pointer], diffs[pointer+1:]...)
+			} else {
+				pointer++
+			}
+			countInsert = 0
+			countDelete = 0
+			textDelete = nil
+			textInsert = nil
+			break
+		}
+	}
+
+	if len(diffs[len(diffs)-1].Text) == 0 {
+		diffs = diffs[0 : len(diffs)-1] // Remove the dummy entry at the end.
+	}
+
+	// Second pass: look for single edits surrounded on both sides by equalities which can be shifted sideways to eliminate an equality. E.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
+	changes := false
+	pointer = 1
+	// Intentionally ignore the first and last element (don't need checking).
+	for pointer < (len(diffs) - 1) {
+		if diffs[pointer-1].Type == DiffEqual &&
+			diffs[pointer+1].Type == DiffEqual {
+			// This is a single edit surrounded by equalities.
+			if strings.HasSuffix(diffs[pointer].Text, diffs[pointer-1].Text) {
+				// Shift the edit over the previous equality.
+				diffs[pointer].Text = diffs[pointer-1].Text +
+					diffs[pointer].Text[:len(diffs[pointer].Text)-len(diffs[pointer-1].Text)]
+				diffs[pointer+1].Text = diffs[pointer-1].Text + diffs[pointer+1].Text
+				diffs = splice(diffs, pointer-1, 1)
+				changes = true
+			} else if strings.HasPrefix(diffs[pointer].Text, diffs[pointer+1].Text) {
+				// Shift the edit over the next equality.
+				diffs[pointer-1].Text += diffs[pointer+1].Text
+				diffs[pointer].Text =
+					diffs[pointer].Text[len(diffs[pointer+1].Text):] + diffs[pointer+1].Text
+				diffs = splice(diffs, pointer+1, 1)
+				changes = true
+			}
+		}
+		pointer++
+	}
+
+	// If shifts were made, the diff needs reordering and another shift sweep.
+	if changes {
+		diffs = dmp.DiffCleanupMerge(diffs)
+	}
+
+	return diffs
+}
+
+// DiffXIndex returns the equivalent location in s2.
+func (dmp *DiffMatchPatch) DiffXIndex(diffs []Diff, loc int) int {
+	chars1 := 0
+	chars2 := 0
+	lastChars1 := 0
+	lastChars2 := 0
+	lastDiff := Diff{}
+	for i := 0; i < len(diffs); i++ {
+		aDiff := diffs[i]
+		if aDiff.Type != DiffInsert {
+			// Equality or deletion.
+			chars1 += len(aDiff.Text)
+		}
+		if aDiff.Type != DiffDelete {
+			// Equality or insertion.
+			chars2 += len(aDiff.Text)
+		}
+		if chars1 > loc {
+			// Overshot the location.
+			lastDiff = aDiff
+			break
+		}
+		lastChars1 = chars1
+		lastChars2 = chars2
+	}
+	if lastDiff.Type == DiffDelete {
+		// The location was deleted.
+		return lastChars2
+	}
+	// Add the remaining character length.
+	return lastChars2 + (loc - lastChars1)
+}
+
+// DiffPrettyHtml converts a []Diff into a pretty HTML report.
+// It is intended as an example from which to write one's own display functions.
+func (dmp *DiffMatchPatch) DiffPrettyHtml(diffs []Diff) string {
+	var buff bytes.Buffer
+	for _, diff := range diffs {
+		text := strings.Replace(html.EscapeString(diff.Text), "\n", "&para;<br>", -1)
+		switch diff.Type {
+		case DiffInsert:
+			_, _ = buff.WriteString("<ins style=\"background:#e6ffe6;\">")
+			_, _ = buff.WriteString(text)
+			_, _ = buff.WriteString("</ins>")
+		case DiffDelete:
+			_, _ = buff.WriteString("<del style=\"background:#ffe6e6;\">")
+			_, _ = buff.WriteString(text)
+			_, _ = buff.WriteString("</del>")
+		case DiffEqual:
+			_, _ = buff.WriteString("<span>")
+			_, _ = buff.WriteString(text)
+			_, _ = buff.WriteString("</span>")
+		}
+	}
+	return buff.String()
+}
+
+// DiffPrettyText converts a []Diff into a colored text report.
+func (dmp *DiffMatchPatch) DiffPrettyText(diffs []Diff) string {
+	var buff bytes.Buffer
+	for _, diff := range diffs {
+		text := diff.Text
+
+		switch diff.Type {
+		case DiffInsert:
+			_, _ = buff.WriteString("\x1b[32m")
+			_, _ = buff.WriteString(text)
+			_, _ = buff.WriteString("\x1b[0m")
+		case DiffDelete:
+			_, _ = buff.WriteString("\x1b[31m")
+			_, _ = buff.WriteString(text)
+			_, _ = buff.WriteString("\x1b[0m")
+		case DiffEqual:
+			_, _ = buff.WriteString(text)
+		}
+	}
+
+	return buff.String()
+}
+
+// DiffText1 computes and returns the source text (all equalities and deletions).
+func (dmp *DiffMatchPatch) DiffText1(diffs []Diff) string {
+	//StringBuilder text = new StringBuilder()
+	var text bytes.Buffer
+
+	for _, aDiff := range diffs {
+		if aDiff.Type != DiffInsert {
+			_, _ = text.WriteString(aDiff.Text)
+		}
+	}
+	return text.String()
+}
+
+// DiffText2 computes and returns the destination text (all equalities and insertions).
+func (dmp *DiffMatchPatch) DiffText2(diffs []Diff) string {
+	var text bytes.Buffer
+
+	for _, aDiff := range diffs {
+		if aDiff.Type != DiffDelete {
+			_, _ = text.WriteString(aDiff.Text)
+		}
+	}
+	return text.String()
+}
+
+// DiffLevenshtein computes the Levenshtein distance that is the number of inserted, deleted or substituted characters.
+func (dmp *DiffMatchPatch) DiffLevenshtein(diffs []Diff) int {
+	levenshtein := 0
+	insertions := 0
+	deletions := 0
+
+	for _, aDiff := range diffs {
+		switch aDiff.Type {
+		case DiffInsert:
+			insertions += utf8.RuneCountInString(aDiff.Text)
+		case DiffDelete:
+			deletions += utf8.RuneCountInString(aDiff.Text)
+		case DiffEqual:
+			// A deletion and an insertion is one substitution.
+			levenshtein += max(insertions, deletions)
+			insertions = 0
+			deletions = 0
+		}
+	}
+
+	levenshtein += max(insertions, deletions)
+	return levenshtein
+}
+
+// DiffToDelta crushes the diff into an encoded string which describes the operations required to transform text1 into text2.
+// E.g. =3\t-2\t+ing  -> Keep 3 chars, delete 2 chars, insert 'ing'. Operations are tab-separated.  Inserted text is escaped using %xx notation.
+func (dmp *DiffMatchPatch) DiffToDelta(diffs []Diff) string {
+	var text bytes.Buffer
+	for _, aDiff := range diffs {
+		switch aDiff.Type {
+		case DiffInsert:
+			_, _ = text.WriteString("+")
+			_, _ = text.WriteString(strings.Replace(url.QueryEscape(aDiff.Text), "+", " ", -1))
+			_, _ = text.WriteString("\t")
+			break
+		case DiffDelete:
+			_, _ = text.WriteString("-")
+			_, _ = text.WriteString(strconv.Itoa(utf8.RuneCountInString(aDiff.Text)))
+			_, _ = text.WriteString("\t")
+			break
+		case DiffEqual:
+			_, _ = text.WriteString("=")
+			_, _ = text.WriteString(strconv.Itoa(utf8.RuneCountInString(aDiff.Text)))
+			_, _ = text.WriteString("\t")
+			break
+		}
+	}
+	delta := text.String()
+	if len(delta) != 0 {
+		// Strip off trailing tab character.
+		delta = delta[0 : utf8.RuneCountInString(delta)-1]
+		delta = unescaper.Replace(delta)
+	}
+	return delta
+}
+
+// DiffFromDelta given the original text1, and an encoded string which describes the operations required to transform text1 into text2, comAdde the full diff.
+func (dmp *DiffMatchPatch) DiffFromDelta(text1 string, delta string) (diffs []Diff, err error) {
+	i := 0
+	runes := []rune(text1)
+
+	for _, token := range strings.Split(delta, "\t") {
+		if len(token) == 0 {
+			// Blank tokens are ok (from a trailing \t).
+			continue
+		}
+
+		// Each token begins with a one character parameter which specifies the operation of this token (delete, insert, equality).
+		param := token[1:]
+
+		switch op := token[0]; op {
+		case '+':
+			// Decode would Diff all "+" to " "
+			param = strings.Replace(param, "+", "%2b", -1)
+			param, err = url.QueryUnescape(param)
+			if err != nil {
+				return nil, err
+			}
+			if !utf8.ValidString(param) {
+				return nil, fmt.Errorf("invalid UTF-8 token: %q", param)
+			}
+
+			diffs = append(diffs, Diff{DiffInsert, param})
+		case '=', '-':
+			n, err := strconv.ParseInt(param, 10, 0)
+			if err != nil {
+				return nil, err
+			} else if n < 0 {
+				return nil, errors.New("Negative number in DiffFromDelta: " + param)
+			}
+
+			i += int(n)
+			// Break out if we are out of bounds, go1.6 can't handle this very well
+			if i > len(runes) {
+				break
+			}
+			// Remember that string slicing is by byte - we want by rune here.
+			text := string(runes[i-int(n) : i])
+
+			if op == '=' {
+				diffs = append(diffs, Diff{DiffEqual, text})
+			} else {
+				diffs = append(diffs, Diff{DiffDelete, text})
+			}
+		default:
+			// Anything else is an error.
+			return nil, errors.New("Invalid diff operation in DiffFromDelta: " + string(token[0]))
+		}
+	}
+
+	if i != len(runes) {
+		return nil, fmt.Errorf("Delta length (%v) is different from source text length (%v)", i, len(text1))
+	}
+
+	return diffs, nil
+}

+ 46 - 0
vendor/github.com/smartystreets/assertions/internal/go-diff/diffmatchpatch/diffmatchpatch.go

@@ -0,0 +1,46 @@
+// Copyright (c) 2012-2016 The go-diff authors. All rights reserved.
+// https://github.com/sergi/go-diff
+// See the included LICENSE file for license details.
+//
+// go-diff is a Go implementation of Google's Diff, Match, and Patch library
+// Original library is Copyright (c) 2006 Google Inc.
+// http://code.google.com/p/google-diff-match-patch/
+
+// Package diffmatchpatch offers robust algorithms to perform the operations required for synchronizing plain text.
+package diffmatchpatch
+
+import (
+	"time"
+)
+
+// DiffMatchPatch holds the configuration for diff-match-patch operations.
+type DiffMatchPatch struct {
+	// Number of seconds to map a diff before giving up (0 for infinity).
+	DiffTimeout time.Duration
+	// Cost of an empty edit operation in terms of edit characters.
+	DiffEditCost int
+	// How far to search for a match (0 = exact location, 1000+ = broad match). A match this many characters away from the expected location will add 1.0 to the score (0.0 is a perfect match).
+	MatchDistance int
+	// When deleting a large block of text (over ~64 characters), how close do the contents have to be to match the expected contents. (0.0 = perfection, 1.0 = very loose).  Note that MatchThreshold controls how closely the end points of a delete need to match.
+	PatchDeleteThreshold float64
+	// Chunk size for context length.
+	PatchMargin int
+	// The number of bits in an int.
+	MatchMaxBits int
+	// At what point is no match declared (0.0 = perfection, 1.0 = very loose).
+	MatchThreshold float64
+}
+
+// New creates a new DiffMatchPatch object with default parameters.
+func New() *DiffMatchPatch {
+	// Defaults.
+	return &DiffMatchPatch{
+		DiffTimeout:          time.Second,
+		DiffEditCost:         4,
+		MatchThreshold:       0.5,
+		MatchDistance:        1000,
+		PatchDeleteThreshold: 0.5,
+		PatchMargin:          4,
+		MatchMaxBits:         32,
+	}
+}

+ 160 - 0
vendor/github.com/smartystreets/assertions/internal/go-diff/diffmatchpatch/match.go

@@ -0,0 +1,160 @@
+// Copyright (c) 2012-2016 The go-diff authors. All rights reserved.
+// https://github.com/sergi/go-diff
+// See the included LICENSE file for license details.
+//
+// go-diff is a Go implementation of Google's Diff, Match, and Patch library
+// Original library is Copyright (c) 2006 Google Inc.
+// http://code.google.com/p/google-diff-match-patch/
+
+package diffmatchpatch
+
+import (
+	"math"
+)
+
+// MatchMain locates the best instance of 'pattern' in 'text' near 'loc'.
+// Returns -1 if no match found.
+func (dmp *DiffMatchPatch) MatchMain(text, pattern string, loc int) int {
+	// Check for null inputs not needed since null can't be passed in C#.
+
+	loc = int(math.Max(0, math.Min(float64(loc), float64(len(text)))))
+	if text == pattern {
+		// Shortcut (potentially not guaranteed by the algorithm)
+		return 0
+	} else if len(text) == 0 {
+		// Nothing to match.
+		return -1
+	} else if loc+len(pattern) <= len(text) && text[loc:loc+len(pattern)] == pattern {
+		// Perfect match at the perfect spot!  (Includes case of null pattern)
+		return loc
+	}
+	// Do a fuzzy compare.
+	return dmp.MatchBitap(text, pattern, loc)
+}
+
+// MatchBitap locates the best instance of 'pattern' in 'text' near 'loc' using the Bitap algorithm.
+// Returns -1 if no match was found.
+func (dmp *DiffMatchPatch) MatchBitap(text, pattern string, loc int) int {
+	// Initialise the alphabet.
+	s := dmp.MatchAlphabet(pattern)
+
+	// Highest score beyond which we give up.
+	scoreThreshold := dmp.MatchThreshold
+	// Is there a nearby exact match? (speedup)
+	bestLoc := indexOf(text, pattern, loc)
+	if bestLoc != -1 {
+		scoreThreshold = math.Min(dmp.matchBitapScore(0, bestLoc, loc,
+			pattern), scoreThreshold)
+		// What about in the other direction? (speedup)
+		bestLoc = lastIndexOf(text, pattern, loc+len(pattern))
+		if bestLoc != -1 {
+			scoreThreshold = math.Min(dmp.matchBitapScore(0, bestLoc, loc,
+				pattern), scoreThreshold)
+		}
+	}
+
+	// Initialise the bit arrays.
+	matchmask := 1 << uint((len(pattern) - 1))
+	bestLoc = -1
+
+	var binMin, binMid int
+	binMax := len(pattern) + len(text)
+	lastRd := []int{}
+	for d := 0; d < len(pattern); d++ {
+		// Scan for the best match; each iteration allows for one more error. Run a binary search to determine how far from 'loc' we can stray at this error level.
+		binMin = 0
+		binMid = binMax
+		for binMin < binMid {
+			if dmp.matchBitapScore(d, loc+binMid, loc, pattern) <= scoreThreshold {
+				binMin = binMid
+			} else {
+				binMax = binMid
+			}
+			binMid = (binMax-binMin)/2 + binMin
+		}
+		// Use the result from this iteration as the maximum for the next.
+		binMax = binMid
+		start := int(math.Max(1, float64(loc-binMid+1)))
+		finish := int(math.Min(float64(loc+binMid), float64(len(text))) + float64(len(pattern)))
+
+		rd := make([]int, finish+2)
+		rd[finish+1] = (1 << uint(d)) - 1
+
+		for j := finish; j >= start; j-- {
+			var charMatch int
+			if len(text) <= j-1 {
+				// Out of range.
+				charMatch = 0
+			} else if _, ok := s[text[j-1]]; !ok {
+				charMatch = 0
+			} else {
+				charMatch = s[text[j-1]]
+			}
+
+			if d == 0 {
+				// First pass: exact match.
+				rd[j] = ((rd[j+1] << 1) | 1) & charMatch
+			} else {
+				// Subsequent passes: fuzzy match.
+				rd[j] = ((rd[j+1]<<1)|1)&charMatch | (((lastRd[j+1] | lastRd[j]) << 1) | 1) | lastRd[j+1]
+			}
+			if (rd[j] & matchmask) != 0 {
+				score := dmp.matchBitapScore(d, j-1, loc, pattern)
+				// This match will almost certainly be better than any existing match.  But check anyway.
+				if score <= scoreThreshold {
+					// Told you so.
+					scoreThreshold = score
+					bestLoc = j - 1
+					if bestLoc > loc {
+						// When passing loc, don't exceed our current distance from loc.
+						start = int(math.Max(1, float64(2*loc-bestLoc)))
+					} else {
+						// Already passed loc, downhill from here on in.
+						break
+					}
+				}
+			}
+		}
+		if dmp.matchBitapScore(d+1, loc, loc, pattern) > scoreThreshold {
+			// No hope for a (better) match at greater error levels.
+			break
+		}
+		lastRd = rd
+	}
+	return bestLoc
+}
+
+// matchBitapScore computes and returns the score for a match with e errors and x location.
+func (dmp *DiffMatchPatch) matchBitapScore(e, x, loc int, pattern string) float64 {
+	accuracy := float64(e) / float64(len(pattern))
+	proximity := math.Abs(float64(loc - x))
+	if dmp.MatchDistance == 0 {
+		// Dodge divide by zero error.
+		if proximity == 0 {
+			return accuracy
+		}
+
+		return 1.0
+	}
+	return accuracy + (proximity / float64(dmp.MatchDistance))
+}
+
+// MatchAlphabet initialises the alphabet for the Bitap algorithm.
+func (dmp *DiffMatchPatch) MatchAlphabet(pattern string) map[byte]int {
+	s := map[byte]int{}
+	charPattern := []byte(pattern)
+	for _, c := range charPattern {
+		_, ok := s[c]
+		if !ok {
+			s[c] = 0
+		}
+	}
+	i := 0
+
+	for _, c := range charPattern {
+		value := s[c] | int(uint(1)<<uint((len(pattern)-i-1)))
+		s[c] = value
+		i++
+	}
+	return s
+}

+ 23 - 0
vendor/github.com/smartystreets/assertions/internal/go-diff/diffmatchpatch/mathutil.go

@@ -0,0 +1,23 @@
+// Copyright (c) 2012-2016 The go-diff authors. All rights reserved.
+// https://github.com/sergi/go-diff
+// See the included LICENSE file for license details.
+//
+// go-diff is a Go implementation of Google's Diff, Match, and Patch library
+// Original library is Copyright (c) 2006 Google Inc.
+// http://code.google.com/p/google-diff-match-patch/
+
+package diffmatchpatch
+
+func min(x, y int) int {
+	if x < y {
+		return x
+	}
+	return y
+}
+
+func max(x, y int) int {
+	if x > y {
+		return x
+	}
+	return y
+}

+ 17 - 0
vendor/github.com/smartystreets/assertions/internal/go-diff/diffmatchpatch/operation_string.go

@@ -0,0 +1,17 @@
+// Code generated by "stringer -type=Operation -trimprefix=Diff"; DO NOT EDIT.
+
+package diffmatchpatch
+
+import "fmt"
+
+const _Operation_name = "DeleteEqualInsert"
+
+var _Operation_index = [...]uint8{0, 6, 11, 17}
+
+func (i Operation) String() string {
+	i -= -1
+	if i < 0 || i >= Operation(len(_Operation_index)-1) {
+		return fmt.Sprintf("Operation(%d)", i+-1)
+	}
+	return _Operation_name[_Operation_index[i]:_Operation_index[i+1]]
+}

+ 556 - 0
vendor/github.com/smartystreets/assertions/internal/go-diff/diffmatchpatch/patch.go

@@ -0,0 +1,556 @@
+// Copyright (c) 2012-2016 The go-diff authors. All rights reserved.
+// https://github.com/sergi/go-diff
+// See the included LICENSE file for license details.
+//
+// go-diff is a Go implementation of Google's Diff, Match, and Patch library
+// Original library is Copyright (c) 2006 Google Inc.
+// http://code.google.com/p/google-diff-match-patch/
+
+package diffmatchpatch
+
+import (
+	"bytes"
+	"errors"
+	"math"
+	"net/url"
+	"regexp"
+	"strconv"
+	"strings"
+)
+
+// Patch represents one patch operation.
+type Patch struct {
+	diffs   []Diff
+	Start1  int
+	Start2  int
+	Length1 int
+	Length2 int
+}
+
+// String emulates GNU diff's format.
+// Header: @@ -382,8 +481,9 @@
+// Indices are printed as 1-based, not 0-based.
+func (p *Patch) String() string {
+	var coords1, coords2 string
+
+	if p.Length1 == 0 {
+		coords1 = strconv.Itoa(p.Start1) + ",0"
+	} else if p.Length1 == 1 {
+		coords1 = strconv.Itoa(p.Start1 + 1)
+	} else {
+		coords1 = strconv.Itoa(p.Start1+1) + "," + strconv.Itoa(p.Length1)
+	}
+
+	if p.Length2 == 0 {
+		coords2 = strconv.Itoa(p.Start2) + ",0"
+	} else if p.Length2 == 1 {
+		coords2 = strconv.Itoa(p.Start2 + 1)
+	} else {
+		coords2 = strconv.Itoa(p.Start2+1) + "," + strconv.Itoa(p.Length2)
+	}
+
+	var text bytes.Buffer
+	_, _ = text.WriteString("@@ -" + coords1 + " +" + coords2 + " @@\n")
+
+	// Escape the body of the patch with %xx notation.
+	for _, aDiff := range p.diffs {
+		switch aDiff.Type {
+		case DiffInsert:
+			_, _ = text.WriteString("+")
+		case DiffDelete:
+			_, _ = text.WriteString("-")
+		case DiffEqual:
+			_, _ = text.WriteString(" ")
+		}
+
+		_, _ = text.WriteString(strings.Replace(url.QueryEscape(aDiff.Text), "+", " ", -1))
+		_, _ = text.WriteString("\n")
+	}
+
+	return unescaper.Replace(text.String())
+}
+
+// PatchAddContext increases the context until it is unique, but doesn't let the pattern expand beyond MatchMaxBits.
+func (dmp *DiffMatchPatch) PatchAddContext(patch Patch, text string) Patch {
+	if len(text) == 0 {
+		return patch
+	}
+
+	pattern := text[patch.Start2 : patch.Start2+patch.Length1]
+	padding := 0
+
+	// Look for the first and last matches of pattern in text.  If two different matches are found, increase the pattern length.
+	for strings.Index(text, pattern) != strings.LastIndex(text, pattern) &&
+		len(pattern) < dmp.MatchMaxBits-2*dmp.PatchMargin {
+		padding += dmp.PatchMargin
+		maxStart := max(0, patch.Start2-padding)
+		minEnd := min(len(text), patch.Start2+patch.Length1+padding)
+		pattern = text[maxStart:minEnd]
+	}
+	// Add one chunk for good luck.
+	padding += dmp.PatchMargin
+
+	// Add the prefix.
+	prefix := text[max(0, patch.Start2-padding):patch.Start2]
+	if len(prefix) != 0 {
+		patch.diffs = append([]Diff{Diff{DiffEqual, prefix}}, patch.diffs...)
+	}
+	// Add the suffix.
+	suffix := text[patch.Start2+patch.Length1 : min(len(text), patch.Start2+patch.Length1+padding)]
+	if len(suffix) != 0 {
+		patch.diffs = append(patch.diffs, Diff{DiffEqual, suffix})
+	}
+
+	// Roll back the start points.
+	patch.Start1 -= len(prefix)
+	patch.Start2 -= len(prefix)
+	// Extend the lengths.
+	patch.Length1 += len(prefix) + len(suffix)
+	patch.Length2 += len(prefix) + len(suffix)
+
+	return patch
+}
+
+// PatchMake computes a list of patches.
+func (dmp *DiffMatchPatch) PatchMake(opt ...interface{}) []Patch {
+	if len(opt) == 1 {
+		diffs, _ := opt[0].([]Diff)
+		text1 := dmp.DiffText1(diffs)
+		return dmp.PatchMake(text1, diffs)
+	} else if len(opt) == 2 {
+		text1 := opt[0].(string)
+		switch t := opt[1].(type) {
+		case string:
+			diffs := dmp.DiffMain(text1, t, true)
+			if len(diffs) > 2 {
+				diffs = dmp.DiffCleanupSemantic(diffs)
+				diffs = dmp.DiffCleanupEfficiency(diffs)
+			}
+			return dmp.PatchMake(text1, diffs)
+		case []Diff:
+			return dmp.patchMake2(text1, t)
+		}
+	} else if len(opt) == 3 {
+		return dmp.PatchMake(opt[0], opt[2])
+	}
+	return []Patch{}
+}
+
+// patchMake2 computes a list of patches to turn text1 into text2.
+// text2 is not provided, diffs are the delta between text1 and text2.
+func (dmp *DiffMatchPatch) patchMake2(text1 string, diffs []Diff) []Patch {
+	// Check for null inputs not needed since null can't be passed in C#.
+	patches := []Patch{}
+	if len(diffs) == 0 {
+		return patches // Get rid of the null case.
+	}
+
+	patch := Patch{}
+	charCount1 := 0 // Number of characters into the text1 string.
+	charCount2 := 0 // Number of characters into the text2 string.
+	// Start with text1 (prepatchText) and apply the diffs until we arrive at text2 (postpatchText). We recreate the patches one by one to determine context info.
+	prepatchText := text1
+	postpatchText := text1
+
+	for i, aDiff := range diffs {
+		if len(patch.diffs) == 0 && aDiff.Type != DiffEqual {
+			// A new patch starts here.
+			patch.Start1 = charCount1
+			patch.Start2 = charCount2
+		}
+
+		switch aDiff.Type {
+		case DiffInsert:
+			patch.diffs = append(patch.diffs, aDiff)
+			patch.Length2 += len(aDiff.Text)
+			postpatchText = postpatchText[:charCount2] +
+				aDiff.Text + postpatchText[charCount2:]
+		case DiffDelete:
+			patch.Length1 += len(aDiff.Text)
+			patch.diffs = append(patch.diffs, aDiff)
+			postpatchText = postpatchText[:charCount2] + postpatchText[charCount2+len(aDiff.Text):]
+		case DiffEqual:
+			if len(aDiff.Text) <= 2*dmp.PatchMargin &&
+				len(patch.diffs) != 0 && i != len(diffs)-1 {
+				// Small equality inside a patch.
+				patch.diffs = append(patch.diffs, aDiff)
+				patch.Length1 += len(aDiff.Text)
+				patch.Length2 += len(aDiff.Text)
+			}
+			if len(aDiff.Text) >= 2*dmp.PatchMargin {
+				// Time for a new patch.
+				if len(patch.diffs) != 0 {
+					patch = dmp.PatchAddContext(patch, prepatchText)
+					patches = append(patches, patch)
+					patch = Patch{}
+					// Unlike Unidiff, our patch lists have a rolling context. http://code.google.com/p/google-diff-match-patch/wiki/Unidiff Update prepatch text & pos to reflect the application of the just completed patch.
+					prepatchText = postpatchText
+					charCount1 = charCount2
+				}
+			}
+		}
+
+		// Update the current character count.
+		if aDiff.Type != DiffInsert {
+			charCount1 += len(aDiff.Text)
+		}
+		if aDiff.Type != DiffDelete {
+			charCount2 += len(aDiff.Text)
+		}
+	}
+
+	// Pick up the leftover patch if not empty.
+	if len(patch.diffs) != 0 {
+		patch = dmp.PatchAddContext(patch, prepatchText)
+		patches = append(patches, patch)
+	}
+
+	return patches
+}
+
+// PatchDeepCopy returns an array that is identical to a given an array of patches.
+func (dmp *DiffMatchPatch) PatchDeepCopy(patches []Patch) []Patch {
+	patchesCopy := []Patch{}
+	for _, aPatch := range patches {
+		patchCopy := Patch{}
+		for _, aDiff := range aPatch.diffs {
+			patchCopy.diffs = append(patchCopy.diffs, Diff{
+				aDiff.Type,
+				aDiff.Text,
+			})
+		}
+		patchCopy.Start1 = aPatch.Start1
+		patchCopy.Start2 = aPatch.Start2
+		patchCopy.Length1 = aPatch.Length1
+		patchCopy.Length2 = aPatch.Length2
+		patchesCopy = append(patchesCopy, patchCopy)
+	}
+	return patchesCopy
+}
+
+// PatchApply merges a set of patches onto the text.  Returns a patched text, as well as an array of true/false values indicating which patches were applied.
+func (dmp *DiffMatchPatch) PatchApply(patches []Patch, text string) (string, []bool) {
+	if len(patches) == 0 {
+		return text, []bool{}
+	}
+
+	// Deep copy the patches so that no changes are made to originals.
+	patches = dmp.PatchDeepCopy(patches)
+
+	nullPadding := dmp.PatchAddPadding(patches)
+	text = nullPadding + text + nullPadding
+	patches = dmp.PatchSplitMax(patches)
+
+	x := 0
+	// delta keeps track of the offset between the expected and actual location of the previous patch.  If there are patches expected at positions 10 and 20, but the first patch was found at 12, delta is 2 and the second patch has an effective expected position of 22.
+	delta := 0
+	results := make([]bool, len(patches))
+	for _, aPatch := range patches {
+		expectedLoc := aPatch.Start2 + delta
+		text1 := dmp.DiffText1(aPatch.diffs)
+		var startLoc int
+		endLoc := -1
+		if len(text1) > dmp.MatchMaxBits {
+			// PatchSplitMax will only provide an oversized pattern in the case of a monster delete.
+			startLoc = dmp.MatchMain(text, text1[:dmp.MatchMaxBits], expectedLoc)
+			if startLoc != -1 {
+				endLoc = dmp.MatchMain(text,
+					text1[len(text1)-dmp.MatchMaxBits:], expectedLoc+len(text1)-dmp.MatchMaxBits)
+				if endLoc == -1 || startLoc >= endLoc {
+					// Can't find valid trailing context.  Drop this patch.
+					startLoc = -1
+				}
+			}
+		} else {
+			startLoc = dmp.MatchMain(text, text1, expectedLoc)
+		}
+		if startLoc == -1 {
+			// No match found.  :(
+			results[x] = false
+			// Subtract the delta for this failed patch from subsequent patches.
+			delta -= aPatch.Length2 - aPatch.Length1
+		} else {
+			// Found a match.  :)
+			results[x] = true
+			delta = startLoc - expectedLoc
+			var text2 string
+			if endLoc == -1 {
+				text2 = text[startLoc:int(math.Min(float64(startLoc+len(text1)), float64(len(text))))]
+			} else {
+				text2 = text[startLoc:int(math.Min(float64(endLoc+dmp.MatchMaxBits), float64(len(text))))]
+			}
+			if text1 == text2 {
+				// Perfect match, just shove the Replacement text in.
+				text = text[:startLoc] + dmp.DiffText2(aPatch.diffs) + text[startLoc+len(text1):]
+			} else {
+				// Imperfect match.  Run a diff to get a framework of equivalent indices.
+				diffs := dmp.DiffMain(text1, text2, false)
+				if len(text1) > dmp.MatchMaxBits && float64(dmp.DiffLevenshtein(diffs))/float64(len(text1)) > dmp.PatchDeleteThreshold {
+					// The end points match, but the content is unacceptably bad.
+					results[x] = false
+				} else {
+					diffs = dmp.DiffCleanupSemanticLossless(diffs)
+					index1 := 0
+					for _, aDiff := range aPatch.diffs {
+						if aDiff.Type != DiffEqual {
+							index2 := dmp.DiffXIndex(diffs, index1)
+							if aDiff.Type == DiffInsert {
+								// Insertion
+								text = text[:startLoc+index2] + aDiff.Text + text[startLoc+index2:]
+							} else if aDiff.Type == DiffDelete {
+								// Deletion
+								startIndex := startLoc + index2
+								text = text[:startIndex] +
+									text[startIndex+dmp.DiffXIndex(diffs, index1+len(aDiff.Text))-index2:]
+							}
+						}
+						if aDiff.Type != DiffDelete {
+							index1 += len(aDiff.Text)
+						}
+					}
+				}
+			}
+		}
+		x++
+	}
+	// Strip the padding off.
+	text = text[len(nullPadding) : len(nullPadding)+(len(text)-2*len(nullPadding))]
+	return text, results
+}
+
+// PatchAddPadding adds some padding on text start and end so that edges can match something.
+// Intended to be called only from within patchApply.
+func (dmp *DiffMatchPatch) PatchAddPadding(patches []Patch) string {
+	paddingLength := dmp.PatchMargin
+	nullPadding := ""
+	for x := 1; x <= paddingLength; x++ {
+		nullPadding += string(x)
+	}
+
+	// Bump all the patches forward.
+	for i := range patches {
+		patches[i].Start1 += paddingLength
+		patches[i].Start2 += paddingLength
+	}
+
+	// Add some padding on start of first diff.
+	if len(patches[0].diffs) == 0 || patches[0].diffs[0].Type != DiffEqual {
+		// Add nullPadding equality.
+		patches[0].diffs = append([]Diff{Diff{DiffEqual, nullPadding}}, patches[0].diffs...)
+		patches[0].Start1 -= paddingLength // Should be 0.
+		patches[0].Start2 -= paddingLength // Should be 0.
+		patches[0].Length1 += paddingLength
+		patches[0].Length2 += paddingLength
+	} else if paddingLength > len(patches[0].diffs[0].Text) {
+		// Grow first equality.
+		extraLength := paddingLength - len(patches[0].diffs[0].Text)
+		patches[0].diffs[0].Text = nullPadding[len(patches[0].diffs[0].Text):] + patches[0].diffs[0].Text
+		patches[0].Start1 -= extraLength
+		patches[0].Start2 -= extraLength
+		patches[0].Length1 += extraLength
+		patches[0].Length2 += extraLength
+	}
+
+	// Add some padding on end of last diff.
+	last := len(patches) - 1
+	if len(patches[last].diffs) == 0 || patches[last].diffs[len(patches[last].diffs)-1].Type != DiffEqual {
+		// Add nullPadding equality.
+		patches[last].diffs = append(patches[last].diffs, Diff{DiffEqual, nullPadding})
+		patches[last].Length1 += paddingLength
+		patches[last].Length2 += paddingLength
+	} else if paddingLength > len(patches[last].diffs[len(patches[last].diffs)-1].Text) {
+		// Grow last equality.
+		lastDiff := patches[last].diffs[len(patches[last].diffs)-1]
+		extraLength := paddingLength - len(lastDiff.Text)
+		patches[last].diffs[len(patches[last].diffs)-1].Text += nullPadding[:extraLength]
+		patches[last].Length1 += extraLength
+		patches[last].Length2 += extraLength
+	}
+
+	return nullPadding
+}
+
+// PatchSplitMax looks through the patches and breaks up any which are longer than the maximum limit of the match algorithm.
+// Intended to be called only from within patchApply.
+func (dmp *DiffMatchPatch) PatchSplitMax(patches []Patch) []Patch {
+	patchSize := dmp.MatchMaxBits
+	for x := 0; x < len(patches); x++ {
+		if patches[x].Length1 <= patchSize {
+			continue
+		}
+		bigpatch := patches[x]
+		// Remove the big old patch.
+		patches = append(patches[:x], patches[x+1:]...)
+		x--
+
+		Start1 := bigpatch.Start1
+		Start2 := bigpatch.Start2
+		precontext := ""
+		for len(bigpatch.diffs) != 0 {
+			// Create one of several smaller patches.
+			patch := Patch{}
+			empty := true
+			patch.Start1 = Start1 - len(precontext)
+			patch.Start2 = Start2 - len(precontext)
+			if len(precontext) != 0 {
+				patch.Length1 = len(precontext)
+				patch.Length2 = len(precontext)
+				patch.diffs = append(patch.diffs, Diff{DiffEqual, precontext})
+			}
+			for len(bigpatch.diffs) != 0 && patch.Length1 < patchSize-dmp.PatchMargin {
+				diffType := bigpatch.diffs[0].Type
+				diffText := bigpatch.diffs[0].Text
+				if diffType == DiffInsert {
+					// Insertions are harmless.
+					patch.Length2 += len(diffText)
+					Start2 += len(diffText)
+					patch.diffs = append(patch.diffs, bigpatch.diffs[0])
+					bigpatch.diffs = bigpatch.diffs[1:]
+					empty = false
+				} else if diffType == DiffDelete && len(patch.diffs) == 1 && patch.diffs[0].Type == DiffEqual && len(diffText) > 2*patchSize {
+					// This is a large deletion.  Let it pass in one chunk.
+					patch.Length1 += len(diffText)
+					Start1 += len(diffText)
+					empty = false
+					patch.diffs = append(patch.diffs, Diff{diffType, diffText})
+					bigpatch.diffs = bigpatch.diffs[1:]
+				} else {
+					// Deletion or equality.  Only take as much as we can stomach.
+					diffText = diffText[:min(len(diffText), patchSize-patch.Length1-dmp.PatchMargin)]
+
+					patch.Length1 += len(diffText)
+					Start1 += len(diffText)
+					if diffType == DiffEqual {
+						patch.Length2 += len(diffText)
+						Start2 += len(diffText)
+					} else {
+						empty = false
+					}
+					patch.diffs = append(patch.diffs, Diff{diffType, diffText})
+					if diffText == bigpatch.diffs[0].Text {
+						bigpatch.diffs = bigpatch.diffs[1:]
+					} else {
+						bigpatch.diffs[0].Text =
+							bigpatch.diffs[0].Text[len(diffText):]
+					}
+				}
+			}
+			// Compute the head context for the next patch.
+			precontext = dmp.DiffText2(patch.diffs)
+			precontext = precontext[max(0, len(precontext)-dmp.PatchMargin):]
+
+			postcontext := ""
+			// Append the end context for this patch.
+			if len(dmp.DiffText1(bigpatch.diffs)) > dmp.PatchMargin {
+				postcontext = dmp.DiffText1(bigpatch.diffs)[:dmp.PatchMargin]
+			} else {
+				postcontext = dmp.DiffText1(bigpatch.diffs)
+			}
+
+			if len(postcontext) != 0 {
+				patch.Length1 += len(postcontext)
+				patch.Length2 += len(postcontext)
+				if len(patch.diffs) != 0 && patch.diffs[len(patch.diffs)-1].Type == DiffEqual {
+					patch.diffs[len(patch.diffs)-1].Text += postcontext
+				} else {
+					patch.diffs = append(patch.diffs, Diff{DiffEqual, postcontext})
+				}
+			}
+			if !empty {
+				x++
+				patches = append(patches[:x], append([]Patch{patch}, patches[x:]...)...)
+			}
+		}
+	}
+	return patches
+}
+
+// PatchToText takes a list of patches and returns a textual representation.
+func (dmp *DiffMatchPatch) PatchToText(patches []Patch) string {
+	var text bytes.Buffer
+	for _, aPatch := range patches {
+		_, _ = text.WriteString(aPatch.String())
+	}
+	return text.String()
+}
+
+// PatchFromText parses a textual representation of patches and returns a List of Patch objects.
+func (dmp *DiffMatchPatch) PatchFromText(textline string) ([]Patch, error) {
+	patches := []Patch{}
+	if len(textline) == 0 {
+		return patches, nil
+	}
+	text := strings.Split(textline, "\n")
+	textPointer := 0
+	patchHeader := regexp.MustCompile("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$")
+
+	var patch Patch
+	var sign uint8
+	var line string
+	for textPointer < len(text) {
+
+		if !patchHeader.MatchString(text[textPointer]) {
+			return patches, errors.New("Invalid patch string: " + text[textPointer])
+		}
+
+		patch = Patch{}
+		m := patchHeader.FindStringSubmatch(text[textPointer])
+
+		patch.Start1, _ = strconv.Atoi(m[1])
+		if len(m[2]) == 0 {
+			patch.Start1--
+			patch.Length1 = 1
+		} else if m[2] == "0" {
+			patch.Length1 = 0
+		} else {
+			patch.Start1--
+			patch.Length1, _ = strconv.Atoi(m[2])
+		}
+
+		patch.Start2, _ = strconv.Atoi(m[3])
+
+		if len(m[4]) == 0 {
+			patch.Start2--
+			patch.Length2 = 1
+		} else if m[4] == "0" {
+			patch.Length2 = 0
+		} else {
+			patch.Start2--
+			patch.Length2, _ = strconv.Atoi(m[4])
+		}
+		textPointer++
+
+		for textPointer < len(text) {
+			if len(text[textPointer]) > 0 {
+				sign = text[textPointer][0]
+			} else {
+				textPointer++
+				continue
+			}
+
+			line = text[textPointer][1:]
+			line = strings.Replace(line, "+", "%2b", -1)
+			line, _ = url.QueryUnescape(line)
+			if sign == '-' {
+				// Deletion.
+				patch.diffs = append(patch.diffs, Diff{DiffDelete, line})
+			} else if sign == '+' {
+				// Insertion.
+				patch.diffs = append(patch.diffs, Diff{DiffInsert, line})
+			} else if sign == ' ' {
+				// Minor equality.
+				patch.diffs = append(patch.diffs, Diff{DiffEqual, line})
+			} else if sign == '@' {
+				// Start of next patch.
+				break
+			} else {
+				// WTF?
+				return patches, errors.New("Invalid patch mode '" + string(sign) + "' in: " + string(line))
+			}
+			textPointer++
+		}
+
+		patches = append(patches, patch)
+	}
+	return patches, nil
+}

+ 88 - 0
vendor/github.com/smartystreets/assertions/internal/go-diff/diffmatchpatch/stringutil.go

@@ -0,0 +1,88 @@
+// Copyright (c) 2012-2016 The go-diff authors. All rights reserved.
+// https://github.com/sergi/go-diff
+// See the included LICENSE file for license details.
+//
+// go-diff is a Go implementation of Google's Diff, Match, and Patch library
+// Original library is Copyright (c) 2006 Google Inc.
+// http://code.google.com/p/google-diff-match-patch/
+
+package diffmatchpatch
+
+import (
+	"strings"
+	"unicode/utf8"
+)
+
+// unescaper unescapes selected chars for compatibility with JavaScript's encodeURI.
+// In speed critical applications this could be dropped since the receiving application will certainly decode these fine. Note that this function is case-sensitive.  Thus "%3F" would not be unescaped.  But this is ok because it is only called with the output of HttpUtility.UrlEncode which returns lowercase hex. Example: "%3f" -> "?", "%24" -> "$", etc.
+var unescaper = strings.NewReplacer(
+	"%21", "!", "%7E", "~", "%27", "'",
+	"%28", "(", "%29", ")", "%3B", ";",
+	"%2F", "/", "%3F", "?", "%3A", ":",
+	"%40", "@", "%26", "&", "%3D", "=",
+	"%2B", "+", "%24", "$", "%2C", ",", "%23", "#", "%2A", "*")
+
+// indexOf returns the first index of pattern in str, starting at str[i].
+func indexOf(str string, pattern string, i int) int {
+	if i > len(str)-1 {
+		return -1
+	}
+	if i <= 0 {
+		return strings.Index(str, pattern)
+	}
+	ind := strings.Index(str[i:], pattern)
+	if ind == -1 {
+		return -1
+	}
+	return ind + i
+}
+
+// lastIndexOf returns the last index of pattern in str, starting at str[i].
+func lastIndexOf(str string, pattern string, i int) int {
+	if i < 0 {
+		return -1
+	}
+	if i >= len(str) {
+		return strings.LastIndex(str, pattern)
+	}
+	_, size := utf8.DecodeRuneInString(str[i:])
+	return strings.LastIndex(str[:i+size], pattern)
+}
+
+// runesIndexOf returns the index of pattern in target, starting at target[i].
+func runesIndexOf(target, pattern []rune, i int) int {
+	if i > len(target)-1 {
+		return -1
+	}
+	if i <= 0 {
+		return runesIndex(target, pattern)
+	}
+	ind := runesIndex(target[i:], pattern)
+	if ind == -1 {
+		return -1
+	}
+	return ind + i
+}
+
+func runesEqual(r1, r2 []rune) bool {
+	if len(r1) != len(r2) {
+		return false
+	}
+	for i, c := range r1 {
+		if c != r2[i] {
+			return false
+		}
+	}
+	return true
+}
+
+// runesIndex is the equivalent of strings.Index for rune slices.
+func runesIndex(r1, r2 []rune) int {
+	last := len(r1) - len(r2)
+	for i := 0; i <= last; i++ {
+		if runesEqual(r1[i:i+len(r2)], r2) {
+			return i
+		}
+	}
+	return -1
+}

+ 208 - 54
vendor/github.com/smartystreets/assertions/internal/go-render/render/render.go

@@ -12,32 +12,46 @@ import (
 	"strconv"
 )
 
-var implicitTypeMap = map[reflect.Kind]string{
+var builtinTypeMap = map[reflect.Kind]string{
 	reflect.Bool:       "bool",
-	reflect.String:     "string",
-	reflect.Int:        "int",
-	reflect.Int8:       "int8",
+	reflect.Complex128: "complex128",
+	reflect.Complex64:  "complex64",
+	reflect.Float32:    "float32",
+	reflect.Float64:    "float64",
 	reflect.Int16:      "int16",
 	reflect.Int32:      "int32",
 	reflect.Int64:      "int64",
-	reflect.Uint:       "uint",
-	reflect.Uint8:      "uint8",
+	reflect.Int8:       "int8",
+	reflect.Int:        "int",
+	reflect.String:     "string",
 	reflect.Uint16:     "uint16",
 	reflect.Uint32:     "uint32",
 	reflect.Uint64:     "uint64",
-	reflect.Float32:    "float32",
-	reflect.Float64:    "float64",
-	reflect.Complex64:  "complex64",
-	reflect.Complex128: "complex128",
+	reflect.Uint8:      "uint8",
+	reflect.Uint:       "uint",
+	reflect.Uintptr:    "uintptr",
+}
+
+var builtinTypeSet = map[string]struct{}{}
+
+func init() {
+	for _, v := range builtinTypeMap {
+		builtinTypeSet[v] = struct{}{}
+	}
 }
 
+var typeOfString = reflect.TypeOf("")
+var typeOfInt = reflect.TypeOf(int(1))
+var typeOfUint = reflect.TypeOf(uint(1))
+var typeOfFloat = reflect.TypeOf(10.1)
+
 // Render converts a structure to a string representation. Unline the "%#v"
 // format string, this resolves pointer types' contents in structs, maps, and
 // slices/arrays and prints their field values.
 func Render(v interface{}) string {
 	buf := bytes.Buffer{}
 	s := (*traverseState)(nil)
-	s.render(&buf, 0, reflect.ValueOf(v))
+	s.render(&buf, 0, reflect.ValueOf(v), false)
 	return buf.String()
 }
 
@@ -72,7 +86,7 @@ func (s *traverseState) forkFor(ptr uintptr) *traverseState {
 	return fs
 }
 
-func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value) {
+func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value, implicit bool) {
 	if v.Kind() == reflect.Invalid {
 		buf.WriteString("nil")
 		return
@@ -107,49 +121,80 @@ func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value) {
 		s = s.forkFor(pe)
 		if s == nil {
 			buf.WriteString("<REC(")
-			writeType(buf, ptrs, vt)
+			if !implicit {
+				writeType(buf, ptrs, vt)
+			}
 			buf.WriteString(")>")
 			return
 		}
 	}
 
+	isAnon := func(t reflect.Type) bool {
+		if t.Name() != "" {
+			if _, ok := builtinTypeSet[t.Name()]; !ok {
+				return false
+			}
+		}
+		return t.Kind() != reflect.Interface
+	}
+
 	switch vk {
 	case reflect.Struct:
-		writeType(buf, ptrs, vt)
+		if !implicit {
+			writeType(buf, ptrs, vt)
+		}
 		buf.WriteRune('{')
-		for i := 0; i < vt.NumField(); i++ {
-			if i > 0 {
-				buf.WriteString(", ")
-			}
-			buf.WriteString(vt.Field(i).Name)
-			buf.WriteRune(':')
+		if rendered, ok := renderTime(v); ok {
+			buf.WriteString(rendered)
+		} else {
+			structAnon := vt.Name() == ""
+			for i := 0; i < vt.NumField(); i++ {
+				if i > 0 {
+					buf.WriteString(", ")
+				}
+				anon := structAnon && isAnon(vt.Field(i).Type)
 
-			s.render(buf, 0, v.Field(i))
+				if !anon {
+					buf.WriteString(vt.Field(i).Name)
+					buf.WriteRune(':')
+				}
+
+				s.render(buf, 0, v.Field(i), anon)
+			}
 		}
 		buf.WriteRune('}')
 
 	case reflect.Slice:
 		if v.IsNil() {
-			writeType(buf, ptrs, vt)
-			buf.WriteString("(nil)")
+			if !implicit {
+				writeType(buf, ptrs, vt)
+				buf.WriteString("(nil)")
+			} else {
+				buf.WriteString("nil")
+			}
 			return
 		}
 		fallthrough
 
 	case reflect.Array:
-		writeType(buf, ptrs, vt)
+		if !implicit {
+			writeType(buf, ptrs, vt)
+		}
+		anon := vt.Name() == "" && isAnon(vt.Elem())
 		buf.WriteString("{")
 		for i := 0; i < v.Len(); i++ {
 			if i > 0 {
 				buf.WriteString(", ")
 			}
 
-			s.render(buf, 0, v.Index(i))
+			s.render(buf, 0, v.Index(i), anon)
 		}
 		buf.WriteRune('}')
 
 	case reflect.Map:
-		writeType(buf, ptrs, vt)
+		if !implicit {
+			writeType(buf, ptrs, vt)
+		}
 		if v.IsNil() {
 			buf.WriteString("(nil)")
 		} else {
@@ -158,14 +203,17 @@ func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value) {
 			mkeys := v.MapKeys()
 			tryAndSortMapKeys(vt, mkeys)
 
+			kt := vt.Key()
+			keyAnon := typeOfString.ConvertibleTo(kt) || typeOfInt.ConvertibleTo(kt) || typeOfUint.ConvertibleTo(kt) || typeOfFloat.ConvertibleTo(kt)
+			valAnon := vt.Name() == "" && isAnon(vt.Elem())
 			for i, mk := range mkeys {
 				if i > 0 {
 					buf.WriteString(", ")
 				}
 
-				s.render(buf, 0, mk)
+				s.render(buf, 0, mk, keyAnon)
 				buf.WriteString(":")
-				s.render(buf, 0, v.MapIndex(mk))
+				s.render(buf, 0, v.MapIndex(mk), valAnon)
 			}
 			buf.WriteRune('}')
 		}
@@ -176,11 +224,9 @@ func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value) {
 	case reflect.Interface:
 		if v.IsNil() {
 			writeType(buf, ptrs, v.Type())
-			buf.WriteRune('(')
-			fmt.Fprint(buf, "nil")
-			buf.WriteRune(')')
+			buf.WriteString("(nil)")
 		} else {
-			s.render(buf, ptrs, v.Elem())
+			s.render(buf, ptrs, v.Elem(), false)
 		}
 
 	case reflect.Chan, reflect.Func, reflect.UnsafePointer:
@@ -191,7 +237,7 @@ func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value) {
 
 	default:
 		tstr := vt.String()
-		implicit := ptrs == 0 && implicitTypeMap[vk] == tstr
+		implicit = implicit || (ptrs == 0 && builtinTypeMap[vk] == tstr)
 		if !implicit {
 			writeType(buf, ptrs, vt)
 			buf.WriteRune('(')
@@ -206,7 +252,7 @@ func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value) {
 		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 			fmt.Fprintf(buf, "%d", v.Int())
 
-		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
 			fmt.Fprintf(buf, "%d", v.Uint())
 
 		case reflect.Float32, reflect.Float64:
@@ -288,40 +334,148 @@ func writeType(buf *bytes.Buffer, ptrs int, t reflect.Type) {
 	}
 }
 
+type cmpFn func(a, b reflect.Value) int
+
 type sortableValueSlice struct {
-	kind     reflect.Kind
+	cmp      cmpFn
 	elements []reflect.Value
 }
 
-func (s *sortableValueSlice) Len() int {
+func (s sortableValueSlice) Len() int {
 	return len(s.elements)
 }
 
-func (s *sortableValueSlice) Less(i, j int) bool {
-	switch s.kind {
+func (s sortableValueSlice) Less(i, j int) bool {
+	return s.cmp(s.elements[i], s.elements[j]) < 0
+}
+
+func (s sortableValueSlice) Swap(i, j int) {
+	s.elements[i], s.elements[j] = s.elements[j], s.elements[i]
+}
+
+// cmpForType returns a cmpFn which sorts the data for some type t in the same
+// order that a go-native map key is compared for equality.
+func cmpForType(t reflect.Type) cmpFn {
+	switch t.Kind() {
 	case reflect.String:
-		return s.elements[i].String() < s.elements[j].String()
+		return func(av, bv reflect.Value) int {
+			a, b := av.String(), bv.String()
+			if a < b {
+				return -1
+			} else if a > b {
+				return 1
+			}
+			return 0
+		}
+
+	case reflect.Bool:
+		return func(av, bv reflect.Value) int {
+			a, b := av.Bool(), bv.Bool()
+			if !a && b {
+				return -1
+			} else if a && !b {
+				return 1
+			}
+			return 0
+		}
 
-	case reflect.Int:
-		return s.elements[i].Int() < s.elements[j].Int()
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return func(av, bv reflect.Value) int {
+			a, b := av.Int(), bv.Int()
+			if a < b {
+				return -1
+			} else if a > b {
+				return 1
+			}
+			return 0
+		}
 
-	default:
-		panic(fmt.Errorf("unsupported sort kind: %s", s.kind))
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
+		reflect.Uint64, reflect.Uintptr, reflect.UnsafePointer:
+		return func(av, bv reflect.Value) int {
+			a, b := av.Uint(), bv.Uint()
+			if a < b {
+				return -1
+			} else if a > b {
+				return 1
+			}
+			return 0
+		}
+
+	case reflect.Float32, reflect.Float64:
+		return func(av, bv reflect.Value) int {
+			a, b := av.Float(), bv.Float()
+			if a < b {
+				return -1
+			} else if a > b {
+				return 1
+			}
+			return 0
+		}
+
+	case reflect.Interface:
+		return func(av, bv reflect.Value) int {
+			a, b := av.InterfaceData(), bv.InterfaceData()
+			if a[0] < b[0] {
+				return -1
+			} else if a[0] > b[0] {
+				return 1
+			}
+			if a[1] < b[1] {
+				return -1
+			} else if a[1] > b[1] {
+				return 1
+			}
+			return 0
+		}
+
+	case reflect.Complex64, reflect.Complex128:
+		return func(av, bv reflect.Value) int {
+			a, b := av.Complex(), bv.Complex()
+			if real(a) < real(b) {
+				return -1
+			} else if real(a) > real(b) {
+				return 1
+			}
+			if imag(a) < imag(b) {
+				return -1
+			} else if imag(a) > imag(b) {
+				return 1
+			}
+			return 0
+		}
+
+	case reflect.Ptr, reflect.Chan:
+		return func(av, bv reflect.Value) int {
+			a, b := av.Pointer(), bv.Pointer()
+			if a < b {
+				return -1
+			} else if a > b {
+				return 1
+			}
+			return 0
+		}
+
+	case reflect.Struct:
+		cmpLst := make([]cmpFn, t.NumField())
+		for i := range cmpLst {
+			cmpLst[i] = cmpForType(t.Field(i).Type)
+		}
+		return func(a, b reflect.Value) int {
+			for i, cmp := range cmpLst {
+				if rslt := cmp(a.Field(i), b.Field(i)); rslt != 0 {
+					return rslt
+				}
+			}
+			return 0
+		}
 	}
-}
 
-func (s *sortableValueSlice) Swap(i, j int) {
-	s.elements[i], s.elements[j] = s.elements[j], s.elements[i]
+	return nil
 }
 
 func tryAndSortMapKeys(mt reflect.Type, k []reflect.Value) {
-	// Try our stock sortable values.
-	switch mt.Key().Kind() {
-	case reflect.String, reflect.Int:
-		vs := &sortableValueSlice{
-			kind:     mt.Key().Kind(),
-			elements: k,
-		}
-		sort.Sort(vs)
+	if cmp := cmpForType(mt.Key()); cmp != nil {
+		sort.Sort(sortableValueSlice{cmp, k})
 	}
 }

+ 26 - 0
vendor/github.com/smartystreets/assertions/internal/go-render/render/render_time.go

@@ -0,0 +1,26 @@
+package render
+
+import (
+	"reflect"
+	"time"
+)
+
+func renderTime(value reflect.Value) (string, bool) {
+	if instant, ok := convertTime(value); !ok {
+		return "", false
+	} else if instant.IsZero() {
+		return "0", true
+	} else {
+		return instant.String(), true
+	}
+}
+
+func convertTime(value reflect.Value) (t time.Time, ok bool) {
+	if value.Type() == timeType {
+		defer func() { recover() }()
+		t, ok = value.Interface().(time.Time)
+	}
+	return
+}
+
+var timeType = reflect.TypeOf(time.Time{})

+ 0 - 70
vendor/github.com/smartystreets/assertions/internal/oglematchers/all_of.go

@@ -1,70 +0,0 @@
-// Copyright 2011 Aaron Jacobs. All Rights Reserved.
-// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package oglematchers
-
-import (
-	"strings"
-)
-
-// AllOf accepts a set of matchers S and returns a matcher that follows the
-// algorithm below when considering a candidate c:
-//
-//  1. Return true if for every Matcher m in S, m matches c.
-//
-//  2. Otherwise, if there is a matcher m in S such that m returns a fatal
-//     error for c, return that matcher's error message.
-//
-//  3. Otherwise, return false with the error from some wrapped matcher.
-//
-// This is akin to a logical AND operation for matchers.
-func AllOf(matchers ...Matcher) Matcher {
-	return &allOfMatcher{matchers}
-}
-
-type allOfMatcher struct {
-	wrappedMatchers []Matcher
-}
-
-func (m *allOfMatcher) Description() string {
-	// Special case: the empty set.
-	if len(m.wrappedMatchers) == 0 {
-		return "is anything"
-	}
-
-	// Join the descriptions for the wrapped matchers.
-	wrappedDescs := make([]string, len(m.wrappedMatchers))
-	for i, wrappedMatcher := range m.wrappedMatchers {
-		wrappedDescs[i] = wrappedMatcher.Description()
-	}
-
-	return strings.Join(wrappedDescs, ", and ")
-}
-
-func (m *allOfMatcher) Matches(c interface{}) (err error) {
-	for _, wrappedMatcher := range m.wrappedMatchers {
-		if wrappedErr := wrappedMatcher.Matches(c); wrappedErr != nil {
-			err = wrappedErr
-
-			// If the error is fatal, return immediately with this error.
-			_, ok := wrappedErr.(*FatalError)
-			if ok {
-				return
-			}
-		}
-	}
-
-	return
-}

+ 0 - 32
vendor/github.com/smartystreets/assertions/internal/oglematchers/any.go

@@ -1,32 +0,0 @@
-// Copyright 2011 Aaron Jacobs. All Rights Reserved.
-// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package oglematchers
-
-// Any returns a matcher that matches any value.
-func Any() Matcher {
-	return &anyMatcher{}
-}
-
-type anyMatcher struct {
-}
-
-func (m *anyMatcher) Description() string {
-	return "is anything"
-}
-
-func (m *anyMatcher) Matches(c interface{}) error {
-	return nil
-}

+ 1 - 1
vendor/github.com/smartystreets/assertions/internal/oglematchers/contains.go

@@ -28,7 +28,7 @@ func Contains(x interface{}) Matcher {
 	var ok bool
 
 	if result.elementMatcher, ok = x.(Matcher); !ok {
-		result.elementMatcher = Equals(x)
+		result.elementMatcher = DeepEquals(x)
 	}
 
 	return &result

+ 0 - 91
vendor/github.com/smartystreets/assertions/internal/oglematchers/elements_are.go

@@ -1,91 +0,0 @@
-// Copyright 2012 Aaron Jacobs. All Rights Reserved.
-// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package oglematchers
-
-import (
-	"errors"
-	"fmt"
-	"reflect"
-	"strings"
-)
-
-// Given a list of arguments M, ElementsAre returns a matcher that matches
-// arrays and slices A where all of the following hold:
-//
-//  *  A is the same length as M.
-//
-//  *  For each i < len(A) where M[i] is a matcher, A[i] matches M[i].
-//
-//  *  For each i < len(A) where M[i] is not a matcher, A[i] matches
-//     Equals(M[i]).
-//
-func ElementsAre(M ...interface{}) Matcher {
-	// Copy over matchers, or convert to Equals(x) for non-matcher x.
-	subMatchers := make([]Matcher, len(M))
-	for i, x := range M {
-		if matcher, ok := x.(Matcher); ok {
-			subMatchers[i] = matcher
-			continue
-		}
-
-		subMatchers[i] = Equals(x)
-	}
-
-	return &elementsAreMatcher{subMatchers}
-}
-
-type elementsAreMatcher struct {
-	subMatchers []Matcher
-}
-
-func (m *elementsAreMatcher) Description() string {
-	subDescs := make([]string, len(m.subMatchers))
-	for i, sm := range m.subMatchers {
-		subDescs[i] = sm.Description()
-	}
-
-	return fmt.Sprintf("elements are: [%s]", strings.Join(subDescs, ", "))
-}
-
-func (m *elementsAreMatcher) Matches(candidates interface{}) error {
-	// The candidate must be a slice or an array.
-	v := reflect.ValueOf(candidates)
-	if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
-		return NewFatalError("which is not a slice or array")
-	}
-
-	// The length must be correct.
-	if v.Len() != len(m.subMatchers) {
-		return errors.New(fmt.Sprintf("which is of length %d", v.Len()))
-	}
-
-	// Check each element.
-	for i, subMatcher := range m.subMatchers {
-		c := v.Index(i)
-		if matchErr := subMatcher.Matches(c.Interface()); matchErr != nil {
-			// Return an errors indicating which element doesn't match. If the
-			// matcher error was fatal, make this one fatal too.
-			err := errors.New(fmt.Sprintf("whose element %d doesn't match", i))
-			if _, isFatal := matchErr.(*FatalError); isFatal {
-				err = NewFatalError(err.Error())
-			}
-
-			return err
-		}
-	}
-
-	return nil
-}

+ 0 - 51
vendor/github.com/smartystreets/assertions/internal/oglematchers/error.go

@@ -1,51 +0,0 @@
-// Copyright 2011 Aaron Jacobs. All Rights Reserved.
-// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package oglematchers
-
-// Error returns a matcher that matches non-nil values implementing the
-// built-in error interface for whom the return value of Error() matches the
-// supplied matcher.
-//
-// For example:
-//
-//     err := errors.New("taco burrito")
-//
-//     Error(Equals("taco burrito"))  // matches err
-//     Error(HasSubstr("taco"))       // matches err
-//     Error(HasSubstr("enchilada"))  // doesn't match err
-//
-func Error(m Matcher) Matcher {
-	return &errorMatcher{m}
-}
-
-type errorMatcher struct {
-	wrappedMatcher Matcher
-}
-
-func (m *errorMatcher) Description() string {
-	return "error " + m.wrappedMatcher.Description()
-}
-
-func (m *errorMatcher) Matches(c interface{}) error {
-	// Make sure that c is an error.
-	e, ok := c.(error)
-	if !ok {
-		return NewFatalError("which is not an error")
-	}
-
-	// Pass on the error text to the wrapped matcher.
-	return m.wrappedMatcher.Matches(e.Error())
-}

+ 0 - 37
vendor/github.com/smartystreets/assertions/internal/oglematchers/has_same_type_as.go

@@ -1,37 +0,0 @@
-// Copyright 2015 Aaron Jacobs. All Rights Reserved.
-// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package oglematchers
-
-import (
-	"fmt"
-	"reflect"
-)
-
-// HasSameTypeAs returns a matcher that matches values with exactly the same
-// type as the supplied prototype.
-func HasSameTypeAs(p interface{}) Matcher {
-	expected := reflect.TypeOf(p)
-	pred := func(c interface{}) error {
-		actual := reflect.TypeOf(c)
-		if actual != expected {
-			return fmt.Errorf("which has type %v", actual)
-		}
-
-		return nil
-	}
-
-	return NewMatcher(pred, fmt.Sprintf("has type %v", expected))
-}

+ 0 - 46
vendor/github.com/smartystreets/assertions/internal/oglematchers/has_substr.go

@@ -1,46 +0,0 @@
-// Copyright 2011 Aaron Jacobs. All Rights Reserved.
-// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package oglematchers
-
-import (
-	"errors"
-	"fmt"
-	"reflect"
-	"strings"
-)
-
-// HasSubstr returns a matcher that matches strings containing s as a
-// substring.
-func HasSubstr(s string) Matcher {
-	return NewMatcher(
-		func(c interface{}) error { return hasSubstr(s, c) },
-		fmt.Sprintf("has substring \"%s\"", s))
-}
-
-func hasSubstr(needle string, c interface{}) error {
-	v := reflect.ValueOf(c)
-	if v.Kind() != reflect.String {
-		return NewFatalError("which is not a string")
-	}
-
-	// Perform the substring search.
-	haystack := v.String()
-	if strings.Contains(haystack, needle) {
-		return nil
-	}
-
-	return errors.New("")
-}

+ 0 - 134
vendor/github.com/smartystreets/assertions/internal/oglematchers/identical_to.go

@@ -1,134 +0,0 @@
-// Copyright 2012 Aaron Jacobs. All Rights Reserved.
-// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package oglematchers
-
-import (
-	"errors"
-	"fmt"
-	"reflect"
-)
-
-// Is the type comparable according to the definition here?
-//
-//     http://weekly.golang.org/doc/go_spec.html#Comparison_operators
-//
-func isComparable(t reflect.Type) bool {
-	switch t.Kind() {
-	case reflect.Array:
-		return isComparable(t.Elem())
-
-	case reflect.Struct:
-		for i := 0; i < t.NumField(); i++ {
-			if !isComparable(t.Field(i).Type) {
-				return false
-			}
-		}
-
-		return true
-
-	case reflect.Slice, reflect.Map, reflect.Func:
-		return false
-	}
-
-	return true
-}
-
-// Should the supplied type be allowed as an argument to IdenticalTo?
-func isLegalForIdenticalTo(t reflect.Type) (bool, error) {
-	// Allow the zero type.
-	if t == nil {
-		return true, nil
-	}
-
-	// Reference types are always okay; we compare pointers.
-	switch t.Kind() {
-	case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan:
-		return true, nil
-	}
-
-	// Reject other non-comparable types.
-	if !isComparable(t) {
-		return false, errors.New(fmt.Sprintf("%v is not comparable", t))
-	}
-
-	return true, nil
-}
-
-// IdenticalTo(x) returns a matcher that matches values v with type identical
-// to x such that:
-//
-//  1. If v and x are of a reference type (slice, map, function, channel), then
-//     they are either both nil or are references to the same object.
-//
-//  2. Otherwise, if v and x are not of a reference type but have a valid type,
-//     then v == x.
-//
-// If v and x are both the invalid type (which results from the predeclared nil
-// value, or from nil interface variables), then the matcher is satisfied.
-//
-// This function will panic if x is of a value type that is not comparable. For
-// example, x cannot be an array of functions.
-func IdenticalTo(x interface{}) Matcher {
-	t := reflect.TypeOf(x)
-
-	// Reject illegal arguments.
-	if ok, err := isLegalForIdenticalTo(t); !ok {
-		panic("IdenticalTo: " + err.Error())
-	}
-
-	return &identicalToMatcher{x}
-}
-
-type identicalToMatcher struct {
-	x interface{}
-}
-
-func (m *identicalToMatcher) Description() string {
-	t := reflect.TypeOf(m.x)
-	return fmt.Sprintf("identical to <%v> %v", t, m.x)
-}
-
-func (m *identicalToMatcher) Matches(c interface{}) error {
-	// Make sure the candidate's type is correct.
-	t := reflect.TypeOf(m.x)
-	if ct := reflect.TypeOf(c); t != ct {
-		return NewFatalError(fmt.Sprintf("which is of type %v", ct))
-	}
-
-	// Special case: two values of the invalid type are always identical.
-	if t == nil {
-		return nil
-	}
-
-	// Handle reference types.
-	switch t.Kind() {
-	case reflect.Slice, reflect.Map, reflect.Func, reflect.Chan:
-		xv := reflect.ValueOf(m.x)
-		cv := reflect.ValueOf(c)
-		if xv.Pointer() == cv.Pointer() {
-			return nil
-		}
-
-		return errors.New("which is not an identical reference")
-	}
-
-	// Are the values equal?
-	if m.x == c {
-		return nil
-	}
-
-	return errors.New("")
-}

+ 0 - 69
vendor/github.com/smartystreets/assertions/internal/oglematchers/matches_regexp.go

@@ -1,69 +0,0 @@
-// Copyright 2011 Aaron Jacobs. All Rights Reserved.
-// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package oglematchers
-
-import (
-	"errors"
-	"fmt"
-	"reflect"
-	"regexp"
-)
-
-// MatchesRegexp returns a matcher that matches strings and byte slices whose
-// contents match the supplied regular expression. The semantics are those of
-// regexp.Match. In particular, that means the match is not implicitly anchored
-// to the ends of the string: MatchesRegexp("bar") will match "foo bar baz".
-func MatchesRegexp(pattern string) Matcher {
-	re, err := regexp.Compile(pattern)
-	if err != nil {
-		panic("MatchesRegexp: " + err.Error())
-	}
-
-	return &matchesRegexpMatcher{re}
-}
-
-type matchesRegexpMatcher struct {
-	re *regexp.Regexp
-}
-
-func (m *matchesRegexpMatcher) Description() string {
-	return fmt.Sprintf("matches regexp \"%s\"", m.re.String())
-}
-
-func (m *matchesRegexpMatcher) Matches(c interface{}) (err error) {
-	v := reflect.ValueOf(c)
-	isString := v.Kind() == reflect.String
-	isByteSlice := v.Kind() == reflect.Slice && v.Elem().Kind() == reflect.Uint8
-
-	err = errors.New("")
-
-	switch {
-	case isString:
-		if m.re.MatchString(v.String()) {
-			err = nil
-		}
-
-	case isByteSlice:
-		if m.re.Match(v.Bytes()) {
-			err = nil
-		}
-
-	default:
-		err = NewFatalError("which is not a string or []byte")
-	}
-
-	return
-}

+ 0 - 43
vendor/github.com/smartystreets/assertions/internal/oglematchers/new_matcher.go

@@ -1,43 +0,0 @@
-// Copyright 2015 Aaron Jacobs. All Rights Reserved.
-// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package oglematchers
-
-// Create a matcher with the given description and predicate function, which
-// will be invoked to handle calls to Matchers.
-//
-// Using this constructor may be a convenience over defining your own type that
-// implements Matcher if you do not need any logic in your Description method.
-func NewMatcher(
-	predicate func(interface{}) error,
-	description string) Matcher {
-	return &predicateMatcher{
-		predicate:   predicate,
-		description: description,
-	}
-}
-
-type predicateMatcher struct {
-	predicate   func(interface{}) error
-	description string
-}
-
-func (pm *predicateMatcher) Matches(c interface{}) error {
-	return pm.predicate(c)
-}
-
-func (pm *predicateMatcher) Description() string {
-	return pm.description
-}

+ 0 - 74
vendor/github.com/smartystreets/assertions/internal/oglematchers/panics.go

@@ -1,74 +0,0 @@
-// Copyright 2011 Aaron Jacobs. All Rights Reserved.
-// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package oglematchers
-
-import (
-	"errors"
-	"fmt"
-	"reflect"
-)
-
-// Panics matches zero-arg functions which, when invoked, panic with an error
-// that matches the supplied matcher.
-//
-// NOTE(jacobsa): This matcher cannot detect the case where the function panics
-// using panic(nil), by design of the language. See here for more info:
-//
-//     http://goo.gl/9aIQL
-//
-func Panics(m Matcher) Matcher {
-	return &panicsMatcher{m}
-}
-
-type panicsMatcher struct {
-	wrappedMatcher Matcher
-}
-
-func (m *panicsMatcher) Description() string {
-	return "panics with: " + m.wrappedMatcher.Description()
-}
-
-func (m *panicsMatcher) Matches(c interface{}) (err error) {
-	// Make sure c is a zero-arg function.
-	v := reflect.ValueOf(c)
-	if v.Kind() != reflect.Func || v.Type().NumIn() != 0 {
-		err = NewFatalError("which is not a zero-arg function")
-		return
-	}
-
-	// Call the function and check its panic error.
-	defer func() {
-		if e := recover(); e != nil {
-			err = m.wrappedMatcher.Matches(e)
-
-			// Set a clearer error message if the matcher said no.
-			if err != nil {
-				wrappedClause := ""
-				if err.Error() != "" {
-					wrappedClause = ", " + err.Error()
-				}
-
-				err = errors.New(fmt.Sprintf("which panicked with: %v%s", e, wrappedClause))
-			}
-		}
-	}()
-
-	v.Call([]reflect.Value{})
-
-	// If we get here, the function didn't panic.
-	err = errors.New("which didn't panic")
-	return
-}

+ 0 - 65
vendor/github.com/smartystreets/assertions/internal/oglematchers/pointee.go

@@ -1,65 +0,0 @@
-// Copyright 2012 Aaron Jacobs. All Rights Reserved.
-// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package oglematchers
-
-import (
-	"errors"
-	"fmt"
-	"reflect"
-)
-
-// Return a matcher that matches non-nil pointers whose pointee matches the
-// wrapped matcher.
-func Pointee(m Matcher) Matcher {
-	return &pointeeMatcher{m}
-}
-
-type pointeeMatcher struct {
-	wrapped Matcher
-}
-
-func (m *pointeeMatcher) Matches(c interface{}) (err error) {
-	// Make sure the candidate is of the appropriate type.
-	cv := reflect.ValueOf(c)
-	if !cv.IsValid() || cv.Kind() != reflect.Ptr {
-		return NewFatalError("which is not a pointer")
-	}
-
-	// Make sure the candidate is non-nil.
-	if cv.IsNil() {
-		return NewFatalError("")
-	}
-
-	// Defer to the wrapped matcher. Fix up empty errors so that failure messages
-	// are more helpful than just printing a pointer for "Actual".
-	pointee := cv.Elem().Interface()
-	err = m.wrapped.Matches(pointee)
-	if err != nil && err.Error() == "" {
-		s := fmt.Sprintf("whose pointee is %v", pointee)
-
-		if _, ok := err.(*FatalError); ok {
-			err = NewFatalError(s)
-		} else {
-			err = errors.New(s)
-		}
-	}
-
-	return err
-}
-
-func (m *pointeeMatcher) Description() string {
-	return fmt.Sprintf("pointee(%s)", m.wrapped.Description())
-}

+ 1 - 1
vendor/github.com/smartystreets/assertions/internal/oglematchers/transform_description.go

@@ -23,7 +23,7 @@ func transformDescription(m Matcher, newDesc string) Matcher {
 }
 
 type transformDescriptionMatcher struct {
-	desc string
+	desc           string
 	wrappedMatcher Matcher
 }
 

+ 73 - 61
vendor/github.com/smartystreets/assertions/messages.go

@@ -1,76 +1,85 @@
 package assertions
 
-const ( // equality
-	shouldHaveBeenEqual             = "Expected: '%v'\nActual:   '%v'\n(Should be equal)"
-	shouldNotHaveBeenEqual          = "Expected     '%v'\nto NOT equal '%v'\n(but it did)!"
-	shouldHaveBeenEqualTypeMismatch = "Expected: '%v' (%T)\nActual:   '%v' (%T)\n(Should be equal, type mismatch)"
-	shouldHaveBeenAlmostEqual       = "Expected '%v' to almost equal '%v' (but it didn't)!"
-	shouldHaveNotBeenAlmostEqual    = "Expected '%v' to NOT almost equal '%v' (but it did)!"
-	shouldHaveResembled             = "Expected: '%#v'\nActual:   '%#v'\n(Should resemble)!"
-	shouldHaveResembledTypeMismatch = "Expected: '%#v' (%T)\nActual:   '%#v' (%T)\n(Should resemble, type mismatch)"
-	shouldNotHaveResembled          = "Expected        '%#v'\nto NOT resemble '%#v'\n(but it did)!"
-	shouldBePointers                = "Both arguments should be pointers "
-	shouldHaveBeenNonNilPointer     = shouldBePointers + "(the %s was %s)!"
-	shouldHavePointedTo             = "Expected '%+v' (address: '%v') and '%+v' (address: '%v') to be the same address (but their weren't)!"
-	shouldNotHavePointedTo          = "Expected '%+v' and '%+v' to be different references (but they matched: '%v')!"
-	shouldHaveBeenNil               = "Expected: nil\nActual:   '%v'"
-	shouldNotHaveBeenNil            = "Expected '%+v' to NOT be nil (but it was)!"
-	shouldHaveBeenTrue              = "Expected: true\nActual:   %v"
-	shouldHaveBeenFalse             = "Expected: false\nActual:   %v"
-	shouldHaveBeenZeroValue         = "'%+v' should have been the zero value" //"Expected: (zero value)\nActual:   %v"
-)
+const (
+	shouldHaveBeenEqual              = "Expected: '%v'\nActual:   '%v'\n(Should be equal)"
+	shouldHaveBeenEqualNoResemblance = "Both the actual and expected values render equally ('%s') and their types are the same. Try using ShouldResemble instead."
+	shouldNotHaveBeenEqual           = "Expected     '%v'\nto NOT equal '%v'\n(but it did)!"
+	shouldHaveBeenEqualTypeMismatch  = "Expected: '%v' (%T)\nActual:   '%v' (%T)\n(Should be equal, type mismatch)"
+
+	shouldHaveBeenAlmostEqual    = "Expected '%v' to almost equal '%v' (but it didn't)!"
+	shouldHaveNotBeenAlmostEqual = "Expected '%v' to NOT almost equal '%v' (but it did)!"
+
+	shouldHaveResembled    = "Expected: '%s'\nActual:   '%s'\n(Should resemble)!"
+	shouldNotHaveResembled = "Expected        '%#v'\nto NOT resemble '%#v'\n(but it did)!"
+
+	shouldBePointers            = "Both arguments should be pointers "
+	shouldHaveBeenNonNilPointer = shouldBePointers + "(the %s was %s)!"
+	shouldHavePointedTo         = "Expected '%+v' (address: '%v') and '%+v' (address: '%v') to be the same address (but their weren't)!"
+	shouldNotHavePointedTo      = "Expected '%+v' and '%+v' to be different references (but they matched: '%v')!"
+
+	shouldHaveBeenNil    = "Expected: nil\nActual:   '%v'"
+	shouldNotHaveBeenNil = "Expected '%+v' to NOT be nil (but it was)!"
+
+	shouldHaveBeenTrue  = "Expected: true\nActual:   %v"
+	shouldHaveBeenFalse = "Expected: false\nActual:   %v"
+
+	shouldHaveBeenZeroValue    = "'%+v' should have been the zero value" //"Expected: (zero value)\nActual:   %v"
+	shouldNotHaveBeenZeroValue = "'%+v' should NOT have been the zero value"
+
+	shouldHaveBeenGreater        = "Expected '%v' to be greater than '%v' (but it wasn't)!"
+	shouldHaveBeenGreaterOrEqual = "Expected '%v' to be greater than or equal to '%v' (but it wasn't)!"
+
+	shouldHaveBeenLess        = "Expected '%v' to be less than '%v' (but it wasn't)!"
+	shouldHaveBeenLessOrEqual = "Expected '%v' to be less than or equal to '%v' (but it wasn't)!"
 
-const ( // quantity comparisons
-	shouldHaveBeenGreater            = "Expected '%v' to be greater than '%v' (but it wasn't)!"
-	shouldHaveBeenGreaterOrEqual     = "Expected '%v' to be greater than or equal to '%v' (but it wasn't)!"
-	shouldHaveBeenLess               = "Expected '%v' to be less than '%v' (but it wasn't)!"
-	shouldHaveBeenLessOrEqual        = "Expected '%v' to be less than or equal to '%v' (but it wasn't)!"
 	shouldHaveBeenBetween            = "Expected '%v' to be between '%v' and '%v' (but it wasn't)!"
 	shouldNotHaveBeenBetween         = "Expected '%v' NOT to be between '%v' and '%v' (but it was)!"
 	shouldHaveDifferentUpperAndLower = "The lower and upper bounds must be different values (they were both '%v')."
-	shouldHaveBeenBetweenOrEqual     = "Expected '%v' to be between '%v' and '%v' or equal to one of them (but it wasn't)!"
-	shouldNotHaveBeenBetweenOrEqual  = "Expected '%v' NOT to be between '%v' and '%v' or equal to one of them (but it was)!"
-)
 
-const ( // collections
+	shouldHaveBeenBetweenOrEqual    = "Expected '%v' to be between '%v' and '%v' or equal to one of them (but it wasn't)!"
+	shouldNotHaveBeenBetweenOrEqual = "Expected '%v' NOT to be between '%v' and '%v' or equal to one of them (but it was)!"
+
 	shouldHaveContained            = "Expected the container (%v) to contain: '%v' (but it didn't)!"
 	shouldNotHaveContained         = "Expected the container (%v) NOT to contain: '%v' (but it did)!"
-	shouldHaveContainedKey         = "Expected the %v to contain the key: %v (but it didn't)!"
-	shouldNotHaveContainedKey      = "Expected the %v NOT to contain the key: %v (but it did)!"
-	shouldHaveBeenIn               = "Expected '%v' to be in the container (%v), but it wasn't!"
-	shouldNotHaveBeenIn            = "Expected '%v' NOT to be in the container (%v), but it was!"
 	shouldHaveBeenAValidCollection = "You must provide a valid container (was %v)!"
-	shouldHaveBeenAValidMap        = "You must provide a valid map type (was %v)!"
-	shouldHaveBeenEmpty            = "Expected %+v to be empty (but it wasn't)!"
-	shouldNotHaveBeenEmpty         = "Expected %+v to NOT be empty (but it was)!"
-	shouldHaveBeenAValidInteger    = "You must provide a valid integer (was %v)!"
-	shouldHaveBeenAValidLength     = "You must provide a valid positive integer (was %v)!"
-	shouldHaveHadLength            = "Expected %+v (length: %v) to have length equal to '%v', but it wasn't!"
-)
 
-const ( // strings
-	shouldHaveStartedWith           = "Expected      '%v'\nto start with '%v'\n(but it didn't)!"
-	shouldNotHaveStartedWith        = "Expected          '%v'\nNOT to start with '%v'\n(but it did)!"
-	shouldHaveEndedWith             = "Expected    '%v'\nto end with '%v'\n(but it didn't)!"
-	shouldNotHaveEndedWith          = "Expected        '%v'\nNOT to end with '%v'\n(but it did)!"
-	shouldAllBeStrings              = "All arguments to this assertion must be strings (you provided: %v)."
-	shouldBothBeStrings             = "Both arguments to this assertion must be strings (you provided %v and %v)."
-	shouldBeString                  = "The argument to this assertion must be a string (you provided %v)."
+	shouldHaveContainedKey    = "Expected the %v to contain the key: %v (but it didn't)!"
+	shouldNotHaveContainedKey = "Expected the %v NOT to contain the key: %v (but it did)!"
+	shouldHaveBeenAValidMap   = "You must provide a valid map type (was %v)!"
+
+	shouldHaveBeenIn    = "Expected '%v' to be in the container (%v), but it wasn't!"
+	shouldNotHaveBeenIn = "Expected '%v' NOT to be in the container (%v), but it was!"
+
+	shouldHaveBeenEmpty    = "Expected %+v to be empty (but it wasn't)!"
+	shouldNotHaveBeenEmpty = "Expected %+v to NOT be empty (but it was)!"
+
+	shouldHaveBeenAValidInteger = "You must provide a valid integer (was %v)!"
+	shouldHaveBeenAValidLength  = "You must provide a valid positive integer (was %v)!"
+	shouldHaveHadLength         = "Expected collection to have length equal to [%v], but it's length was [%v] instead! contents: %+v"
+
+	shouldHaveStartedWith    = "Expected      '%v'\nto start with '%v'\n(but it didn't)!"
+	shouldNotHaveStartedWith = "Expected          '%v'\nNOT to start with '%v'\n(but it did)!"
+
+	shouldHaveEndedWith    = "Expected    '%v'\nto end with '%v'\n(but it didn't)!"
+	shouldNotHaveEndedWith = "Expected        '%v'\nNOT to end with '%v'\n(but it did)!"
+
+	shouldAllBeStrings  = "All arguments to this assertion must be strings (you provided: %v)."
+	shouldBothBeStrings = "Both arguments to this assertion must be strings (you provided %v and %v)."
+
 	shouldHaveContainedSubstring    = "Expected '%s' to contain substring '%s' (but it didn't)!"
 	shouldNotHaveContainedSubstring = "Expected '%s' NOT to contain substring '%s' (but it did)!"
-	shouldHaveBeenBlank             = "Expected '%s' to be blank (but it wasn't)!"
-	shouldNotHaveBeenBlank          = "Expected value to NOT be blank (but it was)!"
-)
 
-const ( // panics
+	shouldBeString         = "The argument to this assertion must be a string (you provided %v)."
+	shouldHaveBeenBlank    = "Expected '%s' to be blank (but it wasn't)!"
+	shouldNotHaveBeenBlank = "Expected value to NOT be blank (but it was)!"
+
 	shouldUseVoidNiladicFunction = "You must provide a void, niladic function as the first argument!"
-	shouldHavePanickedWith       = "Expected func() to panic with '%v' (but it panicked with '%v')!"
 	shouldHavePanicked           = "Expected func() to panic (but it didn't)!"
 	shouldNotHavePanicked        = "Expected func() NOT to panic (error: '%+v')!"
-	shouldNotHavePanickedWith    = "Expected func() NOT to panic with '%v' (but it did)!"
-)
 
-const ( // type checking
+	shouldHavePanickedWith    = "Expected func() to panic with '%v' (but it panicked with '%v')!"
+	shouldNotHavePanickedWith = "Expected func() NOT to panic with '%v' (but it did)!"
+
 	shouldHaveBeenA    = "Expected '%v' to be: '%v' (but was: '%v')!"
 	shouldNotHaveBeenA = "Expected '%v' to NOT be: '%v' (but it was)!"
 
@@ -78,17 +87,20 @@ const ( // type checking
 	shouldNotHaveImplemented          = "Expected         '%v'\nto NOT implement '%v'\n(but it did)!"
 	shouldCompareWithInterfacePointer = "The expected value must be a pointer to an interface type (eg. *fmt.Stringer)"
 	shouldNotBeNilActual              = "The actual value was 'nil' and should be a value or a pointer to a value!"
-)
 
-const ( // time comparisons
-	shouldUseTimes                   = "You must provide time instances as arguments to this assertion."
-	shouldUseTimeSlice               = "You must provide a slice of time instances as the first argument to this assertion."
-	shouldUseDurationAndTime         = "You must provide a duration and a time as arguments to this assertion."
+	shouldBeError                       = "Expected an error value (but was '%v' instead)!"
+	shouldBeErrorInvalidComparisonValue = "The final argument to this assertion must be a string or an error value (you provided: '%v')."
+
+	shouldUseTimes           = "You must provide time instances as arguments to this assertion."
+	shouldUseTimeSlice       = "You must provide a slice of time instances as the first argument to this assertion."
+	shouldUseDurationAndTime = "You must provide a duration and a time as arguments to this assertion."
+
 	shouldHaveHappenedBefore         = "Expected '%v' to happen before '%v' (it happened '%v' after)!"
 	shouldHaveHappenedAfter          = "Expected '%v' to happen after '%v' (it happened '%v' before)!"
 	shouldHaveHappenedBetween        = "Expected '%v' to happen between '%v' and '%v' (it happened '%v' outside threshold)!"
 	shouldNotHaveHappenedOnOrBetween = "Expected '%v' to NOT happen on or between '%v' and '%v' (but it did)!"
 
 	// format params: incorrect-index, previous-index, previous-time, incorrect-index, incorrect-time
-	shouldHaveBeenChronological = "The 'Time' at index [%d] should have happened after the previous one (but it didn't!):\n  [%d]: %s\n  [%d]: %s (see, it happened before!)"
+	shouldHaveBeenChronological    = "The 'Time' at index [%d] should have happened after the previous one (but it didn't!):\n  [%d]: %s\n  [%d]: %s (see, it happened before!)"
+	shouldNotHaveBeenchronological = "The provided times should NOT be chronological, but they were."
 )

+ 1 - 1
vendor/github.com/smartystreets/assertions/quantity.go

@@ -43,7 +43,7 @@ func ShouldBeLessThanOrEqualTo(actual interface{}, expected ...interface{}) stri
 	if fail := need(1, expected); fail != success {
 		return fail
 	} else if matchError := oglematchers.LessOrEqual(expected[0]).Matches(actual); matchError != nil {
-		return fmt.Sprintf(shouldHaveBeenLess, actual, expected[0])
+		return fmt.Sprintf(shouldHaveBeenLessOrEqual, actual, expected[0])
 	}
 	return success
 }

+ 11 - 10
vendor/github.com/smartystreets/assertions/serializer.go

@@ -3,6 +3,7 @@ package assertions
 import (
 	"encoding/json"
 	"fmt"
+	"strings"
 
 	"github.com/smartystreets/assertions/internal/go-render/render"
 )
@@ -15,28 +16,28 @@ type Serializer interface {
 type failureSerializer struct{}
 
 func (self *failureSerializer) serializeDetailed(expected, actual interface{}, message string) string {
+	if index := strings.Index(message, " Diff:"); index > 0 {
+		message = message[:index]
+	}
 	view := FailureView{
 		Message:  message,
 		Expected: render.Render(expected),
 		Actual:   render.Render(actual),
 	}
-	serialized, err := json.Marshal(view)
-	if err != nil {
-		return message
-	}
+	serialized, _ := json.Marshal(view)
 	return string(serialized)
 }
 
 func (self *failureSerializer) serialize(expected, actual interface{}, message string) string {
+	if index := strings.Index(message, " Diff:"); index > 0 {
+		message = message[:index]
+	}
 	view := FailureView{
 		Message:  message,
 		Expected: fmt.Sprintf("%+v", expected),
 		Actual:   fmt.Sprintf("%+v", actual),
 	}
-	serialized, err := json.Marshal(view)
-	if err != nil {
-		return message
-	}
+	serialized, _ := json.Marshal(view)
 	return string(serialized)
 }
 
@@ -57,8 +58,8 @@ type FailureView struct {
 ///////////////////////////////////////////////////////
 
 // noopSerializer just gives back the original message. This is useful when we are using
-// the assertions from a context other than the web UI, that requires the JSON structure
-// provided by the failureSerializer.
+// the assertions from a context other than the GoConvey Web UI, that requires the JSON
+// structure provided by the failureSerializer.
 type noopSerializer struct{}
 
 func (self *noopSerializer) serialize(expected, actual interface{}, message string) string {

+ 17 - 1
vendor/github.com/smartystreets/assertions/time.go

@@ -178,7 +178,7 @@ func ShouldNotHappenWithin(actual interface{}, expected ...interface{}) string {
 	return ShouldNotHappenOnOrBetween(actualTime, min, max)
 }
 
-// ShouldBeChronological receives a []time.Time slice and asserts that the are
+// ShouldBeChronological receives a []time.Time slice and asserts that they are
 // in chronological order starting with the first time.Time as the earliest.
 func ShouldBeChronological(actual interface{}, expected ...interface{}) string {
 	if fail := need(0, expected); fail != success {
@@ -200,3 +200,19 @@ func ShouldBeChronological(actual interface{}, expected ...interface{}) string {
 	}
 	return ""
 }
+
+// ShouldNotBeChronological receives a []time.Time slice and asserts that they are
+// NOT in chronological order.
+func ShouldNotBeChronological(actual interface{}, expected ...interface{}) string {
+	if fail := need(0, expected); fail != success {
+		return fail
+	}
+	if _, ok := actual.([]time.Time); !ok {
+		return shouldUseTimeSlice
+	}
+	result := ShouldBeChronological(actual, expected...)
+	if result != "" {
+		return ""
+	}
+	return shouldNotHaveBeenchronological
+}

Some files were not shown because too many files changed in this diff