浏览代码

Merge branch 'dev' into newcollaboration

Peter Smit 11 年之前
父节点
当前提交
03af37554e
共有 47 个文件被更改,包括 1296 次插入358 次删除
  1. 8 0
      .travis.yml
  2. 6 5
      README.md
  3. 3 3
      README_ZH.md
  4. 1 1
      cmd/fix.go
  5. 16 8
      cmd/web.go
  6. 5 2
      conf/app.ini
  7. 4 1
      conf/locale/TRANSLATORS
  8. 5 14
      conf/locale/locale_en-US.ini
  9. 728 0
      conf/locale/locale_ja-JP.ini
  10. 62 62
      conf/locale/locale_ru-RU.ini
  11. 1 1
      gogs.go
  12. 40 8
      models/action.go
  13. 8 8
      models/models.go
  14. 50 11
      models/publickey.go
  15. 7 5
      models/repo.go
  16. 1 0
      models/user.go
  17. 7 0
      modules/auth/auth.go
  18. 21 20
      modules/auth/user_form.go
  19. 9 3
      modules/base/markdown.go
  20. 7 5
      modules/base/template.go
  21. 3 27
      modules/base/tool.go
  22. 1 1
      modules/middleware/auth.go
  23. 1 1
      modules/setting/setting.go
  24. 0 16
      public/js/app.js
  25. 23 0
      public/ng/css/gogs.css
  26. 80 3
      public/ng/js/gogs.js
  27. 5 5
      public/ng/js/min/gogs-min.js
  28. 3 0
      public/ng/less/gogs/repository.less
  29. 2 0
      public/ng/less/gogs/settings.less
  30. 4 0
      routers/admin/admin.go
  31. 68 88
      routers/install.go
  32. 2 2
      routers/repo/commit.go
  33. 4 4
      routers/repo/download.go
  34. 2 2
      routers/repo/view.go
  35. 46 0
      scripts/init/freebsd/gogs
  36. 0 2
      scripts/start.bat
  37. 1 1
      scripts/start.sh
  38. 1 1
      templates/.VERSION
  39. 1 1
      templates/admin/config.tmpl
  40. 5 0
      templates/admin/dashboard.tmpl
  41. 3 3
      templates/home.tmpl
  42. 27 21
      templates/install.tmpl
  43. 1 1
      templates/repo/commits_table.tmpl
  44. 11 11
      templates/repo/diff.tmpl
  45. 2 2
      templates/repo/view_list.tmpl
  46. 10 8
      templates/user/settings/email.tmpl
  47. 1 1
      templates/user/settings/nav.tmpl

+ 8 - 0
.travis.yml

@@ -3,5 +3,13 @@ language: go
 go:
   - 1.2
   - 1.3
+  - 1.4
+  - tip
 
 sudo: false
+
+script: go build -v
+
+notifications:
+  email:
+    - u@gogs.io

+ 6 - 5
README.md

@@ -1,18 +1,19 @@
-Gogs - Go Git Service [![wercker status](https://app.wercker.com/status/ad0bdb0bc450ac6f09bc56b9640a50aa/s/ "wercker status")](https://app.wercker.com/project/bykey/ad0bdb0bc450ac6f09bc56b9640a50aa) [![Build Status](https://travis-ci.org/gogits/gogs.svg?branch=master)](https://travis-ci.org/gogits/gogs)
+Gogs - Go Git Service [![Build Status](https://travis-ci.org/gogits/gogs.svg?branch=master)](https://travis-ci.org/gogits/gogs)
 =====================
 
 [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gogits/gogs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
 
 Gogs(Go Git Service) is a painless self-hosted Git Service written in Go.
 
-![Demo](https://gowalker.org/public/gogs_demo.gif)
+![Demo](http://gogs.qiniudn.com/gogs_demo.gif)
 
-##### Current version: 0.5.11 Beta
+##### Current version: 0.5.12 Beta
 
 ### NOTICES
 
-- Due to testing purpose, data of [try.gogs.io](https://try.gogs.io) has been reset in **June 21, 2014** and will reset multiple times after. Please do **NOT** put your important data on the site.
+- Due to testing purpose, data of [try.gogs.io](https://try.gogs.io) has been reset in **Jan 28, 2015** and will reset multiple times after. Please do **NOT** put your important data on the site.
 - Demo site [try.gogs.io](https://try.gogs.io) is running under `dev` branch.
+- If you think there are vulnerabilities in the project, please talk private to **u@gogs.io**, thanks!
 
 #### Other language version
 
@@ -50,7 +51,7 @@ The goal of this project is to make the easiest, fastest and most painless way t
 - Drone CI integration
 - Supports MySQL, PostgreSQL and SQLite3
 - Social account login(GitHub, Google, QQ, Weibo)
-- Multi-language support([7 languages](https://crowdin.com/project/gogs))
+- Multi-language support([9 languages](https://crowdin.com/project/gogs))
 
 ## System Requirements
 

+ 3 - 3
README_ZH.md

@@ -3,9 +3,9 @@ Gogs - Go Git Service [![wercker status](https://app.wercker.com/status/ad0bdb0b
 
 Gogs(Go Git Service) 是一个基于 Go 语言的自助 Git 服务。
 
-![Demo](https://gowalker.org/public/gogs_demo.gif)
+![Demo](http://gogs.qiniudn.com/gogs_demo.gif)
 
-##### 当前版本:0.5.11 Beta
+##### 当前版本:0.5.12 Beta
 
 ## 开发目的
 
@@ -39,7 +39,7 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
 - Drone CI 持续部署集成
 - 支持 MySQL、PostgreSQL 以及 SQLite3 数据库
 - 社交帐号登录(GitHub、Google、QQ、微博)
-- 多语言支持([7 种语言]([more](https://crowdin.com/project/gogs)))
+- 多语言支持([9 种语言]([more](https://crowdin.com/project/gogs)))
 
 ## 系统要求
 

+ 1 - 1
cmd/fix.go

@@ -164,7 +164,7 @@ func runFixLocation(ctx *cli.Context) {
 	fmt.Scanln()
 
 	// Fix in authorized_keys file.
-	sshPath := path.Join(models.SshPath, "authorized_keys")
+	sshPath := path.Join(models.SSHPath, "authorized_keys")
 	if com.IsFile(sshPath) {
 		fmt.Printf("Fixing pathes in file: %s\n", sshPath)
 		if err := rewriteAuthorizedKeys(sshPath, oldPath, execPath); err != nil {

+ 16 - 8
cmd/web.go

@@ -53,7 +53,9 @@ var CmdWeb = cli.Command{
 	Description: `Gogs web server is the only thing you need to run,
 and it takes care of all the other things for you`,
 	Action: runWeb,
-	Flags:  []cli.Flag{},
+	Flags: []cli.Flag{
+		cli.StringFlag{"port, p", "3000", "Temporary port number to prevent conflict", ""},
+	},
 }
 
 type VerChecker struct {
@@ -75,13 +77,13 @@ func checkVersion() {
 
 	// Check dependency version.
 	checkers := []VerChecker{
-		{"github.com/Unknwon/macaron", macaron.Version, "0.5.0"},
+		{"github.com/Unknwon/macaron", macaron.Version, "0.5.1"},
 		{"github.com/macaron-contrib/binding", binding.Version, "0.0.4"},
 		{"github.com/macaron-contrib/cache", cache.Version, "0.0.7"},
-		{"github.com/macaron-contrib/csrf", csrf.Version, "0.0.1"},
+		{"github.com/macaron-contrib/csrf", csrf.Version, "0.0.3"},
 		{"github.com/macaron-contrib/i18n", i18n.Version, "0.0.5"},
 		{"github.com/macaron-contrib/session", session.Version, "0.1.6"},
-		{"gopkg.in/ini.v1", ini.Version, "1.0.1"},
+		{"gopkg.in/ini.v1", ini.Version, "1.2.0"},
 	}
 	for _, c := range checkers {
 		ver := strings.Join(strings.Split(c.Version(), ".")[:3], ".")
@@ -162,7 +164,7 @@ func newMacaron() *macaron.Macaron {
 	return m
 }
 
-func runWeb(*cli.Context) {
+func runWeb(ctx *cli.Context) {
 	routers.GlobalInit()
 	checkVersion()
 
@@ -179,9 +181,9 @@ func runWeb(*cli.Context) {
 	// Routers.
 	m.Get("/", ignSignIn, routers.Home)
 	m.Get("/explore", ignSignIn, routers.Explore)
-	// FIXME: when i'm binding form here???
-	m.Get("/install", bindIgnErr(auth.InstallForm{}), routers.Install)
-	m.Post("/install", bindIgnErr(auth.InstallForm{}), routers.InstallPost)
+	m.Combo("/install", routers.InstallInit).
+		Get(routers.Install).
+		Post(bindIgnErr(auth.InstallForm{}), routers.InstallPost)
 	m.Group("", func() {
 		m.Get("/pulls", user.Pulls)
 		m.Get("/issues", user.Issues)
@@ -460,6 +462,12 @@ func runWeb(*cli.Context) {
 	// Not found handler.
 	m.NotFound(routers.NotFound)
 
+	// Flag for port number in case first time run conflict.
+	if ctx.IsSet("port") {
+		setting.AppUrl = strings.Replace(setting.AppUrl, setting.HttpPort, ctx.String("port"), 1)
+		setting.HttpPort = ctx.String("port")
+	}
+
 	var err error
 	listenAddr := fmt.Sprintf("%s:%s", setting.HttpAddr, setting.HttpPort)
 	log.Info("Listen: %v://%s%s", setting.Protocol, listenAddr, setting.AppSubUrl)

+ 5 - 2
conf/app.ini

@@ -1,3 +1,6 @@
+# NEVER EVER MODIFY THIS FILE
+# PLEASE MAKE CHANGES ON CORRESPONDING CUSTOM CONFIG FILE
+
 ; App name that shows on every page title
 APP_NAME = Gogs: Go Git Service
 ; Change it if you run locally
@@ -275,5 +278,5 @@ INTERVAL = 24
 ARGS = 
 
 [i18n]
-LANGS = en-US,zh-CN,zh-HK,de-DE,fr-CA,nl-NL,lv-LV,ru-RU
-NAMES = English,简体中文,繁體中文,Deutsch,Français,Nederlands,Latviešu,Русский
+LANGS = en-US,zh-CN,zh-HK,de-DE,fr-CA,nl-NL,lv-LV,ru-RU,ja-JP
+NAMES = English,简体中文,繁體中文,Deutsch,Français,Nederlands,Latviešu,Русский,日本语

+ 4 - 1
conf/locale/TRANSLATORS

@@ -1,6 +1,9 @@
 # This file lists all PUBLIC individuals having contributed content to the translation.
 # Order of name is meaningless.
 
+Akihiro YAGASAKI <yaggytter@momiage.com>
+Christoph Kisfeld <christoph.kisfeld@gmail.com>
 Thomas Fanninger <gogs.thomas@fanninger.at>
 Łukasz Jan Niemier <lukasz@niemier.pl>
-Lafriks <lafriks@gmail.com>
+Lafriks <lafriks@gmail.com>
+Miguel de la Cruz <miguel@mcrx.me>

+ 5 - 14
conf/locale/locale_en-US.ini

@@ -59,12 +59,14 @@ run_user = Run User
 run_user_helper = The user must have access to Repository Root Path and run Gogs.
 domain = Domain
 domain_helper = This affects SSH clone URLs.
+http_port = HTTP Port
+http_port_helper = Port number which application will listen on.
 app_url = Application URL
 app_url_helper = This affects HTTP/HTTPS clone URL and somewhere in e-mail.
 email_title = E-mail Service Settings (Optional)
 smtp_host = SMTP Host
 mailer_user = Sender E-mail
-mailer_password = Sender Password 
+mailer_password = Sender Password
 notify_title = Notification Settings(Optional)
 register_confirm = Enable Register Confirmation
 mail_notify = Enable Mail Notification
@@ -512,6 +514,8 @@ dashboard.delete_repo_archives = Delete all repositories archives
 dashboard.delete_repo_archives_success = All repositories archives have been deleted successfully.
 dashboard.git_gc_repos = Do garbage collection on repositories
 dashboard.git_gc_repos_success = All repositories have done garbage collection successfully.
+dashboard.resync_all_sshkeys = Rewrite '.ssh/autorized_key' file(caution: non-Gogs keys will be lost)
+dashboard.resync_all_sshkeys_success = All public keys have been rewritten successfully.
 dashboard.server_uptime = Server Uptime
 dashboard.current_goroutine = Current Goroutines
 dashboard.current_memory_usage = Current Memory Usage
@@ -712,16 +716,3 @@ months = %d months %s
 years = %d years %s
 raw_seconds = seconds
 raw_minutes = minutes
-
-
-
-
-
-
-
-
-
-
-
-
-

文件差异内容过多而无法显示
+ 728 - 0
conf/locale/locale_ja-JP.ini


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

@@ -61,7 +61,7 @@ domain=Домен
 domain_helper=This affects SSH clone URLs.
 app_url=URL приложения
 app_url_helper=This affects HTTP/HTTPS clone URL and somewhere in e-mail.
-email_title=E-mail Service Settings (Optional)
+email_title=Настройки службы электронной почты (опционально)
 smtp_host=Узел SMTP
 mailer_user=Электронная почта отправителя
 mailer_password=Пароль отправителя
@@ -75,7 +75,7 @@ confirm_password=Подтвердить пароль
 admin_email=Эл. почта
 install_gogs=Установить Gogs
 test_git_failed=Не удалось проверить 'git' команду: %v
-sqlite3_not_available=Your release version does not support SQLite3, please download the official binary version from %s, NOT the gobuild version.
+sqlite3_not_available=Ваша версия не поддерживает SQLite3, пожалуйста скачайте официальную бинарную версию от %s, а не версию gobuild.
 invalid_db_setting=Настройки базы данных не правильные: %v
 invalid_repo_path=Недопустимый путь к корню репозитория: %v
 run_user_not_match=Run user isn't the current user: %s -> %s
@@ -98,9 +98,9 @@ repos=Репозитории
 [auth]
 create_new_account=Создать новый аккаунт
 register_hepler_msg=Уже есть аккаунт? Авторизуйтесь!
-social_register_hepler_msg=Уже есть учетная запись? Свяжите ее соцсетью!
+social_register_hepler_msg=Уже есть учетная запись? Свяжите ее с соцсетью!
 disable_register_prompt=Извините,  возможность регистрации отключена. Пожалуйста, свяжитесь с администратором сайта.
-disable_register_mail=Sorry, Register Mail Confirmation has been disabled.
+disable_register_mail=К сожалению подтверждение регистрации по почте отключено.
 remember_me=Запомнить меня
 forgot_password=Забыли пароль
 forget_password=Забыли пароль?
@@ -109,7 +109,7 @@ confirmation_mail_sent_prompt=Новое письмо для подтвержд
 sign_in_email=Войдите в свой адрес электронной почты
 active_your_account=Активируйте свой аккаунт
 resent_limit_prompt=Вы слишком часто отправляете письмо с активацией. Подождите 3 минуты, пожалуйста.
-has_unconfirmed_mail=Hi %s, you have an unconfirmed e-mail address <b>%s</b>). If you haven't received a confirmation e-mail or need to resend a new one, please click on the button below.
+has_unconfirmed_mail=Здравствуйте, %s! У вас есть неподтвержденный адрес электронной почты (<b>%s</b>). Если вам не приходило письмо с подтверждением или нужно выслать новое письмо, нажмите на кнопку ниже.
 resend_mail=Нажмите здесь, чтобы переотправить активационное письмо
 email_not_associate=Этот адрес электронной почты не связан ни с одной учетной записью.
 send_reset_mail=Нажмите сюда, чтобы отправить письмо для сброса пароля
@@ -157,17 +157,17 @@ enterred_invalid_repo_name=Пожалуйста, убедитесь, что вв
 enterred_invalid_owner_name=Убедитесь, что введенное имя владельца верное.
 enterred_invalid_password=Убедитесь, что введенный пароль верен.
 user_not_exist=Данный пользователь не существует.
-last_org_owner=The user to remove is the last member in owner team. There must be another owner.
+last_org_owner=Удаляемый пользователь является последним в команде владельцев. Должен быть хотя бы один владелец.
 
 invalid_ssh_key=К сожалению, мы не смогли проверить ваш SSH-ключ: %s
 unable_verify_ssh_key=Gogs не может проверить ваш SSH-ключ, но мы допускаем, что он действителен. Пожалуйста, удостоверьтесь самостоятельно, что ключ действителен.
 auth_failed=Ошибка аутентификации: %v
 
-still_own_repo=Your account still have ownership of repository, you have to delete or transfer them first.
-still_has_org=Your account still have membership of organization, you have to left or delete them first.
+still_own_repo=На вашем аккаунте все еще остается как минимум один репозиторий, сначала вам нужно удалить или передать его.
+still_has_org=Вы находитесь в организации, сперва Вам необходимо покинуть ее или удалить.
 org_still_own_repo=Данная организация все еще является владельцем репозиториев, необходимо удалить или переместить их в начале.
 
-still_own_user=This authentication still has used by some users, you should move them and then delete again.
+still_own_user=Эта проверка подлинности по-прежнему используется некоторыми пользователями, вы должны переместить их и затем снова удалить.
 
 target_branch_not_exist=Целевая ветка не существует
 
@@ -192,7 +192,7 @@ delete=Удалить аккаунт
 uid=UID
 
 public_profile=Открытый профиль
-profile_desc=Your E-mail address is public and will be used for any account related notifications, and any web based operations made via the site.
+profile_desc=Адрес вашей электронной почты является публичным и будет использован для любых уведомлений, связанных с аккаунтом, а также для любых действий, совершенных через сайт.
 full_name=ФИО
 website=Веб-сайт
 location=Местоположение
@@ -217,15 +217,15 @@ new_password=Новый пароль
 password_incorrect=Текущий пароль не правильный.
 change_password_success=Пароль сменен успешно. Теперь вы можете войти с новым паролем.
 
-emails=E-mail Addresses
-manage_emails=Manage e-mail addresses
-email_desc=Your primary e-mail address will be used for notifications and other operations.
-primary=Primary
-primary_email=Set as primary
-delete_email=Delete
-add_new_email=Add new e-mail address
-add_email=Add e-mail
-add_email_success=Your new E-mail address was successfully added.
+emails=Адреса электронной почты
+manage_emails=Управление адресами электронной почты
+email_desc=Ваш основной адрес электронной почты будет использован для уведомлений и других операций.
+primary=Основной
+primary_email=Установить как основной
+delete_email=Удалить
+add_new_email=Добавить новый адрес электронной почты
+add_email=Добавить электронную почту
+add_email_success=Новый адрес электронной почты успешно добавлен.
 
 manage_ssh_keys=Управление SSH ключами
 add_key=Добавить ключ
@@ -240,20 +240,20 @@ add_on=Добавлено
 last_used=Последний раз использовался
 no_activity=Еще не применялся
 
-manage_social=Manage Associated Social Accounts
-social_desc=This is a list of associated social accounts. Remove any binding that you do not recognize.
+manage_social=Управление привязанными учетными записями в соцсетях
+social_desc=Это список привязанных учетных записей в соцсетях. Удаляйте любые неизвестные вам привязки.
 unbind=Отвязать
 unbind_success=Социальная учетная запись отвязана.
 
-manage_access_token=Manage Personal Access Tokens
+manage_access_token=Управление Токенами Персонального Доступа
 generate_new_token=Создать новый token
-tokens_desc=Tokens you have generated that can be used to access the Gogs API.
-new_token_desc=As for now, every token will have full access to your account.
+tokens_desc=Созданные вами токены могут использоваться для доступа к Gogs API.
+new_token_desc=Пока что каждый токен будет иметь полный доступ к вашей учетной записи.
 token_name=Имя маркера
 generate_token=Генерировать маркер
-generate_token_succees=New access token has been generated successfully! Make sure to copy your new personal access token now. You won't be able to see it again!
+generate_token_succees=Успешно создан новый токен доступа! Пожалуйста сделайте копию вашего нового токена персонального доступа. Вы не сможете увидеть его снова!
 delete_token=Удалить
-delete_token_success=Personal access token has been deleted successfully! Don't forget to update your applications as well.
+delete_token_success=Персональный токен доступа был успешно удален! Не забудьте так же обновить ваши приложения.
 
 delete_account=Удалить свой аккаунт
 delete_prompt=Этим действием вы удалите свою учетную запись навсегда и <strong>НЕ СМОЖЕТЕ</strong> ее вернуть!
@@ -285,7 +285,7 @@ goget_meta_helper=This repository will be <span class="label label-blue label-ra
 need_auth=Требуется авторизация
 migrate_type=Тип миграции
 migrate_type_helper=Этот репозиторий будет <span class="label label-blue label-radius">зеркалом</span>
-migrate_repo=Migrate Repository
+migrate_repo=Перенос репозитория
 
 copy_link=Копировать
 click_to_copy=Скопировать в буфер обмена
@@ -322,7 +322,7 @@ commits.author=Автор
 commits.message=Сообщение
 commits.date=Дата
 commits.older=Раньше
-commits.newer=Newer
+commits.newer=Новее
 
 settings=Настройки
 settings.options=Опции
@@ -347,9 +347,9 @@ settings.transfer_owner=Новый владелец
 settings.make_transfer=Выполнить передачу
 settings.transfer_succeed=Repository ownership has been transferred successfully.
 settings.confirm_delete=Подтвердить удаление
-settings.add_collaborator=Add New Collaborator
-settings.add_collaborator_success=New collaborator has been added.
-settings.remove_collaborator_success=Collaborator has been removed.
+settings.add_collaborator=Добавить нового соавтора
+settings.add_collaborator_success=Был добавлен новый соавтор.
+settings.remove_collaborator_success=Соавтор был удален.
 settings.user_is_org_member=User is organization member who cannot be added as a collaborator.
 settings.add_webhook=Добавить Webhook
 settings.hooks_desc=Webhooks allow external services to be notified when certain events happen on Gogs. When the specified events happen, we'll send a POST request to each of the URLs you provide. Learn more in our <a target="_blank" href="%s">Webhooks Guide</a>.
@@ -462,14 +462,14 @@ members.invite_now=Пригласите сейчас
 teams.join=Объединить
 teams.leave=Выйти
 teams.read_access=Доступ на чтение
-teams.read_access_helper=This team will be able to view and clone its repositories.
+teams.read_access_helper=Эта команда будет иметь возможность просматривать и клонировать ее репозитории.
 teams.write_access=Доступ на запись
-teams.write_access_helper=This team will be able to read its repositories, as well as push to them.
+teams.write_access_helper=Эта команда будет в состоянии прочитать ее репозитории, а также посылать изменения.
 teams.admin_access=Доступ администратора
 teams.admin_access_helper=This team will be able to push/pull to its repositories, as well as add other collaborators to them.
 teams.no_desc=Эта группа не имеет описания
 teams.settings=Настройки
-teams.owners_permission_desc=Owners have full access to <strong>all repositories</strong> and have <strong>admin rights</strong> to the organization.
+teams.owners_permission_desc=Владельцы имеют полный доступ ко <strong>всем репозиториям</strong> и имеют <strong>права администратора</strong> организации.
 teams.members=Члены группы разработки
 teams.update_settings=Обновить настройки
 teams.delete_team=Удалить эту группу разработки
@@ -486,7 +486,7 @@ teams.remove_repo=Удалить
 teams.add_nonexistent_repo=Вы добавляете в отсутствующий репозиторий, пожалуйста сначала его создайте.
 
 [admin]
-dashboard=Dashboard
+dashboard=Панель управления
 users=Пользователи
 organizations=Организации
 repositories=Репозитории
@@ -508,14 +508,14 @@ dashboard.clean_unbind_oauth=Clean unbound OAuthes
 dashboard.clean_unbind_oauth_success=All unbind OAuthes have been deleted successfully.
 dashboard.delete_inactivate_accounts=Удалить все неактивированные учетные записи
 dashboard.delete_inactivate_accounts_success=Все неактивированные учетные записи удалены успешно.
-dashboard.delete_repo_archives=Delete all repositories archives
-dashboard.delete_repo_archives_success=All repositories archives have been deleted successfully.
-dashboard.git_gc_repos=Do garbage collection on repositories
-dashboard.git_gc_repos_success=All repositories have done garbage collection successfully.
+dashboard.delete_repo_archives=Удаление всех архивов репозиториев
+dashboard.delete_repo_archives_success=Все архивы репозиториев были успешно удалены.
+dashboard.git_gc_repos=Выполнить сборку мусора на репозиториях
+dashboard.git_gc_repos_success=Сборка мусора на всех репозиториях успешно выполнена.
 dashboard.server_uptime=Время непрерывной работы сервера
 dashboard.current_goroutine=Current Goroutines
 dashboard.current_memory_usage=Текущее использование памяти
-dashboard.total_memory_allocated=Total Memory Allocated
+dashboard.total_memory_allocated=Всего памяти выделено
 dashboard.memory_obtained=Memory Obtained
 dashboard.pointer_lookup_times=Pointer Lookup Times
 dashboard.memory_allocate_times=Memory Allocate Times
@@ -543,7 +543,7 @@ dashboard.last_gc_pause=Last GC Pause
 dashboard.gc_times=GC Times
 
 users.user_manage_panel=User Manage Panel
-users.new_account=Create New Account
+users.new_account=Создать новый аккаунт
 users.name=Имя
 users.activated=Активирован
 users.admin=Администратор
@@ -560,7 +560,7 @@ users.is_admin=У этой учетной записи есть права ад
 users.allow_git_hook=Пользователь имеет право создать Git перехватчик
 users.update_profile=Обновить профиль учетной записи
 users.delete_account=Удалить эту учетную запись
-users.still_own_repo=This account still have ownership of repository, you have to delete or transfer them first.
+users.still_own_repo=На вашем аккаунте все еще остается как минимум один репозиторий, сначала вам нужно удалить или передать его.
 users.still_has_org=This account still have membership of organization, you have to left or delete them first.
 
 orgs.org_manage_panel=Управление группами
@@ -630,30 +630,30 @@ config.db_path=Path
 config.db_path_helper=(for "sqlite3" only)
 config.service_config=Service Configuration
 config.register_email_confirm=Require E-mail Confirmation
-config.disable_register=Disable Registration
-config.require_sign_in_view=Require Sign In View
-config.mail_notify=Mail Notification
-config.enable_cache_avatar=Enable Cache Avatar
+config.disable_register=Отключить регистрацию
+config.require_sign_in_view=Для просмотра необходима авторизация
+config.mail_notify=Почтовые уведомления
+config.enable_cache_avatar=Кешировать аватар
 config.active_code_lives=Active Code Lives
 config.reset_password_code_lives=Reset Password Code Lives
 config.webhook_config=Настройка автоматического обновления репозиции
-config.task_interval=Task Interval
-config.deliver_timeout=Deliver Timeout
-config.mailer_config=Mailer Configuration
-config.mailer_enabled=Enabled
-config.mailer_name=Name
-config.mailer_host=Host
-config.mailer_user=User
-config.oauth_config=OAuth Configuration
-config.oauth_enabled=Enabled
-config.cache_config=Cache Configuration
+config.task_interval=Интервал задания
+config.deliver_timeout=Задержка доставки
+config.mailer_config=Настройки почты
+config.mailer_enabled=Включено
+config.mailer_name=Имя
+config.mailer_host=Сервер
+config.mailer_user=Пользователь
+config.oauth_config=Конфигурация OAuth
+config.oauth_enabled=Включено
+config.cache_config=Настройки кеша
 config.cache_adapter=Cache Adapter
 config.cache_interval=Cache Interval
 config.cache_conn=Cache Connection
 config.session_config=Session Configuration
 config.session_provider=Session Provider
 config.provider_config=Provider Config
-config.cookie_name=Cookie Name
+config.cookie_name=Имя файла cookie
 config.enable_set_cookie=Enable Set Cookie
 config.gc_interval_time=GC Interval Time
 config.session_life_time=Время жизни сессии
@@ -674,7 +674,7 @@ monitor.execute_times=Execute Times
 monitor.process=Запущенные процессы
 monitor.desc=Описание
 monitor.start=Start Time
-monitor.execute_time=Execution Time
+monitor.execute_time=Время выполнения
 
 notices.system_notice_list=Система уведомлений
 notices.type=Тип
@@ -684,7 +684,7 @@ notices.op=Op.
 notices.delete_success=System notice has been deleted successfully.
 
 [action]
-create_repo=created repository <a href="%s/%s">%s</a>
+create_repo=создан репозиторий <a href="%s/%s"> %s</a>
 commit_repo=pushed to <a href="%s/%s/src/%s">%s</a> at <a href="%s/%s">%s</a>
 create_issue=opened issue <a href="%s/%s/issues/%s">%s#%s</a>
 comment_issue=commented on issue <a href="%s/%s/issues/%s">%s#%s</a>
@@ -703,9 +703,9 @@ now=сейчас
 1w=1 week %s
 1mon=1 month %s
 1y=1 year %s
-seconds=%d seconds %s
-minutes=%d minutes %s
-hours=%d hours %s
+seconds=%d секунд %s
+minutes=%d минут %s
+hours=%d часов %s
 days=%d days %s
 weeks=%d weeks %s
 months=%d months %s

+ 1 - 1
gogs.go

@@ -17,7 +17,7 @@ import (
 	"github.com/gogits/gogs/modules/setting"
 )
 
-const APP_VER = "0.5.12.0120 Beta"
+const APP_VER = "0.5.12.0204 Beta"
 
 func init() {
 	runtime.GOMAXPROCS(runtime.NumCPU())

+ 40 - 8
models/action.go

@@ -41,12 +41,14 @@ var (
 
 var (
 	// Same as Github. See https://help.github.com/articles/closing-issues-via-commit-messages
-	IssueKeywords    = []string{"close", "closes", "closed", "fix", "fixes", "fixed", "resolve", "resolves", "resolved"}
-	IssueKeywordsPat *regexp.Regexp
+	IssueCloseKeywords    = []string{"close", "closes", "closed", "fix", "fixes", "fixed", "resolve", "resolves", "resolved"}
+	IssueCloseKeywordsPat *regexp.Regexp
+	IssueReferenceKeywordsPat *regexp.Regexp
 )
 
 func init() {
-	IssueKeywordsPat = regexp.MustCompile(fmt.Sprintf(`(?i)(?:%s) \S+`, strings.Join(IssueKeywords, "|")))
+	IssueCloseKeywordsPat = regexp.MustCompile(fmt.Sprintf(`(?i)(?:%s) \S+`, strings.Join(IssueCloseKeywords, "|")))
+	IssueReferenceKeywordsPat = regexp.MustCompile(fmt.Sprintf(`(?i)(?:) \S+`))
 }
 
 // Action represents user operation type and other information to repository.,
@@ -110,13 +112,13 @@ func (a Action) GetIssueInfos() []string {
 
 func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, commits []*base.PushCommit) error {
 	for _, c := range commits {
-		refs := IssueKeywordsPat.FindAllString(c.Message, -1)
-
-		for _, ref := range refs {
+		references := IssueReferenceKeywordsPat.FindAllString(c.Message, -1)
+		
+		for _, ref := range references {
 			ref := ref[strings.IndexByte(ref, byte(' '))+1:]
 			ref = strings.TrimRightFunc(ref, func(c rune) bool {
-				return !unicode.IsDigit(c)
-			})
+					return !unicode.IsDigit(c)
+				})
 
 			if len(ref) == 0 {
 				continue
@@ -144,6 +146,35 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
 			if _, err = CreateComment(userId, issue.RepoId, issue.Id, 0, 0, COMMIT, message, nil); err != nil {
 				return err
 			}
+		}
+
+		closes := IssueCloseKeywordsPat.FindAllString(c.Message, -1)
+
+		for _, ref := range closes {
+			ref := ref[strings.IndexByte(ref, byte(' '))+1:]
+			ref = strings.TrimRightFunc(ref, func(c rune) bool {
+					return !unicode.IsDigit(c)
+				})
+
+			if len(ref) == 0 {
+				continue
+			}
+
+			// Add repo name if missing
+			if ref[0] == '#' {
+				ref = fmt.Sprintf("%s/%s%s", repoUserName, repoName, ref)
+			} else if strings.Contains(ref, "/") == false {
+				// We don't support User#ID syntax yet
+				// return ErrNotImplemented
+
+				continue
+			}
+
+			issue, err := GetIssueByRef(ref)
+
+			if err != nil {
+				return err
+			}
 
 			if issue.RepoId == repoId {
 				if issue.IsClosed {
@@ -168,6 +199,7 @@ func updateIssuesCommit(userId, repoId int64, repoUserName, repoName string, com
 				}
 			}
 		}
+		
 	}
 
 	return nil

+ 8 - 8
models/models.go

@@ -33,7 +33,7 @@ var (
 	HasEngine bool
 
 	DbCfg struct {
-		Type, Host, Name, User, Pwd, Path, SslMode string
+		Type, Host, Name, User, Passwd, Path, SSLMode string
 	}
 
 	EnableSQLite3 bool
@@ -59,10 +59,10 @@ func LoadModelsConfig() {
 	DbCfg.Host = sec.Key("HOST").String()
 	DbCfg.Name = sec.Key("NAME").String()
 	DbCfg.User = sec.Key("USER").String()
-	if len(DbCfg.Pwd) == 0 {
-		DbCfg.Pwd = sec.Key("PASSWD").String()
+	if len(DbCfg.Passwd) == 0 {
+		DbCfg.Passwd = sec.Key("PASSWD").String()
 	}
-	DbCfg.SslMode = sec.Key("SSL_MODE").String()
+	DbCfg.SSLMode = sec.Key("SSL_MODE").String()
 	DbCfg.Path = sec.Key("PATH").MustString("data/gogs.db")
 }
 
@@ -71,7 +71,7 @@ func getEngine() (*xorm.Engine, error) {
 	switch DbCfg.Type {
 	case "mysql":
 		cnnstr = fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8",
-			DbCfg.User, DbCfg.Pwd, DbCfg.Host, DbCfg.Name)
+			DbCfg.User, DbCfg.Passwd, DbCfg.Host, DbCfg.Name)
 	case "postgres":
 		var host, port = "127.0.0.1", "5432"
 		fields := strings.Split(DbCfg.Host, ":")
@@ -82,7 +82,7 @@ func getEngine() (*xorm.Engine, error) {
 			port = fields[1]
 		}
 		cnnstr = fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=%s",
-			DbCfg.User, DbCfg.Pwd, host, port, DbCfg.Name, DbCfg.SslMode)
+			DbCfg.User, DbCfg.Passwd, host, port, DbCfg.Name, DbCfg.SSLMode)
 	case "sqlite3":
 		if !EnableSQLite3 {
 			return nil, fmt.Errorf("Unknown database type: %s", DbCfg.Type)
@@ -98,7 +98,7 @@ func getEngine() (*xorm.Engine, error) {
 func NewTestEngine(x *xorm.Engine) (err error) {
 	x, err = getEngine()
 	if err != nil {
-		return fmt.Errorf("models.init(fail to connect to database): %v", err)
+		return fmt.Errorf("connect to database: %v", err)
 	}
 
 	x.SetMapper(core.GonicMapper{})
@@ -108,7 +108,7 @@ func NewTestEngine(x *xorm.Engine) (err error) {
 func SetEngine() (err error) {
 	x, err = getEngine()
 	if err != nil {
-		return fmt.Errorf("models.init(fail to connect to database): %v", err)
+		return fmt.Errorf("connect to database: %v", err)
 	}
 
 	x.SetMapper(core.GonicMapper{})

+ 50 - 11
models/publickey.go

@@ -33,7 +33,7 @@ const (
 )
 
 var (
-	ErrKeyAlreadyExist = errors.New("Public key already exist")
+	ErrKeyAlreadyExist = errors.New("Public key already exists")
 	ErrKeyNotExist     = errors.New("Public key does not exist")
 	ErrKeyUnableVerify = errors.New("Unable to verify public key")
 )
@@ -41,7 +41,7 @@ var (
 var sshOpLocker = sync.Mutex{}
 
 var (
-	SshPath string // SSH directory.
+	SSHPath string // SSH directory.
 	appPath string // Execution(binary) path.
 )
 
@@ -72,9 +72,9 @@ func init() {
 	appPath = strings.Replace(appPath, "\\", "/", -1)
 
 	// Determine and create .ssh path.
-	SshPath = filepath.Join(homeDir(), ".ssh")
-	if err = os.MkdirAll(SshPath, 0700); err != nil {
-		log.Fatal(4, "fail to create SshPath(%s): %v\n", SshPath, err)
+	SSHPath = filepath.Join(homeDir(), ".ssh")
+	if err = os.MkdirAll(SSHPath, 0700); err != nil {
+		log.Fatal(4, "fail to create '%s': %v", SSHPath, err)
 	}
 }
 
@@ -244,16 +244,17 @@ func CheckPublicKeyString(content string) (bool, error) {
 }
 
 // saveAuthorizedKeyFile writes SSH key content to authorized_keys file.
-func saveAuthorizedKeyFile(key *PublicKey) error {
+func saveAuthorizedKeyFile(keys ...*PublicKey) error {
 	sshOpLocker.Lock()
 	defer sshOpLocker.Unlock()
 
-	fpath := filepath.Join(SshPath, "authorized_keys")
+	fpath := filepath.Join(SSHPath, "authorized_keys")
 	f, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
 	if err != nil {
 		return err
 	}
 	defer f.Close()
+
 	finfo, err := f.Stat()
 	if err != nil {
 		return err
@@ -269,8 +270,12 @@ func saveAuthorizedKeyFile(key *PublicKey) error {
 		}
 	}
 
-	_, err = f.WriteString(key.GetAuthorizedString())
-	return err
+	for _, key := range keys {
+		if _, err = f.WriteString(key.GetAuthorizedString()); err != nil {
+			return err
+		}
+	}
+	return nil
 }
 
 // AddPublicKey adds new public key to database and authorized_keys file.
@@ -413,8 +418,8 @@ func DeletePublicKey(key *PublicKey) error {
 		return err
 	}
 
-	fpath := filepath.Join(SshPath, "authorized_keys")
-	tmpPath := filepath.Join(SshPath, "authorized_keys.tmp")
+	fpath := filepath.Join(SSHPath, "authorized_keys")
+	tmpPath := filepath.Join(SSHPath, "authorized_keys.tmp")
 	if err = rewriteAuthorizedKeys(key, fpath, tmpPath); err != nil {
 		return err
 	} else if err = os.Remove(fpath); err != nil {
@@ -422,3 +427,37 @@ func DeletePublicKey(key *PublicKey) error {
 	}
 	return os.Rename(tmpPath, fpath)
 }
+
+// RewriteAllPublicKeys removes any authorized key and rewrite all keys from database again.
+func RewriteAllPublicKeys() error {
+	sshOpLocker.Lock()
+	defer sshOpLocker.Unlock()
+
+	tmpPath := filepath.Join(SSHPath, "authorized_keys.tmp")
+	f, err := os.Create(tmpPath)
+	if err != nil {
+		return err
+	}
+	defer os.Remove(tmpPath)
+
+	err = x.Iterate(new(PublicKey), func(idx int, bean interface{}) (err error) {
+		_, err = f.WriteString((bean.(*PublicKey)).GetAuthorizedString())
+		return err
+	})
+	f.Close()
+	if err != nil {
+		return err
+	}
+
+	fpath := filepath.Join(SSHPath, "authorized_keys")
+	if com.IsExist(fpath) {
+		if err = os.Remove(fpath); err != nil {
+			return err
+		}
+	}
+	if err = os.Rename(tmpPath, fpath); err != nil {
+		return err
+	}
+
+	return nil
+}

+ 7 - 5
models/repo.go

@@ -7,7 +7,6 @@ package models
 import (
 	"errors"
 	"fmt"
-	"html"
 	"html/template"
 	"io/ioutil"
 	"os"
@@ -218,11 +217,9 @@ func (repo *Repository) HasAccess(uname string) bool {
 // DescriptionHtml does special handles to description and return HTML string.
 func (repo *Repository) DescriptionHtml() template.HTML {
 	sanitize := func(s string) string {
-		// TODO(nuss-justin): Improve sanitization. Strip all tags?
-		ss := html.EscapeString(s)
-		return fmt.Sprintf(`<a href="%s" target="_blank">%s</a>`, ss, ss)
+		return fmt.Sprintf(`<a href="%[1]s" target="_blank">%[1]s</a>`, s)
 	}
-	return template.HTML(DescPattern.ReplaceAllStringFunc(base.XSSString(repo.Description), sanitize))
+	return template.HTML(DescPattern.ReplaceAllStringFunc(base.Sanitizer.Sanitize(repo.Description), sanitize))
 }
 
 // IsRepositoryExist returns true if the repository with given name under user has already existed.
@@ -507,6 +504,11 @@ func initRepository(f string, u *User, repo *Repository, initReadme bool, repoLa
 	}
 
 	if len(fileName) == 0 {
+		// Re-fetch the repository from database before updating it (else it would
+		// override changes that were done earlier with sql)
+		if repo, err = GetRepositoryById(repo.Id); err != nil {
+			return err
+		}
 		repo.IsBare = true
 		repo.DefaultBranch = "master"
 		return UpdateRepository(repo)

+ 1 - 0
models/user.go

@@ -477,6 +477,7 @@ func UpdateUser(u *User) error {
 	}
 	u.Avatar = avatar.HashEmail(u.AvatarEmail)
 
+	u.FullName = base.Sanitizer.Sanitize(u.FullName)
 	_, err = x.Id(u.Id).AllCols().Update(u)
 	return err
 }

+ 7 - 0
modules/auth/auth.go

@@ -9,6 +9,7 @@ import (
 	"reflect"
 	"strings"
 
+	"github.com/Unknwon/com"
 	"github.com/Unknwon/macaron"
 	"github.com/macaron-contrib/binding"
 	"github.com/macaron-contrib/session"
@@ -135,6 +136,10 @@ type Form interface {
 	binding.Validator
 }
 
+func init() {
+	binding.SetNameMapper(com.ToSnakeCase)
+}
+
 // AssignForm assign form values back to the template data.
 func AssignForm(form interface{}, data map[string]interface{}) {
 	typ := reflect.TypeOf(form)
@@ -152,6 +157,8 @@ func AssignForm(form interface{}, data map[string]interface{}) {
 		// Allow ignored fields in the struct
 		if fieldName == "-" {
 			continue
+		} else if len(fieldName) == 0 {
+			fieldName = com.ToSnakeCase(field.Name)
 		}
 
 		data[fieldName] = val.Field(i).Interface()

+ 21 - 20
modules/auth/user_form.go

@@ -12,26 +12,27 @@ import (
 )
 
 type InstallForm struct {
-	Database        string `form:"database" binding:"Required"`
-	DbHost          string `form:"host"`
-	DbUser          string `form:"user"`
-	DbPasswd        string `form:"passwd"`
-	DatabaseName    string `form:"database_name"`
-	SslMode         string `form:"ssl_mode"`
-	DatabasePath    string `form:"database_path"`
-	RepoRootPath    string `form:"repo_path" binding:"Required"`
-	RunUser         string `form:"run_user" binding:"Required"`
-	Domain          string `form:"domain" binding:"Required"`
-	AppUrl          string `form:"app_url" binding:"Required"`
-	SmtpHost        string `form:"smtp_host"`
-	SmtpEmail       string `form:"mailer_user"`
-	SmtpPasswd      string `form:"mailer_pwd"`
-	RegisterConfirm string `form:"register_confirm"`
-	MailNotify      string `form:"mail_notify"`
-	AdminName       string `form:"admin_name" binding:"Required;AlphaDashDot;MaxSize(30)"`
-	AdminPasswd     string `form:"admin_pwd" binding:"Required;MinSize(6);MaxSize(255)"`
-	ConfirmPasswd   string `form:"confirm_passwd" binding:"Required;MinSize(6);MaxSize(255)"`
-	AdminEmail      string `form:"admin_email" binding:"Required;Email;MaxSize(50)"`
+	DbType             string `binding:"Required"`
+	DbHost             string
+	DbUser             string
+	DbPasswd           string
+	DbName             string
+	SSLMode            string
+	DbPath             string
+	RepoRootPath       string `binding:"Required"`
+	RunUser            string `binding:"Required"`
+	Domain             string `binding:"Required"`
+	HTTPPort           string `binding:"Required"`
+	AppUrl             string `binding:"Required"`
+	SMTPHost           string
+	SMTPEmail          string
+	SMTPPasswd         string
+	RegisterConfirm    string
+	MailNotify         string
+	AdminName          string `binding:"Required;AlphaDashDot;MaxSize(30)"`
+	AdminPasswd        string `binding:"Required;MinSize(6);MaxSize(255)"`
+	AdminConfirmPasswd string `binding:"Required;MinSize(6);MaxSize(255)"`
+	AdminEmail         string `binding:"Required;Email;MaxSize(50)"`
 }
 
 func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {

+ 9 - 3
modules/base/markdown.go

@@ -63,12 +63,18 @@ func IsImageFile(data []byte) (string, bool) {
 	return contentType, false
 }
 
+// IsReadmeFile returns true if given file name suppose to be a README file.
 func IsReadmeFile(name string) bool {
 	name = strings.ToLower(name)
 	if len(name) < 6 {
 		return false
+	} else if len(name) == 6 {
+		if name == "readme" {
+			return true
+		}
+		return false
 	}
-	if name[:6] == "readme" {
+	if name[:7] == "readme." {
 		return true
 	}
 	return false
@@ -103,7 +109,7 @@ var (
 	MentionPattern     = regexp.MustCompile(`@[0-9a-zA-Z_]{1,}`)
 	commitPattern      = regexp.MustCompile(`(\s|^)https?.*commit/[0-9a-zA-Z]+(#+[0-9a-zA-Z-]*)?`)
 	issueFullPattern   = regexp.MustCompile(`(\s|^)https?.*issues/[0-9]+(#+[0-9a-zA-Z-]*)?`)
-	issueIndexPattern  = regexp.MustCompile(`#[0-9]+`)
+	issueIndexPattern  = regexp.MustCompile(`( |^)#[0-9]+`)
 	sha1CurrentPattern = regexp.MustCompile(`\b[0-9a-f]{40}\b`)
 )
 
@@ -212,7 +218,7 @@ func RenderRawMarkdown(body []byte, urlPrefix string) []byte {
 func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
 	body := RenderSpecialLink(rawBytes, urlPrefix)
 	body = RenderRawMarkdown(body, urlPrefix)
-	body = XSS(body)
+	body = Sanitizer.SanitizeBytes(body)
 	return body
 }
 

+ 7 - 5
modules/base/template.go

@@ -13,7 +13,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/microcosm-cc/bluemonday"
 	"golang.org/x/net/html/charset"
 	"golang.org/x/text/transform"
 
@@ -21,11 +20,8 @@ import (
 	"github.com/gogits/gogs/modules/setting"
 )
 
-// FIXME: use me to Markdown API renders
-var p = bluemonday.UGCPolicy()
-
 func Str2html(raw string) template.HTML {
-	return template.HTML(p.Sanitize(raw))
+	return template.HTML(Sanitizer.Sanitize(raw))
 }
 
 func Range(l int) []int {
@@ -90,6 +86,11 @@ func ToUtf8(content string) string {
 	return res
 }
 
+// RenderCommitMessage renders commit message with XSS-safe and special links.
+func RenderCommitMessage(msg, urlPrefix string) template.HTML {
+	return template.HTML(string(RenderIssueIndexPattern([]byte(template.HTMLEscapeString(msg)), urlPrefix)))
+}
+
 var mailDomains = map[string]string{
 	"gmail.com": "gmail.com",
 }
@@ -163,6 +164,7 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
 	"EscapePound": func(str string) string {
 		return strings.Replace(str, "#", "%23", -1)
 	},
+	"RenderCommitMessage": RenderCommitMessage,
 }
 
 type Actioner interface {

+ 3 - 27
modules/base/tool.go

@@ -15,17 +15,19 @@ import (
 	"hash"
 	"html/template"
 	"math"
-	"regexp"
 	"strings"
 	"time"
 
 	"github.com/Unknwon/com"
 	"github.com/Unknwon/i18n"
+	"github.com/microcosm-cc/bluemonday"
 
 	"github.com/gogits/gogs/modules/avatar"
 	"github.com/gogits/gogs/modules/setting"
 )
 
+var Sanitizer = bluemonday.UGCPolicy()
+
 // Encode string to md5 hex value.
 func EncodeMd5(str string) string {
 	m := md5.New()
@@ -473,29 +475,3 @@ func DateFormat(t time.Time, format string) string {
 	format = replacer.Replace(format)
 	return t.Format(format)
 }
-
-type xssFilter struct {
-	reg  *regexp.Regexp
-	repl []byte
-}
-
-var (
-	whiteSpace = []byte(" ")
-	xssFilters = []xssFilter{
-		{regexp.MustCompile(`\ [ONon]\w*=["]*`), whiteSpace},
-		{regexp.MustCompile(`<[SCRIPTscript]{6}`), whiteSpace},
-		{regexp.MustCompile(`=[` + "`" + `'"]*[JAVASCRIPTjavascript \t\0&#x0D;]*:`), whiteSpace},
-	}
-)
-
-// XSS goes through all the XSS filters to make user input content as safe as possible.
-func XSS(in []byte) []byte {
-	for _, filter := range xssFilters {
-		in = filter.reg.ReplaceAll(in, filter.repl)
-	}
-	return in
-}
-
-func XSSString(in string) string {
-	return string(XSS([]byte(in)))
-}

+ 1 - 1
modules/middleware/auth.go

@@ -54,7 +54,7 @@ func Toggle(options *ToggleOptions) macaron.Handler {
 				if strings.HasSuffix(ctx.Req.RequestURI, "watch") {
 					return
 				}
-				ctx.SetCookie("redirect_to", "/"+url.QueryEscape(setting.AppSubUrl+ctx.Req.RequestURI), 0, setting.AppSubUrl)
+				ctx.SetCookie("redirect_to", url.QueryEscape(setting.AppSubUrl+ctx.Req.RequestURI), 0, setting.AppSubUrl)
 				ctx.Redirect(setting.AppSubUrl + "/user/login")
 				return
 			} else if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {

+ 1 - 1
modules/setting/setting.go

@@ -178,7 +178,7 @@ func NewConfigContext() {
 			log.Fatal(4, "Fail to load custom 'conf/app.ini': %v", err)
 		}
 	} else {
-		log.Warn("No custom 'conf/app.ini' found, please go to '/install'")
+		log.Warn("No custom 'conf/app.ini' found, ignore this if you're running first time")
 	}
 	Cfg.NameMapper = ini.AllCapsUnderscore
 

+ 0 - 16
public/js/app.js

@@ -1052,22 +1052,6 @@ function initRepoSetting() {
             return;
         }
         Gogits.getUsers($this.val(), $this.next());
-        /*$.ajax({
-         url: '/api/v1/users/search?q=' + $this.val(),
-         dataType: "json",
-         success: function (json) {
-         if (json.ok && json.data.length) {
-         var html = '';
-         $.each(json.data, function (i, item) {
-         html += '<li><img src="' + item.avatar + '">' + item.username + '</li>';
-         });
-         $this.next().toggleShow();
-         $this.next().find('ul').html(html);
-         } else {
-         $this.next().toggleHide();
-         }
-         }
-         });*/
     }).on('focus', function () {
         if (!$(this).val()) {
             $(this).next().toggleHide();

+ 23 - 0
public/ng/css/gogs.css

@@ -1630,6 +1630,10 @@ The register and sign-in page style
   background-color: #d1ffd6 !important;
   border-color: #b4e2b4 !important;
 }
+.diff-file-box .code-diff tbody tr.add-code td.selected-line,
+.diff-file-box .code-diff tbody tr.add-code td.selected-line pre {
+  background-color: #ffffdd !important;
+}
 .diff-file-box .code-diff tbody tr:hover td,
 .diff-file-box .code-diff tbody tr:hover pre {
   background-color: #FFF8D2 !important;
@@ -1761,6 +1765,7 @@ The register and sign-in page style
 #org-setting-form,
 #repo-setting-form,
 #user-profile-form,
+#add-email-form,
 .repo-setting-form {
   background-color: #FFF;
   padding: 30px 0;
@@ -1769,6 +1774,7 @@ The register and sign-in page style
 #org-setting-form textarea,
 #repo-setting-form textarea,
 #user-profile-form textarea,
+#add-email-form textarea,
 .repo-setting-form textarea {
   margin-left: 4px;
   height: 100px;
@@ -1777,11 +1783,13 @@ The register and sign-in page style
 #org-setting-form label,
 #repo-setting-form label,
 #user-profile-form label,
+#add-email-form label,
 .repo-setting-form label,
 #auth-setting-form .form-label,
 #org-setting-form .form-label,
 #repo-setting-form .form-label,
 #user-profile-form .form-label,
+#add-email-form .form-label,
 .repo-setting-form .form-label {
   width: 240px;
 }
@@ -1789,6 +1797,7 @@ The register and sign-in page style
 #org-setting-form .ipt,
 #repo-setting-form .ipt,
 #user-profile-form .ipt,
+#add-email-form .ipt,
 .repo-setting-form .ipt {
   width: 360px;
 }
@@ -1796,6 +1805,7 @@ The register and sign-in page style
 #org-setting-form .field,
 #repo-setting-form .field,
 #user-profile-form .field,
+#add-email-form .field,
 .repo-setting-form .field {
   margin-bottom: 24px;
 }
@@ -1813,6 +1823,7 @@ The register and sign-in page style
 #repo-hooks-history-panel,
 #user-social-panel,
 #user-applications-panel,
+#user-email-panel,
 #user-ssh-panel {
   margin-bottom: 20px;
 }
@@ -1820,6 +1831,7 @@ The register and sign-in page style
 #repo-hooks-history-panel .setting-list,
 #user-social-panel .setting-list,
 #user-applications-panel .setting-list,
+#user-email-panel .setting-list,
 #user-ssh-panel .setting-list {
   background-color: #FFF;
 }
@@ -1827,6 +1839,7 @@ The register and sign-in page style
 #repo-hooks-history-panel .setting-list li,
 #user-social-panel .setting-list li,
 #user-applications-panel .setting-list li,
+#user-email-panel .setting-list li,
 #user-ssh-panel .setting-list li {
   padding: 8px 20px;
   border-bottom: 1px solid #eaeaea;
@@ -1835,6 +1848,7 @@ The register and sign-in page style
 #repo-hooks-history-panel .setting-list li.ssh:hover,
 #user-social-panel .setting-list li.ssh:hover,
 #user-applications-panel .setting-list li.ssh:hover,
+#user-email-panel .setting-list li.ssh:hover,
 #user-ssh-panel .setting-list li.ssh:hover {
   background-color: #ffffEE;
 }
@@ -1842,6 +1856,7 @@ The register and sign-in page style
 #repo-hooks-history-panel .setting-list li i,
 #user-social-panel .setting-list li i,
 #user-applications-panel .setting-list li i,
+#user-email-panel .setting-list li i,
 #user-ssh-panel .setting-list li i {
   padding-right: 5px;
 }
@@ -1849,6 +1864,7 @@ The register and sign-in page style
 #repo-hooks-history-panel .active-icon,
 #user-social-panel .active-icon,
 #user-applications-panel .active-icon,
+#user-email-panel .active-icon,
 #user-ssh-panel .active-icon {
   width: 10px;
   height: 10px;
@@ -1861,6 +1877,7 @@ The register and sign-in page style
 #repo-hooks-history-panel .ssh-content,
 #user-social-panel .ssh-content,
 #user-applications-panel .ssh-content,
+#user-email-panel .ssh-content,
 #user-ssh-panel .ssh-content {
   margin-left: 24px;
 }
@@ -1868,6 +1885,7 @@ The register and sign-in page style
 #repo-hooks-history-panel .ssh-content .octicon,
 #user-social-panel .ssh-content .octicon,
 #user-applications-panel .ssh-content .octicon,
+#user-email-panel .ssh-content .octicon,
 #user-ssh-panel .ssh-content .octicon {
   margin-right: 4px;
 }
@@ -1875,16 +1893,19 @@ The register and sign-in page style
 #repo-hooks-history-panel .ssh-content .print,
 #user-social-panel .ssh-content .print,
 #user-applications-panel .ssh-content .print,
+#user-email-panel .ssh-content .print,
 #user-ssh-panel .ssh-content .print,
 #repo-hooks-panel .ssh-content .access,
 #repo-hooks-history-panel .ssh-content .access,
 #user-social-panel .ssh-content .access,
 #user-applications-panel .ssh-content .access,
+#user-email-panel .ssh-content .access,
 #user-ssh-panel .ssh-content .access,
 #repo-hooks-panel .ssh-content .activity,
 #repo-hooks-history-panel .ssh-content .activity,
 #user-social-panel .ssh-content .activity,
 #user-applications-panel .ssh-content .activity,
+#user-email-panel .ssh-content .activity,
 #user-ssh-panel .ssh-content .activity {
   color: #888;
 }
@@ -1892,6 +1913,7 @@ The register and sign-in page style
 #repo-hooks-history-panel .ssh-content .access,
 #user-social-panel .ssh-content .access,
 #user-applications-panel .ssh-content .access,
+#user-email-panel .ssh-content .access,
 #user-ssh-panel .ssh-content .access {
   max-width: 500px;
 }
@@ -1899,6 +1921,7 @@ The register and sign-in page style
 #repo-hooks-history-panel .ssh-btn,
 #user-social-panel .ssh-btn,
 #user-applications-panel .ssh-btn,
+#user-email-panel .ssh-btn,
 #user-ssh-panel .ssh-btn {
   margin-top: 6px;
 }

+ 80 - 3
public/ng/js/gogs.js

@@ -202,6 +202,78 @@ var Gogs = {};
         }).trigger('hashchange');
     };
 
+    // Render diff view.
+    Gogs.renderDiffView = function () {
+        function selectRange($list, $select, $from) {
+            $list.removeClass('active');
+            $list.parents('tr').find('td').removeClass('selected-line');
+            if ($from) {
+                var a = parseInt($select.attr('rel').substr(1));
+                var b = parseInt($from.attr('rel').substr(1));
+                var c;
+                if (a != b) {
+                    if (a > b) {
+                        c = a;
+                        a = b;
+                        b = c;
+                    }
+                    var classes = [];
+                    for (i = a; i <= b; i++) {
+                        classes.push('[rel=L' + i + ']');
+                    }
+                    $list.filter(classes.join(',')).addClass('active');
+                    $list.filter(classes.join(',')).parents('tr').find('td').addClass('selected-line');
+                    $.changeHash('#L' + a + '-' + 'L' + b);
+                    return
+                }
+            }
+            $select.addClass('active');
+            $select.parents('tr').find('td').addClass('selected-line');
+            $.changeHash('#' + $select.attr('rel'));
+        }
+
+        $(document).on('click', '.code-diff .lines-num span', function (e) {
+            var $select = $(this);
+            var $list = $select.parent().siblings('.lines-code').parents().find('td.lines-num > span');
+            selectRange(
+                $list,
+                $list.filter('[rel=' + $select.attr('rel') + ']'),
+                (e.shiftKey && $list.filter('.active').length ? $list.filter('.active').eq(0) : null)
+            );
+            $.deSelect();
+        });
+
+        $('.code-diff .lines-code > pre').each(function () {
+            var $pre = $(this);
+            var $lineCode = $pre.parent();
+            var $lineNums = $lineCode.siblings('.lines-num');
+            if ($lineNums.length > 0) {
+                var nums = $pre.find('ol.linenums > li').length;
+                for (var i = 1; i <= nums; i++) {
+                    $lineNums.append('<span id="L' + i + '" rel="L' + i + '">' + i + '</span>');
+                }
+            }
+        });
+
+        $(window).on('hashchange', function (e) {
+            var m = window.location.hash.match(/^#(L\d+)\-(L\d+)$/);
+            var $list = $('.code-diff td.lines-num > span');
+            var $first;
+            if (m) {
+                $first = $list.filter('[rel=' + m[1] + ']');
+                selectRange($list, $first, $list.filter('[rel=' + m[2] + ']'));
+                $("html, body").scrollTop($first.offset().top - 200);
+                return;
+            }
+            m = window.location.hash.match(/^#(L\d+)$/);
+            if (m) {
+                $first = $list.filter('[rel=' + m[1] + ']');
+                selectRange($list, $first);
+                $("html, body").scrollTop($first.offset().top - 200);
+            }
+        }).trigger('hashchange');
+    };
+
     // Search users by keyword.
     Gogs.searchUsers = function (val, $target) {
         var notEmpty = function (str) {
@@ -287,7 +359,12 @@ var Gogs = {};
 
 function initCore() {
     Gogs.renderMarkdown();
-    Gogs.renderCodeView();
+
+    if ($('.code-diff').length == 0) {
+        Gogs.renderCodeView();
+    } else {
+        Gogs.renderDiffView();
+    }
 
     // Switch list.
     $('.js-tab-nav').click(function (e) {
@@ -508,7 +585,7 @@ function initRepoSetting() {
             $ul.toggleShow();
         }
     }).next().next().find('ul').on("click", 'li', function () {
-        $('#repo-collaborator').val($(this).text());
+        $('#repo-collaborator').val($(this).find('.username').text());
         $ul.toggleHide();
     });
 }
@@ -608,7 +685,7 @@ function initTeamMembersList() {
             $ul.toggleShow();
         }
     }).next().next().find('ul').on("click", 'li', function () {
-        $('#org-team-members-add').val($(this).text());
+        $('#org-team-members-add').val($(this).find('.username').text());
         $ul.toggleHide();
     });
 }

文件差异内容过多而无法显示
+ 5 - 5
public/ng/js/min/gogs-min.js


+ 3 - 0
public/ng/less/gogs/repository.less

@@ -665,6 +665,9 @@
                         background-color: #d1ffd6 !important;
                         border-color: #b4e2b4 !important;
                     }
+                    td.selected-line, td.selected-line pre {
+                        background-color: #ffffdd !important;
+                    }
                 }
                 &:hover {
                     td, pre {

+ 2 - 0
public/ng/less/gogs/settings.less

@@ -35,6 +35,7 @@
 #org-setting-form,
 #repo-setting-form,
 #user-profile-form,
+#add-email-form,
 .repo-setting-form {
     background-color: #FFF;
     padding: 30px 0;
@@ -69,6 +70,7 @@
 #repo-hooks-history-panel,
 #user-social-panel,
 #user-applications-panel,
+#user-email-panel,
 #user-ssh-panel {
     margin-bottom: 20px;
     .setting-list {

+ 4 - 0
routers/admin/admin.go

@@ -118,6 +118,7 @@ const (
 	CLEAN_INACTIVATE_USER
 	CLEAN_REPO_ARCHIVES
 	GIT_GC_REPOS
+	SYNC_SSH_AUTHORIZED_KEY
 )
 
 func Dashboard(ctx *middleware.Context) {
@@ -144,6 +145,9 @@ func Dashboard(ctx *middleware.Context) {
 		case GIT_GC_REPOS:
 			success = ctx.Tr("admin.dashboard.git_gc_repos_success")
 			err = models.GitGcRepos()
+		case SYNC_SSH_AUTHORIZED_KEY:
+			success = ctx.Tr("admin.dashboard.resync_all_sshkeys_success")
+			err = models.RewriteAllPublicKeys()
 		}
 
 		if err != nil {

+ 68 - 88
routers/install.go

@@ -14,6 +14,7 @@ import (
 	"github.com/Unknwon/com"
 	"github.com/Unknwon/macaron"
 	"github.com/go-xorm/xorm"
+	"gopkg.in/ini.v1"
 
 	"github.com/gogits/gogs/models"
 	"github.com/gogits/gogs/modules/auth"
@@ -73,12 +74,7 @@ func GlobalInit() {
 	checkRunMode()
 }
 
-func renderDbOption(ctx *middleware.Context) {
-	ctx.Data["DbOptions"] = []string{"MySQL", "PostgreSQL", "SQLite3"}
-}
-
-// @router /install [get]
-func Install(ctx *middleware.Context, form auth.InstallForm) {
+func InstallInit(ctx *middleware.Context) {
 	if setting.InstallLock {
 		ctx.Handle(404, "Install", errors.New("Installation is prohibited"))
 		return
@@ -87,46 +83,35 @@ func Install(ctx *middleware.Context, form auth.InstallForm) {
 	ctx.Data["Title"] = ctx.Tr("install.install")
 	ctx.Data["PageIsInstall"] = true
 
-	// FIXME: when i'm ckeching length here? should they all be 0 no matter when?
-	// Get and assign values to install form.
-	if len(form.DbHost) == 0 {
-		form.DbHost = models.DbCfg.Host
-	}
-	if len(form.DbUser) == 0 {
-		form.DbUser = models.DbCfg.User
-	}
-	if len(form.DbPasswd) == 0 {
-		form.DbPasswd = models.DbCfg.Pwd
-	}
-	if len(form.DatabaseName) == 0 {
-		form.DatabaseName = models.DbCfg.Name
-	}
-	if len(form.DatabasePath) == 0 {
-		form.DatabasePath = models.DbCfg.Path
-	}
+	ctx.Data["DbOptions"] = []string{"MySQL", "PostgreSQL", "SQLite3"}
+}
 
-	if len(form.RepoRootPath) == 0 {
-		form.RepoRootPath = setting.RepoRootPath
-	}
-	if len(form.RunUser) == 0 {
-		// Note: it's not normall to use SSH in windows so current user can be first option(not git).
-		if setting.IsWindows && setting.RunUser == "git" {
-			form.RunUser = os.Getenv("USER")
-			if len(form.RunUser) == 0 {
-				form.RunUser = os.Getenv("USERNAME")
-			}
-		} else {
-			form.RunUser = setting.RunUser
+func Install(ctx *middleware.Context) {
+	form := auth.InstallForm{}
+
+	form.DbHost = models.DbCfg.Host
+	form.DbUser = models.DbCfg.User
+	form.DbPasswd = models.DbCfg.Passwd
+	form.DbName = models.DbCfg.Name
+	form.DbPath = models.DbCfg.Path
+
+	form.RepoRootPath = setting.RepoRootPath
+
+	// Note(unknwon): it's hard for Windows users change a running user,
+	// 	so just use current one if config says default.
+	if setting.IsWindows && setting.RunUser == "git" {
+		form.RunUser = os.Getenv("USER")
+		if len(form.RunUser) == 0 {
+			form.RunUser = os.Getenv("USERNAME")
 		}
-	}
-	if len(form.Domain) == 0 {
-		form.Domain = setting.Domain
-	}
-	if len(form.AppUrl) == 0 {
-		form.AppUrl = setting.AppUrl
+	} else {
+		form.RunUser = setting.RunUser
 	}
 
-	renderDbOption(ctx)
+	form.Domain = setting.Domain
+	form.HTTPPort = setting.HttpPort
+	form.AppUrl = setting.AppUrl
+
 	curDbOp := ""
 	if models.EnableSQLite3 {
 		curDbOp = "SQLite3" // Default when enabled.
@@ -138,16 +123,7 @@ func Install(ctx *middleware.Context, form auth.InstallForm) {
 }
 
 func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
-	if setting.InstallLock {
-		ctx.Handle(404, "InstallPost", errors.New("Installation is prohibited"))
-		return
-	}
-
-	ctx.Data["Title"] = ctx.Tr("install.install")
-	ctx.Data["PageIsInstall"] = true
-
-	renderDbOption(ctx)
-	ctx.Data["CurDbOption"] = form.Database
+	ctx.Data["CurDbOption"] = form.DbType
 
 	if ctx.HasError() {
 		ctx.HTML(200, INSTALL)
@@ -162,18 +138,17 @@ func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
 	// Pass basic check, now test configuration.
 	// Test database setting.
 	dbTypes := map[string]string{"MySQL": "mysql", "PostgreSQL": "postgres", "SQLite3": "sqlite3"}
-	models.DbCfg.Type = dbTypes[form.Database]
+	models.DbCfg.Type = dbTypes[form.DbType]
 	models.DbCfg.Host = form.DbHost
 	models.DbCfg.User = form.DbUser
-	models.DbCfg.Pwd = form.DbPasswd
-	models.DbCfg.Name = form.DatabaseName
-	models.DbCfg.SslMode = form.SslMode
-	models.DbCfg.Path = form.DatabasePath
+	models.DbCfg.Passwd = form.DbPasswd
+	models.DbCfg.Name = form.DbName
+	models.DbCfg.SSLMode = form.SSLMode
+	models.DbCfg.Path = form.DbPath
 
 	// Set test engine.
 	var x *xorm.Engine
 	if err := models.NewTestEngine(x); err != nil {
-		// FIXME: should use core.QueryDriver (github.com/go-xorm/core)
 		if strings.Contains(err.Error(), `Unknown database type: sqlite3`) {
 			ctx.RenderWithErr(ctx.Tr("install.sqlite3_not_available", "http://gogs.io/docs/installation/install_from_binary.html"), INSTALL, &form)
 		} else {
@@ -194,7 +169,6 @@ func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
 	if len(curUser) == 0 {
 		curUser = os.Getenv("USERNAME")
 	}
-	// Does not check run user when the install lock is off.
 	if form.RunUser != curUser {
 		ctx.Data["Err_RunUser"] = true
 		ctx.RenderWithErr(ctx.Tr("install.run_user_not_match", form.RunUser, curUser), INSTALL, &form)
@@ -202,47 +176,53 @@ func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
 	}
 
 	// Check admin password.
-	if form.AdminPasswd != form.ConfirmPasswd {
+	if form.AdminPasswd != form.AdminConfirmPasswd {
 		ctx.Data["Err_AdminPasswd"] = true
 		ctx.RenderWithErr(ctx.Tr("form.password_not_match"), INSTALL, form)
 		return
 	}
 
+	if form.AppUrl[len(form.AppUrl)-1] != '/' {
+		form.AppUrl += "/"
+	}
+
 	// Save settings.
-	setting.Cfg.Section("database").Key("DB_TYPE").SetValue(models.DbCfg.Type)
-	setting.Cfg.Section("database").Key("HOST").SetValue(models.DbCfg.Host)
-	setting.Cfg.Section("database").Key("NAME").SetValue(models.DbCfg.Name)
-	setting.Cfg.Section("database").Key("USER").SetValue(models.DbCfg.User)
-	setting.Cfg.Section("database").Key("PASSWD").SetValue(models.DbCfg.Pwd)
-	setting.Cfg.Section("database").Key("SSL_MODE").SetValue(models.DbCfg.SslMode)
-	setting.Cfg.Section("database").Key("PATH").SetValue(models.DbCfg.Path)
-
-	setting.Cfg.Section("repository").Key("ROOT").SetValue(form.RepoRootPath)
-	setting.Cfg.Section("").Key("RUN_USER").SetValue(form.RunUser)
-	setting.Cfg.Section("server").Key("DOMAIN").SetValue(form.Domain)
-	setting.Cfg.Section("server").Key("ROOT_URL").SetValue(form.AppUrl)
-
-	if len(strings.TrimSpace(form.SmtpHost)) > 0 {
-		setting.Cfg.Section("mailer").Key("ENABLED").SetValue("true")
-		setting.Cfg.Section("mailer").Key("HOST").SetValue(form.SmtpHost)
-		setting.Cfg.Section("mailer").Key("USER").SetValue(form.SmtpEmail)
-		setting.Cfg.Section("mailer").Key("PASSWD").SetValue(form.SmtpPasswd)
-
-		setting.Cfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").SetValue(com.ToStr(form.RegisterConfirm == "on"))
-		setting.Cfg.Section("service").Key("ENABLE_NOTIFY_MAIL").SetValue(com.ToStr(form.MailNotify == "on"))
+	cfg := ini.Empty()
+	cfg.Section("database").Key("DB_TYPE").SetValue(models.DbCfg.Type)
+	cfg.Section("database").Key("HOST").SetValue(models.DbCfg.Host)
+	cfg.Section("database").Key("NAME").SetValue(models.DbCfg.Name)
+	cfg.Section("database").Key("USER").SetValue(models.DbCfg.User)
+	cfg.Section("database").Key("PASSWD").SetValue(models.DbCfg.Passwd)
+	cfg.Section("database").Key("SSL_MODE").SetValue(models.DbCfg.SSLMode)
+	cfg.Section("database").Key("PATH").SetValue(models.DbCfg.Path)
+
+	cfg.Section("repository").Key("ROOT").SetValue(form.RepoRootPath)
+	cfg.Section("").Key("RUN_USER").SetValue(form.RunUser)
+	cfg.Section("server").Key("DOMAIN").SetValue(form.Domain)
+	cfg.Section("server").Key("HTTP_PORT").SetValue(form.HTTPPort)
+	cfg.Section("server").Key("ROOT_URL").SetValue(form.AppUrl)
+
+	if len(strings.TrimSpace(form.SMTPHost)) > 0 {
+		cfg.Section("mailer").Key("ENABLED").SetValue("true")
+		cfg.Section("mailer").Key("HOST").SetValue(form.SMTPHost)
+		cfg.Section("mailer").Key("USER").SetValue(form.SMTPEmail)
+		cfg.Section("mailer").Key("PASSWD").SetValue(form.SMTPPasswd)
+
+		cfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").SetValue(com.ToStr(form.RegisterConfirm == "on"))
+		cfg.Section("service").Key("ENABLE_NOTIFY_MAIL").SetValue(com.ToStr(form.MailNotify == "on"))
 	}
 
-	setting.Cfg.Section("").Key("RUN_MODE").SetValue("prod")
+	cfg.Section("").Key("RUN_MODE").SetValue("prod")
 
-	setting.Cfg.Section("session").Key("PROVIDER").SetValue("file")
+	cfg.Section("session").Key("PROVIDER").SetValue("file")
 
-	setting.Cfg.Section("log").Key("MODE").SetValue("file")
+	cfg.Section("log").Key("MODE").SetValue("file")
 
-	setting.Cfg.Section("security").Key("INSTALL_LOCK").SetValue("true")
-	setting.Cfg.Section("security").Key("SECRET_KEY").SetValue(base.GetRandomString(15))
+	cfg.Section("security").Key("INSTALL_LOCK").SetValue("true")
+	cfg.Section("security").Key("SECRET_KEY").SetValue(base.GetRandomString(15))
 
 	os.MkdirAll("custom/conf", os.ModePerm)
-	if err := setting.Cfg.SaveTo(path.Join(setting.CustomPath, "conf/app.ini")); err != nil {
+	if err := cfg.SaveTo(path.Join(setting.CustomPath, "conf/app.ini")); err != nil {
 		ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), INSTALL, &form)
 		return
 	}
@@ -264,5 +244,5 @@ func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
 
 	log.Info("First-time run install finished!")
 	ctx.Flash.Success(ctx.Tr("install.install_success"))
-	ctx.Redirect(setting.AppSubUrl + "/user/login")
+	ctx.Redirect(form.AppUrl + "user/login")
 }

+ 2 - 2
routers/repo/commit.go

@@ -37,7 +37,7 @@ func RenderIssueLinks(oldCommits *list.List, repoLink string) *list.List {
 	newCommits := list.New()
 	for e := oldCommits.Front(); e != nil; e = e.Next() {
 		c := e.Value.(*git.Commit)
-		c.CommitMessage = string(base.RenderIssueIndexPattern([]byte(c.CommitMessage), repoLink))
+		c.CommitMessage = c.CommitMessage
 		newCommits.PushBack(c)
 	}
 	return newCommits
@@ -206,7 +206,7 @@ func Diff(ctx *middleware.Context) {
 	commitId := ctx.Repo.CommitId
 
 	commit := ctx.Repo.Commit
-	commit.CommitMessage = string(base.RenderIssueIndexPattern([]byte(commit.CommitMessage), ctx.Repo.RepoLink))
+	commit.CommitMessage = commit.CommitMessage
 	diff, err := models.GetDiffCommit(models.RepoPath(userName, repoName),
 		commitId, setting.Git.MaxGitDiffLines)
 	if err != nil {

+ 4 - 4
routers/repo/download.go

@@ -25,16 +25,16 @@ func ServeBlob(ctx *middleware.Context, blob *git.Blob) error {
 		buf = buf[:n]
 	}
 
-	contentType, isTextFile := base.IsTextFile(buf)
+	_, isTextFile := base.IsTextFile(buf)
 	_, isImageFile := base.IsImageFile(buf)
-	ctx.Resp.Header().Set("Content-Type", contentType)
+	ctx.Resp.Header().Set("Content-Type", "text/plain")
 	if !isTextFile && !isImageFile {
 		ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+path.Base(ctx.Repo.TreeName))
 		ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
 	}
 	ctx.Resp.Write(buf)
-	io.Copy(ctx.Resp, dataRc)
-	return nil
+	_, err = io.Copy(ctx.Resp, dataRc)
+	return err
 }
 
 func SingleDownload(ctx *middleware.Context) {

+ 2 - 2
routers/repo/view.go

@@ -156,9 +156,9 @@ func Home(ctx *middleware.Context) {
 		for _, f := range files {
 			switch c := f[1].(type) {
 			case *git.Commit:
-				c.CommitMessage = string(base.RenderIssueIndexPattern([]byte(c.CommitMessage), ctx.Repo.RepoLink))
+				c.CommitMessage = c.CommitMessage
 			case *git.SubModuleFile:
-				c.CommitMessage = string(base.RenderIssueIndexPattern([]byte(c.CommitMessage), ctx.Repo.RepoLink))
+				c.CommitMessage = c.CommitMessage
 			}
 		}
 		ctx.Data["Files"] = files

+ 46 - 0
scripts/init/freebsd/gogs

@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+# $FreeBSD$
+#
+# PROVIDE: gogs
+# REQUIRE: NETWORKING SYSLOG
+# KEYWORD: shutdown
+#
+# Add the following lines to /etc/rc.conf to enable gogs:
+#
+#gogs_enable="YES"
+
+. /etc/rc.subr
+
+name="gogs"
+rcvar="gogs_enable"
+
+load_rc_config $name
+
+: ${gogs_user:="git"}
+: ${gogs_enable:="NO"}
+: ${gogs_directory:="/home/git"}
+
+command="${gogs_directory}/scripts/start.sh"
+
+pidfile="${gogs_directory}/${name}.pid"
+
+start_cmd="${name}_start"
+stop_cmd="${name}_stop"
+
+gogs_start() {
+	cd ${gogs_directory}
+	export USER=${gogs_user}
+	export HOME=${gogs_directory}
+	/usr/sbin/daemon -f -u ${gogs_user} -p ${pidfile} $command
+}
+
+gogs_stop() {
+	if [ ! -f $pidfile ]; then
+		echo "GOGS PID File not found. Maybe GOGS is not running?"
+	else
+		kill $(cat $pidfile)
+	fi
+}
+
+run_rc_command "$1"

+ 0 - 2
scripts/start.bat

@@ -1,2 +0,0 @@
-@echo off
-..\\gogs.exe web

+ 1 - 1
scripts/start.sh

@@ -3,7 +3,7 @@
 # Use of this source code is governed by a MIT-style
 # license that can be found in the LICENSE file.
 #
-# start gogs web
+# MUST EXECUTE THIS AT ROOT DIRECTORY: ./scripts/start.sh
 #
 IFS='
 	'

+ 1 - 1
templates/.VERSION

@@ -1 +1 @@
-0.5.12.0120 Beta
+0.5.12.0204 Beta

+ 1 - 1
templates/admin/config.tmpl

@@ -61,7 +61,7 @@
                                     <dt>{{.i18n.Tr "admin.config.db_user"}}</dt>
                                     <dd>{{.DbCfg.User}}</dd>
                                     <dt>{{.i18n.Tr "admin.config.db_ssl_mode"}}</dt>
-                                    <dd>{{.DbCfg.SslMode}} {{.i18n.Tr "admin.config.db_ssl_mode_helper"}}</dd>
+                                    <dd>{{.DbCfg.SSLMode}} {{.i18n.Tr "admin.config.db_ssl_mode_helper"}}</dd>
                                     <dt>{{.i18n.Tr "admin.config.db_path"}}</dt>
                                     <dd>{{.DbCfg.Path}} {{.i18n.Tr "admin.config.db_path_helper"}}</dd>
                                 </dl>

+ 5 - 0
templates/admin/dashboard.tmpl

@@ -48,6 +48,11 @@
                                                 <td>{{.i18n.Tr "admin.dashboard.git_gc_repos"}}</td>
                                                 <td><i class="fa fa-caret-square-o-right"></i> <a href="{{AppSubUrl}}/admin?op=4">{{.i18n.Tr "admin.dashboard.operation_run"}}</a></td>
                                             </tr>
+                                            <tr>
+                                                <td>{{.i18n.Tr "admin.dashboard.resync_all_sshkeys"}}</td>
+                                                <td><i class="fa fa-caret-square-o-right"></i> <a href="{{AppSubUrl}}/admin?op=5">{{.i18n.Tr "admin.dashboard.operation_run"}}</a></td>
+                                            </tr>
+
                                         </tbody>
                                     </table>
                                 </div>

文件差异内容过多而无法显示
+ 3 - 3
templates/home.tmpl


+ 27 - 21
templates/install.tmpl

@@ -13,7 +13,7 @@
                         <div class="text-center panel-desc">{{.i18n.Tr "install.requite_db_desc"}}</div>
                         <div class="field">
                             <label class="req">{{.i18n.Tr "install.db_type"}}</label>
-                            <select name="database" id="install-database" class="form-control">
+                            <select name="db_type" id="install-database" class="form-control">
                                 {{range .DbOptions}}
                                 <option value="{{.}}"{{if eq $.CurDbOption .}}selected{{end}}>{{.}}</option>
                                 {{end}}
@@ -22,20 +22,20 @@
 
                         <div class="server-sql {{if eq .CurDbOption "SQLite3"}}hide{{end}}">
                             <div class="field">
-                                <label class="req" for="host">{{.i18n.Tr "install.host"}}</label>
-                                <input class="ipt ipt-large ipt-radius {{if .Err_DbHost}}ipt-error{{end}}" id="host" name="host" value="{{.host}}" />
+                                <label class="req" for="db_host">{{.i18n.Tr "install.host"}}</label>
+                                <input class="ipt ipt-large ipt-radius {{if .Err_DbHost}}ipt-error{{end}}" id="db_host" name="db_host" value="{{.db_host}}" />
                             </div>
                             <div class="field">
-                                <label class="req" for="user">{{.i18n.Tr "install.user"}}</label>
-                                <input class="ipt ipt-large ipt-radius {{if .Err_DbUser}}ipt-error{{end}}" id="user" name="user" value="{{.user}}" />
+                                <label class="req" for="db_user">{{.i18n.Tr "install.user"}}</label>
+                                <input class="ipt ipt-large ipt-radius {{if .Err_DbUser}}ipt-error{{end}}" id="db_user" name="db_user" value="{{.db_user}}" />
                             </div>
                             <div class="field">
-                                <label class="req" for="passwd">{{.i18n.Tr "install.password"}}</label>
-                                <input class="ipt ipt-large ipt-radius {{if .Err_DbPasswd}}ipt-error{{end}}" id="passwd" name="passwd" type="password" value="{{.passwd}}" />
+                                <label class="req" for="db_passwd">{{.i18n.Tr "install.password"}}</label>
+                                <input class="ipt ipt-large ipt-radius {{if .Err_DbPasswd}}ipt-error{{end}}" id="db_passwd" name="db_passwd" type="password" value="{{.db_passwd}}" />
                             </div>
                             <div class="field">
-                                <label class="req" for="database_name">{{.i18n.Tr "install.db_name"}}</label>
-                                <input class="ipt ipt-large ipt-radius {{if .Err_DatabaseName}}ipt-error{{end}}" id="database_name" name="database_name" value="{{.database_name}}" />
+                                <label class="req" for="db_name">{{.i18n.Tr "install.db_name"}}</label>
+                                <input class="ipt ipt-large ipt-radius {{if .Err_DbName}}ipt-error{{end}}" id="db_name" name="db_name" value="{{.db_name}}" />
                                 <label></label>
                                 <span class="help">{{.i18n.Tr "install.db_helper"}}</span>
                             </div>
@@ -51,8 +51,8 @@
                         </div>
 
                         <div class="field sqlite-setting {{if not (eq .CurDbOption "SQLite3")}}hide{{end}}">
-                            <label class="req" for="database_path">{{.i18n.Tr "install.path"}}</label>
-                            <input class="ipt ipt-large ipt-radius {{if .Err_DatabasePath}}ipt-error{{end}}" id="database_path" name="database_path" value="{{.database_path}}" />
+                            <label class="req" for="db_path">{{.i18n.Tr "install.path"}}</label>
+                            <input class="ipt ipt-large ipt-radius {{if .Err_DbPath}}ipt-error{{end}}" id="db_path" name="db_path" value="{{.db_path}}" />
                             <label></label>
                             <span class="help">{{.i18n.Tr "install.sqlite_helper"}}</span>
                         </div>
@@ -61,8 +61,8 @@
 
                         <div class="text-center panel-desc">{{.i18n.Tr "install.general_title"}}</div>
                         <div class="field">
-                            <label class="req" for="repo_path">{{.i18n.Tr "install.repo_path"}}</label>
-                            <input class="ipt ipt-large ipt-radius {{if .Err_RepoRootPath}}ipt-error{{end}}" id="repo_path" name="repo_path" value="{{.repo_path}}" required />
+                            <label class="req" for="repo_root_path">{{.i18n.Tr "install.repo_path"}}</label>
+                            <input class="ipt ipt-large ipt-radius {{if .Err_RepoRootPath}}ipt-error{{end}}" id="repo_root_path" name="repo_root_path" value="{{.repo_root_path}}" required />
                             <label></label>
                             <span class="help">{{.i18n.Tr "install.repo_path_helper"}}</span>
                         </div>
@@ -79,6 +79,12 @@
                             <span class="help">{{.i18n.Tr "install.domain_helper"}}</span>
                         </div>
                         <div class="field">
+                            <label class="req" for="http_port">{{.i18n.Tr "install.http_port"}}</label>
+                            <input class="ipt ipt-large ipt-radius {{if .Err_HttpPort}}ipt-error{{end}}" id="http_port" name="http_port" value="{{.http_port}}" required />
+                            <label></label>
+                            <span class="help">{{.i18n.Tr "install.http_port_helper"}}</span>
+                        </div>
+                        <div class="field">
                             <label class="req" for="app_url">{{.i18n.Tr "install.app_url"}}</label>
                             <input class="ipt ipt-large ipt-radius {{if .Err_AppUrl}}ipt-error{{end}}" id="app_url" name="app_url" value="{{.app_url}}" required />
                             <label></label>
@@ -93,12 +99,12 @@
                             <input class="ipt ipt-large ipt-radius {{if .Err_SmtpHost}}ipt-error{{end}}" id="smtp_host" name="smtp_host" value="{{.smtp_host}}" />
                         </div>
                         <div class="field">
-                            <label for="mailer_user">{{.i18n.Tr "install.mailer_user"}}</label>
-                            <input class="ipt ipt-large ipt-radius {{if .Err_SmtpEmail}}ipt-error{{end}}" id="mailer_user" name="mailer_user" value="{{.mailer_user}}" />
+                            <label for="smtp_user">{{.i18n.Tr "install.mailer_user"}}</label>
+                            <input class="ipt ipt-large ipt-radius {{if .Err_SMTPEmail}}ipt-error{{end}}" id="smtp_user" name="smtp_user" value="{{.smtp_user}}" />
                         </div>
                         <div class="field">
-                            <label for="mailer_pwd">{{.i18n.Tr "install.mailer_password"}}</label>
-                            <input class="ipt ipt-large ipt-radius {{if .Err_SmtpPasswd}}ipt-error{{end}}" id="mailer_pwd" name="mailer_pwd" type="password" value="{{.mailer_pwd}}" />
+                            <label for="smtp_pwd">{{.i18n.Tr "install.mailer_password"}}</label>
+                            <input class="ipt ipt-large ipt-radius {{if .Err_SMTPPasswd}}ipt-error{{end}}" id="smtp_pwd" name="smtp_pwd" type="password" value="{{.smtp_pwd}}" />
                         </div>
 
                         <hr>
@@ -122,12 +128,12 @@
                             <input class="ipt ipt-large ipt-radius {{if .Err_AdminName}}ipt-error{{end}}" id="admin_name" name="admin_name" value="{{.admin_name}}" required />
                         </div>
                         <div class="field">
-                            <label class="req" for="admin_pwd">{{.i18n.Tr "install.admin_password"}}</label>
-                            <input class="ipt ipt-large ipt-radius {{if .Err_AdminPasswd}}ipt-error{{end}}" id="admin_pwd" name="admin_pwd" type="password" value="{{.admin_pwd}}" required />
+                            <label class="req" for="admin_passwd">{{.i18n.Tr "install.admin_password"}}</label>
+                            <input class="ipt ipt-large ipt-radius {{if .Err_AdminPasswd}}ipt-error{{end}}" id="admin_passwd" name="admin_passwd" type="password" value="{{.admin_passwd}}" required />
                         </div>
                         <div class="field">
-                            <label class="req" for="confirm_passwd">{{.i18n.Tr "install.confirm_password"}}</label>
-                            <input class="ipt ipt-large ipt-radius {{if .Err_AdminPasswd}}ipt-error{{end}}" id="confirm_passwd" name="confirm_passwd" type="password" required />
+                            <label class="req" for="admin_confirm_passwd">{{.i18n.Tr "install.confirm_password"}}</label>
+                            <input class="ipt ipt-large ipt-radius {{if .Err_AdminPasswd}}ipt-error{{end}}" id="admin_confirm_passwd" name="admin_confirm_passwd" type="password" required />
                         </div>
                         <div class="field">
                             <label class="req" for="admin_email">{{.i18n.Tr "install.admin_email"}}</label>

+ 1 - 1
templates/repo/commits_table.tmpl

@@ -32,7 +32,7 @@
                     {{end}}
                 </td>
                 <td class="sha"><a rel="nofollow" class="label label-green" href="{{AppSubUrl}}/{{$username}}/{{$reponame}}/commit/{{.Id}} ">{{SubStr .Id.String 0 10}} </a></td>
-                <td class="message"><span class="text-truncate">{{Str2html .Summary}}</span></td>
+                <td class="message"><span class="text-truncate">{{RenderCommitMessage .Summary $.RepoLink}}</span></td>
                 <td class="date">{{TimeSince .Author.When $.Lang}}</td>
             </tr>
             {{end}}

+ 11 - 11
templates/repo/diff.tmpl

@@ -17,7 +17,7 @@
         <div class="panel panel-info panel-radius diff-head-box">
             <div class="panel-header">
                 <a class="pull-right btn btn-blue btn-header btn-medium btn-radius" rel="nofollow" href="{{.SourcePath}}">{{.i18n.Tr "repo.diff.browse_source"}}</a>
-                <h4 class="commit-message">{{Str2html .Commit.Message}}</h4>
+                <h4 class="commit-message">{{RenderCommitMessage .Commit.Message $.RepoLink}}</h4>
             </div>
             <div class="panel-body">
                 <span class="pull-right">
@@ -74,11 +74,11 @@
             </ol>
         </div>
 
-        {{range .Diff.Files}}
+        {{range $i, $file := .Diff.Files}}
         <div class="panel panel-radius diff-file-box diff-box file-content" id="diff-{{.Index}}">
             <div class="panel-header">
                 <div class="diff-counter count pull-left">
-                    {{if not .IsBin}}
+                    {{if not $file.IsBin}}
                     <span class="add" data-line="{{.Addition}}">+ {{.Addition}}</span>
                     <span class="bar">
                         <span class="pull-left add"></span>
@@ -90,9 +90,9 @@
                     {{end}}
                 </div>
                 <a class="btn btn-gray btn-header btn-radius text-black pull-right" rel="nofollow" href="{{$.SourcePath}}/{{.Name}}">{{$.i18n.Tr "repo.diff.view_file"}}</a>
-                <span class="file">{{.Name}}</span>
+                <span class="file">{{$file.Name}}</span>
             </div>
-            {{$isImage := (call $.IsImageFile .Name)}}
+            {{$isImage := (call $.IsImageFile $file.Name)}}
             <div class="panel-body file-body file-code code-view code-diff">
                 {{if $isImage}}
                     <div class="text-center">
@@ -101,18 +101,18 @@
                 {{else}}
                 <table>
                     <tbody>
-                        {{range .Sections}}
-                        {{range .Lines}}
-                        <tr class="{{DiffLineTypeToStr .Type}}-code nl-1 ol-1">
+                        {{range $j, $section := $file.Sections}}
+                        {{range $k, $line := $section.Lines}}                        
+                        <tr class="{{DiffLineTypeToStr .Type}}-code nl-{{$i}} ol-{{$i}}">
                             <td class="lines-num lines-num-old">
-                                <span rel="L1">{{if .LeftIdx}}{{.LeftIdx}}{{end}}</span>
+                                <span rel="diff-{{Add $i 1}}L{{$j}}{{$k}}">{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}</span>
                             </td>
                             <td class="lines-num lines-num-new">
-                                <span rel="L1">{{if .RightIdx}}{{.RightIdx}}{{end}}</span>
+                                <span rel="diff-{{Add $i 1}}L{{$j}}{{$k}}">{{if $line.RightIdx}}{{$line.RightIdx}}{{end}}</span>
                             </td>
                             
                             <td class="lines-code">
-                                <pre>{{.Content}}</pre>
+                                <pre>{{$line.Content}}</pre>
                             </td>
                         </tr>
                         {{end}}

+ 2 - 2
templates/repo/view_list.tmpl

@@ -14,7 +14,7 @@
             </span>
             <span class="last-commit"><a href="{{.RepoLink}}/commit/{{.LastCommit.Id}}" rel="nofollow">
                 <strong>{{ShortSha .LastCommit.Id.String}}</strong></a>
-                <span class="text-truncate">{{Str2html .LastCommit.Summary}}</span>
+                <span class="text-truncate">{{RenderCommitMessage .LastCommit.Summary .RepoLink}}</span>
             </span>
             <span class="age right">{{TimeSince .LastCommit.Author.When $.Lang}}</span>
         </th>
@@ -53,7 +53,7 @@
                   <a rel="nofollow" class="label label-green" href="{{AppSubUrl}}/{{$.Username}}/{{$.Reponame}}/commit/{{$commit.Id}} ">{{SubStr $commit.Id.String 0 10}} </a>
                 </td>
                 <td class="message">
-                  <span class="text-truncate">{{Str2html $commit.Summary}}</span>
+                  <span class="text-truncate">{{RenderCommitMessage $commit.Summary $.RepoLink}}</span>
                 </td>
                 <td class="age">{{TimeSince $commit.Committer.When $.Lang}}</td>
             </tr>

+ 10 - 8
templates/user/settings/email.tmpl

@@ -36,19 +36,21 @@
 							   {{end}}
                             </li>
                             {{end}}
-                             <form action="{{AppSubUrl}}/user/settings/email" method="post">
-							{{.CsrfTokenHtml}}
-							<p class="panel-header"><strong>{{.i18n.Tr "settings.add_new_email"}}</strong></p>
-							<p class="field">
+                        </ul>
+                        <div class="panel-header">
+                             <strong>{{.i18n.Tr "settings.add_new_email"}}</strong>
+                        </div>
+                        <form class="form form-align panel-body" id="add-email-form" action="{{AppSubUrl}}/user/settings/email" method="post">
+                            {{.CsrfTokenHtml}}                            
+                            <p class="field">
                                 <label class="req" for="email">{{.i18n.Tr "email"}}</label>
-                                <input class="ipt ipt-radius" id="email" name="email" type="text" required />
+                                <input class="ipt ipt-large ipt-radius" id="email" name="email" type="text" required />
                             </p>
                             <p class="field">
                                 <label></label>
-                                <button class="btn btn-green btn-radius" id="email-add-btn">{{.i18n.Tr "settings.add_email"}}</button>
+                                <button class="btn btn-green btn-large btn-radius" id="email-add-btn">{{.i18n.Tr "settings.add_email"}}</button>
                             </p>
-							</form>
-                        </ul>
+                        </form>
                     </div>                    
                 </div>
             </div>

+ 1 - 1
templates/user/settings/nav.tmpl

@@ -4,7 +4,7 @@
         <ul class="menu menu-vertical switching-list grid-1-5 left">
             <li {{if .PageIsSettingsProfile}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings">{{.i18n.Tr "settings.profile"}}</a></li>
             <li {{if .PageIsSettingsPassword}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/password">{{.i18n.Tr "settings.password"}}</a></li>
-            <li {{if .PageIsSettingsEmail}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/email">{{.i18n.Tr "settings.emails"}}</a></li>
+            <li {{if .PageIsSettingsEmails}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/email">{{.i18n.Tr "settings.emails"}}</a></li>
             <li {{if .PageIsSettingsSSHKeys}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/ssh">{{.i18n.Tr "settings.ssh_keys"}}</a></li>
             <li {{if .PageIsSettingsSocial}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/social">{{.i18n.Tr "settings.social"}}</a></li>
             <li {{if .PageIsSettingsApplications}}class="current"{{end}}><a href="{{AppSubUrl}}/user/settings/applications">{{.i18n.Tr "settings.applications"}}</a></li>