瀏覽代碼

Merge branch 'dev' of github.com:gogits/gogs into dev

Unknwon 10 年之前
父節點
當前提交
bf5595f9a0

+ 4 - 2
cmd/web.go

@@ -284,9 +284,11 @@ func runWeb(*cli.Context) {
 			r.Route("/collaboration", "GET,POST", repo.SettingsCollaboration)
 			r.Get("/hooks", repo.Webhooks)
 			r.Get("/hooks/new", repo.WebHooksNew)
-			r.Post("/hooks/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
+			r.Post("/hooks/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
+			r.Post("/hooks/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
 			r.Get("/hooks/:id", repo.WebHooksEdit)
-			r.Post("/hooks/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
+			r.Post("/hooks/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
+			r.Post("/hooks/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
 		})
 	}, reqSignIn, middleware.RepoAssignment(true), reqTrueOwner)
 

+ 178 - 173
conf/locale/locale_de-DE.ini

@@ -1,4 +1,4 @@
-app_desc = Ein schmerzloses selbst gehostetes Git-Service welches in Go geschrieben wurde
+app_desc = Ein einfacher, selbst gehostetes Git-Service, geschrieben in Go.
 
 home = Home
 dashboard = Dashboard
@@ -12,16 +12,16 @@ register = Registrieren
 website = Webseite
 version = Version
 page = Seite
-template = Template
+template = Vorlage
 language = Sprache
 
 username = Benutzername
-email = E-mail
-password = Kennwort
-re_type = neu tippen
+email = E-Mail
+password = Passwort
+re_type = wiederholen
 captcha = Captcha
 
-repository = Repositorie
+repository = Repository
 organization = Organisation
 mirror = Spiegel
 new_repo = Neues Repository
@@ -39,207 +39,213 @@ issues = Issues
 cancel = Abbrechen
 
 [home]
-uname_holder = Benutzername oder E-mail
-password_holder = Kennwort
+uname_holder = Benutzername oder E-Mail
+password_holder = Passwort
 switch_dashboard_context = Switch Dashboard Context
-my_repos = Meine Repositories
-collaborative_repos = Collaborative Repositories
+my_repos = Meine Repositorys
+collaborative_repos = Gemeinschaftliche Repositorys
 my_orgs = Meine Organisationen
 my_mirrors = Meine Spiegel
 
 [auth]
 create_new_account = Neues Konto erstellen
-register_hepler_msg = Sie haben bereits ein Konto? Jetzt anmelden!
-social_register_hepler_msg = Sie haben bereits ein Konto? Verknüpfe es jetzt!
-disable_register_prompt = Es tut uns leid, die Registrierung wurde deaktiviert. Bitte wende Sie sich an den Administrator.
-disable_register_mail = Es tut uns leid, die Registrierung E-Mail Bestätigung wurde deaktiviert.
-remember_me = Erinnere mich
-forgot_password= Kennwort vergessen
-forget_password = Kennwort vergessen?
-sign_up_now = Benötigen Sie ein Konto? Registrieren Sie sich jetzt.
+register_hepler_msg = Du hast schon ein Konto? Jetzt anmelden!
+social_register_hepler_msg = Du hast schon ein Konto? Jetzt verknüpfen!
+disable_register_prompt = Es tut uns leid, die Registrierung wurde deaktiviert. Bitte wende dich an den Administrator.
+disable_register_mail = Es tut uns leid, die Bestätigung der Registrierungs-E-Mail wurde deaktiviert.
+remember_me = angemeldet bleiben
+forgot_password= Passwort vergessen
+forget_password = Passwort vergessen?
+sign_up_now = Du willst ein Konto? Jetzt registrieren!
 confirmation_mail_sent_prompt = A new confirmation e-mail has been sent to <b>%s</b>, please check your inbox within the next %d hours to complete your registration.
-sign_in_email = Melden Sie sich mit Ihrer E-Mailadresse an
-active_your_account = Aktivieren Sie Ihr Konto
+sign_in_email = Melden dich mit deiner E-Mail-Adresse an
+active_your_account = Aktivieren dein Konto
 resent_limit_prompt = Sorry, you are sending an activation e-mail too frequently. Please wait 3 minutes.
 has_unconfirmed_mail = Hi %s, you have an unconfirmed email 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.
-resend_mail = Klicken Sie hier, um Ihre Aktivierungs-E-Mail erneut senden
+resend_mail = Klicke hier, um deine Aktivierungs-E-Mail nochmal zu senden
 email_not_associate = Diese E-Mail-Adresse ist mit keinen Konto verknüpft.
-send_reset_mail = Klicken Sie hier, um sich das E-Mail zum Kennwort zurücksetzen erneut zu senden
-reset_password = Ihr Kennwort zurücksetzen
-invalid_code = Es tut uns leid, Ihre Bestätigungscode abgelaufen ist oder nicht gültig.
-reset_password_helper = Klicken Sie hier, um Ihr Kennwort zurückzusetzen
-password_too_short = Passwortlänge kann nicht weniger als 6 sein.
+send_reset_mail = Hier klicken, um die E-Mail zum Passwort-zurücksetzen erneut zu versenden
+reset_password = Passwort zurücksetzen
+invalid_code = Es tut uns leid, der Bestätigungscode ist abgelaufen oder ungültig.
+reset_password_helper = Hier klicken, um das Passwort zurückzusetzen
+password_too_short = Das Passwort muss mindenstens 6 Zeichen lang sein
 
 [form]
 UserName = Benutzername
 RepoName = Repository Name
 Email = E-Mail-Adresse
-Password = Kennwort
-Retype = Kennwort erneut eingeben
-SSHTitle = SSH Schlüsselname
-HttpsUrl = HTTPS URL
+Password = Passwort
+Retype = Passwort erneut eingeben
+SSHTitle = SSH-Schlüsselname
+HttpsUrl = HTTPS-URL
 PayloadUrl = Payload URL
 TeamName = Teamname
 AuthName = Authentifizierungsname
 
 require_error = ` darf nicht leer sein.`
-alpha_dash_error = ` must be valid alpha or numeric or dash(-_) characters.`
-alpha_dash_dot_error = ` must be valid alpha or numeric or dash(-_) or dot characters.`
+alpha_dash_error = ` kann ausschließlich alphanumerische Zeichen und "-_" enthalten.`
+alpha_dash_dot_error = ` kann ausschließlich alphanumerische Zeichen und ".-_" enthalten.`
 min_size_error = ` muss mindestens %s Zeichen enthalten.`
 max_size_error = ` darf höchstens %s Zeichen enthalten.`
 email_error = ` ist keine gültige E-Mail-Adresse.`
 url_error = ` ist keine gültige URL.`
 unknown_error = Unbekannter Fehler:
 captcha_incorrect = Captcha stimmt nicht überein.
-password_not_match = Die Kennwörter stimmen nicht überein.
+password_not_match = Die Passwörter stimmen nicht überein.
 
 username_been_taken = Benutzername ist bereits vergeben.
 repo_name_been_taken = Repository Name ist bereits vergeben.
 org_name_been_taken = Organisationsname ist bereits vergeben.
 team_name_been_taken = Teamname ist bereits vergeben.
-email_been_used = E-Mail-Adresse ist bereits vergeben.
-ssh_key_been_used = Public-Key Name wird bereits verwendet.
+email_been_used = E-Mail-Adresse wird bereits verwendet.
+ssh_key_been_used = SSH-Schlüsselname wird bereits verwendet.
 illegal_username = Ihr Benutzername enthält ungültige Zeichen.
-illegal_repo_name = Repository Name enthält ungültige Zeichen.
+illegal_repo_name = Repository-Name enthält ungültige Zeichen.
 illegal_org_name = Organisationsname enthält ungültige Zeichen.
 illegal_team_name = Teamname enthält ungültige Zeichen.
-username_password_incorrect = Benutzername oder Kennwort ist nicht korrekt.
-enterred_invalid_repo_name = Bitte stellen Sie sicher, dass der eingegeben Repository Name richtig ist.
-enterred_invalid_owner_name = Bitte stellen Sie sicher, dass der eingegeben Eigentümername richtig ist.
+username_password_incorrect = Benutzername oder Passwort ist nicht korrekt.
+enterred_invalid_repo_name = Bitte stellen Sie sicher, dass der eingegeben Repository-Name richtig ist.
+enterred_invalid_owner_name = Bitte stellen Sie sicher, dass der eingegeben Besitzername richtig ist.
 enterred_invalid_password = Bitte stellen Sie sicher, dass das eingegebene Passwort richtig ist.
 user_not_exist = Angegebener Benutzer existiert nicht.
-last_org_owner = The user to remove is the last member in owner team. There must be another owner.
+last_org_owner = Der zu entfernende Benutzer ist der letzte Teambesitzer. Es muss einen anderen Besitzer geben.
 
 invalid_ssh_key = Leider sind wir nicht in der Lage, Ihren SSH-Schlüssel zu überprüfen: %s
 auth_failed = Authentifizierung fehlgeschlagen: %v
 
-still_own_repo = Your account still have ownership of repository, you have to delete or transfer them first.
-org_still_own_repo = This organization still have ownership of repository, you have to delete or transfer them first.
+still_own_repo = Dein Konto besitzt noch Repositorys. Diese müssen zuerst gelöscht oder übertragen werden.
+org_still_own_repo = Diese Organisation besitzt noch Repositorys. Diese müssen zuerst gelöscht oder übertragen werden.
 
-still_own_user = This authentication still has used by some users, you should move them and then delete again.
+still_own_user = Diese Authentifizierung wird noch von einigen Benutzern genutzt. Entferne diese zuvor und lösche erneut.
 
 [settings]
 profile = Profil
-password = Kennwort
+password = Passwort
 ssh_keys = SSH-Schlüssel
-social = Social Konten
+social = Soziale Konten
 orgs = Organisationen
-delete = Delete Accoount
+delete = Konto löschen
 
 public_profile = Öffentliches Profil
 profile_desc = Your Email address is public and will be used for any account related notifications, and any web based operations made via the site.
 full_name = Vollständiger Name
-website = Website
+website = Webseite
 location = Standort
 update_profile = Profil aktualisieren
-update_profile_success = Your profile has been successfully updated.
+update_profile_success = Dein Profil wurde aktualisiert.
 
-change_password = Kennwort ändern
-old_password = Aktuelles Kennwort
-new_password = Neues Kennwort
-password_incorrect = Aktuelles Kennwort ist nicht korrekt.
-change_password_success = Kennwort erfolgreich geändert. Sie können nun mittels des neuen Kennwortes anmelden.
+change_password = Passwort ändern
+old_password = Aktuelles Passwort
+new_password = Neues Passwort
+password_incorrect = Aktuelles Passwort ist nicht korrekt.
+change_password_success = Passwort erfolgreich geändert. Du kannst dich jetzt mit dem neuen Passwort anmelden.
 
 manage_ssh_keys = SSH-Schlüssel verwalten
-add_key = Schlüssel hinzufügen
-ssh_desc = This is a list of SSH keys associated with your account. Remove any keys that you do not recognize.
-ssh_helper = <strong>Need help?</strong> Check out our guide to <a href="https://help.github.com/articles/generating-ssh-keys">generating SSH keys</a> or troubleshoot <a href="https://help.github.com/ssh-issues/">common SSH Problems</a>.
+add_key = SSH-Schlüssel hinzufügen
+ssh_desc = Dies ist eine Liste aller SSH-Schlüssel, die mit deinem Konto verküpft sind. Entferne alle Schlüssel, die du nicht kennst.
+ssh_helper = <strong>Du brauchst Hilfe?</strong> Hier ist eine Anleitung zum <a href="https://help.github.com/articles/generating-ssh-keys">Erzeugen von SSH-Schlüssel</a> oder <a href="https://help.github.com/ssh-issues/">Problemlösung einfacher SSH-Probleme</a>.
 add_new_key = SSH-Schlüssel hinzufügen
 key_name = Schlüsselname
 key_content = Inhalt
-add_key_success = New SSH Key has been added!
-delete_key = löschen
+add_key_success = SSH-Schlüssel wurde hinzugefügt!
+delete_key = SSH-Schlüssel löschen
 add_on = Hinzugefügt am
 last_used = Zuletzt verwendet auf
 no_activity = Keine neuen Aktivitäten
 
-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 = Verküpfte soziale Konten verwalten
+social_desc = Dies ist eine Liste verküpfter sozialer Konten. Entferne alle Verküpfungen, die du nicht kennst.
 unbind = Verknüpfung entfernen
-unbind_success = Die Verknüpfung zum Social Konto wurde entfernt.
+unbind_success = Die Verknüpfung zum sozialen Konto wurde entfernt.
 
-delete_account = Löschen Sie Ihr Konto
-delete_prompt = Die Operation wird Ihr Konto dauerhaft löschen und kann <strong>NICHT</strong> rückgängig gemacht werden!
-confirm_delete_account = Löschung bestätigen
+delete_account = Konto löschen
+delete_prompt = Diese Aktion wird dein Konto dauerhaft löschen und kann <strong>NICHT</strong> rückgängig gemacht werden!
+confirm_delete_account = Löschen bestätigen
 
 [repo]
-owner = Eigentümer
-repo_name = Repositorie Name
-repo_name_helper = Tolle Repositorienamen sind kurz, einprägsam und <strong>einzigartig</strong>.
+owner = Besitzer
+repo_name = Repository-Name
+repo_name_helper = Gute Repository-Namen sind kurz, einprägsam und <strong>einzigartig</strong>.
 visibility = Sichtbarkeit
-visiblity_helper = Dieses Repositorie ist <span class="label label-red label-radius">Privat</span>
+visiblity_helper = Dieses Repository ist <span class="label label-red label-radius">Privat</span>
 repo_desc = Beschreibung
 repo_lang = Sprache
-repo_lang_helper = Wählen Sie eine .gitignore Datei
+repo_lang_helper = Wähle eine .gitignore Datei
 license = Lizenz
-license_helper = Wählen Sie eine Lizenzdatei aus
-init_readme = Initialisieren dieses Repository mit einem README.md
+license_helper = Wählen Sie eine Lizenz aus
+init_readme = Repository mit README.md initialisieren
 create_repo = Repository erstellen
-default_branch = Default Branch
-mirror_interval = Mirror Intervall(Stunden)
+default_branch = Standard-Branch
+mirror_interval = Spiegel-Intervall (in Stunden)
 goget_meta = Go-Get Meta
 goget_meta_helper = This repository will be <span class="label label-blue label-radius">Go-Getable</span>
 
-need_auth = Berechtigung erforderlich
+need_auth = Authorisierung benötigt
 migrate_type = Migrationstyp
-migrate_type_helper = Dieses Repository wird ein <span class="label label-blue label-radius">Mirror</span>
-migrate_repo = Repositorie migrieren
+migrate_type_helper = Dieses Repository wird ein <span class="label label-blue label-radius">Spiegel</span>
+migrate_repo = Repository migrieren
 
-clone_helper = Need help cloning? Visit <a target="_blank" href="http://git-scm.com/book/en/Git-Basics-Getting-a-Git-Repository">Help</a>!
+copy_link = Klonen
+clone_helper = Du brauchst Hilef beim klonen? Hier gibt es<a target="_blank" href="http://git-scm.com/book/en/Git-Basics-Getting-a-Git-Repository">Hilfe</a>!
 unwatch = Beobachtung beenden
 watch = Beobachtung
 unstar = Markierung aufheben
 star = Markierung
 fork = Abspaltung
 
+quick_guide = Kurzanleitung
+clone_this_repo = Dieses Repositorie klonen
+create_new_repo_command = Erstellen Sie ein neues Repositorie mittels der Kommandozeile
+push_exist_repo = Push an existing repository from the command line
+
 settings = Einstellungen
 settings.options = Optionen
 settings.collaboration = Zusammenarbeit
 settings.hooks = Webhooks
 settings.deploy_keys = Schlüssel bereitstellen
 settings.basic_settings = Grundeinstellungen
-settings.danger_zone = Danger Zone
+settings.danger_zone = Gefahrenzone
 settings.site = Offizielle Website
 settings.update_settings = Aktualisierungseinstellungen
-settings.transfer = Eigentum übertragen
-settings.transfer_desc = Transfer this repo to another user or to an organization where you have admin rights.
-settings.delete = Dieses Repository löschen
-settings.delete_desc = Sobald Sie ein Repository löschen, gibt es keinen Weg zurück. Bitte seien Sie sicher.
-settings.update_settings_success = Repository Optionen wurde erfolgreich aktualisiert.
+settings.transfer = Besitz übertragen
+settings.transfer_desc = Übertrage dieses Repository einem anderen Benutzer oder einer Organisation.
+settings.delete = Repository löschen
+settings.delete_desc = Wenn dieses Repository gelöschet ist, gibt es keinen Weg zurück. Sei dir sicher!
+settings.update_settings_success = Repository-Optionen wurde erfolgreich aktualisiert.
 settings.transfer_owner = Neuer Besitzer
-settings.make_transfer = Make Transfer
-settings.confirm_delete = Löschung bestätigen
-settings.add_collaborator = Neuer Mitarbeiter hinzufügen
+settings.make_transfer = übertragen
+settings.confirm_delete = Löschen bestätigen
+settings.add_collaborator = Neuen Mitarbeiter hinzufügen
 settings.add_collaborator_success = Neuer Mitarbeiter wurde hinzugefügt.
 settings.remove_collaborator_success = Mitarbeiter wurde entfernt.
 settings.add_webhook = Webhook hinzufügen
-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="http://gogs.io/docs/features/webhook.html">Webhooks Guide</a>.
+settings.hooks_desc = Webhooks erlauben es externe Dienste zu informieren, wenn etwas bestimmtes in deinem Repository passiert. GoGS sendet dann eine POST-Request an alle angegebenen URLs. Erfahre mehr in unserem <a target="_blank" href="http://gogs.io/docs/features/webhook.html">Webhooks Guide</a>.
 settings.remove_hook_success = Webhook wurde entfernt.
-settings.add_webhook_desc = We’ll send a <code>POST</code> request to the URL below with details of any subscribed events. You can also specify which data format you'd like to receive (JSON, <code>x-www-form-urlencoded</code>, <em>etc</em>). More information can be found in <a target="_blank" href="http://gogs.io/docs/features/webhook.html">Webhooks Guide</a>.
+settings.add_webhook_desc = GoGS sendet einen <code>POST</code>-Request an die unten stehende URL mit Details aller abonierten Ereignisse. Du kannst auch angeben, welches Datenformat du erhalten willst (JSON, <code>x-www-form-urlencoded</code>, <em>etc</em>). Mehr Informationen findest du im <a target="_blank" href="http://gogs.io/docs/features/webhook.html">Webhooks Guide</a>.
 settings.payload_url = Payload URL
 settings.content_type = Inhaltstyp
 settings.secret = Geheimnis
-settings.event_desc = Durch welche Ereignisse würden Sie gerne diesen Webhook auslösen?
-settings.event_push_only = Just the <code>push</code> event.
+settings.event_desc = Welche Ereignisse sollen diesen Webhook auslösen?
+settings.event_push_only = Nur das <code>push</code>-Ereignis.
 settings.active = Aktiv
-settings.active_helper = We will deliver event details when this hook is triggered.
+settings.active_helper = Ereignisdetails werden ausgeliefert, wenn dieser Webhook ausgelöst wird.
 settings.add_hook_success = Neuer Webhook wurde hinzugefügt.
 settings.update_webhook = Webhook aktualisieren
 settings.update_hook_success = Webhook wurde aktualisiert.
 settings.delete_webhook = Webhook löschen
-settings.recent_deliveries = Jüngste Zustellungen
+settings.recent_deliveries = letzte Zustellungen
 
 [org]
 org_name_holder = Name der Organisation
-org_name_helper = Namen großer Organisationen sind kurz und einprägsam.
-org_email_helper = E-Mail der Organisation empfängt alle Benachrichtigungen und Bestätigungen.
+org_name_helper = Gute Namen von Organisationen sind kurz und einprägsam.
+org_email_helper = Das E-Mail-Konto der Organisation empfängt alle Benachrichtigungen.
 create_org = Organisation erstellen
 repo_updated = Aktualisiert
 people = Personen
 invite_someone = Jemanden einladen
 teams = Teams
 lower_members = Mitglieder
-lower_repositories = Repositories
+lower_repositories = Repositorys
 create_new_team = Neues Team erstellen
 org_desc = Beschreibung
 team_name = Teamname
@@ -254,68 +260,68 @@ settings.full_name = Vollständiger Name
 settings.website = Webseite
 settings.location = Standort
 settings.update_settings = Aktualisierungseinstellungen
-settings.update_setting_success = Einstellung der Organisation wurde erfolgreich aktualisiert.
+settings.update_setting_success = Einstellungen der Organisation wurden aktualisiert.
 settings.delete = Organisation löschen
 settings.delete_account = Diese Organisation löschen
-settings.delete_prompt = Die Operation wird diese Organisation dauerhaft löschen und kann <strong>NICHT</strong> rückgängig gemacht werden!
-settings.confirm_delete_account = Löschung bestätigen
+settings.delete_prompt = Die Organisation wird dauerhaft gelöscht. Dies kann <strong>NICHT</strong> rückgängig gemacht werden!
+settings.confirm_delete_account = Löschen bestätigen
 
 members.public = Öffentlich
 members.public_helper = Privat machen
 members.private = Privat
 members.private_helper = Öffentlich machen
-members.owner = Eigentümer
+members.owner = Besitzer
 members.member = Mitglied
 members.conceal = Verbergen
 members.remove = Entfernen
 members.leave = Verlassen
-members.invite_desc = Start typing a username to invite a new member to %s:
+members.invite_desc = Benutzernamen eingeben, um ihn als neues Mitglied in %s einzuladen:
 members.invite_now = Jetzt einladen
 
 teams.join = Beitreten
 teams.leave = Verlassen
 teams.read_access = Lesezugriff
-teams.read_access_helper = This team will be able to view and clone its repositories.
+teams.read_access_helper = Dieses Team wird Repositorys einsehen und klonen können.
 teams.write_access = Schreibzugriff
-teams.write_access_helper = This team will be able to read its repositories, as well as push to them.
+teams.write_access_helper = Dieses Team wird die Repositorys einsehen und in sie hinein pushen können.
 teams.admin_access = Adminzugriff
-teams.admin_access_helper = This team will be able to push/pull to its repositories, as well as add other collaborators to them.
+teams.admin_access_helper = Dieses Team wird pull- und push-Rechte für die Repositorys haben und Mitarbeiter einladen können.
 teams.no_desc = Dieses Team hat keine Beschreibung
 teams.settings = Einstellungen
-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 = Besitzer haben vollen Zugriff auf <strong>alle Repositorys</strong> und <strong>Admin Rechte</strong> für diese Organisation.
 teams.members = Teammitglieder
 teams.update_settings = Einstellungen aktualisieren
 teams.delete_team = Dieses Team löschen
 teams.add_team_member = Teammitglied hinzufügen
-teams.delete_team_success = Das Team wurde erfolgreich gelöscht.
-teams.read_permission_desc = This team grants <strong>Read</strong> access: members can view and clone the team's repositories.
-teams.write_permission_desc = This team grants <strong>Write</strong> access: members can read from and push to the team's repositories.
-teams.admin_permission_desc = This team grants <strong>Admin</strong> access: members can read from, push to, and add collaborators to the team's repositories.
-teams.repositories = Team Repositories
-teams.add_team_repository = Teamrepositorie hinzufügen
+teams.delete_team_success = Team gelöscht
+teams.read_permission_desc = Dieses Team erlaubt <strong>Lesezugriff</strong: Mitglieder können Team-Repositorys einsehen und klonen.
+teams.write_permission_desc = Dieses Team erlaubt <strong>Schreibzugriff</strong>: Mitglieder können Team-Repositorys einsehen und hinein pushen.
+teams.admin_permission_desc = Diese Team erlaubt <strong>Adminzugriff</strong>: Mitglieder dieses Teams können pullen, pushen und dem Team Mitarbeiter hinzufügen.
+teams.repositories = Team Repositorys
+teams.add_team_repository = Team-Repository hinzufügen
 teams.remove_repo = Entfernen
 
 [admin]
 dashboard = Dashboard
 users = Benutzer
 organizations = Organisationen
-repositories = Repositories
+repositories = Repositorys
 authentication = Authentifizierung
 config = Konfiguration
 monitor = Monitoring
-prev = Prev.
-next = Next
+prev = zurück
+next = vor
 
 dashboard.statistic = Statistik
 dashboard.operations = Operationen
-dashboard.system_status = System Monitor Status
-dashboard.statistic_info = Gogs database has <b>%d</b> users, <b>%d</b> organizations, <b>%d</b> public keys, <b>%d</b> repositories, <b>%d</b> watches, <b>%d</b> stars, <b>%d</b> actions, <b>%d</b> accesses, <b>%d</b> issues, <b>%d</b> comments, <b>%d</b> social accounts, <b>%d</b> follows, <b>%d</b> mirrors, <b>%d</b> releases, <b>%d</b> login sources, <b>%d</b> webhooks, <b>%d</b> milestones, <b>%d</b> labels, <b>%d</b> hook tasks, <b>%d</b> teams, <b>%d</b> update tasks, <b>%d</b> attachments.
+dashboard.system_status = System-Monitor-Status
+dashboard.statistic_info = GoGS Datenbank hat <b>%d</b> Benutzer, <b>%d</b> Organizationen, <b>%d</b> öffentliche Schlüssel, <b>%d</b> Repositorys, <b>%d</b> watches, <b>%d</b> stars, <b>%d</b> actions, <b>%d</b> Zugriffe, <b>%d</b> issues, <b>%d</b> Kommentare, <b>%d</b> soziale Konten, <b>%d</b> follows, <b>%d</b> Spiegel, <b>%d</b> Releases, <b>%d</b> Login-Quellen, <b>%d</b> Webhooks, <b>%d</b> Milestones, <b>%d</b> Labels, <b>%d</b> Hook-Tasks, <b>%d</b> Teams, <b>%d</b> Aktualisierungs-Tasks, <b>%d</b> Anhänge.
 dashboard.operation_name = Operation Name
 dashboard.operation_switch = Switch
-dashboard.operation_run = Run
-dashboard.clean_unbind_oauth = Clean unbound OAuthes
-dashboard.delete_inactivate_accounts = Alle inaktiven Konten löschen
-dashboard.server_uptime = Server Uptime
+dashboard.operation_run = Ausführen
+dashboard.clean_unbind_oauth = ungebundene OAuthes bereinigen
+dashboard.delete_inactivate_accounts = inaktiven Konten löschen
+dashboard.server_uptime = Server-Uptime
 dashboard.current_goroutine = Aktuelle Goroutines
 dashboard.current_memory_usage = Aktuelle Speichernutzung
 dashboard.total_memory_allocated = Zugeteilter Gesamtspeicher
@@ -323,62 +329,61 @@ dashboard.memory_obtained = Erhaltener Speicher
 dashboard.pointer_lookup_times = Pointer Lookup Times
 dashboard.memory_allocate_times = Memory Allocate Times
 dashboard.memory_free_times = Memory Free Times
-dashboard.current_heap_usage = Current Heap Usage
-dashboard.heap_memory_obtained = Heap Memory Obtained
-dashboard.heap_memory_idle = Heap Memory Idle
-dashboard.heap_memory_in_use = Heap Memory In Use
-dashboard.heap_memory_released = Heap Memory Released
-dashboard.heap_objects = Heap Objects
-dashboard.bootstrap_stack_usage = Bootstrap Stack Usage
-dashboard.stack_memory_obtained = Stack Memory Obtained
-dashboard.mspan_structures_usage = MSpan Structures Usage
-dashboard.mspan_structures_obtained = MSpan Structures Obtained
-dashboard.mcache_structures_usage = MCache Structures Usage
-dashboard.mcache_structures_obtained = MCache Structures Obtained
+dashboard.current_heap_usage = Aktuelle Heap-Auslastung
+dashboard.heap_memory_obtained = erhaltener Heap-Memory
+dashboard.heap_memory_idle = unbenutzter Heap-Memory
+dashboard.heap_memory_in_use = benutzer Heap-Memory
+dashboard.heap_memory_released = freigegebener Heap-Memory
+dashboard.heap_objects = Heap-Objekte
+dashboard.bootstrap_stack_usage = Bootstrap-Stack-Auslastung
+dashboard.stack_memory_obtained = erhaltener Stack-Memory
+dashboard.mspan_structures_usage = MSpan-Structures-Auslastung
+dashboard.mspan_structures_obtained = erhaltene MSpan-Structures
+dashboard.mcache_structures_usage = MCache-Structures-Auslastung
+dashboard.mcache_structures_obtained = erhaltene MCache-Structures
 dashboard.profiling_bucket_hash_table_obtained = Profiling Bucket Hash Table Obtained
-dashboard.gc_metadata_obtained = GC Metadada Obtained
-dashboard.other_system_allocation_obtained = Other System Allocation Obtained
-dashboard.next_gc_recycle = Next GC Recycle
-dashboard.last_gc_time = Since Last GC Time
-dashboard.total_gc_time = Total GC Pause
-dashboard.total_gc_pause = Total GC Pause
-dashboard.last_gc_pause = Last GC Pause
-dashboard.gc_times = GC Takt
-
-users.user_manage_panel = User Manage Panel
+dashboard.gc_metadata_obtained = erhaltene GC-Metadata
+dashboard.other_system_allocation_obtained = andere erhaltene Sustem-Allokatoren
+dashboard.next_gc_recycle = nächster GC-Zyklus
+dashboard.last_gc_time = seit leztem GC-Zyklus
+dashboard.total_gc_time = gesammte GC-Zeit
+dashboard.total_gc_pause = gesammte GC-Pause
+dashboard.last_gc_pause = letzte GC-Pause
+dashboard.gc_times = GC-Takt
+
+users.user_manage_panel = Benutzerverwaltung
 users.new_account = Neues Konto erstellen
 users.name = Name
-users.email = E-mail
 users.activated = Aktiviert
 users.admin = Admin
 users.repos = Repos
 users.created = Erzeugt
 users.edit = Bearbeiten
-users.auth_source = Auth Source
+users.auth_source = Auth-Quelle
 users.local = Lokal
-users.auth_login_name = Auth Login Name
+users.auth_login_name = Auth-Login-Name
 users.update_profile_success = Kontoprofil wurde erfolgreich aktualisiert.
 users.edit_account = Konto bearbeiten
 users.is_activated = Dieses Konto ist aktiviert
 users.is_admin = Dieses Konto hat Administratorrechte
-users.update_profile = Kontopprofil aktualisieren
+users.update_profile = Kontoprofil aktualisieren
 users.delete_account = Dieses Konto löschen
-users.still_own_repo = Dieses Konto ist noch Eigentümer von Repositories, Sie müssen zuerst diese löschen oder übertragen.
+users.still_own_repo = Dieses Konto besitzt noch Repositorys. Diese müssen zuerst gelöscht oder übertragen werden.
 
-orgs.org_manage_panel = Organization Manage Panel
+orgs.org_manage_panel = Organisationenverwaltung
 orgs.name = Name
 orgs.teams = Teams
 orgs.members = Mitglieder
 
-repos.repo_manage_panel = Repository Manage Panel
-repos.owner = Eigentümer
+repos.repo_manage_panel = Repositoryverwaltung
+repos.owner = Besitzer
 repos.name = Name
 repos.private = Privat
 repos.watches = Watches
 repos.stars = Stars
-repos.issues = Themen
+repos.issues = Issues
 
-auths.auth_manage_panel = Authorization Manage Panel
+auths.auth_manage_panel = Authentifizierungsverwaltung
 auths.new = Neu Authentifizierungsquelle hinzufügen
 auths.name = Name
 auths.type = Typ
@@ -403,20 +408,20 @@ auths.edit = Authentifizierungseinstellungen bearbeiten
 auths.activated = Diese Authentifizierung ist aktiviert
 auths.update_success = Authentifizierungseinstellungen wurde erfolgreich aktualisiert.
 auths.update = Authentifizierungseinstellungen aktualisieren
-auths.delete = Diese Authentifizierung löschen
+auths.delete = Authentifizierung löschen
 
 config.server_config = Server-Konfiguration
 config.app_name = Anwendungsname
 config.app_ver = Anwendungsversion
-config.app_url = Anwendungs URL
+config.app_url = Anwendungs-URL
 config.domain = Domain
-config.offline_mode = Offline Mode
-config.disable_router_log = Router Log deaktivieren
-config.run_user = Run User
-config.run_mode = Run Mode
-config.repo_root_path = Repositorie Wurzelverzeichnis
-config.static_file_root_path = Wurzelverzeichnis für statische Dateien
-config.log_file_root_path = Wurzelverzeichnis für Log Dateien
+config.offline_mode = Offline-Modus
+config.disable_router_log = Router-Log deaktivieren
+config.run_user = Laufzeit-Benutzer
+config.run_mode = Laufzeit-Modus
+config.repo_root_path = Repository-Verzeichnis
+config.static_file_root_path = Verzeichnis für statische Dateien
+config.log_file_root_path = Log-Verzeichnis
 config.script_type = Skript-Typ
 config.reverse_auth_user = Reverse Authentication User
 config.db_config = Datenbankkonfiguration
@@ -424,7 +429,7 @@ config.db_type = Typ
 config.db_host = Host
 config.db_name = Name
 config.db_user = Benutzer
-config.db_ssl_mode = SSL Modus
+config.db_ssl_mode = SSL-Modus
 config.db_ssl_mode_helper = (nur für "postgres")
 config.db_path = Verzeichnis
 config.db_path_helper = (nur für "sqlite3")
@@ -433,13 +438,13 @@ config.register_email_confirm = E-Mail Bestätigung bei Registrierung
 config.disable_register = Registrierung deaktivieren
 config.require_sign_in_view = Require Sign In View
 config.mail_notify = E-Mail Benachrichtigung
-config.enable_cache_avatar = Avatar Cache avtivieren
+config.enable_cache_avatar = Avatar-Cache aktivieren
 config.active_code_lives = Active Code Lives
 config.reset_password_code_lives = Reset Password Code Lives
 config.webhook_config = Webhook Einstellungen
-config.task_interval = Task Intervall
+config.task_interval = Task-Intervall
 config.deliver_timeout = Zeitlimit für Zustellung
-config.mailer_config = Mailer Einstellungen
+config.mailer_config = Mailer-Einstellungen
 config.mailer_enabled = Aktiviert
 config.mailer_name = Name
 config.mailer_host = Host
@@ -455,23 +460,23 @@ config.session_provider = Session Provider
 config.provider_config = Provider Einstellungen
 config.cookie_name = Cookie Name
 config.enable_set_cookie = Enable Set Cookie
-config.gc_interval_time = GC Interval Time
+config.gc_interval_time = GC-Intervallzeit
 config.session_life_time = Session Lebensdauer
 config.https_only = nur HTTPS
 config.cookie_life_time = Cookie Lebensdauer
-config.session_hash_function = Session ID Hash Function
-config.session_hash_key = Session ID Hash Key
-config.picture_config = Bildereinstellungen
-config.picture_service = Bilderservice
+config.session_hash_function = Session-ID Hashfunktion
+config.session_hash_key = Session-ID Hashschlüssel
+config.picture_config = Bildeinstellungen
+config.picture_service = Bildservice
 config.disable_gravatar = Gravatar deaktivieren
 config.log_config = Log Einstellungen
 config.log_mode = Log Modus
 
-monitor.cron = Cron Tasks
+monitor.cron = Cron-Tasks
 monitor.name = Name
 monitor.schedule = Zeitplan
-monitor.next = Next Time
-monitor.previous = Previous Time
+monitor.next = nächste Ausführung
+monitor.previous = letzte Ausführung
 monitor.execute_times = Execute Times
 monitor.process = Laufende Prozesse
 monitor.desc = Beschreibung
@@ -479,7 +484,7 @@ monitor.start = Startzeit
 monitor.execute_time = Ausführungszeit
 
 [action]
-create_repo = Repositorie erstellen <a href="/%s">%s</a>
+create_repo = Repository erstellen <a href="/%s">%s</a>
 commit_repo = pushed to <a href="/%s/src/%s">%s</a> at <a href="/%s">%s</a>
 create_issue = opened issue <a href="/%s/issues/%s">%s#%s</a>
 comment_issue = commented on issue <a href="/%s/issues/%s">%s#%s</a>

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

@@ -234,6 +234,11 @@ settings.update_webhook = Update Webhook
 settings.update_hook_success = Webhook has been updated.
 settings.delete_webhook = Delete Webhook
 settings.recent_deliveries = Recent Deliveries
+settings.hook_type = Hook Type
+settings.add_slack_hook_desc = Add <a href="http://slack.com">Slack</a> integration to your repository.
+settings.slack_token = Token
+settings.slack_domain = Domain
+settings.slack_channel = Channel
 
 [org]
 org_name_holder = Organization Name

+ 27 - 8
models/action.go

@@ -266,14 +266,33 @@ func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
 			continue
 		}
 
-		p.Secret = w.Secret
-		CreateHookTask(&HookTask{
-			Type:        WEBHOOK,
-			Url:         w.Url,
-			Payload:     p,
-			ContentType: w.ContentType,
-			IsSsl:       w.IsSsl,
-		})
+		switch w.HookTaskType {
+		case SLACK:
+			{
+				s, err := GetSlackPayload(p, w.Meta)
+				if err != nil {
+					return errors.New("action.GetSlackPayload: " + err.Error())
+				}
+				CreateHookTask(&HookTask{
+					Type:        w.HookTaskType,
+					Url:         w.Url,
+					BasePayload: s,
+					ContentType: w.ContentType,
+					IsSsl:       w.IsSsl,
+				})
+			}
+		default:
+			{
+				p.Secret = w.Secret
+				CreateHookTask(&HookTask{
+					Type:        w.HookTaskType,
+					Url:         w.Url,
+					BasePayload: p,
+					ContentType: w.ContentType,
+					IsSsl:       w.IsSsl,
+				})
+			}
+		}
 	}
 	return nil
 }

+ 114 - 0
models/slack.go

@@ -0,0 +1,114 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package models
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"strings"
+)
+
+const (
+	SLACK_COLOR string = "#dd4b39"
+)
+
+type Slack struct {
+	Domain  string `json:"domain"`
+	Token   string `json:"token"`
+	Channel string `json:"channel"`
+}
+
+type SlackPayload struct {
+	Channel     string            `json:"channel"`
+	Text        string            `json:"text"`
+	Username    string            `json:"username"`
+	IconUrl     string            `json:"icon_url"`
+	UnfurlLinks int               `json:"unfurl_links"`
+	LinkNames   int               `json:"link_names"`
+	Attachments []SlackAttachment `json:"attachments"`
+}
+
+type SlackAttachment struct {
+	Color string `json:"color"`
+	Text  string `json:"text"`
+}
+
+func GetSlackURL(domain string, token string) string {
+	return fmt.Sprintf(
+		"https://%s.slack.com/services/hooks/incoming-webhook?token=%s",
+		domain,
+		token,
+	)
+}
+
+func (p SlackPayload) GetJSONPayload() ([]byte, error) {
+	data, err := json.Marshal(p)
+	if err != nil {
+		return []byte{}, err
+	}
+	return data, nil
+}
+
+func GetSlackPayload(p *Payload, meta string) (*SlackPayload, error) {
+	slack := &Slack{}
+	slackPayload := &SlackPayload{}
+	if err := json.Unmarshal([]byte(meta), &slack); err != nil {
+		return slackPayload, errors.New("GetSlackPayload meta json:" + err.Error())
+	}
+
+	// TODO: handle different payload types: push, new branch, delete branch etc.
+	// when they are added to gogs. Only handles push now
+	return getSlackPushPayload(p, slack)
+}
+
+func getSlackPushPayload(p *Payload, slack *Slack) (*SlackPayload, error) {
+	// n new commits
+	refSplit := strings.Split(p.Ref, "/")
+	branchName := refSplit[len(refSplit)-1]
+	var commitString string
+
+	// TODO: add commit compare before/after link when gogs adds it
+	if len(p.Commits) == 1 {
+		commitString = "1 new commit"
+	} else {
+		commitString = fmt.Sprintf("%d new commits", len(p.Commits))
+	}
+
+	text := fmt.Sprintf("[%s:%s] %s pushed by %s", p.Repo.Name, branchName, commitString, p.Pusher.Name)
+	var attachmentText string
+
+	// for each commit, generate attachment text
+	for i, commit := range p.Commits {
+		attachmentText += fmt.Sprintf("<%s|%s>: %s - %s", commit.Url, commit.Id[:7], SlackFormatter(commit.Message), commit.Author.Name)
+		// add linebreak to each commit but the last
+		if i < len(p.Commits)-1 {
+			attachmentText += "\n"
+		}
+	}
+
+	slackAttachments := []SlackAttachment{{Color: SLACK_COLOR, Text: attachmentText}}
+
+	return &SlackPayload{
+		Channel:     slack.Channel,
+		Text:        text,
+		Username:    "gogs",
+		IconUrl:     "https://raw.githubusercontent.com/gogits/gogs/master/public/img/favicon.png",
+		UnfurlLinks: 0,
+		LinkNames:   0,
+		Attachments: slackAttachments,
+	}, nil
+}
+
+// see: https://api.slack.com/docs/formatting
+func SlackFormatter(s string) string {
+	// take only first line of commit
+	first := strings.Split(s, "\n")[0]
+	// replace & < >
+	first = strings.Replace(first, "&", "&amp;", -1)
+	first = strings.Replace(first, "<", "&lt;", -1)
+	first = strings.Replace(first, ">", "&gt;", -1)
+	return first
+}

+ 66 - 20
models/webhook.go

@@ -7,6 +7,7 @@ package models
 import (
 	"encoding/json"
 	"errors"
+	"io/ioutil"
 	"time"
 
 	"github.com/gogits/gogs/modules/httplib"
@@ -33,15 +34,17 @@ type HookEvent struct {
 
 // Webhook represents a web hook object.
 type Webhook struct {
-	Id          int64
-	RepoId      int64
-	Url         string `xorm:"TEXT"`
-	ContentType HookContentType
-	Secret      string `xorm:"TEXT"`
-	Events      string `xorm:"TEXT"`
-	*HookEvent  `xorm:"-"`
-	IsSsl       bool
-	IsActive    bool
+	Id           int64
+	RepoId       int64
+	Url          string `xorm:"TEXT"`
+	ContentType  HookContentType
+	Secret       string `xorm:"TEXT"`
+	Events       string `xorm:"TEXT"`
+	*HookEvent   `xorm:"-"`
+	IsSsl        bool
+	IsActive     bool
+	HookTaskType HookTaskType
+	Meta         string `xorm:"TEXT"` // store hook-specific attributes
 }
 
 // GetEvent handles conversion from Events to HookEvent.
@@ -52,6 +55,14 @@ func (w *Webhook) GetEvent() {
 	}
 }
 
+func (w *Webhook) GetSlackHook() *Slack {
+	s := &Slack{}
+	if err := json.Unmarshal([]byte(w.Meta), s); err != nil {
+		log.Error(4, "webhook.GetSlackHook(%d): %v", w.Id, err)
+	}
+	return s
+}
+
 // UpdateEvent handles conversion from HookEvent to Events.
 func (w *Webhook) UpdateEvent() error {
 	data, err := json.Marshal(w.HookEvent)
@@ -119,8 +130,8 @@ func DeleteWebhook(hookId int64) error {
 type HookTaskType int
 
 const (
-	WEBHOOK HookTaskType = iota + 1
-	SERVICE
+	GOGS HookTaskType = iota + 1
+	SLACK
 )
 
 type HookEventType string
@@ -152,6 +163,10 @@ type PayloadRepo struct {
 	Private     bool           `json:"private"`
 }
 
+type BasePayload interface {
+	GetJSONPayload() ([]byte, error)
+}
+
 // Payload represents a payload information of hook.
 type Payload struct {
 	Secret  string           `json:"secret"`
@@ -161,25 +176,33 @@ type Payload struct {
 	Pusher  *PayloadAuthor   `json:"pusher"`
 }
 
+func (p Payload) GetJSONPayload() ([]byte, error) {
+	data, err := json.Marshal(p)
+	if err != nil {
+		return []byte{}, err
+	}
+	return data, nil
+}
+
 // HookTask represents a hook task.
 type HookTask struct {
 	Id             int64
 	Uuid           string
 	Type           HookTaskType
 	Url            string
-	*Payload       `xorm:"-"`
+	BasePayload    `xorm:"-"`
 	PayloadContent string `xorm:"TEXT"`
 	ContentType    HookContentType
 	EventType      HookEventType
 	IsSsl          bool
-	IsDeliveried   bool
+	IsDelivered    bool
 	IsSucceed      bool
 }
 
 // CreateHookTask creates a new hook task,
 // it handles conversion from Payload to PayloadContent.
 func CreateHookTask(t *HookTask) error {
-	data, err := json.Marshal(t.Payload)
+	data, err := t.BasePayload.GetJSONPayload()
 	if err != nil {
 		return err
 	}
@@ -198,7 +221,7 @@ func UpdateHookTask(t *HookTask) error {
 // DeliverHooks checks and delivers undelivered hooks.
 func DeliverHooks() {
 	timeout := time.Duration(setting.WebhookDeliverTimeout) * time.Second
-	x.Where("is_deliveried=?", false).Iterate(new(HookTask),
+	x.Where("is_delivered=?", false).Iterate(new(HookTask),
 		func(idx int, bean interface{}) error {
 			t := bean.(*HookTask)
 			req := httplib.Post(t.Url).SetTimeout(timeout, timeout).
@@ -212,13 +235,36 @@ func DeliverHooks() {
 				req.Param("payload", t.PayloadContent)
 			}
 
-			t.IsDeliveried = true
+			t.IsDelivered = true
 
 			// TODO: record response.
-			if _, err := req.Response(); err != nil {
-				log.Error(4, "Delivery: %v", err)
-			} else {
-				t.IsSucceed = true
+			switch t.Type {
+			case GOGS:
+				{
+					if _, err := req.Response(); err != nil {
+						log.Error(4, "Delivery: %v", err)
+					} else {
+						t.IsSucceed = true
+					}
+				}
+			case SLACK:
+				{
+					if res, err := req.Response(); err != nil {
+						log.Error(4, "Delivery: %v", err)
+					} else {
+						defer res.Body.Close()
+						contents, err := ioutil.ReadAll(res.Body)
+						if err != nil {
+							log.Error(4, "%s", err)
+						} else {
+							if string(contents) != "ok" {
+								log.Error(4, "slack failed with: %s", string(contents))
+							} else {
+								t.IsSucceed = true
+							}
+						}
+					}
+				}
 			}
 
 			if err := UpdateHookTask(t); err != nil {

+ 19 - 5
modules/auth/repo_form.go

@@ -69,17 +69,31 @@ func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs *binding.Errors, l
 //        \/       \/    \/     \/     \/            \/
 
 type NewWebhookForm struct {
-	PayloadUrl  string `form:"payload_url" binding:"Required;Url"`
-	ContentType string `form:"content_type" binding:"Required"`
-	Secret      string `form:"secret"`
-	PushOnly    bool   `form:"push_only"`
-	Active      bool   `form:"active"`
+	HookTaskType string `form:"hook_type" binding:"Required"`
+	PayloadUrl   string `form:"payload_url" binding:"Required;Url"`
+	ContentType  string `form:"content_type" binding:"Required"`
+	Secret       string `form:"secret"`
+	PushOnly     bool   `form:"push_only"`
+	Active       bool   `form:"active"`
 }
 
 func (f *NewWebhookForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
 	validate(errs, ctx.Data, f, l)
 }
 
+type NewSlackHookForm struct {
+	HookTaskType string `form:"hook_type" binding:"Required"`
+	Domain       string `form:"domain" binding:"Required`
+	Token        string `form:"token" binding:"Required"`
+	Channel      string `form:"channel" binding:"Required"`
+	PushOnly     bool   `form:"push_only"`
+	Active       bool   `form:"active"`
+}
+
+func (f *NewSlackHookForm) Validate(ctx *macaron.Context, errs *binding.Errors, l i18n.Locale) {
+	validate(errs, ctx.Data, f, l)
+}
+
 // .___
 // |   | ______ ________ __   ____
 // |   |/  ___//  ___/  |  \_/ __ \

+ 21 - 5
public/ng/css/gogs.css

@@ -1404,14 +1404,16 @@ The register and sign-in page style
 #auth-setting-form,
 #org-setting-form,
 #repo-setting-form,
-#user-profile-form {
+#user-profile-form,
+.repo-setting-form {
   background-color: #FFF;
   padding: 30px 0;
 }
 #auth-setting-form textarea,
 #org-setting-form textarea,
 #repo-setting-form textarea,
-#user-profile-form textarea {
+#user-profile-form textarea,
+.repo-setting-form textarea {
   margin-left: 4px;
   height: 100px;
 }
@@ -1419,24 +1421,38 @@ The register and sign-in page style
 #org-setting-form label,
 #repo-setting-form label,
 #user-profile-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 {
+#user-profile-form .form-label,
+.repo-setting-form .form-label {
   width: 240px;
 }
 #auth-setting-form .ipt,
 #org-setting-form .ipt,
 #repo-setting-form .ipt,
-#user-profile-form .ipt {
+#user-profile-form .ipt,
+.repo-setting-form .ipt {
   width: 360px;
 }
 #auth-setting-form .field,
 #org-setting-form .field,
 #repo-setting-form .field,
-#user-profile-form .field {
+#user-profile-form .field,
+.repo-setting-form .field {
   margin-bottom: 24px;
 }
+#hook-type {
+  padding: 10px 0 0 0;
+  background-color: #fff;
+}
+#hook-type .field {
+  margin-bottom: 24px;
+}
+#hook-type label {
+  width: 240px;
+}
 #repo-hooks-panel,
 #repo-hooks-history-panel,
 #user-social-panel,

+ 17 - 1
public/ng/js/gogs.js

@@ -359,6 +359,22 @@ function initRepoSetting() {
             return true;
         }
     });
+
+    // web hook type change
+    $('select#hook-type').on("change", function () {
+      hookTypes = ['Gogs','Slack'];
+
+      var curHook = $(this).val();
+      hookTypes.forEach(function(hookType) {
+        if (curHook === hookType) {
+          $('div#'+hookType.toLowerCase()).toggleShow();
+        }
+        else {
+          $('div#'+hookType.toLowerCase()).toggleHide();
+        }
+      });
+    });
+
     $('#transfer-button').click(function () {
         $('#transfer-form').show();
     });
@@ -594,4 +610,4 @@ function homepage() {
         }
         $('#promo-form').attr('action', '/user/sign_up');
     });
-}
+}

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

@@ -34,7 +34,8 @@
 #auth-setting-form,
 #org-setting-form,
 #repo-setting-form,
-#user-profile-form {
+#user-profile-form,
+.repo-setting-form {
     background-color: #FFF;
     padding: 30px 0;
     textarea {
@@ -53,6 +54,17 @@
     }
 }
 
+#hook-type {
+    padding: 10px 0 0 0;
+    background-color: #fff;
+    .field {
+        margin-bottom: 24px;
+    }
+    label {
+        width: 240px;
+    }
+}
+
 #repo-hooks-panel,
 #repo-hooks-history-panel,
 #user-social-panel,
@@ -109,4 +121,4 @@
   .field {
     margin-bottom: 24px;
   }
-}
+}

+ 125 - 1
routers/repo/setting.go

@@ -5,6 +5,7 @@
 package repo
 
 import (
+	"encoding/json"
 	"fmt"
 	"strings"
 	"time"
@@ -272,11 +273,17 @@ func Webhooks(ctx *middleware.Context) {
 	ctx.HTML(200, HOOKS)
 }
 
+func renderHookTypes(ctx *middleware.Context) {
+	ctx.Data["HookTypes"] = []string{"Gogs", "Slack"}
+	ctx.Data["HookType"] = "Gogs"
+}
+
 func WebHooksNew(ctx *middleware.Context) {
 	ctx.Data["Title"] = ctx.Tr("repo.settings")
 	ctx.Data["PageIsSettingsHooks"] = true
 	ctx.Data["PageIsSettingsHooksNew"] = true
 	ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}}
+	renderHookTypes(ctx)
 	ctx.HTML(200, HOOK_NEW)
 }
 
@@ -304,8 +311,11 @@ func WebHooksNewPost(ctx *middleware.Context, form auth.NewWebhookForm) {
 		HookEvent: &models.HookEvent{
 			PushOnly: form.PushOnly,
 		},
-		IsActive: form.Active,
+		IsActive:     form.Active,
+		HookTaskType: models.GOGS,
+		Meta:         "",
 	}
+
 	if err := w.UpdateEvent(); err != nil {
 		ctx.Handle(500, "UpdateEvent", err)
 		return
@@ -338,6 +348,19 @@ func WebHooksEdit(ctx *middleware.Context) {
 		}
 		return
 	}
+
+	// set data per HookTaskType
+	switch w.HookTaskType {
+	case models.SLACK:
+		{
+			ctx.Data["SlackHook"] = w.GetSlackHook()
+			ctx.Data["HookType"] = "slack"
+		}
+	default:
+		{
+			ctx.Data["HookType"] = "gogs"
+		}
+	}
 	w.GetEvent()
 	ctx.Data["Webhook"] = w
 	ctx.HTML(200, HOOK_NEW)
@@ -394,3 +417,104 @@ func WebHooksEditPost(ctx *middleware.Context, form auth.NewWebhookForm) {
 	ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
 	ctx.Redirect(fmt.Sprintf("%s/settings/hooks/%d", ctx.Repo.RepoLink, hookId))
 }
+
+func SlackHooksNewPost(ctx *middleware.Context, form auth.NewSlackHookForm) {
+	ctx.Data["Title"] = ctx.Tr("repo.settings")
+	ctx.Data["PageIsSettingsHooks"] = true
+	ctx.Data["PageIsSettingsHooksNew"] = true
+	ctx.Data["Webhook"] = models.Webhook{HookEvent: &models.HookEvent{}}
+
+	if ctx.HasError() {
+		ctx.HTML(200, HOOK_NEW)
+		return
+	}
+
+	meta, err := json.Marshal(&models.Slack{
+		Domain:  form.Domain,
+		Channel: form.Channel,
+		Token:   form.Token,
+	})
+	if err != nil {
+		ctx.Handle(500, "SlackHooksNewPost: JSON marshal failed: ", err)
+		return
+	}
+
+	w := &models.Webhook{
+		RepoId:      ctx.Repo.Repository.Id,
+		Url:         models.GetSlackURL(form.Domain, form.Token),
+		ContentType: models.JSON,
+		Secret:      "",
+		HookEvent: &models.HookEvent{
+			PushOnly: form.PushOnly,
+		},
+		IsActive:     form.Active,
+		HookTaskType: models.SLACK,
+		Meta:         string(meta),
+	}
+	if err := w.UpdateEvent(); err != nil {
+		ctx.Handle(500, "UpdateEvent", err)
+		return
+	} else if err := models.CreateWebhook(w); err != nil {
+		ctx.Handle(500, "CreateWebhook", err)
+		return
+	}
+
+	ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
+	ctx.Redirect(ctx.Repo.RepoLink + "/settings/hooks")
+}
+
+func SlackHooksEditPost(ctx *middleware.Context, form auth.NewSlackHookForm) {
+	ctx.Data["Title"] = ctx.Tr("repo.settings")
+	ctx.Data["PageIsSettingsHooks"] = true
+	ctx.Data["PageIsSettingsHooksEdit"] = true
+
+	hookId := com.StrTo(ctx.Params(":id")).MustInt64()
+	fmt.Println("hookId slack=%d", hookId)
+	if hookId == 0 {
+		ctx.Handle(404, "setting.WebHooksEditPost", nil)
+		return
+	}
+
+	w, err := models.GetWebhookById(hookId)
+	if err != nil {
+		if err == models.ErrWebhookNotExist {
+			ctx.Handle(404, "GetWebhookById", nil)
+		} else {
+			ctx.Handle(500, "GetWebhookById", err)
+		}
+		return
+	}
+	w.GetEvent()
+	ctx.Data["Webhook"] = w
+
+	if ctx.HasError() {
+		ctx.HTML(200, HOOK_NEW)
+		return
+	}
+	meta, err := json.Marshal(&models.Slack{
+		Domain:  form.Domain,
+		Channel: form.Channel,
+		Token:   form.Token,
+	})
+	if err != nil {
+		ctx.Handle(500, "SlackHooksNewPost: JSON marshal failed: ", err)
+		return
+	}
+
+	w.Url = models.GetSlackURL(form.Domain, form.Token)
+	w.Meta = string(meta)
+	w.HookEvent = &models.HookEvent{
+		PushOnly: form.PushOnly,
+	}
+	w.IsActive = form.Active
+	if err := w.UpdateEvent(); err != nil {
+		ctx.Handle(500, "UpdateEvent", err)
+		return
+	} else if err := models.UpdateWebhook(w); err != nil {
+		ctx.Handle(500, "SlackHooksEditPost", err)
+		return
+	}
+
+	ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
+	ctx.Redirect(fmt.Sprintf("%s/settings/hooks/%d", ctx.Repo.RepoLink, hookId))
+}

+ 23 - 0
templates/repo/settings/gogs_hook.tmpl

@@ -0,0 +1,23 @@
+<div id="gogs" class="{{if (and .PageIsSettingsHooksEdit (not (eq .HookType "gogs")))}}hidden{{end}}">
+  <form class="form form-align panel-body repo-setting-form" id="repo-setting-form-gogs"  action="{{.RepoLink}}/settings/hooks/gogs/{{if .PageIsSettingsHooksNew}}new{{else}}{{.Webhook.Id}}{{end}}" method="post">
+    {{.CsrfTokenHtml}}
+    <input type="hidden" name="hook_type" value="gogs">
+    <div class="text-center panel-desc">{{.i18n.Tr "repo.settings.add_webhook_desc" | Str2html}}</div>
+    <div class="field">
+        <label class="req" for="payload-url">{{.i18n.Tr "repo.settings.payload_url"}}</label>
+        <input class="ipt ipt-large ipt-radius {{if .Err_UserName}}ipt-error{{end}}" id="payload-url" name="payload_url" type="url" value="{{.Webhook.Url}}" required />
+    </div>
+    <div class="field">
+      <label class="req">{{.i18n.Tr "repo.settings.content_type"}}</label>
+      <select name="content_type">
+          <option value="1" {{if or .PageIsSettingsHooksNew (eq .Webhook.ContentType 1)}}selected{{end}}>application/json</option>
+          <option value="2" {{if eq .Webhook.ContentType 2}}selected{{end}}>application/x-www-form-urlencoded</option>
+      </select>
+    </div>
+    <div class="field">
+        <label for="secret">{{.i18n.Tr "repo.settings.secret"}}</label>
+        <input class="ipt ipt-large ipt-radius {{if .Err_UserName}}ipt-error{{end}}" id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off" />
+    </div>
+    {{template "repo/settings/hook_settings" .}}
+  </form>
+</div>

+ 4 - 35
templates/repo/settings/hook_new.tmpl

@@ -13,40 +13,9 @@
 	                        <div class="panel-header">
 	                        	<strong>{{if .PageIsSettingsHooksNew}}{{.i18n.Tr "repo.settings.add_webhook"}}{{else}}{{.i18n.Tr "repo.settings.update_webhook"}}{{end}}</strong>
 	                        </div>
-	                        <form class="form form-align panel-body" id="repo-setting-form" action="{{.RepoLink}}/settings/hooks/{{if .PageIsSettingsHooksNew}}new{{else}}{{.Webhook.Id}}{{end}}" method="post">
-	                            {{.CsrfTokenHtml}}
-                        		<div class="text-center panel-desc">{{.i18n.Tr "repo.settings.add_webhook_desc" | Str2html}}</div>
-	                            <div class="field">
-	                                <label class="req" for="payload-url">{{.i18n.Tr "repo.settings.payload_url"}}</label>
-	                                <input class="ipt ipt-large ipt-radius {{if .Err_UserName}}ipt-error{{end}}" id="payload-url" name="payload_url" type="url" value="{{.Webhook.Url}}" required />
-	                            </div>
-					            <div class="field">
-					                <label class="req">{{.i18n.Tr "repo.settings.content_type"}}</label>
-					                <select name="content_type">
-                                		<option value="1" {{if or .PageIsSettingsHooksNew (eq .Webhook.ContentType 1)}}selected{{end}}>application/json</option>
-                                		<option value="2" {{if eq .Webhook.ContentType 2}}selected{{end}}>application/x-www-form-urlencoded</option>
-					                </select>
-					            </div>
-	                            <div class="field">
-	                                <label for="secret">{{.i18n.Tr "repo.settings.secret"}}</label>
-	                                <input class="ipt ipt-large ipt-radius {{if .Err_UserName}}ipt-error{{end}}" id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off" />
-	                            </div>
-								<div class="field">
-									<h4 class="text-center">{{.i18n.Tr "repo.settings.event_desc"}}</h4>
-									<label></label>
-	                                <input name="push_only" type="radio" {{if or .PageIsSettingsHooksNew .Webhook.PushOnly}}checked{{end}}> {{.i18n.Tr "repo.settings.event_push_only" | Str2html}}
-	                            </div>
-					            <div class="field">
-					                <label for="active">{{.i18n.Tr "repo.settings.active"}}</label>
-					                <input class="ipt-chk" id="active" name="active" type="checkbox" {{if or .PageIsSettingsHooksNew .Webhook.IsActive}}checked{{end}} />
-					                <span>{{.i18n.Tr "repo.settings.active_helper"}}</span>
-					            </div>
-	                            <div class="field">
-	                                <label></label>
-	                                <button class="btn btn-green btn-large btn-radius">{{if .PageIsSettingsHooksNew}}{{.i18n.Tr "repo.settings.add_webhook"}}{{else}}{{.i18n.Tr "repo.settings.update_webhook"}}{{end}}</button>
-	                                {{if .PageIsSettingsHooksEdit}}<a class="btn btn-red btn-large btn-link btn-radius" href="{{.RepoLink}}/settings/hooks?remove={{.Webhook.Id}}"><strong>{{.i18n.Tr "repo.settings.delete_webhook"}}</strong></a>{{end}}
-	                            </div>
-							</form>
+                          {{template "repo/settings/hook_types" .}}
+                          {{template "repo/settings/gogs_hook" .}}
+                          {{template "repo/settings/slack_hook" .}}
 	                    </div>
 	                </div>
 	                {{if .PageIsSettingsHooksEdit}}
@@ -67,4 +36,4 @@
 		</div>
 	</div>
 </div>
-{{template "ng/base/footer" .}}
+{{template "ng/base/footer" .}}

+ 15 - 0
templates/repo/settings/hook_settings.tmpl

@@ -0,0 +1,15 @@
+<div class="field">
+  <h4 class="text-center">{{.i18n.Tr "repo.settings.event_desc"}}</h4>
+  <label></label>
+  <input name="push_only" type="radio" {{if or .PageIsSettingsHooksNew .Webhook.PushOnly}}checked{{end}}> {{.i18n.Tr "repo.settings.event_push_only" | Str2html}}
+</div>
+<div class="field">
+  <label for="active">{{.i18n.Tr "repo.settings.active"}}</label>
+  <input class="ipt-chk" id="active" name="active" type="checkbox" {{if or .PageIsSettingsHooksNew .Webhook.IsActive}}checked{{end}} />
+<span>{{.i18n.Tr "repo.settings.active_helper"}}</span>
+</div>
+<div class="field">
+    <label></label>
+    <button class="btn btn-green btn-large btn-radius">{{if .PageIsSettingsHooksNew}}{{.i18n.Tr "repo.settings.add_webhook"}}{{else}}{{.i18n.Tr "repo.settings.update_webhook"}}{{end}}</button>
+    {{if .PageIsSettingsHooksEdit}}<a class="btn btn-red btn-large btn-link btn-radius" href="{{.RepoLink}}/settings/hooks?remove={{.Webhook.Id}}"><strong>{{.i18n.Tr "repo.settings.delete_webhook"}}</strong></a>{{end}}
+</div>

+ 11 - 0
templates/repo/settings/hook_types.tmpl

@@ -0,0 +1,11 @@
+{{if .PageIsSettingsHooksNew}}
+<div id="hook-type" class="form-align">
+  <label class="req">{{.i18n.Tr "repo.settings.hook_type"}}</label>
+  <select name="hook_type" id="hook-type" class="form-control">
+    {{if .HookType}}<option value="{{.HookType}}">{{.HookType}}</option>{{end}}
+    {{range .HookTypes}}
+    {{if not (eq $.HookType .)}}<option value="{{.}}" >{{.}}</option>{{end}}
+    {{end}}
+  </select>
+</div>
+{{end}}

+ 20 - 0
templates/repo/settings/slack_hook.tmpl

@@ -0,0 +1,20 @@
+<div id="slack" class="{{if or .PageIsSettingsHooksNew (and .PageIsSettingsHooksEdit (not (eq .HookType "slack")))}}hidden{{end}}">
+  <form class="form form-align panel-body repo-setting-form" id="repo-setting-form-slack" action="{{.RepoLink}}/settings/hooks/slack/{{if .PageIsSettingsHooksNew}}new{{else}}{{.Webhook.Id}}{{end}}" method="post">
+    {{.CsrfTokenHtml}}
+    <input type="hidden" name="hook_type" value="slack">
+    <div class="text-center panel-desc">{{.i18n.Tr "repo.settings.add_slack_hook_desc" | Str2html}}</div>
+    <div class="field">
+        <label class="req" for="domain">{{.i18n.Tr "repo.settings.slack_domain"}}</label>
+        <input class="ipt ipt-large ipt-radius {{if .Err_UserName}}ipt-error{{end}}" id="domain" name="domain" type="text" value="{{.SlackHook.Domain}}" placeholde="myslack" required />
+    </div>
+    <div class="field">
+        <label class="req" for="token">{{.i18n.Tr "repo.settings.slack_token"}}</label>
+        <input class="ipt ipt-large ipt-radius {{if .Err_UserName}}ipt-error{{end}}" id="token" name="token" type="text" value="{{.SlackHook.Token}}" autocomplete="off" required />
+    </div>
+    <div class="field">
+        <label class="req" for="channel">{{.i18n.Tr "repo.settings.slack_channel"}}</label>
+        <input class="ipt ipt-large ipt-radius {{if .Err_UserName}}ipt-error{{end}}" id="channel" name="channel" type="text" value="{{.SlackHook.Channel}}" placeholder="#general" required />
+    </div>
+    {{template "repo/settings/hook_settings" .}}
+  </form>
+</div>

+ 2 - 1
templates/user/dashboard/dashboard.tmpl

@@ -58,6 +58,7 @@
                     </button>
                     <ul class="menu menu-vertical drop-down" id="dashboard-new-repo-menu">
                         <li><a href="/repo/create"><i class="octicon octicon-repo-create"></i>{{.i18n.Tr "new_repo"}}</a></li>
+                        <li><a href="/repo/migrate"><i class="octicon octicon-repo-clone"></i>{{.i18n.Tr "new_migrate"}}</a></li>
                         <li><a href="/org/create"><i class="octicon octicon-organization"></i>{{.i18n.Tr "new_org"}}</a></li>
                     </ul>
                 </li>
@@ -165,4 +166,4 @@
         </div>
     </div>
 </div>
-{{template "ng/base/footer" .}}
+{{template "ng/base/footer" .}}