##// END OF EJS Templates
integrations: add email integration, fixes #4159
dan -
r640:6c88399f default
parent child Browse files
Show More
@@ -0,0 +1,127 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 from __future__ import unicode_literals
22
23 import deform
24 import logging
25 import colander
26
27 from mako.template import Template
28
29 from rhodecode import events
30 from rhodecode.translation import _, lazy_ugettext
31 from rhodecode.lib.celerylib import run_task
32 from rhodecode.lib.celerylib import tasks
33 from rhodecode.integrations.types.base import IntegrationTypeBase
34 from rhodecode.integrations.schema import IntegrationSettingsSchemaBase
35
36
37 log = logging.getLogger()
38
39
40
41 class EmailSettingsSchema(IntegrationSettingsSchemaBase):
42 @colander.instantiate(validator=colander.Length(min=1))
43 class recipients(colander.SequenceSchema):
44 title = lazy_ugettext('Recipients')
45 description = lazy_ugettext('Email addresses to send push events to')
46 widget = deform.widget.SequenceWidget(min_len=1)
47
48 recipient = colander.SchemaNode(
49 colander.String(),
50 title=lazy_ugettext('Email address'),
51 description=lazy_ugettext('Email address'),
52 default='',
53 validator=colander.Email(),
54 widget=deform.widget.TextInputWidget(
55 placeholder='user@domain.com',
56 ),
57 )
58
59
60 class EmailIntegrationType(IntegrationTypeBase):
61 key = 'email'
62 display_name = lazy_ugettext('Email')
63 SettingsSchema = EmailSettingsSchema
64
65 def settings_schema(self):
66 schema = EmailSettingsSchema()
67 return schema
68
69 def send_event(self, event):
70 data = event.as_dict()
71 log.debug('got event: %r', event)
72
73 if isinstance(event, events.RepoPushEvent):
74 repo_push_handler(data, self.settings)
75 else:
76 log.debug('ignoring event: %r', event)
77
78
79 def repo_push_handler(data, settings):
80 for commit in data['push']['commits']:
81 email_body_plaintext = repo_push_template_plaintext.render(
82 data=data,
83 commit=commit,
84 commit_msg=commit['message'],
85 )
86 email_body_html = repo_push_template_html.render(
87 data=data,
88 commit=commit,
89 commit_msg=commit['message_html'],
90 )
91
92 subject = '[%(repo_name)s] %(commit_id)s: %(commit_msg)s' % {
93 'repo_name': data['repo']['repo_name'],
94 'commit_id': commit['short_id'],
95 'commit_msg': commit['message'].split('\n')[0][:150]
96 }
97 for email_address in settings['recipients']:
98 task = run_task(
99 tasks.send_email, email_address, subject,
100 email_body_plaintext, email_body_html)
101
102
103 # TODO: dan: add changed files, make html pretty
104 repo_push_template_plaintext = Template('''
105 User: ${data['actor']['username']}
106 Branches: ${', '.join(branch['name'] for branch in data['push']['branches'])}
107 Repository: ${data['repo']['url']}
108 Commit: ${commit['raw_id']}
109 URL: ${commit['url']}
110 Author: ${commit['author']}
111 Date: ${commit['date']}
112 Commit Message:
113
114 ${commit_msg}
115 ''')
116
117 repo_push_template_html = Template('''
118 User: ${data['actor']['username']}<br>
119 Branches: ${', '.join(branch['name'] for branch in data['push']['branches'])}<br>
120 Repository: ${data['repo']['url']}<br>
121 Commit: ${commit['raw_id']}<br>
122 URL: ${commit['url']}<br>
123 Author: ${commit['author']}<br>
124 Date: ${commit['date']}<br>
125 Commit Message:<br>
126 <p>${commit_msg}</p>
127 ''')
@@ -1,60 +1,62 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from rhodecode.integrations.registry import IntegrationTypeRegistry
23 from rhodecode.integrations.registry import IntegrationTypeRegistry
24 from rhodecode.integrations.types import webhook, slack, hipchat
24 from rhodecode.integrations.types import webhook, slack, hipchat, email
25
25
26 log = logging.getLogger(__name__)
26 log = logging.getLogger(__name__)
27
27
28
28
29 # TODO: dan: This is currently global until we figure out what to do about
29 # TODO: dan: This is currently global until we figure out what to do about
30 # VCS's not having a pyramid context - move it to pyramid app configuration
30 # VCS's not having a pyramid context - move it to pyramid app configuration
31 # includeme level later to allow per instance integration setup
31 # includeme level later to allow per instance integration setup
32 integration_type_registry = IntegrationTypeRegistry()
32 integration_type_registry = IntegrationTypeRegistry()
33
33
34 integration_type_registry.register_integration_type(
34 integration_type_registry.register_integration_type(
35 webhook.WebhookIntegrationType)
35 webhook.WebhookIntegrationType)
36 integration_type_registry.register_integration_type(
36 integration_type_registry.register_integration_type(
37 slack.SlackIntegrationType)
37 slack.SlackIntegrationType)
38 integration_type_registry.register_integration_type(
38 integration_type_registry.register_integration_type(
39 hipchat.HipchatIntegrationType)
39 hipchat.HipchatIntegrationType)
40 integration_type_registry.register_integration_type(
41 email.EmailIntegrationType)
40
42
41
43
42 def integrations_event_handler(event):
44 def integrations_event_handler(event):
43 """
45 """
44 Takes an event and passes it to all enabled integrations
46 Takes an event and passes it to all enabled integrations
45 """
47 """
46 from rhodecode.model.integration import IntegrationModel
48 from rhodecode.model.integration import IntegrationModel
47
49
48 integration_model = IntegrationModel()
50 integration_model = IntegrationModel()
49 integrations = integration_model.get_for_event(event)
51 integrations = integration_model.get_for_event(event)
50 for integration in integrations:
52 for integration in integrations:
51 try:
53 try:
52 integration_model.send_event(integration, event)
54 integration_model.send_event(integration, event)
53 except Exception:
55 except Exception:
54 log.exception(
56 log.exception(
55 'failure occured when sending event %s to integration %s' % (
57 'failure occured when sending event %s to integration %s' % (
56 event, integration))
58 event, integration))
57
59
58
60
59 def includeme(config):
61 def includeme(config):
60 config.include('rhodecode.integrations.routes')
62 config.include('rhodecode.integrations.routes')
@@ -1,284 +1,284 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 RhodeCode task modules, containing all task that suppose to be run
22 RhodeCode task modules, containing all task that suppose to be run
23 by celery daemon
23 by celery daemon
24 """
24 """
25
25
26
26
27 import os
27 import os
28 import logging
28 import logging
29
29
30 from celery.task import task
30 from celery.task import task
31 from pylons import config
31 from pylons import config
32
32
33 import rhodecode
33 import rhodecode
34 from rhodecode.lib.celerylib import (
34 from rhodecode.lib.celerylib import (
35 run_task, dbsession, __get_lockkey, LockHeld, DaemonLock,
35 run_task, dbsession, __get_lockkey, LockHeld, DaemonLock,
36 get_session, vcsconnection, RhodecodeCeleryTask)
36 get_session, vcsconnection, RhodecodeCeleryTask)
37 from rhodecode.lib.hooks_base import log_create_repository
37 from rhodecode.lib.hooks_base import log_create_repository
38 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
38 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
39 from rhodecode.lib.utils import add_cache, action_logger
39 from rhodecode.lib.utils import add_cache, action_logger
40 from rhodecode.lib.utils2 import safe_int, str2bool
40 from rhodecode.lib.utils2 import safe_int, str2bool
41 from rhodecode.model.db import Repository, User
41 from rhodecode.model.db import Repository, User
42
42
43
43
44 add_cache(config) # pragma: no cover
44 add_cache(config) # pragma: no cover
45
45
46
46
47 def get_logger(cls):
47 def get_logger(cls):
48 if rhodecode.CELERY_ENABLED:
48 if rhodecode.CELERY_ENABLED:
49 try:
49 try:
50 log = cls.get_logger()
50 log = cls.get_logger()
51 except Exception:
51 except Exception:
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53 else:
53 else:
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56 return log
56 return log
57
57
58
58
59 @task(ignore_result=True, base=RhodecodeCeleryTask)
59 @task(ignore_result=True, base=RhodecodeCeleryTask)
60 @dbsession
60 @dbsession
61 def send_email(recipients, subject, body='', html_body='', email_config=None):
61 def send_email(recipients, subject, body='', html_body='', email_config=None):
62 """
62 """
63 Sends an email with defined parameters from the .ini files.
63 Sends an email with defined parameters from the .ini files.
64
64
65 :param recipients: list of recipients, it this is empty the defined email
65 :param recipients: list of recipients, it this is empty the defined email
66 address from field 'email_to' is used instead
66 address from field 'email_to' is used instead
67 :param subject: subject of the mail
67 :param subject: subject of the mail
68 :param body: body of the mail
68 :param body: body of the mail
69 :param html_body: html version of body
69 :param html_body: html version of body
70 """
70 """
71 log = get_logger(send_email)
71 log = get_logger(send_email)
72
72
73 email_config = email_config or config
73 email_config = email_config or rhodecode.CONFIG
74 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
74 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
75 if not recipients:
75 if not recipients:
76 # if recipients are not defined we send to email_config + all admins
76 # if recipients are not defined we send to email_config + all admins
77 admins = [
77 admins = [
78 u.email for u in User.query().filter(User.admin == True).all()]
78 u.email for u in User.query().filter(User.admin == True).all()]
79 recipients = [email_config.get('email_to')] + admins
79 recipients = [email_config.get('email_to')] + admins
80
80
81 mail_server = email_config.get('smtp_server') or None
81 mail_server = email_config.get('smtp_server') or None
82 if mail_server is None:
82 if mail_server is None:
83 log.error("SMTP server information missing. Sending email failed. "
83 log.error("SMTP server information missing. Sending email failed. "
84 "Make sure that `smtp_server` variable is configured "
84 "Make sure that `smtp_server` variable is configured "
85 "inside the .ini file")
85 "inside the .ini file")
86 return False
86 return False
87
87
88 mail_from = email_config.get('app_email_from', 'RhodeCode')
88 mail_from = email_config.get('app_email_from', 'RhodeCode')
89 user = email_config.get('smtp_username')
89 user = email_config.get('smtp_username')
90 passwd = email_config.get('smtp_password')
90 passwd = email_config.get('smtp_password')
91 mail_port = email_config.get('smtp_port')
91 mail_port = email_config.get('smtp_port')
92 tls = str2bool(email_config.get('smtp_use_tls'))
92 tls = str2bool(email_config.get('smtp_use_tls'))
93 ssl = str2bool(email_config.get('smtp_use_ssl'))
93 ssl = str2bool(email_config.get('smtp_use_ssl'))
94 debug = str2bool(email_config.get('debug'))
94 debug = str2bool(email_config.get('debug'))
95 smtp_auth = email_config.get('smtp_auth')
95 smtp_auth = email_config.get('smtp_auth')
96
96
97 try:
97 try:
98 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
98 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
99 mail_port, ssl, tls, debug=debug)
99 mail_port, ssl, tls, debug=debug)
100 m.send(recipients, subject, body, html_body)
100 m.send(recipients, subject, body, html_body)
101 except Exception:
101 except Exception:
102 log.exception('Mail sending failed')
102 log.exception('Mail sending failed')
103 return False
103 return False
104 return True
104 return True
105
105
106
106
107 @task(ignore_result=True, base=RhodecodeCeleryTask)
107 @task(ignore_result=True, base=RhodecodeCeleryTask)
108 @dbsession
108 @dbsession
109 @vcsconnection
109 @vcsconnection
110 def create_repo(form_data, cur_user):
110 def create_repo(form_data, cur_user):
111 from rhodecode.model.repo import RepoModel
111 from rhodecode.model.repo import RepoModel
112 from rhodecode.model.user import UserModel
112 from rhodecode.model.user import UserModel
113 from rhodecode.model.settings import SettingsModel
113 from rhodecode.model.settings import SettingsModel
114
114
115 log = get_logger(create_repo)
115 log = get_logger(create_repo)
116 DBS = get_session()
116 DBS = get_session()
117
117
118 cur_user = UserModel(DBS)._get_user(cur_user)
118 cur_user = UserModel(DBS)._get_user(cur_user)
119 owner = cur_user
119 owner = cur_user
120
120
121 repo_name = form_data['repo_name']
121 repo_name = form_data['repo_name']
122 repo_name_full = form_data['repo_name_full']
122 repo_name_full = form_data['repo_name_full']
123 repo_type = form_data['repo_type']
123 repo_type = form_data['repo_type']
124 description = form_data['repo_description']
124 description = form_data['repo_description']
125 private = form_data['repo_private']
125 private = form_data['repo_private']
126 clone_uri = form_data.get('clone_uri')
126 clone_uri = form_data.get('clone_uri')
127 repo_group = safe_int(form_data['repo_group'])
127 repo_group = safe_int(form_data['repo_group'])
128 landing_rev = form_data['repo_landing_rev']
128 landing_rev = form_data['repo_landing_rev']
129 copy_fork_permissions = form_data.get('copy_permissions')
129 copy_fork_permissions = form_data.get('copy_permissions')
130 copy_group_permissions = form_data.get('repo_copy_permissions')
130 copy_group_permissions = form_data.get('repo_copy_permissions')
131 fork_of = form_data.get('fork_parent_id')
131 fork_of = form_data.get('fork_parent_id')
132 state = form_data.get('repo_state', Repository.STATE_PENDING)
132 state = form_data.get('repo_state', Repository.STATE_PENDING)
133
133
134 # repo creation defaults, private and repo_type are filled in form
134 # repo creation defaults, private and repo_type are filled in form
135 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
135 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
136 enable_statistics = form_data.get(
136 enable_statistics = form_data.get(
137 'enable_statistics', defs.get('repo_enable_statistics'))
137 'enable_statistics', defs.get('repo_enable_statistics'))
138 enable_locking = form_data.get(
138 enable_locking = form_data.get(
139 'enable_locking', defs.get('repo_enable_locking'))
139 'enable_locking', defs.get('repo_enable_locking'))
140 enable_downloads = form_data.get(
140 enable_downloads = form_data.get(
141 'enable_downloads', defs.get('repo_enable_downloads'))
141 'enable_downloads', defs.get('repo_enable_downloads'))
142
142
143 try:
143 try:
144 RepoModel(DBS)._create_repo(
144 RepoModel(DBS)._create_repo(
145 repo_name=repo_name_full,
145 repo_name=repo_name_full,
146 repo_type=repo_type,
146 repo_type=repo_type,
147 description=description,
147 description=description,
148 owner=owner,
148 owner=owner,
149 private=private,
149 private=private,
150 clone_uri=clone_uri,
150 clone_uri=clone_uri,
151 repo_group=repo_group,
151 repo_group=repo_group,
152 landing_rev=landing_rev,
152 landing_rev=landing_rev,
153 fork_of=fork_of,
153 fork_of=fork_of,
154 copy_fork_permissions=copy_fork_permissions,
154 copy_fork_permissions=copy_fork_permissions,
155 copy_group_permissions=copy_group_permissions,
155 copy_group_permissions=copy_group_permissions,
156 enable_statistics=enable_statistics,
156 enable_statistics=enable_statistics,
157 enable_locking=enable_locking,
157 enable_locking=enable_locking,
158 enable_downloads=enable_downloads,
158 enable_downloads=enable_downloads,
159 state=state
159 state=state
160 )
160 )
161
161
162 action_logger(cur_user, 'user_created_repo',
162 action_logger(cur_user, 'user_created_repo',
163 repo_name_full, '', DBS)
163 repo_name_full, '', DBS)
164 DBS.commit()
164 DBS.commit()
165
165
166 # now create this repo on Filesystem
166 # now create this repo on Filesystem
167 RepoModel(DBS)._create_filesystem_repo(
167 RepoModel(DBS)._create_filesystem_repo(
168 repo_name=repo_name,
168 repo_name=repo_name,
169 repo_type=repo_type,
169 repo_type=repo_type,
170 repo_group=RepoModel(DBS)._get_repo_group(repo_group),
170 repo_group=RepoModel(DBS)._get_repo_group(repo_group),
171 clone_uri=clone_uri,
171 clone_uri=clone_uri,
172 )
172 )
173 repo = Repository.get_by_repo_name(repo_name_full)
173 repo = Repository.get_by_repo_name(repo_name_full)
174 log_create_repository(created_by=owner.username, **repo.get_dict())
174 log_create_repository(created_by=owner.username, **repo.get_dict())
175
175
176 # update repo commit caches initially
176 # update repo commit caches initially
177 repo.update_commit_cache()
177 repo.update_commit_cache()
178
178
179 # set new created state
179 # set new created state
180 repo.set_state(Repository.STATE_CREATED)
180 repo.set_state(Repository.STATE_CREATED)
181 DBS.commit()
181 DBS.commit()
182 except Exception as e:
182 except Exception as e:
183 log.warning('Exception %s occurred when creating repository, '
183 log.warning('Exception %s occurred when creating repository, '
184 'doing cleanup...', e)
184 'doing cleanup...', e)
185 # rollback things manually !
185 # rollback things manually !
186 repo = Repository.get_by_repo_name(repo_name_full)
186 repo = Repository.get_by_repo_name(repo_name_full)
187 if repo:
187 if repo:
188 Repository.delete(repo.repo_id)
188 Repository.delete(repo.repo_id)
189 DBS.commit()
189 DBS.commit()
190 RepoModel(DBS)._delete_filesystem_repo(repo)
190 RepoModel(DBS)._delete_filesystem_repo(repo)
191 raise
191 raise
192
192
193 # it's an odd fix to make celery fail task when exception occurs
193 # it's an odd fix to make celery fail task when exception occurs
194 def on_failure(self, *args, **kwargs):
194 def on_failure(self, *args, **kwargs):
195 pass
195 pass
196
196
197 return True
197 return True
198
198
199
199
200 @task(ignore_result=True, base=RhodecodeCeleryTask)
200 @task(ignore_result=True, base=RhodecodeCeleryTask)
201 @dbsession
201 @dbsession
202 @vcsconnection
202 @vcsconnection
203 def create_repo_fork(form_data, cur_user):
203 def create_repo_fork(form_data, cur_user):
204 """
204 """
205 Creates a fork of repository using internal VCS methods
205 Creates a fork of repository using internal VCS methods
206
206
207 :param form_data:
207 :param form_data:
208 :param cur_user:
208 :param cur_user:
209 """
209 """
210 from rhodecode.model.repo import RepoModel
210 from rhodecode.model.repo import RepoModel
211 from rhodecode.model.user import UserModel
211 from rhodecode.model.user import UserModel
212
212
213 log = get_logger(create_repo_fork)
213 log = get_logger(create_repo_fork)
214 DBS = get_session()
214 DBS = get_session()
215
215
216 cur_user = UserModel(DBS)._get_user(cur_user)
216 cur_user = UserModel(DBS)._get_user(cur_user)
217 owner = cur_user
217 owner = cur_user
218
218
219 repo_name = form_data['repo_name'] # fork in this case
219 repo_name = form_data['repo_name'] # fork in this case
220 repo_name_full = form_data['repo_name_full']
220 repo_name_full = form_data['repo_name_full']
221 repo_type = form_data['repo_type']
221 repo_type = form_data['repo_type']
222 description = form_data['description']
222 description = form_data['description']
223 private = form_data['private']
223 private = form_data['private']
224 clone_uri = form_data.get('clone_uri')
224 clone_uri = form_data.get('clone_uri')
225 repo_group = safe_int(form_data['repo_group'])
225 repo_group = safe_int(form_data['repo_group'])
226 landing_rev = form_data['landing_rev']
226 landing_rev = form_data['landing_rev']
227 copy_fork_permissions = form_data.get('copy_permissions')
227 copy_fork_permissions = form_data.get('copy_permissions')
228 fork_id = safe_int(form_data.get('fork_parent_id'))
228 fork_id = safe_int(form_data.get('fork_parent_id'))
229
229
230 try:
230 try:
231 fork_of = RepoModel(DBS)._get_repo(fork_id)
231 fork_of = RepoModel(DBS)._get_repo(fork_id)
232 RepoModel(DBS)._create_repo(
232 RepoModel(DBS)._create_repo(
233 repo_name=repo_name_full,
233 repo_name=repo_name_full,
234 repo_type=repo_type,
234 repo_type=repo_type,
235 description=description,
235 description=description,
236 owner=owner,
236 owner=owner,
237 private=private,
237 private=private,
238 clone_uri=clone_uri,
238 clone_uri=clone_uri,
239 repo_group=repo_group,
239 repo_group=repo_group,
240 landing_rev=landing_rev,
240 landing_rev=landing_rev,
241 fork_of=fork_of,
241 fork_of=fork_of,
242 copy_fork_permissions=copy_fork_permissions
242 copy_fork_permissions=copy_fork_permissions
243 )
243 )
244 action_logger(cur_user, 'user_forked_repo:%s' % repo_name_full,
244 action_logger(cur_user, 'user_forked_repo:%s' % repo_name_full,
245 fork_of.repo_name, '', DBS)
245 fork_of.repo_name, '', DBS)
246 DBS.commit()
246 DBS.commit()
247
247
248 base_path = Repository.base_path()
248 base_path = Repository.base_path()
249 source_repo_path = os.path.join(base_path, fork_of.repo_name)
249 source_repo_path = os.path.join(base_path, fork_of.repo_name)
250
250
251 # now create this repo on Filesystem
251 # now create this repo on Filesystem
252 RepoModel(DBS)._create_filesystem_repo(
252 RepoModel(DBS)._create_filesystem_repo(
253 repo_name=repo_name,
253 repo_name=repo_name,
254 repo_type=repo_type,
254 repo_type=repo_type,
255 repo_group=RepoModel(DBS)._get_repo_group(repo_group),
255 repo_group=RepoModel(DBS)._get_repo_group(repo_group),
256 clone_uri=source_repo_path,
256 clone_uri=source_repo_path,
257 )
257 )
258 repo = Repository.get_by_repo_name(repo_name_full)
258 repo = Repository.get_by_repo_name(repo_name_full)
259 log_create_repository(created_by=owner.username, **repo.get_dict())
259 log_create_repository(created_by=owner.username, **repo.get_dict())
260
260
261 # update repo commit caches initially
261 # update repo commit caches initially
262 config = repo._config
262 config = repo._config
263 config.set('extensions', 'largefiles', '')
263 config.set('extensions', 'largefiles', '')
264 repo.update_commit_cache(config=config)
264 repo.update_commit_cache(config=config)
265
265
266 # set new created state
266 # set new created state
267 repo.set_state(Repository.STATE_CREATED)
267 repo.set_state(Repository.STATE_CREATED)
268 DBS.commit()
268 DBS.commit()
269 except Exception as e:
269 except Exception as e:
270 log.warning('Exception %s occurred when forking repository, '
270 log.warning('Exception %s occurred when forking repository, '
271 'doing cleanup...', e)
271 'doing cleanup...', e)
272 # rollback things manually !
272 # rollback things manually !
273 repo = Repository.get_by_repo_name(repo_name_full)
273 repo = Repository.get_by_repo_name(repo_name_full)
274 if repo:
274 if repo:
275 Repository.delete(repo.repo_id)
275 Repository.delete(repo.repo_id)
276 DBS.commit()
276 DBS.commit()
277 RepoModel(DBS)._delete_filesystem_repo(repo)
277 RepoModel(DBS)._delete_filesystem_repo(repo)
278 raise
278 raise
279
279
280 # it's an odd fix to make celery fail task when exception occurs
280 # it's an odd fix to make celery fail task when exception occurs
281 def on_failure(self, *args, **kwargs):
281 def on_failure(self, *args, **kwargs):
282 pass
282 pass
283
283
284 return True
284 return True
@@ -1,35 +1,35 b''
1 <div tal:omit-tag="field.widget.hidden"
1 <div tal:omit-tag="field.widget.hidden"
2 tal:define="hidden hidden|field.widget.hidden;
2 tal:define="hidden hidden|field.widget.hidden;
3 error_class error_class|field.widget.error_class;
3 error_class error_class|field.widget.error_class;
4 description description|field.description;
4 description description|field.description;
5 title title|field.title;
5 title title|field.title;
6 oid oid|field.oid"
6 oid oid|field.oid"
7 class="form-group row deform-seq-item ${field.error and error_class or ''} ${field.widget.item_css_class or ''}"
7 class="form-group row deform-seq-item ${field.error and error_class or ''} ${field.widget.item_css_class or ''}"
8 i18n:domain="deform">
8 i18n:domain="deform">
9 <div class="deform-seq-item-group">
9 <div class="deform-seq-item-group">
10 <span tal:replace="structure field.serialize(cstruct)"/>
10 <span tal:replace="structure field.serialize(cstruct)"/>
11 <tal:errors condition="field.error and not hidden"
11 <tal:errors condition="field.error and not hidden"
12 define="errstr 'error-%s' % oid"
12 define="errstr 'error-%s' % oid"
13 repeat="msg field.error.messages()">
13 repeat="msg field.error.messages()">
14 <p tal:condition="msg"
14 <p tal:condition="msg"
15 id="${errstr if repeat.msg.index==0 else '%s-%s' % (errstr, repeat.msg.index)}"
15 id="${errstr if repeat.msg.index==0 else '%s-%s' % (errstr, repeat.msg.index)}"
16 class="${error_class} help-block"
16 class="${error_class} help-block error-block"
17 i18n:translate="">${msg}</p>
17 i18n:translate="">${msg}</p>
18 </tal:errors>
18 </tal:errors>
19 </div>
19 </div>
20 <div class="deform-seq-item-handle" style="padding:0">
20 <div class="deform-seq-item-handle" style="padding:0">
21 <!-- sequence_item -->
21 <!-- sequence_item -->
22 <span class="deform-order-button close glyphicon glyphicon-resize-vertical"
22 <span class="deform-order-button close glyphicon glyphicon-resize-vertical"
23 id="${oid}-order"
23 id="${oid}-order"
24 tal:condition="not hidden"
24 tal:condition="not hidden"
25 title="Reorder (via drag and drop)"
25 title="Reorder (via drag and drop)"
26 i18n:attributes="title"></span>
26 i18n:attributes="title"></span>
27 <a class="deform-close-button close"
27 <a class="deform-close-button close"
28 id="${oid}-close"
28 id="${oid}-close"
29 tal:condition="not field.widget.hidden"
29 tal:condition="not field.widget.hidden"
30 title="Remove"
30 title="Remove"
31 i18n:attributes="title"
31 i18n:attributes="title"
32 onclick="javascript:deform.removeSequenceItem(this);">&times;</a>
32 onclick="javascript:deform.removeSequenceItem(this);">&times;</a>
33 </div>
33 </div>
34 <!-- /sequence_item -->
34 <!-- /sequence_item -->
35 </div>
35 </div>
General Comments 0
You need to be logged in to leave comments. Login now