##// END OF EJS Templates
Implemented simple gist functionality ref #530....
marcink -
r3840:dc464486 beta
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,159 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.bin.gist
4 ~~~~~~~~~~~~~~~~~~
5
6 Gist CLI client for RhodeCode
7
8 :created_on: May 9, 2013
9 :author: marcink
10 :copyright: (C) 2010-2013 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
12 """
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
26 from __future__ import with_statement
27 import os
28 import sys
29 import stat
30 import argparse
31 import fileinput
32
33 from rhodecode.bin.base import api_call, RcConf
34
35
36 def argparser(argv):
37 usage = (
38 "rhodecode-gist [-h] [--format=FORMAT] [--apikey=APIKEY] [--apihost=APIHOST] "
39 "[--config=CONFIG] [--save-config] "
40 "[filename or stdin use - for terminal stdin ]\n"
41 "Create config file: rhodecode-gist --apikey=<key> --apihost=http://rhodecode.server --save-config"
42 )
43
44 parser = argparse.ArgumentParser(description='RhodeCode Gist cli',
45 usage=usage)
46
47 ## config
48 group = parser.add_argument_group('config')
49 group.add_argument('--apikey', help='api access key')
50 group.add_argument('--apihost', help='api host')
51 group.add_argument('--config', help='config file')
52 group.add_argument('--save-config', action='store_true',
53 help='save the given config into a file')
54
55 group = parser.add_argument_group('GIST')
56 group.add_argument('-f', '--filename', help='set uploaded gist filename')
57 group.add_argument('-p', '--private', action='store_true',
58 help='Create private Gist')
59 group.add_argument('-d', '--description', help='Gist description')
60 group.add_argument('-l', '--lifetime', metavar='MINUTES',
61 help='Gist lifetime in minutes, -1 (Default) is forever')
62
63 args, other = parser.parse_known_args()
64 return parser, args, other
65
66
67 def _run(argv):
68 conf = None
69 parser, args, other = argparser(argv)
70
71 api_credentials_given = (args.apikey and args.apihost)
72 if args.save_config:
73 if not api_credentials_given:
74 raise parser.error('--save-config requires --apikey and --apihost')
75 conf = RcConf(config_location=args.config,
76 autocreate=True, config={'apikey': args.apikey,
77 'apihost': args.apihost})
78 sys.exit()
79
80 if not conf:
81 conf = RcConf(config_location=args.config, autoload=True)
82 if not conf:
83 if not api_credentials_given:
84 parser.error('Could not find config file and missing '
85 '--apikey or --apihost in params')
86
87 apikey = args.apikey or conf['apikey']
88 host = args.apihost or conf['apihost']
89 DEFAULT_FILENAME = 'gistfile1.txt'
90 if other:
91 # skip multifiles for now
92 filename = other[0]
93 if filename == '-':
94 filename = DEFAULT_FILENAME
95 gist_content = ''
96 for line in fileinput.input():
97 gist_content += line
98 else:
99 with open(filename, 'rb') as f:
100 gist_content = f.read()
101
102 else:
103 filename = DEFAULT_FILENAME
104 gist_content = None
105 # little bit hacky but cross platform check where the
106 # stdin comes from we skip the terminal case it can be handled by '-'
107 mode = os.fstat(0).st_mode
108 if stat.S_ISFIFO(mode):
109 # "stdin is piped"
110 gist_content = sys.stdin.read()
111 elif stat.S_ISREG(mode):
112 # "stdin is redirected"
113 gist_content = sys.stdin.read()
114 else:
115 # "stdin is terminal"
116 pass
117
118 # make sure we don't upload binary stuff
119 if gist_content and '\0' in gist_content:
120 raise Exception('Error: binary files upload is not possible')
121
122 filename = args.filename or filename
123 if gist_content:
124 files = {
125 filename: {
126 'content': gist_content,
127 'lexer': None
128 }
129 }
130
131 margs = dict(
132 gist_lifetime=args.lifetime,
133 gist_description=args.description,
134 gist_type='private' if args.private else 'public',
135 files=files
136 )
137
138 api_call(apikey, host, 'json', 'create_gist', **margs)
139 return 0
140
141
142 def main(argv=None):
143 """
144 Main execution function for cli
145
146 :param argv:
147 """
148 if argv is None:
149 argv = sys.argv
150
151 try:
152 return _run(argv)
153 except Exception, e:
154 print e
155 return 1
156
157
158 if __name__ == '__main__':
159 sys.exit(main(sys.argv))
@@ -0,0 +1,180 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.controllers.admin.gist
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6 gist controller for RhodeCode
7
8 :created_on: May 9, 2013
9 :author: marcink
10 :copyright: (C) 2010-2013 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
12 """
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import time
26 import logging
27 import traceback
28 import formencode
29 from formencode import htmlfill
30
31 from pylons import request, tmpl_context as c, url
32 from pylons.controllers.util import abort, redirect
33 from pylons.i18n.translation import _
34
35 from rhodecode.model.forms import GistForm
36 from rhodecode.model.gist import GistModel
37 from rhodecode.model.meta import Session
38 from rhodecode.model.db import Gist
39 from rhodecode.lib import helpers as h
40 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.auth import LoginRequired, NotAnonymous
42 from rhodecode.lib.utils2 import safe_str, safe_int, time_to_datetime
43 from rhodecode.lib.helpers import Page
44 from webob.exc import HTTPNotFound
45 from sqlalchemy.sql.expression import or_
46 from rhodecode.lib.vcs.exceptions import VCSError
47
48 log = logging.getLogger(__name__)
49
50
51 class GistsController(BaseController):
52 """REST Controller styled on the Atom Publishing Protocol"""
53
54 def __load_defaults(self):
55 c.lifetime_values = [
56 (str(-1), _('forever')),
57 (str(5), _('5 minutes')),
58 (str(60), _('1 hour')),
59 (str(60 * 24), _('1 day')),
60 (str(60 * 24 * 30), _('1 month')),
61 ]
62 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
63
64 @LoginRequired()
65 def index(self, format='html'):
66 """GET /admin/gists: All items in the collection"""
67 # url('gists')
68 c.show_private = request.GET.get('private') and c.rhodecode_user.username != 'default'
69 gists = Gist().query()\
70 .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
71 .order_by(Gist.created_on.desc())
72 if c.show_private:
73 c.gists = gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
74 .filter(Gist.gist_owner == c.rhodecode_user.user_id)
75 else:
76 c.gists = gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
77 p = safe_int(request.GET.get('page', 1), 1)
78 c.gists_pager = Page(c.gists, page=p, items_per_page=10)
79 return render('admin/gists/index.html')
80
81 @LoginRequired()
82 @NotAnonymous()
83 def create(self):
84 """POST /admin/gists: Create a new item"""
85 # url('gists')
86 self.__load_defaults()
87 gist_form = GistForm([x[0] for x in c.lifetime_values])()
88 try:
89 form_result = gist_form.to_python(dict(request.POST))
90 #TODO: multiple files support, from the form
91 nodes = {
92 form_result['filename'] or 'gistfile1.txt': {
93 'content': form_result['content'],
94 'lexer': None # autodetect
95 }
96 }
97 _public = form_result['public']
98 gist_type = Gist.GIST_PUBLIC if _public else Gist.GIST_PRIVATE
99 gist = GistModel().create(
100 description=form_result['description'],
101 owner=c.rhodecode_user,
102 gist_mapping=nodes,
103 gist_type=gist_type,
104 lifetime=form_result['lifetime']
105 )
106 Session().commit()
107 new_gist_id = gist.gist_access_id
108 except formencode.Invalid, errors:
109 defaults = errors.value
110
111 return formencode.htmlfill.render(
112 render('admin/gists/new.html'),
113 defaults=defaults,
114 errors=errors.error_dict or {},
115 prefix_error=False,
116 encoding="UTF-8"
117 )
118
119 except Exception, e:
120 log.error(traceback.format_exc())
121 h.flash(_('Error occurred during gist creation'), category='error')
122 return redirect(url('new_gist'))
123 return redirect(url('gist', id=new_gist_id))
124
125 @LoginRequired()
126 @NotAnonymous()
127 def new(self, format='html'):
128 """GET /admin/gists/new: Form to create a new item"""
129 # url('new_gist')
130 self.__load_defaults()
131 return render('admin/gists/new.html')
132
133 @LoginRequired()
134 @NotAnonymous()
135 def update(self, id):
136 """PUT /admin/gists/id: Update an existing item"""
137 # Forms posted to this method should contain a hidden field:
138 # <input type="hidden" name="_method" value="PUT" />
139 # Or using helpers:
140 # h.form(url('gist', id=ID),
141 # method='put')
142 # url('gist', id=ID)
143
144 @LoginRequired()
145 @NotAnonymous()
146 def delete(self, id):
147 """DELETE /admin/gists/id: Delete an existing item"""
148 # Forms posted to this method should contain a hidden field:
149 # <input type="hidden" name="_method" value="DELETE" />
150 # Or using helpers:
151 # h.form(url('gist', id=ID),
152 # method='delete')
153 # url('gist', id=ID)
154
155 @LoginRequired()
156 def show(self, id, format='html'):
157 """GET /admin/gists/id: Show a specific item"""
158 # url('gist', id=ID)
159 gist_id = id
160 c.gist = Gist.get_or_404(gist_id)
161
162 #check if this gist is not expired
163 if c.gist.gist_expires != -1:
164 if time.time() > c.gist.gist_expires:
165 log.error('Gist expired at %s' %
166 (time_to_datetime(c.gist.gist_expires)))
167 raise HTTPNotFound()
168 try:
169 c.file_changeset, c.files = GistModel().get_gist_files(gist_id)
170 except VCSError:
171 log.error(traceback.format_exc())
172 raise HTTPNotFound()
173
174 return render('admin/gists/show.html')
175
176 @LoginRequired()
177 @NotAnonymous()
178 def edit(self, id, format='html'):
179 """GET /admin/gists/id/edit: Form to edit an existing item"""
180 # url('edit_gist', id=ID)
@@ -0,0 +1,161 b''
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.model.gist
4 ~~~~~~~~~~~~~~~~~~~~
5
6 gist model for RhodeCode
7
8 :created_on: May 9, 2013
9 :author: marcink
10 :copyright: (C) 2011-2013 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
12 """
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
17 #
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
26 import os
27 import time
28 import logging
29 import traceback
30 import shutil
31
32 from pylons.i18n.translation import _
33 from rhodecode.lib.utils2 import safe_unicode, unique_id, safe_int, \
34 time_to_datetime, safe_str, AttributeDict
35 from rhodecode.lib import helpers as h
36 from rhodecode.model import BaseModel
37 from rhodecode.model.db import Gist
38 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.scm import ScmModel
40 from rhodecode.lib.vcs import get_repo
41
42 log = logging.getLogger(__name__)
43
44 GIST_STORE_LOC = '.gist_store'
45
46
47 class GistModel(BaseModel):
48
49 def _get_gist(self, gist):
50 """
51 Helper method to get gist by ID, or gist_access_id as a fallback
52
53 :param gist: GistID, gist_access_id, or Gist instance
54 """
55 return self._get_instance(Gist, gist,
56 callback=Gist.get_by_access_id)
57
58 def __delete_gist(self, gist):
59 """
60 removes gist from filesystem
61
62 :param gist: gist object
63 """
64 root_path = RepoModel().repos_path
65 rm_path = os.path.join(root_path, GIST_STORE_LOC, gist.gist_access_id)
66 log.info("Removing %s" % (rm_path))
67 shutil.rmtree(rm_path)
68
69 def get_gist_files(self, gist_access_id):
70 """
71 Get files for given gist
72
73 :param gist_access_id:
74 """
75 root_path = RepoModel().repos_path
76 r = get_repo(os.path.join(*map(safe_str,
77 [root_path, GIST_STORE_LOC, gist_access_id])))
78 cs = r.get_changeset()
79 return (
80 cs, [n for n in cs.get_node('/')]
81 )
82
83 def create(self, description, owner, gist_mapping,
84 gist_type=Gist.GIST_PUBLIC, lifetime=-1):
85 """
86
87 :param description: description of the gist
88 :param owner: user who created this gist
89 :param gist_mapping: mapping {filename:{'content':content},...}
90 :param gist_type: type of gist private/public
91 :param lifetime: in minutes, -1 == forever
92 """
93 gist_id = safe_unicode(unique_id(20))
94 lifetime = safe_int(lifetime, -1)
95 gist_expires = time.time() + (lifetime * 60) if lifetime != -1 else -1
96 log.debug('set GIST expiration date to: %s'
97 % (time_to_datetime(gist_expires)
98 if gist_expires != -1 else 'forever'))
99 #create the Database version
100 gist = Gist()
101 gist.gist_description = description
102 gist.gist_access_id = gist_id
103 gist.gist_owner = owner.user_id
104 gist.gist_expires = gist_expires
105 gist.gist_type = safe_unicode(gist_type)
106 self.sa.add(gist)
107 self.sa.flush()
108 if gist_type == Gist.GIST_PUBLIC:
109 # use DB ID for easy to use GIST ID
110 gist_id = safe_unicode(gist.gist_id)
111 gist.gist_access_id = gist_id
112 self.sa.add(gist)
113
114 gist_repo_path = os.path.join(GIST_STORE_LOC, gist_id)
115 log.debug('Creating new %s GIST repo in %s' % (gist_type, gist_repo_path))
116 repo = RepoModel()._create_repo(repo_name=gist_repo_path, alias='hg',
117 parent=None)
118
119 processed_mapping = {}
120 for filename in gist_mapping:
121 content = gist_mapping[filename]['content']
122 #TODO: expand support for setting explicit lexers
123 # if lexer is None:
124 # try:
125 # lexer = pygments.lexers.guess_lexer_for_filename(filename,content)
126 # except pygments.util.ClassNotFound:
127 # lexer = 'text'
128 processed_mapping[filename] = {'content': content}
129
130 # now create single multifile commit
131 message = 'added file'
132 message += 's: ' if len(processed_mapping) > 1 else ': '
133 message += ', '.join([x for x in processed_mapping])
134
135 #fake RhodeCode Repository object
136 fake_repo = AttributeDict(dict(
137 repo_name=gist_repo_path,
138 scm_instance_no_cache=lambda: repo,
139 ))
140 ScmModel().create_nodes(
141 user=owner.user_id, repo=fake_repo,
142 message=message,
143 nodes=processed_mapping,
144 trigger_push_hook=False
145 )
146
147 return gist
148
149 def delete(self, gist, fs_remove=True):
150 gist = self._get_gist(gist)
151
152 try:
153 self.sa.delete(gist)
154 if fs_remove:
155 self.__delete_gist(gist)
156 else:
157 log.debug('skipping removal from filesystem')
158
159 except Exception:
160 log.error(traceback.format_exc())
161 raise
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,148 +1,148 b''
1 """
1 """
2 Base utils for shell scripts
2 Base utils for shell scripts
3 """
3 """
4 import os
4 import os
5 import sys
5 import sys
6 import random
6 import random
7 import urllib2
7 import urllib2
8 import pprint
8 import pprint
9
9
10 try:
10 try:
11 from rhodecode.lib.ext_json import json
11 from rhodecode.lib.ext_json import json
12 except ImportError:
12 except ImportError:
13 try:
13 try:
14 import simplejson as json
14 import simplejson as json
15 except ImportError:
15 except ImportError:
16 import json
16 import json
17
17
18 CONFIG_NAME = '.rhodecode'
18 CONFIG_NAME = '.rhodecode'
19 FORMAT_PRETTY = 'pretty'
19 FORMAT_PRETTY = 'pretty'
20 FORMAT_JSON = 'json'
20 FORMAT_JSON = 'json'
21
21
22
22
23 def api_call(apikey, apihost, format, method=None, **kw):
23 def api_call(apikey, apihost, format, method=None, **kw):
24 """
24 """
25 Api_call wrapper for RhodeCode
25 Api_call wrapper for RhodeCode
26
26
27 :param apikey:
27 :param apikey:
28 :param apihost:
28 :param apihost:
29 :param format: formatting, pretty means prints and pprint of json
29 :param format: formatting, pretty means prints and pprint of json
30 json returns unparsed json
30 json returns unparsed json
31 :param method:
31 :param method:
32 """
32 """
33 def _build_data(random_id):
33 def _build_data(random_id):
34 """
34 """
35 Builds API data with given random ID
35 Builds API data with given random ID
36
36
37 :param random_id:
37 :param random_id:
38 :type random_id:
39 """
38 """
40 return {
39 return {
41 "id": random_id,
40 "id": random_id,
42 "api_key": apikey,
41 "api_key": apikey,
43 "method": method,
42 "method": method,
44 "args": kw
43 "args": kw
45 }
44 }
46
45
47 if not method:
46 if not method:
48 raise Exception('please specify method name !')
47 raise Exception('please specify method name !')
49 id_ = random.randrange(1, 9999)
48 id_ = random.randrange(1, 9999)
50 req = urllib2.Request('%s/_admin/api' % apihost,
49 req = urllib2.Request('%s/_admin/api' % apihost,
51 data=json.dumps(_build_data(id_)),
50 data=json.dumps(_build_data(id_)),
52 headers={'content-type': 'text/plain'})
51 headers={'content-type': 'text/plain'})
53 if format == FORMAT_PRETTY:
52 if format == FORMAT_PRETTY:
54 sys.stdout.write('calling %s to %s \n' % (req.get_data(), apihost))
53 sys.stdout.write('calling %s to %s \n' % (req.get_data(), apihost))
55 ret = urllib2.urlopen(req)
54 ret = urllib2.urlopen(req)
56 raw_json = ret.read()
55 raw_json = ret.read()
57 json_data = json.loads(raw_json)
56 json_data = json.loads(raw_json)
58 id_ret = json_data['id']
57 id_ret = json_data['id']
59 _formatted_json = pprint.pformat(json_data)
58 _formatted_json = pprint.pformat(json_data)
60 if id_ret == id_:
59 if id_ret == id_:
61 if format == FORMAT_JSON:
60 if format == FORMAT_JSON:
62 sys.stdout.write(str(raw_json))
61 sys.stdout.write(str(raw_json))
63 else:
62 else:
64 sys.stdout.write('rhodecode returned:\n%s\n' % (_formatted_json))
63 sys.stdout.write('rhodecode returned:\n%s\n' % (_formatted_json))
65
64
66 else:
65 else:
67 raise Exception('something went wrong. '
66 raise Exception('something went wrong. '
68 'ID mismatch got %s, expected %s | %s' % (
67 'ID mismatch got %s, expected %s | %s' % (
69 id_ret, id_, _formatted_json))
68 id_ret, id_, _formatted_json))
70
69
71
70
72 class RcConf(object):
71 class RcConf(object):
73 """
72 """
74 RhodeCode config for API
73 RhodeCode config for API
75
74
76 conf = RcConf()
75 conf = RcConf()
77 conf['key']
76 conf['key']
78
77
79 """
78 """
80
79
81 def __init__(self, config_location=None, autoload=True, autocreate=False,
80 def __init__(self, config_location=None, autoload=True, autocreate=False,
82 config=None):
81 config=None):
83 self._conf_name = CONFIG_NAME if not config_location else config_location
82 HOME = os.getenv('HOME', os.getenv('USERPROFILE')) or ''
83 HOME_CONF = os.path.abspath(os.path.join(HOME, CONFIG_NAME))
84 self._conf_name = HOME_CONF if not config_location else config_location
84 self._conf = {}
85 self._conf = {}
85 if autocreate:
86 if autocreate:
86 self.make_config(config)
87 self.make_config(config)
87 if autoload:
88 if autoload:
88 self._conf = self.load_config()
89 self._conf = self.load_config()
89
90
90 def __getitem__(self, key):
91 def __getitem__(self, key):
91 return self._conf[key]
92 return self._conf[key]
92
93
93 def __nonzero__(self):
94 def __nonzero__(self):
94 if self._conf:
95 if self._conf:
95 return True
96 return True
96 return False
97 return False
97
98
98 def __eq__(self):
99 def __eq__(self):
99 return self._conf.__eq__()
100 return self._conf.__eq__()
100
101
101 def __repr__(self):
102 def __repr__(self):
102 return 'RcConf<%s>' % self._conf.__repr__()
103 return 'RcConf<%s>' % self._conf.__repr__()
103
104
104 def make_config(self, config):
105 def make_config(self, config):
105 """
106 """
106 Saves given config as a JSON dump in the _conf_name location
107 Saves given config as a JSON dump in the _conf_name location
107
108
108 :param config:
109 :param config:
109 :type config:
110 """
110 """
111 update = False
111 update = False
112 if os.path.exists(self._conf_name):
112 if os.path.exists(self._conf_name):
113 update = True
113 update = True
114 with open(self._conf_name, 'wb') as f:
114 with open(self._conf_name, 'wb') as f:
115 json.dump(config, f, indent=4)
115 json.dump(config, f, indent=4)
116
116
117 if update:
117 if update:
118 sys.stdout.write('Updated config in %s\n' % self._conf_name)
118 sys.stdout.write('Updated config in %s\n' % self._conf_name)
119 else:
119 else:
120 sys.stdout.write('Created new config in %s\n' % self._conf_name)
120 sys.stdout.write('Created new config in %s\n' % self._conf_name)
121
121
122 def update_config(self, new_config):
122 def update_config(self, new_config):
123 """
123 """
124 Reads the JSON config updates it's values with new_config and
124 Reads the JSON config updates it's values with new_config and
125 saves it back as JSON dump
125 saves it back as JSON dump
126
126
127 :param new_config:
127 :param new_config:
128 """
128 """
129 config = {}
129 config = {}
130 try:
130 try:
131 with open(self._conf_name, 'rb') as conf:
131 with open(self._conf_name, 'rb') as conf:
132 config = json.load(conf)
132 config = json.load(conf)
133 except IOError, e:
133 except IOError, e:
134 sys.stderr.write(str(e) + '\n')
134 sys.stderr.write(str(e) + '\n')
135
135
136 config.update(new_config)
136 config.update(new_config)
137 self.make_config(config)
137 self.make_config(config)
138
138
139 def load_config(self):
139 def load_config(self):
140 """
140 """
141 Loads config from file and returns loaded JSON object
141 Loads config from file and returns loaded JSON object
142 """
142 """
143 try:
143 try:
144 with open(self._conf_name, 'rb') as conf:
144 with open(self._conf_name, 'rb') as conf:
145 return json.load(conf)
145 return json.load(conf)
146 except IOError, e:
146 except IOError, e:
147 #sys.stderr.write(str(e) + '\n')
147 #sys.stderr.write(str(e) + '\n')
148 pass
148 pass
@@ -1,107 +1,106 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.bin.api
3 rhodecode.bin.api
4 ~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~
5
5
6 Api CLI client for RhodeCode
6 Api CLI client for RhodeCode
7
7
8 :created_on: Jun 3, 2012
8 :created_on: Jun 3, 2012
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 from __future__ import with_statement
26 from __future__ import with_statement
27 import sys
27 import sys
28 import argparse
28 import argparse
29
29
30 from rhodecode.bin.base import api_call, RcConf, FORMAT_JSON, FORMAT_PRETTY
30 from rhodecode.bin.base import api_call, RcConf, FORMAT_JSON, FORMAT_PRETTY
31
31
32
32
33 def argparser(argv):
33 def argparser(argv):
34 usage = (
34 usage = (
35 "rhodecode-api [-h] [--format=FORMAT] [--apikey=APIKEY] [--apihost=APIHOST] "
35 "rhodecode-api [-h] [--format=FORMAT] [--apikey=APIKEY] [--apihost=APIHOST] "
36 "[--config=CONFIG] [--save-config] "
36 "[--config=CONFIG] [--save-config] "
37 "METHOD <key:val> <key2:val> ...\n"
37 "METHOD <key:val> <key2:val> ...\n"
38 "Create config file: rhodecode-gist --apikey=<key> --apihost=http://rhodecode.server --save-config"
38 "Create config file: rhodecode-gist --apikey=<key> --apihost=http://rhodecode.server --save-config"
39 )
39 )
40
40
41 parser = argparse.ArgumentParser(description='RhodeCode API cli',
41 parser = argparse.ArgumentParser(description='RhodeCode API cli',
42 usage=usage)
42 usage=usage)
43
43
44 ## config
44 ## config
45 group = parser.add_argument_group('config')
45 group = parser.add_argument_group('config')
46 group.add_argument('--apikey', help='api access key')
46 group.add_argument('--apikey', help='api access key')
47 group.add_argument('--apihost', help='api host')
47 group.add_argument('--apihost', help='api host')
48 group.add_argument('--config', help='config file')
48 group.add_argument('--config', help='config file')
49 group.add_argument('--save-config', action='store_true', help='save the given config into a file')
49 group.add_argument('--save-config', action='store_true', help='save the given config into a file')
50
50
51 group = parser.add_argument_group('API')
51 group = parser.add_argument_group('API')
52 group.add_argument('method', metavar='METHOD', nargs='?', type=str, default=None,
52 group.add_argument('method', metavar='METHOD', nargs='?', type=str, default=None,
53 help='API method name to call followed by key:value attributes',
53 help='API method name to call followed by key:value attributes',
54 )
54 )
55 group.add_argument('--format', dest='format', type=str,
55 group.add_argument('--format', dest='format', type=str,
56 help='output format default: `pretty` can '
56 help='output format default: `pretty` can '
57 'be also `%s`' % FORMAT_JSON,
57 'be also `%s`' % FORMAT_JSON,
58 default=FORMAT_PRETTY
58 default=FORMAT_PRETTY
59 )
59 )
60 args, other = parser.parse_known_args()
60 args, other = parser.parse_known_args()
61 return parser, args, other
61 return parser, args, other
62
62
63
63
64 def main(argv=None):
64 def main(argv=None):
65 """
65 """
66 Main execution function for cli
66 Main execution function for cli
67
67
68 :param argv:
68 :param argv:
69 :type argv:
70 """
69 """
71 if argv is None:
70 if argv is None:
72 argv = sys.argv
71 argv = sys.argv
73
72
74 conf = None
73 conf = None
75 parser, args, other = argparser(argv)
74 parser, args, other = argparser(argv)
76
75
77 api_credentials_given = (args.apikey and args.apihost)
76 api_credentials_given = (args.apikey and args.apihost)
78 if args.save_config:
77 if args.save_config:
79 if not api_credentials_given:
78 if not api_credentials_given:
80 raise parser.error('--save-config requires --apikey and --apihost')
79 raise parser.error('--save-config requires --apikey and --apihost')
81 conf = RcConf(config_location=args.config,
80 conf = RcConf(config_location=args.config,
82 autocreate=True, config={'apikey': args.apikey,
81 autocreate=True, config={'apikey': args.apikey,
83 'apihost': args.apihost})
82 'apihost': args.apihost})
84 sys.exit()
83 sys.exit()
85
84
86 if not conf:
85 if not conf:
87 conf = RcConf(config_location=args.config, autoload=True)
86 conf = RcConf(config_location=args.config, autoload=True)
88 if not conf:
87 if not conf:
89 if not api_credentials_given:
88 if not api_credentials_given:
90 parser.error('Could not find config file and missing '
89 parser.error('Could not find config file and missing '
91 '--apikey or --apihost in params')
90 '--apikey or --apihost in params')
92
91
93 apikey = args.apikey or conf['apikey']
92 apikey = args.apikey or conf['apikey']
94 host = args.apihost or conf['apihost']
93 host = args.apihost or conf['apihost']
95 method = args.method
94 method = args.method
96
95
97 try:
96 try:
98 margs = dict(map(lambda s: s.split(':', 1), other))
97 margs = dict(map(lambda s: s.split(':', 1), other))
99 except Exception:
98 except Exception:
100 sys.stderr.write('Error parsing arguments \n')
99 sys.stderr.write('Error parsing arguments \n')
101 sys.exit()
100 sys.exit()
102
101
103 api_call(apikey, host, args.format, method, **margs)
102 api_call(apikey, host, args.format, method, **margs)
104 return 0
103 return 0
105
104
106 if __name__ == '__main__':
105 if __name__ == '__main__':
107 sys.exit(main(sys.argv))
106 sys.exit(main(sys.argv))
@@ -1,680 +1,683 b''
1 """
1 """
2 Routes configuration
2 Routes configuration
3
3
4 The more specific and detailed routes should be defined first so they
4 The more specific and detailed routes should be defined first so they
5 may take precedent over the more generic routes. For more information
5 may take precedent over the more generic routes. For more information
6 refer to the routes manual at http://routes.groovie.org/docs/
6 refer to the routes manual at http://routes.groovie.org/docs/
7 """
7 """
8 from __future__ import with_statement
8 from __future__ import with_statement
9 from routes import Mapper
9 from routes import Mapper
10
10
11 # prefix for non repository related links needs to be prefixed with `/`
11 # prefix for non repository related links needs to be prefixed with `/`
12 ADMIN_PREFIX = '/_admin'
12 ADMIN_PREFIX = '/_admin'
13
13
14
14
15 def make_map(config):
15 def make_map(config):
16 """Create, configure and return the routes Mapper"""
16 """Create, configure and return the routes Mapper"""
17 rmap = Mapper(directory=config['pylons.paths']['controllers'],
17 rmap = Mapper(directory=config['pylons.paths']['controllers'],
18 always_scan=config['debug'])
18 always_scan=config['debug'])
19 rmap.minimization = False
19 rmap.minimization = False
20 rmap.explicit = False
20 rmap.explicit = False
21
21
22 from rhodecode.lib.utils import is_valid_repo
22 from rhodecode.lib.utils import is_valid_repo
23 from rhodecode.lib.utils import is_valid_repos_group
23 from rhodecode.lib.utils import is_valid_repos_group
24
24
25 def check_repo(environ, match_dict):
25 def check_repo(environ, match_dict):
26 """
26 """
27 check for valid repository for proper 404 handling
27 check for valid repository for proper 404 handling
28
28
29 :param environ:
29 :param environ:
30 :param match_dict:
30 :param match_dict:
31 """
31 """
32 from rhodecode.model.db import Repository
32 from rhodecode.model.db import Repository
33 repo_name = match_dict.get('repo_name')
33 repo_name = match_dict.get('repo_name')
34
34
35 if match_dict.get('f_path'):
35 if match_dict.get('f_path'):
36 #fix for multiple initial slashes that causes errors
36 #fix for multiple initial slashes that causes errors
37 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
37 match_dict['f_path'] = match_dict['f_path'].lstrip('/')
38
38
39 try:
39 try:
40 by_id = repo_name.split('_')
40 by_id = repo_name.split('_')
41 if len(by_id) == 2 and by_id[1].isdigit() and by_id[0] == '':
41 if len(by_id) == 2 and by_id[1].isdigit() and by_id[0] == '':
42 repo_name = Repository.get(by_id[1]).repo_name
42 repo_name = Repository.get(by_id[1]).repo_name
43 match_dict['repo_name'] = repo_name
43 match_dict['repo_name'] = repo_name
44 except Exception:
44 except Exception:
45 pass
45 pass
46
46
47 return is_valid_repo(repo_name, config['base_path'])
47 return is_valid_repo(repo_name, config['base_path'])
48
48
49 def check_group(environ, match_dict):
49 def check_group(environ, match_dict):
50 """
50 """
51 check for valid repository group for proper 404 handling
51 check for valid repository group for proper 404 handling
52
52
53 :param environ:
53 :param environ:
54 :param match_dict:
54 :param match_dict:
55 """
55 """
56 repos_group_name = match_dict.get('group_name')
56 repos_group_name = match_dict.get('group_name')
57 return is_valid_repos_group(repos_group_name, config['base_path'])
57 return is_valid_repos_group(repos_group_name, config['base_path'])
58
58
59 def check_group_skip_path(environ, match_dict):
59 def check_group_skip_path(environ, match_dict):
60 """
60 """
61 check for valid repository group for proper 404 handling, but skips
61 check for valid repository group for proper 404 handling, but skips
62 verification of existing path
62 verification of existing path
63
63
64 :param environ:
64 :param environ:
65 :param match_dict:
65 :param match_dict:
66 """
66 """
67 repos_group_name = match_dict.get('group_name')
67 repos_group_name = match_dict.get('group_name')
68 return is_valid_repos_group(repos_group_name, config['base_path'],
68 return is_valid_repos_group(repos_group_name, config['base_path'],
69 skip_path_check=True)
69 skip_path_check=True)
70
70
71 def check_user_group(environ, match_dict):
71 def check_user_group(environ, match_dict):
72 """
72 """
73 check for valid user group for proper 404 handling
73 check for valid user group for proper 404 handling
74
74
75 :param environ:
75 :param environ:
76 :param match_dict:
76 :param match_dict:
77 """
77 """
78 return True
78 return True
79
79
80 def check_int(environ, match_dict):
80 def check_int(environ, match_dict):
81 return match_dict.get('id').isdigit()
81 return match_dict.get('id').isdigit()
82
82
83 # The ErrorController route (handles 404/500 error pages); it should
83 # The ErrorController route (handles 404/500 error pages); it should
84 # likely stay at the top, ensuring it can always be resolved
84 # likely stay at the top, ensuring it can always be resolved
85 rmap.connect('/error/{action}', controller='error')
85 rmap.connect('/error/{action}', controller='error')
86 rmap.connect('/error/{action}/{id}', controller='error')
86 rmap.connect('/error/{action}/{id}', controller='error')
87
87
88 #==========================================================================
88 #==========================================================================
89 # CUSTOM ROUTES HERE
89 # CUSTOM ROUTES HERE
90 #==========================================================================
90 #==========================================================================
91
91
92 #MAIN PAGE
92 #MAIN PAGE
93 rmap.connect('home', '/', controller='home', action='index')
93 rmap.connect('home', '/', controller='home', action='index')
94 rmap.connect('repo_switcher', '/repos', controller='home',
94 rmap.connect('repo_switcher', '/repos', controller='home',
95 action='repo_switcher')
95 action='repo_switcher')
96 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*?}',
96 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*?}',
97 controller='home', action='branch_tag_switcher')
97 controller='home', action='branch_tag_switcher')
98 rmap.connect('bugtracker',
98 rmap.connect('bugtracker',
99 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
99 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
100 _static=True)
100 _static=True)
101 rmap.connect('rst_help',
101 rmap.connect('rst_help',
102 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
102 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
103 _static=True)
103 _static=True)
104 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
104 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
105
105
106 #ADMIN REPOSITORY REST ROUTES
106 #ADMIN REPOSITORY REST ROUTES
107 with rmap.submapper(path_prefix=ADMIN_PREFIX,
107 with rmap.submapper(path_prefix=ADMIN_PREFIX,
108 controller='admin/repos') as m:
108 controller='admin/repos') as m:
109 m.connect("repos", "/repos",
109 m.connect("repos", "/repos",
110 action="create", conditions=dict(method=["POST"]))
110 action="create", conditions=dict(method=["POST"]))
111 m.connect("repos", "/repos",
111 m.connect("repos", "/repos",
112 action="index", conditions=dict(method=["GET"]))
112 action="index", conditions=dict(method=["GET"]))
113 m.connect("formatted_repos", "/repos.{format}",
113 m.connect("formatted_repos", "/repos.{format}",
114 action="index",
114 action="index",
115 conditions=dict(method=["GET"]))
115 conditions=dict(method=["GET"]))
116 m.connect("new_repo", "/create_repository",
116 m.connect("new_repo", "/create_repository",
117 action="create_repository", conditions=dict(method=["GET"]))
117 action="create_repository", conditions=dict(method=["GET"]))
118 m.connect("/repos/{repo_name:.*?}",
118 m.connect("/repos/{repo_name:.*?}",
119 action="update", conditions=dict(method=["PUT"],
119 action="update", conditions=dict(method=["PUT"],
120 function=check_repo))
120 function=check_repo))
121 m.connect("/repos/{repo_name:.*?}",
121 m.connect("/repos/{repo_name:.*?}",
122 action="delete", conditions=dict(method=["DELETE"],
122 action="delete", conditions=dict(method=["DELETE"],
123 function=check_repo))
123 function=check_repo))
124 m.connect("formatted_edit_repo", "/repos/{repo_name:.*?}.{format}/edit",
124 m.connect("formatted_edit_repo", "/repos/{repo_name:.*?}.{format}/edit",
125 action="edit", conditions=dict(method=["GET"],
125 action="edit", conditions=dict(method=["GET"],
126 function=check_repo))
126 function=check_repo))
127 m.connect("repo", "/repos/{repo_name:.*?}",
127 m.connect("repo", "/repos/{repo_name:.*?}",
128 action="show", conditions=dict(method=["GET"],
128 action="show", conditions=dict(method=["GET"],
129 function=check_repo))
129 function=check_repo))
130 m.connect("formatted_repo", "/repos/{repo_name:.*?}.{format}",
130 m.connect("formatted_repo", "/repos/{repo_name:.*?}.{format}",
131 action="show", conditions=dict(method=["GET"],
131 action="show", conditions=dict(method=["GET"],
132 function=check_repo))
132 function=check_repo))
133 #add repo perm member
133 #add repo perm member
134 m.connect('set_repo_perm_member',
134 m.connect('set_repo_perm_member',
135 "/repos/{repo_name:.*?}/grant_perm",
135 "/repos/{repo_name:.*?}/grant_perm",
136 action="set_repo_perm_member",
136 action="set_repo_perm_member",
137 conditions=dict(method=["POST"], function=check_repo))
137 conditions=dict(method=["POST"], function=check_repo))
138
138
139 #ajax delete repo perm user
139 #ajax delete repo perm user
140 m.connect('delete_repo_perm_member',
140 m.connect('delete_repo_perm_member',
141 "/repos/{repo_name:.*?}/revoke_perm",
141 "/repos/{repo_name:.*?}/revoke_perm",
142 action="delete_repo_perm_member",
142 action="delete_repo_perm_member",
143 conditions=dict(method=["DELETE"], function=check_repo))
143 conditions=dict(method=["DELETE"], function=check_repo))
144
144
145 #settings actions
145 #settings actions
146 m.connect('repo_stats', "/repos_stats/{repo_name:.*?}",
146 m.connect('repo_stats', "/repos_stats/{repo_name:.*?}",
147 action="repo_stats", conditions=dict(method=["DELETE"],
147 action="repo_stats", conditions=dict(method=["DELETE"],
148 function=check_repo))
148 function=check_repo))
149 m.connect('repo_cache', "/repos_cache/{repo_name:.*?}",
149 m.connect('repo_cache', "/repos_cache/{repo_name:.*?}",
150 action="repo_cache", conditions=dict(method=["DELETE"],
150 action="repo_cache", conditions=dict(method=["DELETE"],
151 function=check_repo))
151 function=check_repo))
152 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*?}",
152 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*?}",
153 action="repo_public_journal", conditions=dict(method=["PUT"],
153 action="repo_public_journal", conditions=dict(method=["PUT"],
154 function=check_repo))
154 function=check_repo))
155 m.connect('repo_pull', "/repo_pull/{repo_name:.*?}",
155 m.connect('repo_pull', "/repo_pull/{repo_name:.*?}",
156 action="repo_pull", conditions=dict(method=["PUT"],
156 action="repo_pull", conditions=dict(method=["PUT"],
157 function=check_repo))
157 function=check_repo))
158 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}",
158 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}",
159 action="repo_as_fork", conditions=dict(method=["PUT"],
159 action="repo_as_fork", conditions=dict(method=["PUT"],
160 function=check_repo))
160 function=check_repo))
161 m.connect('repo_locking', "/repo_locking/{repo_name:.*?}",
161 m.connect('repo_locking', "/repo_locking/{repo_name:.*?}",
162 action="repo_locking", conditions=dict(method=["PUT"],
162 action="repo_locking", conditions=dict(method=["PUT"],
163 function=check_repo))
163 function=check_repo))
164 m.connect('toggle_locking', "/locking_toggle/{repo_name:.*?}",
164 m.connect('toggle_locking', "/locking_toggle/{repo_name:.*?}",
165 action="toggle_locking", conditions=dict(method=["GET"],
165 action="toggle_locking", conditions=dict(method=["GET"],
166 function=check_repo))
166 function=check_repo))
167
167
168 #repo fields
168 #repo fields
169 m.connect('create_repo_fields', "/repo_fields/{repo_name:.*?}/new",
169 m.connect('create_repo_fields', "/repo_fields/{repo_name:.*?}/new",
170 action="create_repo_field", conditions=dict(method=["PUT"],
170 action="create_repo_field", conditions=dict(method=["PUT"],
171 function=check_repo))
171 function=check_repo))
172
172
173 m.connect('delete_repo_fields', "/repo_fields/{repo_name:.*?}/{field_id}",
173 m.connect('delete_repo_fields', "/repo_fields/{repo_name:.*?}/{field_id}",
174 action="delete_repo_field", conditions=dict(method=["DELETE"],
174 action="delete_repo_field", conditions=dict(method=["DELETE"],
175 function=check_repo))
175 function=check_repo))
176
176
177 with rmap.submapper(path_prefix=ADMIN_PREFIX,
177 with rmap.submapper(path_prefix=ADMIN_PREFIX,
178 controller='admin/repos_groups') as m:
178 controller='admin/repos_groups') as m:
179 m.connect("repos_groups", "/repos_groups",
179 m.connect("repos_groups", "/repos_groups",
180 action="create", conditions=dict(method=["POST"]))
180 action="create", conditions=dict(method=["POST"]))
181 m.connect("repos_groups", "/repos_groups",
181 m.connect("repos_groups", "/repos_groups",
182 action="index", conditions=dict(method=["GET"]))
182 action="index", conditions=dict(method=["GET"]))
183 m.connect("formatted_repos_groups", "/repos_groups.{format}",
183 m.connect("formatted_repos_groups", "/repos_groups.{format}",
184 action="index", conditions=dict(method=["GET"]))
184 action="index", conditions=dict(method=["GET"]))
185 m.connect("new_repos_group", "/repos_groups/new",
185 m.connect("new_repos_group", "/repos_groups/new",
186 action="new", conditions=dict(method=["GET"]))
186 action="new", conditions=dict(method=["GET"]))
187 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
187 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
188 action="new", conditions=dict(method=["GET"]))
188 action="new", conditions=dict(method=["GET"]))
189 m.connect("update_repos_group", "/repos_groups/{group_name:.*?}",
189 m.connect("update_repos_group", "/repos_groups/{group_name:.*?}",
190 action="update", conditions=dict(method=["PUT"],
190 action="update", conditions=dict(method=["PUT"],
191 function=check_group))
191 function=check_group))
192 #add repo group perm member
192 #add repo group perm member
193 m.connect('set_repo_group_perm_member',
193 m.connect('set_repo_group_perm_member',
194 "/repos_groups/{group_name:.*?}/grant_perm",
194 "/repos_groups/{group_name:.*?}/grant_perm",
195 action="set_repo_group_perm_member",
195 action="set_repo_group_perm_member",
196 conditions=dict(method=["POST"], function=check_group))
196 conditions=dict(method=["POST"], function=check_group))
197
197
198 #ajax delete repo group perm
198 #ajax delete repo group perm
199 m.connect('delete_repo_group_perm_member',
199 m.connect('delete_repo_group_perm_member',
200 "/repos_groups/{group_name:.*?}/revoke_perm",
200 "/repos_groups/{group_name:.*?}/revoke_perm",
201 action="delete_repo_group_perm_member",
201 action="delete_repo_group_perm_member",
202 conditions=dict(method=["DELETE"], function=check_group))
202 conditions=dict(method=["DELETE"], function=check_group))
203
203
204 m.connect("delete_repos_group", "/repos_groups/{group_name:.*?}",
204 m.connect("delete_repos_group", "/repos_groups/{group_name:.*?}",
205 action="delete", conditions=dict(method=["DELETE"],
205 action="delete", conditions=dict(method=["DELETE"],
206 function=check_group_skip_path))
206 function=check_group_skip_path))
207 m.connect("edit_repos_group", "/repos_groups/{group_name:.*?}/edit",
207 m.connect("edit_repos_group", "/repos_groups/{group_name:.*?}/edit",
208 action="edit", conditions=dict(method=["GET"],
208 action="edit", conditions=dict(method=["GET"],
209 function=check_group))
209 function=check_group))
210 m.connect("formatted_edit_repos_group",
210 m.connect("formatted_edit_repos_group",
211 "/repos_groups/{group_name:.*?}.{format}/edit",
211 "/repos_groups/{group_name:.*?}.{format}/edit",
212 action="edit", conditions=dict(method=["GET"],
212 action="edit", conditions=dict(method=["GET"],
213 function=check_group))
213 function=check_group))
214 m.connect("repos_group", "/repos_groups/{group_name:.*?}",
214 m.connect("repos_group", "/repos_groups/{group_name:.*?}",
215 action="show", conditions=dict(method=["GET"],
215 action="show", conditions=dict(method=["GET"],
216 function=check_group))
216 function=check_group))
217 m.connect("formatted_repos_group", "/repos_groups/{group_name:.*?}.{format}",
217 m.connect("formatted_repos_group", "/repos_groups/{group_name:.*?}.{format}",
218 action="show", conditions=dict(method=["GET"],
218 action="show", conditions=dict(method=["GET"],
219 function=check_group))
219 function=check_group))
220
220
221 #ADMIN USER REST ROUTES
221 #ADMIN USER REST ROUTES
222 with rmap.submapper(path_prefix=ADMIN_PREFIX,
222 with rmap.submapper(path_prefix=ADMIN_PREFIX,
223 controller='admin/users') as m:
223 controller='admin/users') as m:
224 m.connect("users", "/users",
224 m.connect("users", "/users",
225 action="create", conditions=dict(method=["POST"]))
225 action="create", conditions=dict(method=["POST"]))
226 m.connect("users", "/users",
226 m.connect("users", "/users",
227 action="index", conditions=dict(method=["GET"]))
227 action="index", conditions=dict(method=["GET"]))
228 m.connect("formatted_users", "/users.{format}",
228 m.connect("formatted_users", "/users.{format}",
229 action="index", conditions=dict(method=["GET"]))
229 action="index", conditions=dict(method=["GET"]))
230 m.connect("new_user", "/users/new",
230 m.connect("new_user", "/users/new",
231 action="new", conditions=dict(method=["GET"]))
231 action="new", conditions=dict(method=["GET"]))
232 m.connect("formatted_new_user", "/users/new.{format}",
232 m.connect("formatted_new_user", "/users/new.{format}",
233 action="new", conditions=dict(method=["GET"]))
233 action="new", conditions=dict(method=["GET"]))
234 m.connect("update_user", "/users/{id}",
234 m.connect("update_user", "/users/{id}",
235 action="update", conditions=dict(method=["PUT"]))
235 action="update", conditions=dict(method=["PUT"]))
236 m.connect("delete_user", "/users/{id}",
236 m.connect("delete_user", "/users/{id}",
237 action="delete", conditions=dict(method=["DELETE"]))
237 action="delete", conditions=dict(method=["DELETE"]))
238 m.connect("edit_user", "/users/{id}/edit",
238 m.connect("edit_user", "/users/{id}/edit",
239 action="edit", conditions=dict(method=["GET"]))
239 action="edit", conditions=dict(method=["GET"]))
240 m.connect("formatted_edit_user",
240 m.connect("formatted_edit_user",
241 "/users/{id}.{format}/edit",
241 "/users/{id}.{format}/edit",
242 action="edit", conditions=dict(method=["GET"]))
242 action="edit", conditions=dict(method=["GET"]))
243 m.connect("user", "/users/{id}",
243 m.connect("user", "/users/{id}",
244 action="show", conditions=dict(method=["GET"]))
244 action="show", conditions=dict(method=["GET"]))
245 m.connect("formatted_user", "/users/{id}.{format}",
245 m.connect("formatted_user", "/users/{id}.{format}",
246 action="show", conditions=dict(method=["GET"]))
246 action="show", conditions=dict(method=["GET"]))
247
247
248 #EXTRAS USER ROUTES
248 #EXTRAS USER ROUTES
249 m.connect("user_perm", "/users_perm/{id}",
249 m.connect("user_perm", "/users_perm/{id}",
250 action="update_perm", conditions=dict(method=["PUT"]))
250 action="update_perm", conditions=dict(method=["PUT"]))
251 m.connect("user_emails", "/users_emails/{id}",
251 m.connect("user_emails", "/users_emails/{id}",
252 action="add_email", conditions=dict(method=["PUT"]))
252 action="add_email", conditions=dict(method=["PUT"]))
253 m.connect("user_emails_delete", "/users_emails/{id}",
253 m.connect("user_emails_delete", "/users_emails/{id}",
254 action="delete_email", conditions=dict(method=["DELETE"]))
254 action="delete_email", conditions=dict(method=["DELETE"]))
255 m.connect("user_ips", "/users_ips/{id}",
255 m.connect("user_ips", "/users_ips/{id}",
256 action="add_ip", conditions=dict(method=["PUT"]))
256 action="add_ip", conditions=dict(method=["PUT"]))
257 m.connect("user_ips_delete", "/users_ips/{id}",
257 m.connect("user_ips_delete", "/users_ips/{id}",
258 action="delete_ip", conditions=dict(method=["DELETE"]))
258 action="delete_ip", conditions=dict(method=["DELETE"]))
259
259
260 #ADMIN USER GROUPS REST ROUTES
260 #ADMIN USER GROUPS REST ROUTES
261 with rmap.submapper(path_prefix=ADMIN_PREFIX,
261 with rmap.submapper(path_prefix=ADMIN_PREFIX,
262 controller='admin/users_groups') as m:
262 controller='admin/users_groups') as m:
263 m.connect("users_groups", "/users_groups",
263 m.connect("users_groups", "/users_groups",
264 action="create", conditions=dict(method=["POST"]))
264 action="create", conditions=dict(method=["POST"]))
265 m.connect("users_groups", "/users_groups",
265 m.connect("users_groups", "/users_groups",
266 action="index", conditions=dict(method=["GET"]))
266 action="index", conditions=dict(method=["GET"]))
267 m.connect("formatted_users_groups", "/users_groups.{format}",
267 m.connect("formatted_users_groups", "/users_groups.{format}",
268 action="index", conditions=dict(method=["GET"]))
268 action="index", conditions=dict(method=["GET"]))
269 m.connect("new_users_group", "/users_groups/new",
269 m.connect("new_users_group", "/users_groups/new",
270 action="new", conditions=dict(method=["GET"]))
270 action="new", conditions=dict(method=["GET"]))
271 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
271 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
272 action="new", conditions=dict(method=["GET"]))
272 action="new", conditions=dict(method=["GET"]))
273 m.connect("update_users_group", "/users_groups/{id}",
273 m.connect("update_users_group", "/users_groups/{id}",
274 action="update", conditions=dict(method=["PUT"]))
274 action="update", conditions=dict(method=["PUT"]))
275 m.connect("delete_users_group", "/users_groups/{id}",
275 m.connect("delete_users_group", "/users_groups/{id}",
276 action="delete", conditions=dict(method=["DELETE"]))
276 action="delete", conditions=dict(method=["DELETE"]))
277 m.connect("edit_users_group", "/users_groups/{id}/edit",
277 m.connect("edit_users_group", "/users_groups/{id}/edit",
278 action="edit", conditions=dict(method=["GET"]),
278 action="edit", conditions=dict(method=["GET"]),
279 function=check_user_group)
279 function=check_user_group)
280 m.connect("formatted_edit_users_group",
280 m.connect("formatted_edit_users_group",
281 "/users_groups/{id}.{format}/edit",
281 "/users_groups/{id}.{format}/edit",
282 action="edit", conditions=dict(method=["GET"]))
282 action="edit", conditions=dict(method=["GET"]))
283 m.connect("users_group", "/users_groups/{id}",
283 m.connect("users_group", "/users_groups/{id}",
284 action="show", conditions=dict(method=["GET"]))
284 action="show", conditions=dict(method=["GET"]))
285 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
285 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
286 action="show", conditions=dict(method=["GET"]))
286 action="show", conditions=dict(method=["GET"]))
287
287
288 #EXTRAS USER ROUTES
288 #EXTRAS USER ROUTES
289 # update
289 # update
290 m.connect("users_group_perm", "/users_groups/{id}/update_global_perm",
290 m.connect("users_group_perm", "/users_groups/{id}/update_global_perm",
291 action="update_perm", conditions=dict(method=["PUT"]))
291 action="update_perm", conditions=dict(method=["PUT"]))
292
292
293 #add user group perm member
293 #add user group perm member
294 m.connect('set_user_group_perm_member', "/users_groups/{id}/grant_perm",
294 m.connect('set_user_group_perm_member', "/users_groups/{id}/grant_perm",
295 action="set_user_group_perm_member",
295 action="set_user_group_perm_member",
296 conditions=dict(method=["POST"]))
296 conditions=dict(method=["POST"]))
297
297
298 #ajax delete user group perm
298 #ajax delete user group perm
299 m.connect('delete_user_group_perm_member', "/users_groups/{id}/revoke_perm",
299 m.connect('delete_user_group_perm_member', "/users_groups/{id}/revoke_perm",
300 action="delete_user_group_perm_member",
300 action="delete_user_group_perm_member",
301 conditions=dict(method=["DELETE"]))
301 conditions=dict(method=["DELETE"]))
302
302
303 #ADMIN GROUP REST ROUTES
303 #ADMIN GROUP REST ROUTES
304 rmap.resource('group', 'groups',
304 rmap.resource('group', 'groups',
305 controller='admin/groups', path_prefix=ADMIN_PREFIX)
305 controller='admin/groups', path_prefix=ADMIN_PREFIX)
306
306
307 #ADMIN PERMISSIONS REST ROUTES
307 #ADMIN PERMISSIONS REST ROUTES
308 rmap.resource('permission', 'permissions',
308 rmap.resource('permission', 'permissions',
309 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
309 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
310
310
311 #ADMIN DEFAULTS REST ROUTES
311 #ADMIN DEFAULTS REST ROUTES
312 rmap.resource('default', 'defaults',
312 rmap.resource('default', 'defaults',
313 controller='admin/defaults', path_prefix=ADMIN_PREFIX)
313 controller='admin/defaults', path_prefix=ADMIN_PREFIX)
314
314
315 ##ADMIN LDAP SETTINGS
315 ##ADMIN LDAP SETTINGS
316 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
316 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
317 controller='admin/ldap_settings', action='ldap_settings',
317 controller='admin/ldap_settings', action='ldap_settings',
318 conditions=dict(method=["POST"]))
318 conditions=dict(method=["POST"]))
319
319
320 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
320 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
321 controller='admin/ldap_settings')
321 controller='admin/ldap_settings')
322
322
323 #ADMIN SETTINGS REST ROUTES
323 #ADMIN SETTINGS REST ROUTES
324 with rmap.submapper(path_prefix=ADMIN_PREFIX,
324 with rmap.submapper(path_prefix=ADMIN_PREFIX,
325 controller='admin/settings') as m:
325 controller='admin/settings') as m:
326 m.connect("admin_settings", "/settings",
326 m.connect("admin_settings", "/settings",
327 action="create", conditions=dict(method=["POST"]))
327 action="create", conditions=dict(method=["POST"]))
328 m.connect("admin_settings", "/settings",
328 m.connect("admin_settings", "/settings",
329 action="index", conditions=dict(method=["GET"]))
329 action="index", conditions=dict(method=["GET"]))
330 m.connect("formatted_admin_settings", "/settings.{format}",
330 m.connect("formatted_admin_settings", "/settings.{format}",
331 action="index", conditions=dict(method=["GET"]))
331 action="index", conditions=dict(method=["GET"]))
332 m.connect("admin_new_setting", "/settings/new",
332 m.connect("admin_new_setting", "/settings/new",
333 action="new", conditions=dict(method=["GET"]))
333 action="new", conditions=dict(method=["GET"]))
334 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
334 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
335 action="new", conditions=dict(method=["GET"]))
335 action="new", conditions=dict(method=["GET"]))
336 m.connect("/settings/{setting_id}",
336 m.connect("/settings/{setting_id}",
337 action="update", conditions=dict(method=["PUT"]))
337 action="update", conditions=dict(method=["PUT"]))
338 m.connect("/settings/{setting_id}",
338 m.connect("/settings/{setting_id}",
339 action="delete", conditions=dict(method=["DELETE"]))
339 action="delete", conditions=dict(method=["DELETE"]))
340 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
340 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
341 action="edit", conditions=dict(method=["GET"]))
341 action="edit", conditions=dict(method=["GET"]))
342 m.connect("formatted_admin_edit_setting",
342 m.connect("formatted_admin_edit_setting",
343 "/settings/{setting_id}.{format}/edit",
343 "/settings/{setting_id}.{format}/edit",
344 action="edit", conditions=dict(method=["GET"]))
344 action="edit", conditions=dict(method=["GET"]))
345 m.connect("admin_setting", "/settings/{setting_id}",
345 m.connect("admin_setting", "/settings/{setting_id}",
346 action="show", conditions=dict(method=["GET"]))
346 action="show", conditions=dict(method=["GET"]))
347 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
347 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
348 action="show", conditions=dict(method=["GET"]))
348 action="show", conditions=dict(method=["GET"]))
349 m.connect("admin_settings_my_account", "/my_account",
349 m.connect("admin_settings_my_account", "/my_account",
350 action="my_account", conditions=dict(method=["GET"]))
350 action="my_account", conditions=dict(method=["GET"]))
351 m.connect("admin_settings_my_account_update", "/my_account_update",
351 m.connect("admin_settings_my_account_update", "/my_account_update",
352 action="my_account_update", conditions=dict(method=["PUT"]))
352 action="my_account_update", conditions=dict(method=["PUT"]))
353 m.connect("admin_settings_my_repos", "/my_account/repos",
353 m.connect("admin_settings_my_repos", "/my_account/repos",
354 action="my_account_my_repos", conditions=dict(method=["GET"]))
354 action="my_account_my_repos", conditions=dict(method=["GET"]))
355 m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
355 m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
356 action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
356 action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
357
357
358 #NOTIFICATION REST ROUTES
358 #NOTIFICATION REST ROUTES
359 with rmap.submapper(path_prefix=ADMIN_PREFIX,
359 with rmap.submapper(path_prefix=ADMIN_PREFIX,
360 controller='admin/notifications') as m:
360 controller='admin/notifications') as m:
361 m.connect("notifications", "/notifications",
361 m.connect("notifications", "/notifications",
362 action="create", conditions=dict(method=["POST"]))
362 action="create", conditions=dict(method=["POST"]))
363 m.connect("notifications", "/notifications",
363 m.connect("notifications", "/notifications",
364 action="index", conditions=dict(method=["GET"]))
364 action="index", conditions=dict(method=["GET"]))
365 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
365 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
366 action="mark_all_read", conditions=dict(method=["GET"]))
366 action="mark_all_read", conditions=dict(method=["GET"]))
367 m.connect("formatted_notifications", "/notifications.{format}",
367 m.connect("formatted_notifications", "/notifications.{format}",
368 action="index", conditions=dict(method=["GET"]))
368 action="index", conditions=dict(method=["GET"]))
369 m.connect("new_notification", "/notifications/new",
369 m.connect("new_notification", "/notifications/new",
370 action="new", conditions=dict(method=["GET"]))
370 action="new", conditions=dict(method=["GET"]))
371 m.connect("formatted_new_notification", "/notifications/new.{format}",
371 m.connect("formatted_new_notification", "/notifications/new.{format}",
372 action="new", conditions=dict(method=["GET"]))
372 action="new", conditions=dict(method=["GET"]))
373 m.connect("/notification/{notification_id}",
373 m.connect("/notification/{notification_id}",
374 action="update", conditions=dict(method=["PUT"]))
374 action="update", conditions=dict(method=["PUT"]))
375 m.connect("/notification/{notification_id}",
375 m.connect("/notification/{notification_id}",
376 action="delete", conditions=dict(method=["DELETE"]))
376 action="delete", conditions=dict(method=["DELETE"]))
377 m.connect("edit_notification", "/notification/{notification_id}/edit",
377 m.connect("edit_notification", "/notification/{notification_id}/edit",
378 action="edit", conditions=dict(method=["GET"]))
378 action="edit", conditions=dict(method=["GET"]))
379 m.connect("formatted_edit_notification",
379 m.connect("formatted_edit_notification",
380 "/notification/{notification_id}.{format}/edit",
380 "/notification/{notification_id}.{format}/edit",
381 action="edit", conditions=dict(method=["GET"]))
381 action="edit", conditions=dict(method=["GET"]))
382 m.connect("notification", "/notification/{notification_id}",
382 m.connect("notification", "/notification/{notification_id}",
383 action="show", conditions=dict(method=["GET"]))
383 action="show", conditions=dict(method=["GET"]))
384 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
384 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
385 action="show", conditions=dict(method=["GET"]))
385 action="show", conditions=dict(method=["GET"]))
386
386
387 #ADMIN MAIN PAGES
387 #ADMIN MAIN PAGES
388 with rmap.submapper(path_prefix=ADMIN_PREFIX,
388 with rmap.submapper(path_prefix=ADMIN_PREFIX,
389 controller='admin/admin') as m:
389 controller='admin/admin') as m:
390 m.connect('admin_home', '', action='index')
390 m.connect('admin_home', '', action='index')
391 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
391 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
392 action='add_repo')
392 action='add_repo')
393
393
394 #ADMIN GIST
395 rmap.resource('gist', 'gists', controller='admin/gists',
396 path_prefix=ADMIN_PREFIX)
394 #==========================================================================
397 #==========================================================================
395 # API V2
398 # API V2
396 #==========================================================================
399 #==========================================================================
397 with rmap.submapper(path_prefix=ADMIN_PREFIX,
400 with rmap.submapper(path_prefix=ADMIN_PREFIX,
398 controller='api/api') as m:
401 controller='api/api') as m:
399 m.connect('api', '/api')
402 m.connect('api', '/api')
400
403
401 #USER JOURNAL
404 #USER JOURNAL
402 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
405 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
403 controller='journal', action='index')
406 controller='journal', action='index')
404 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
407 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
405 controller='journal', action='journal_rss')
408 controller='journal', action='journal_rss')
406 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
409 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
407 controller='journal', action='journal_atom')
410 controller='journal', action='journal_atom')
408
411
409 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
412 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
410 controller='journal', action="public_journal")
413 controller='journal', action="public_journal")
411
414
412 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
415 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
413 controller='journal', action="public_journal_rss")
416 controller='journal', action="public_journal_rss")
414
417
415 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
418 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
416 controller='journal', action="public_journal_rss")
419 controller='journal', action="public_journal_rss")
417
420
418 rmap.connect('public_journal_atom',
421 rmap.connect('public_journal_atom',
419 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
422 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
420 action="public_journal_atom")
423 action="public_journal_atom")
421
424
422 rmap.connect('public_journal_atom_old',
425 rmap.connect('public_journal_atom_old',
423 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
426 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
424 action="public_journal_atom")
427 action="public_journal_atom")
425
428
426 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
429 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
427 controller='journal', action='toggle_following',
430 controller='journal', action='toggle_following',
428 conditions=dict(method=["POST"]))
431 conditions=dict(method=["POST"]))
429
432
430 #SEARCH
433 #SEARCH
431 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
434 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
432 rmap.connect('search_repo_admin', '%s/search/{repo_name:.*}' % ADMIN_PREFIX,
435 rmap.connect('search_repo_admin', '%s/search/{repo_name:.*}' % ADMIN_PREFIX,
433 controller='search',
436 controller='search',
434 conditions=dict(function=check_repo))
437 conditions=dict(function=check_repo))
435 rmap.connect('search_repo', '/{repo_name:.*?}/search',
438 rmap.connect('search_repo', '/{repo_name:.*?}/search',
436 controller='search',
439 controller='search',
437 conditions=dict(function=check_repo),
440 conditions=dict(function=check_repo),
438 )
441 )
439
442
440 #LOGIN/LOGOUT/REGISTER/SIGN IN
443 #LOGIN/LOGOUT/REGISTER/SIGN IN
441 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
444 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
442 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
445 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
443 action='logout')
446 action='logout')
444
447
445 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
448 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
446 action='register')
449 action='register')
447
450
448 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
451 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
449 controller='login', action='password_reset')
452 controller='login', action='password_reset')
450
453
451 rmap.connect('reset_password_confirmation',
454 rmap.connect('reset_password_confirmation',
452 '%s/password_reset_confirmation' % ADMIN_PREFIX,
455 '%s/password_reset_confirmation' % ADMIN_PREFIX,
453 controller='login', action='password_reset_confirmation')
456 controller='login', action='password_reset_confirmation')
454
457
455 #FEEDS
458 #FEEDS
456 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
459 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
457 controller='feed', action='rss',
460 controller='feed', action='rss',
458 conditions=dict(function=check_repo))
461 conditions=dict(function=check_repo))
459
462
460 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
463 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
461 controller='feed', action='atom',
464 controller='feed', action='atom',
462 conditions=dict(function=check_repo))
465 conditions=dict(function=check_repo))
463
466
464 #==========================================================================
467 #==========================================================================
465 # REPOSITORY ROUTES
468 # REPOSITORY ROUTES
466 #==========================================================================
469 #==========================================================================
467 rmap.connect('summary_home', '/{repo_name:.*?}',
470 rmap.connect('summary_home', '/{repo_name:.*?}',
468 controller='summary',
471 controller='summary',
469 conditions=dict(function=check_repo))
472 conditions=dict(function=check_repo))
470
473
471 rmap.connect('repo_size', '/{repo_name:.*?}/repo_size',
474 rmap.connect('repo_size', '/{repo_name:.*?}/repo_size',
472 controller='summary', action='repo_size',
475 controller='summary', action='repo_size',
473 conditions=dict(function=check_repo))
476 conditions=dict(function=check_repo))
474
477
475 rmap.connect('repos_group_home', '/{group_name:.*}',
478 rmap.connect('repos_group_home', '/{group_name:.*}',
476 controller='admin/repos_groups', action="show_by_name",
479 controller='admin/repos_groups', action="show_by_name",
477 conditions=dict(function=check_group))
480 conditions=dict(function=check_group))
478
481
479 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
482 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
480 controller='changeset', revision='tip',
483 controller='changeset', revision='tip',
481 conditions=dict(function=check_repo))
484 conditions=dict(function=check_repo))
482
485
483 # no longer user, but kept for routes to work
486 # no longer user, but kept for routes to work
484 rmap.connect("_edit_repo", "/{repo_name:.*?}/edit",
487 rmap.connect("_edit_repo", "/{repo_name:.*?}/edit",
485 controller='admin/repos', action="edit",
488 controller='admin/repos', action="edit",
486 conditions=dict(method=["GET"], function=check_repo)
489 conditions=dict(method=["GET"], function=check_repo)
487 )
490 )
488
491
489 rmap.connect("edit_repo", "/{repo_name:.*?}/settings",
492 rmap.connect("edit_repo", "/{repo_name:.*?}/settings",
490 controller='admin/repos', action="edit",
493 controller='admin/repos', action="edit",
491 conditions=dict(method=["GET"], function=check_repo)
494 conditions=dict(method=["GET"], function=check_repo)
492 )
495 )
493
496
494 #still working url for backward compat.
497 #still working url for backward compat.
495 rmap.connect('raw_changeset_home_depraced',
498 rmap.connect('raw_changeset_home_depraced',
496 '/{repo_name:.*?}/raw-changeset/{revision}',
499 '/{repo_name:.*?}/raw-changeset/{revision}',
497 controller='changeset', action='changeset_raw',
500 controller='changeset', action='changeset_raw',
498 revision='tip', conditions=dict(function=check_repo))
501 revision='tip', conditions=dict(function=check_repo))
499
502
500 ## new URLs
503 ## new URLs
501 rmap.connect('changeset_raw_home',
504 rmap.connect('changeset_raw_home',
502 '/{repo_name:.*?}/changeset-diff/{revision}',
505 '/{repo_name:.*?}/changeset-diff/{revision}',
503 controller='changeset', action='changeset_raw',
506 controller='changeset', action='changeset_raw',
504 revision='tip', conditions=dict(function=check_repo))
507 revision='tip', conditions=dict(function=check_repo))
505
508
506 rmap.connect('changeset_patch_home',
509 rmap.connect('changeset_patch_home',
507 '/{repo_name:.*?}/changeset-patch/{revision}',
510 '/{repo_name:.*?}/changeset-patch/{revision}',
508 controller='changeset', action='changeset_patch',
511 controller='changeset', action='changeset_patch',
509 revision='tip', conditions=dict(function=check_repo))
512 revision='tip', conditions=dict(function=check_repo))
510
513
511 rmap.connect('changeset_download_home',
514 rmap.connect('changeset_download_home',
512 '/{repo_name:.*?}/changeset-download/{revision}',
515 '/{repo_name:.*?}/changeset-download/{revision}',
513 controller='changeset', action='changeset_download',
516 controller='changeset', action='changeset_download',
514 revision='tip', conditions=dict(function=check_repo))
517 revision='tip', conditions=dict(function=check_repo))
515
518
516 rmap.connect('changeset_comment',
519 rmap.connect('changeset_comment',
517 '/{repo_name:.*?}/changeset/{revision}/comment',
520 '/{repo_name:.*?}/changeset/{revision}/comment',
518 controller='changeset', revision='tip', action='comment',
521 controller='changeset', revision='tip', action='comment',
519 conditions=dict(function=check_repo))
522 conditions=dict(function=check_repo))
520
523
521 rmap.connect('changeset_comment_preview',
524 rmap.connect('changeset_comment_preview',
522 '/{repo_name:.*?}/changeset/comment/preview',
525 '/{repo_name:.*?}/changeset/comment/preview',
523 controller='changeset', action='preview_comment',
526 controller='changeset', action='preview_comment',
524 conditions=dict(function=check_repo, method=["POST"]))
527 conditions=dict(function=check_repo, method=["POST"]))
525
528
526 rmap.connect('changeset_comment_delete',
529 rmap.connect('changeset_comment_delete',
527 '/{repo_name:.*?}/changeset/comment/{comment_id}/delete',
530 '/{repo_name:.*?}/changeset/comment/{comment_id}/delete',
528 controller='changeset', action='delete_comment',
531 controller='changeset', action='delete_comment',
529 conditions=dict(function=check_repo, method=["DELETE"]))
532 conditions=dict(function=check_repo, method=["DELETE"]))
530
533
531 rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
534 rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
532 controller='changeset', action='changeset_info')
535 controller='changeset', action='changeset_info')
533
536
534 rmap.connect('compare_url',
537 rmap.connect('compare_url',
535 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref:.*?}...{other_ref_type}@{other_ref:.*?}',
538 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref:.*?}...{other_ref_type}@{other_ref:.*?}',
536 controller='compare', action='index',
539 controller='compare', action='index',
537 conditions=dict(function=check_repo),
540 conditions=dict(function=check_repo),
538 requirements=dict(
541 requirements=dict(
539 org_ref_type='(branch|book|tag|rev|__other_ref_type__)',
542 org_ref_type='(branch|book|tag|rev|__other_ref_type__)',
540 other_ref_type='(branch|book|tag|rev|__org_ref_type__)')
543 other_ref_type='(branch|book|tag|rev|__org_ref_type__)')
541 )
544 )
542
545
543 rmap.connect('pullrequest_home',
546 rmap.connect('pullrequest_home',
544 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
547 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
545 action='index', conditions=dict(function=check_repo,
548 action='index', conditions=dict(function=check_repo,
546 method=["GET"]))
549 method=["GET"]))
547
550
548 rmap.connect('pullrequest',
551 rmap.connect('pullrequest',
549 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
552 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
550 action='create', conditions=dict(function=check_repo,
553 action='create', conditions=dict(function=check_repo,
551 method=["POST"]))
554 method=["POST"]))
552
555
553 rmap.connect('pullrequest_show',
556 rmap.connect('pullrequest_show',
554 '/{repo_name:.*?}/pull-request/{pull_request_id}',
557 '/{repo_name:.*?}/pull-request/{pull_request_id}',
555 controller='pullrequests',
558 controller='pullrequests',
556 action='show', conditions=dict(function=check_repo,
559 action='show', conditions=dict(function=check_repo,
557 method=["GET"]))
560 method=["GET"]))
558 rmap.connect('pullrequest_update',
561 rmap.connect('pullrequest_update',
559 '/{repo_name:.*?}/pull-request/{pull_request_id}',
562 '/{repo_name:.*?}/pull-request/{pull_request_id}',
560 controller='pullrequests',
563 controller='pullrequests',
561 action='update', conditions=dict(function=check_repo,
564 action='update', conditions=dict(function=check_repo,
562 method=["PUT"]))
565 method=["PUT"]))
563 rmap.connect('pullrequest_delete',
566 rmap.connect('pullrequest_delete',
564 '/{repo_name:.*?}/pull-request/{pull_request_id}',
567 '/{repo_name:.*?}/pull-request/{pull_request_id}',
565 controller='pullrequests',
568 controller='pullrequests',
566 action='delete', conditions=dict(function=check_repo,
569 action='delete', conditions=dict(function=check_repo,
567 method=["DELETE"]))
570 method=["DELETE"]))
568
571
569 rmap.connect('pullrequest_show_all',
572 rmap.connect('pullrequest_show_all',
570 '/{repo_name:.*?}/pull-request',
573 '/{repo_name:.*?}/pull-request',
571 controller='pullrequests',
574 controller='pullrequests',
572 action='show_all', conditions=dict(function=check_repo,
575 action='show_all', conditions=dict(function=check_repo,
573 method=["GET"]))
576 method=["GET"]))
574
577
575 rmap.connect('pullrequest_comment',
578 rmap.connect('pullrequest_comment',
576 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
579 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
577 controller='pullrequests',
580 controller='pullrequests',
578 action='comment', conditions=dict(function=check_repo,
581 action='comment', conditions=dict(function=check_repo,
579 method=["POST"]))
582 method=["POST"]))
580
583
581 rmap.connect('pullrequest_comment_delete',
584 rmap.connect('pullrequest_comment_delete',
582 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
585 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
583 controller='pullrequests', action='delete_comment',
586 controller='pullrequests', action='delete_comment',
584 conditions=dict(function=check_repo, method=["DELETE"]))
587 conditions=dict(function=check_repo, method=["DELETE"]))
585
588
586 rmap.connect('summary_home_summary', '/{repo_name:.*?}/summary',
589 rmap.connect('summary_home_summary', '/{repo_name:.*?}/summary',
587 controller='summary', conditions=dict(function=check_repo))
590 controller='summary', conditions=dict(function=check_repo))
588
591
589 rmap.connect('branches_home', '/{repo_name:.*?}/branches',
592 rmap.connect('branches_home', '/{repo_name:.*?}/branches',
590 controller='branches', conditions=dict(function=check_repo))
593 controller='branches', conditions=dict(function=check_repo))
591
594
592 rmap.connect('tags_home', '/{repo_name:.*?}/tags',
595 rmap.connect('tags_home', '/{repo_name:.*?}/tags',
593 controller='tags', conditions=dict(function=check_repo))
596 controller='tags', conditions=dict(function=check_repo))
594
597
595 rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
598 rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
596 controller='bookmarks', conditions=dict(function=check_repo))
599 controller='bookmarks', conditions=dict(function=check_repo))
597
600
598 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
601 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
599 controller='changelog', conditions=dict(function=check_repo))
602 controller='changelog', conditions=dict(function=check_repo))
600
603
601 rmap.connect('changelog_summary_home', '/{repo_name:.*?}/changelog_summary',
604 rmap.connect('changelog_summary_home', '/{repo_name:.*?}/changelog_summary',
602 controller='changelog', action='changelog_summary',
605 controller='changelog', action='changelog_summary',
603 conditions=dict(function=check_repo))
606 conditions=dict(function=check_repo))
604
607
605 rmap.connect('changelog_file_home', '/{repo_name:.*?}/changelog/{revision}/{f_path:.*}',
608 rmap.connect('changelog_file_home', '/{repo_name:.*?}/changelog/{revision}/{f_path:.*}',
606 controller='changelog', f_path=None,
609 controller='changelog', f_path=None,
607 conditions=dict(function=check_repo))
610 conditions=dict(function=check_repo))
608
611
609 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
612 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
610 controller='changelog', action='changelog_details',
613 controller='changelog', action='changelog_details',
611 conditions=dict(function=check_repo))
614 conditions=dict(function=check_repo))
612
615
613 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
616 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
614 controller='files', revision='tip', f_path='',
617 controller='files', revision='tip', f_path='',
615 conditions=dict(function=check_repo))
618 conditions=dict(function=check_repo))
616
619
617 rmap.connect('files_home_nopath', '/{repo_name:.*?}/files/{revision}',
620 rmap.connect('files_home_nopath', '/{repo_name:.*?}/files/{revision}',
618 controller='files', revision='tip', f_path='',
621 controller='files', revision='tip', f_path='',
619 conditions=dict(function=check_repo))
622 conditions=dict(function=check_repo))
620
623
621 rmap.connect('files_history_home',
624 rmap.connect('files_history_home',
622 '/{repo_name:.*?}/history/{revision}/{f_path:.*}',
625 '/{repo_name:.*?}/history/{revision}/{f_path:.*}',
623 controller='files', action='history', revision='tip', f_path='',
626 controller='files', action='history', revision='tip', f_path='',
624 conditions=dict(function=check_repo))
627 conditions=dict(function=check_repo))
625
628
626 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
629 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
627 controller='files', action='diff', revision='tip', f_path='',
630 controller='files', action='diff', revision='tip', f_path='',
628 conditions=dict(function=check_repo))
631 conditions=dict(function=check_repo))
629
632
630 rmap.connect('files_rawfile_home',
633 rmap.connect('files_rawfile_home',
631 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
634 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
632 controller='files', action='rawfile', revision='tip',
635 controller='files', action='rawfile', revision='tip',
633 f_path='', conditions=dict(function=check_repo))
636 f_path='', conditions=dict(function=check_repo))
634
637
635 rmap.connect('files_raw_home',
638 rmap.connect('files_raw_home',
636 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
639 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
637 controller='files', action='raw', revision='tip', f_path='',
640 controller='files', action='raw', revision='tip', f_path='',
638 conditions=dict(function=check_repo))
641 conditions=dict(function=check_repo))
639
642
640 rmap.connect('files_annotate_home',
643 rmap.connect('files_annotate_home',
641 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
644 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
642 controller='files', action='index', revision='tip',
645 controller='files', action='index', revision='tip',
643 f_path='', annotate=True, conditions=dict(function=check_repo))
646 f_path='', annotate=True, conditions=dict(function=check_repo))
644
647
645 rmap.connect('files_edit_home',
648 rmap.connect('files_edit_home',
646 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
649 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
647 controller='files', action='edit', revision='tip',
650 controller='files', action='edit', revision='tip',
648 f_path='', conditions=dict(function=check_repo))
651 f_path='', conditions=dict(function=check_repo))
649
652
650 rmap.connect('files_add_home',
653 rmap.connect('files_add_home',
651 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
654 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
652 controller='files', action='add', revision='tip',
655 controller='files', action='add', revision='tip',
653 f_path='', conditions=dict(function=check_repo))
656 f_path='', conditions=dict(function=check_repo))
654
657
655 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
658 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
656 controller='files', action='archivefile',
659 controller='files', action='archivefile',
657 conditions=dict(function=check_repo))
660 conditions=dict(function=check_repo))
658
661
659 rmap.connect('files_nodelist_home',
662 rmap.connect('files_nodelist_home',
660 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
663 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
661 controller='files', action='nodelist',
664 controller='files', action='nodelist',
662 conditions=dict(function=check_repo))
665 conditions=dict(function=check_repo))
663
666
664 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
667 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
665 controller='forks', action='fork_create',
668 controller='forks', action='fork_create',
666 conditions=dict(function=check_repo, method=["POST"]))
669 conditions=dict(function=check_repo, method=["POST"]))
667
670
668 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
671 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
669 controller='forks', action='fork',
672 controller='forks', action='fork',
670 conditions=dict(function=check_repo))
673 conditions=dict(function=check_repo))
671
674
672 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
675 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
673 controller='forks', action='forks',
676 controller='forks', action='forks',
674 conditions=dict(function=check_repo))
677 conditions=dict(function=check_repo))
675
678
676 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
679 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
677 controller='followers', action='followers',
680 controller='followers', action='followers',
678 conditions=dict(function=check_repo))
681 conditions=dict(function=check_repo))
679
682
680 return rmap
683 return rmap
@@ -1,1066 +1,1109 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.api
3 rhodecode.controllers.api
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 API controller for RhodeCode
6 API controller for RhodeCode
7
7
8 :created_on: Aug 20, 2011
8 :created_on: Aug 20, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import time
28 import time
29 import traceback
29 import traceback
30 import logging
30 import logging
31
31
32 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
32 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
33 from rhodecode.lib.auth import PasswordGenerator, AuthUser, \
33 from rhodecode.lib.auth import PasswordGenerator, AuthUser, \
34 HasPermissionAllDecorator, HasPermissionAnyDecorator, \
34 HasPermissionAllDecorator, HasPermissionAnyDecorator, \
35 HasPermissionAnyApi, HasRepoPermissionAnyApi
35 HasPermissionAnyApi, HasRepoPermissionAnyApi
36 from rhodecode.lib.utils import map_groups, repo2db_mapper
36 from rhodecode.lib.utils import map_groups, repo2db_mapper
37 from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_int
37 from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_int
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.model.meta import Session
39 from rhodecode.model.meta import Session
40 from rhodecode.model.scm import ScmModel
40 from rhodecode.model.scm import ScmModel
41 from rhodecode.model.repo import RepoModel
41 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
43 from rhodecode.model.users_group import UserGroupModel
43 from rhodecode.model.users_group import UserGroupModel
44 from rhodecode.model.db import Repository, RhodeCodeSetting, UserIpMap,\
44 from rhodecode.model.db import Repository, RhodeCodeSetting, UserIpMap,\
45 Permission, User
45 Permission, User, Gist
46 from rhodecode.lib.compat import json
46 from rhodecode.lib.compat import json
47 from rhodecode.lib.exceptions import DefaultUserException
47 from rhodecode.lib.exceptions import DefaultUserException
48 from rhodecode.model.gist import GistModel
48
49
49 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
50
51
51
52
52 class OptionalAttr(object):
53 class OptionalAttr(object):
53 """
54 """
54 Special Optional Option that defines other attribute
55 Special Optional Option that defines other attribute
55 """
56 """
56 def __init__(self, attr_name):
57 def __init__(self, attr_name):
57 self.attr_name = attr_name
58 self.attr_name = attr_name
58
59
59 def __repr__(self):
60 def __repr__(self):
60 return '<OptionalAttr:%s>' % self.attr_name
61 return '<OptionalAttr:%s>' % self.attr_name
61
62
62 def __call__(self):
63 def __call__(self):
63 return self
64 return self
64 #alias
65 #alias
65 OAttr = OptionalAttr
66 OAttr = OptionalAttr
66
67
67
68
68 class Optional(object):
69 class Optional(object):
69 """
70 """
70 Defines an optional parameter::
71 Defines an optional parameter::
71
72
72 param = param.getval() if isinstance(param, Optional) else param
73 param = param.getval() if isinstance(param, Optional) else param
73 param = param() if isinstance(param, Optional) else param
74 param = param() if isinstance(param, Optional) else param
74
75
75 is equivalent of::
76 is equivalent of::
76
77
77 param = Optional.extract(param)
78 param = Optional.extract(param)
78
79
79 """
80 """
80 def __init__(self, type_):
81 def __init__(self, type_):
81 self.type_ = type_
82 self.type_ = type_
82
83
83 def __repr__(self):
84 def __repr__(self):
84 return '<Optional:%s>' % self.type_.__repr__()
85 return '<Optional:%s>' % self.type_.__repr__()
85
86
86 def __call__(self):
87 def __call__(self):
87 return self.getval()
88 return self.getval()
88
89
89 def getval(self):
90 def getval(self):
90 """
91 """
91 returns value from this Optional instance
92 returns value from this Optional instance
92 """
93 """
93 return self.type_
94 return self.type_
94
95
95 @classmethod
96 @classmethod
96 def extract(cls, val):
97 def extract(cls, val):
97 if isinstance(val, cls):
98 if isinstance(val, cls):
98 return val.getval()
99 return val.getval()
99 return val
100 return val
100
101
101
102
102 def get_user_or_error(userid):
103 def get_user_or_error(userid):
103 """
104 """
104 Get user by id or name or return JsonRPCError if not found
105 Get user by id or name or return JsonRPCError if not found
105
106
106 :param userid:
107 :param userid:
107 """
108 """
108 user = UserModel().get_user(userid)
109 user = UserModel().get_user(userid)
109 if user is None:
110 if user is None:
110 raise JSONRPCError("user `%s` does not exist" % userid)
111 raise JSONRPCError("user `%s` does not exist" % userid)
111 return user
112 return user
112
113
113
114
114 def get_repo_or_error(repoid):
115 def get_repo_or_error(repoid):
115 """
116 """
116 Get repo by id or name or return JsonRPCError if not found
117 Get repo by id or name or return JsonRPCError if not found
117
118
118 :param userid:
119 :param userid:
119 """
120 """
120 repo = RepoModel().get_repo(repoid)
121 repo = RepoModel().get_repo(repoid)
121 if repo is None:
122 if repo is None:
122 raise JSONRPCError('repository `%s` does not exist' % (repoid))
123 raise JSONRPCError('repository `%s` does not exist' % (repoid))
123 return repo
124 return repo
124
125
125
126
126 def get_users_group_or_error(usersgroupid):
127 def get_users_group_or_error(usersgroupid):
127 """
128 """
128 Get user group by id or name or return JsonRPCError if not found
129 Get user group by id or name or return JsonRPCError if not found
129
130
130 :param userid:
131 :param userid:
131 """
132 """
132 users_group = UserGroupModel().get_group(usersgroupid)
133 users_group = UserGroupModel().get_group(usersgroupid)
133 if users_group is None:
134 if users_group is None:
134 raise JSONRPCError('user group `%s` does not exist' % usersgroupid)
135 raise JSONRPCError('user group `%s` does not exist' % usersgroupid)
135 return users_group
136 return users_group
136
137
137
138
138 def get_perm_or_error(permid):
139 def get_perm_or_error(permid):
139 """
140 """
140 Get permission by id or name or return JsonRPCError if not found
141 Get permission by id or name or return JsonRPCError if not found
141
142
142 :param userid:
143 :param userid:
143 """
144 """
144 perm = Permission.get_by_key(permid)
145 perm = Permission.get_by_key(permid)
145 if perm is None:
146 if perm is None:
146 raise JSONRPCError('permission `%s` does not exist' % (permid))
147 raise JSONRPCError('permission `%s` does not exist' % (permid))
147 return perm
148 return perm
148
149
149
150
150 class ApiController(JSONRPCController):
151 class ApiController(JSONRPCController):
151 """
152 """
152 API Controller
153 API Controller
153
154
154
155
155 Each method needs to have USER as argument this is then based on given
156 Each method needs to have USER as argument this is then based on given
156 API_KEY propagated as instance of user object
157 API_KEY propagated as instance of user object
157
158
158 Preferably this should be first argument also
159 Preferably this should be first argument also
159
160
160
161
161 Each function should also **raise** JSONRPCError for any
162 Each function should also **raise** JSONRPCError for any
162 errors that happens
163 errors that happens
163
164
164 """
165 """
165
166
166 @HasPermissionAllDecorator('hg.admin')
167 @HasPermissionAllDecorator('hg.admin')
167 def pull(self, apiuser, repoid):
168 def pull(self, apiuser, repoid):
168 """
169 """
169 Dispatch pull action on given repo
170 Dispatch pull action on given repo
170
171
171 :param apiuser:
172 :param apiuser:
172 :param repoid:
173 :param repoid:
173 """
174 """
174
175
175 repo = get_repo_or_error(repoid)
176 repo = get_repo_or_error(repoid)
176
177
177 try:
178 try:
178 ScmModel().pull_changes(repo.repo_name,
179 ScmModel().pull_changes(repo.repo_name,
179 self.rhodecode_user.username)
180 self.rhodecode_user.username)
180 return 'Pulled from `%s`' % repo.repo_name
181 return 'Pulled from `%s`' % repo.repo_name
181 except Exception:
182 except Exception:
182 log.error(traceback.format_exc())
183 log.error(traceback.format_exc())
183 raise JSONRPCError(
184 raise JSONRPCError(
184 'Unable to pull changes from `%s`' % repo.repo_name
185 'Unable to pull changes from `%s`' % repo.repo_name
185 )
186 )
186
187
187 @HasPermissionAllDecorator('hg.admin')
188 @HasPermissionAllDecorator('hg.admin')
188 def rescan_repos(self, apiuser, remove_obsolete=Optional(False)):
189 def rescan_repos(self, apiuser, remove_obsolete=Optional(False)):
189 """
190 """
190 Dispatch rescan repositories action. If remove_obsolete is set
191 Dispatch rescan repositories action. If remove_obsolete is set
191 than also delete repos that are in database but not in the filesystem.
192 than also delete repos that are in database but not in the filesystem.
192 aka "clean zombies"
193 aka "clean zombies"
193
194
194 :param apiuser:
195 :param apiuser:
195 :param remove_obsolete:
196 :param remove_obsolete:
196 """
197 """
197
198
198 try:
199 try:
199 rm_obsolete = Optional.extract(remove_obsolete)
200 rm_obsolete = Optional.extract(remove_obsolete)
200 added, removed = repo2db_mapper(ScmModel().repo_scan(),
201 added, removed = repo2db_mapper(ScmModel().repo_scan(),
201 remove_obsolete=rm_obsolete)
202 remove_obsolete=rm_obsolete)
202 return {'added': added, 'removed': removed}
203 return {'added': added, 'removed': removed}
203 except Exception:
204 except Exception:
204 log.error(traceback.format_exc())
205 log.error(traceback.format_exc())
205 raise JSONRPCError(
206 raise JSONRPCError(
206 'Error occurred during rescan repositories action'
207 'Error occurred during rescan repositories action'
207 )
208 )
208
209
209 def invalidate_cache(self, apiuser, repoid):
210 def invalidate_cache(self, apiuser, repoid):
210 """
211 """
211 Dispatch cache invalidation action on given repo
212 Dispatch cache invalidation action on given repo
212
213
213 :param apiuser:
214 :param apiuser:
214 :param repoid:
215 :param repoid:
215 """
216 """
216 repo = get_repo_or_error(repoid)
217 repo = get_repo_or_error(repoid)
217 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
218 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
218 # check if we have admin permission for this repo !
219 # check if we have admin permission for this repo !
219 if HasRepoPermissionAnyApi('repository.admin',
220 if HasRepoPermissionAnyApi('repository.admin',
220 'repository.write')(user=apiuser,
221 'repository.write')(user=apiuser,
221 repo_name=repo.repo_name) is False:
222 repo_name=repo.repo_name) is False:
222 raise JSONRPCError('repository `%s` does not exist' % (repoid))
223 raise JSONRPCError('repository `%s` does not exist' % (repoid))
223
224
224 try:
225 try:
225 ScmModel().mark_for_invalidation(repo.repo_name)
226 ScmModel().mark_for_invalidation(repo.repo_name)
226 return ('Caches of repository `%s` was invalidated' % repoid)
227 return ('Caches of repository `%s` was invalidated' % repoid)
227 except Exception:
228 except Exception:
228 log.error(traceback.format_exc())
229 log.error(traceback.format_exc())
229 raise JSONRPCError(
230 raise JSONRPCError(
230 'Error occurred during cache invalidation action'
231 'Error occurred during cache invalidation action'
231 )
232 )
232
233
233 def lock(self, apiuser, repoid, locked=Optional(None),
234 def lock(self, apiuser, repoid, locked=Optional(None),
234 userid=Optional(OAttr('apiuser'))):
235 userid=Optional(OAttr('apiuser'))):
235 """
236 """
236 Set locking state on particular repository by given user, if
237 Set locking state on particular repository by given user, if
237 this command is runned by non-admin account userid is set to user
238 this command is runned by non-admin account userid is set to user
238 who is calling this method
239 who is calling this method
239
240
240 :param apiuser:
241 :param apiuser:
241 :param repoid:
242 :param repoid:
242 :param userid:
243 :param userid:
243 :param locked:
244 :param locked:
244 """
245 """
245 repo = get_repo_or_error(repoid)
246 repo = get_repo_or_error(repoid)
246 if HasPermissionAnyApi('hg.admin')(user=apiuser):
247 if HasPermissionAnyApi('hg.admin')(user=apiuser):
247 pass
248 pass
248 elif HasRepoPermissionAnyApi('repository.admin',
249 elif HasRepoPermissionAnyApi('repository.admin',
249 'repository.write')(user=apiuser,
250 'repository.write')(user=apiuser,
250 repo_name=repo.repo_name):
251 repo_name=repo.repo_name):
251 #make sure normal user does not pass someone else userid,
252 #make sure normal user does not pass someone else userid,
252 #he is not allowed to do that
253 #he is not allowed to do that
253 if not isinstance(userid, Optional) and userid != apiuser.user_id:
254 if not isinstance(userid, Optional) and userid != apiuser.user_id:
254 raise JSONRPCError(
255 raise JSONRPCError(
255 'userid is not the same as your user'
256 'userid is not the same as your user'
256 )
257 )
257 else:
258 else:
258 raise JSONRPCError('repository `%s` does not exist' % (repoid))
259 raise JSONRPCError('repository `%s` does not exist' % (repoid))
259
260
260 if isinstance(userid, Optional):
261 if isinstance(userid, Optional):
261 userid = apiuser.user_id
262 userid = apiuser.user_id
262
263
263 user = get_user_or_error(userid)
264 user = get_user_or_error(userid)
264
265
265 if isinstance(locked, Optional):
266 if isinstance(locked, Optional):
266 lockobj = Repository.getlock(repo)
267 lockobj = Repository.getlock(repo)
267
268
268 if lockobj[0] is None:
269 if lockobj[0] is None:
269 _d = {
270 _d = {
270 'repo': repo.repo_name,
271 'repo': repo.repo_name,
271 'locked': False,
272 'locked': False,
272 'locked_since': None,
273 'locked_since': None,
273 'locked_by': None,
274 'locked_by': None,
274 'msg': 'Repo `%s` not locked.' % repo.repo_name
275 'msg': 'Repo `%s` not locked.' % repo.repo_name
275 }
276 }
276 return _d
277 return _d
277 else:
278 else:
278 userid, time_ = lockobj
279 userid, time_ = lockobj
279 lock_user = get_user_or_error(userid)
280 lock_user = get_user_or_error(userid)
280 _d = {
281 _d = {
281 'repo': repo.repo_name,
282 'repo': repo.repo_name,
282 'locked': True,
283 'locked': True,
283 'locked_since': time_,
284 'locked_since': time_,
284 'locked_by': lock_user.username,
285 'locked_by': lock_user.username,
285 'msg': ('Repo `%s` locked by `%s`. '
286 'msg': ('Repo `%s` locked by `%s`. '
286 % (repo.repo_name,
287 % (repo.repo_name,
287 json.dumps(time_to_datetime(time_))))
288 json.dumps(time_to_datetime(time_))))
288 }
289 }
289 return _d
290 return _d
290
291
291 # force locked state through a flag
292 # force locked state through a flag
292 else:
293 else:
293 locked = str2bool(locked)
294 locked = str2bool(locked)
294 try:
295 try:
295 if locked:
296 if locked:
296 lock_time = time.time()
297 lock_time = time.time()
297 Repository.lock(repo, user.user_id, lock_time)
298 Repository.lock(repo, user.user_id, lock_time)
298 else:
299 else:
299 lock_time = None
300 lock_time = None
300 Repository.unlock(repo)
301 Repository.unlock(repo)
301 _d = {
302 _d = {
302 'repo': repo.repo_name,
303 'repo': repo.repo_name,
303 'locked': locked,
304 'locked': locked,
304 'locked_since': lock_time,
305 'locked_since': lock_time,
305 'locked_by': user.username,
306 'locked_by': user.username,
306 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
307 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
307 % (user.username, repo.repo_name, locked))
308 % (user.username, repo.repo_name, locked))
308 }
309 }
309 return _d
310 return _d
310 except Exception:
311 except Exception:
311 log.error(traceback.format_exc())
312 log.error(traceback.format_exc())
312 raise JSONRPCError(
313 raise JSONRPCError(
313 'Error occurred locking repository `%s`' % repo.repo_name
314 'Error occurred locking repository `%s`' % repo.repo_name
314 )
315 )
315
316
316 def get_locks(self, apiuser, userid=Optional(OAttr('apiuser'))):
317 def get_locks(self, apiuser, userid=Optional(OAttr('apiuser'))):
317 """
318 """
318 Get all locks for given userid, if
319 Get all locks for given userid, if
319 this command is runned by non-admin account userid is set to user
320 this command is runned by non-admin account userid is set to user
320 who is calling this method, thus returning locks for himself
321 who is calling this method, thus returning locks for himself
321
322
322 :param apiuser:
323 :param apiuser:
323 :param userid:
324 :param userid:
324 """
325 """
325 if HasPermissionAnyApi('hg.admin')(user=apiuser):
326 if HasPermissionAnyApi('hg.admin')(user=apiuser):
326 pass
327 pass
327 else:
328 else:
328 #make sure normal user does not pass someone else userid,
329 #make sure normal user does not pass someone else userid,
329 #he is not allowed to do that
330 #he is not allowed to do that
330 if not isinstance(userid, Optional) and userid != apiuser.user_id:
331 if not isinstance(userid, Optional) and userid != apiuser.user_id:
331 raise JSONRPCError(
332 raise JSONRPCError(
332 'userid is not the same as your user'
333 'userid is not the same as your user'
333 )
334 )
334 ret = []
335 ret = []
335 if isinstance(userid, Optional):
336 if isinstance(userid, Optional):
336 user = None
337 user = None
337 else:
338 else:
338 user = get_user_or_error(userid)
339 user = get_user_or_error(userid)
339
340
340 #show all locks
341 #show all locks
341 for r in Repository.getAll():
342 for r in Repository.getAll():
342 userid, time_ = r.locked
343 userid, time_ = r.locked
343 if time_:
344 if time_:
344 _api_data = r.get_api_data()
345 _api_data = r.get_api_data()
345 # if we use userfilter just show the locks for this user
346 # if we use userfilter just show the locks for this user
346 if user:
347 if user:
347 if safe_int(userid) == user.user_id:
348 if safe_int(userid) == user.user_id:
348 ret.append(_api_data)
349 ret.append(_api_data)
349 else:
350 else:
350 ret.append(_api_data)
351 ret.append(_api_data)
351
352
352 return ret
353 return ret
353
354
354 @HasPermissionAllDecorator('hg.admin')
355 @HasPermissionAllDecorator('hg.admin')
355 def show_ip(self, apiuser, userid):
356 def show_ip(self, apiuser, userid):
356 """
357 """
357 Shows IP address as seen from RhodeCode server, together with all
358 Shows IP address as seen from RhodeCode server, together with all
358 defined IP addresses for given user
359 defined IP addresses for given user
359
360
360 :param apiuser:
361 :param apiuser:
361 :param userid:
362 :param userid:
362 """
363 """
363 user = get_user_or_error(userid)
364 user = get_user_or_error(userid)
364 ips = UserIpMap.query().filter(UserIpMap.user == user).all()
365 ips = UserIpMap.query().filter(UserIpMap.user == user).all()
365 return dict(
366 return dict(
366 ip_addr_server=self.ip_addr,
367 ip_addr_server=self.ip_addr,
367 user_ips=ips
368 user_ips=ips
368 )
369 )
369
370
370 def get_user(self, apiuser, userid=Optional(OAttr('apiuser'))):
371 def get_user(self, apiuser, userid=Optional(OAttr('apiuser'))):
371 """"
372 """"
372 Get a user by username, or userid, if userid is given
373 Get a user by username, or userid, if userid is given
373
374
374 :param apiuser:
375 :param apiuser:
375 :param userid:
376 :param userid:
376 """
377 """
377 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
378 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
378 #make sure normal user does not pass someone else userid,
379 #make sure normal user does not pass someone else userid,
379 #he is not allowed to do that
380 #he is not allowed to do that
380 if not isinstance(userid, Optional) and userid != apiuser.user_id:
381 if not isinstance(userid, Optional) and userid != apiuser.user_id:
381 raise JSONRPCError(
382 raise JSONRPCError(
382 'userid is not the same as your user'
383 'userid is not the same as your user'
383 )
384 )
384
385
385 if isinstance(userid, Optional):
386 if isinstance(userid, Optional):
386 userid = apiuser.user_id
387 userid = apiuser.user_id
387
388
388 user = get_user_or_error(userid)
389 user = get_user_or_error(userid)
389 data = user.get_api_data()
390 data = user.get_api_data()
390 data['permissions'] = AuthUser(user_id=user.user_id).permissions
391 data['permissions'] = AuthUser(user_id=user.user_id).permissions
391 return data
392 return data
392
393
393 @HasPermissionAllDecorator('hg.admin')
394 @HasPermissionAllDecorator('hg.admin')
394 def get_users(self, apiuser):
395 def get_users(self, apiuser):
395 """"
396 """"
396 Get all users
397 Get all users
397
398
398 :param apiuser:
399 :param apiuser:
399 """
400 """
400
401
401 result = []
402 result = []
402 users_list = User.query().order_by(User.username)\
403 users_list = User.query().order_by(User.username)\
403 .filter(User.username != User.DEFAULT_USER)\
404 .filter(User.username != User.DEFAULT_USER)\
404 .all()
405 .all()
405 for user in users_list:
406 for user in users_list:
406 result.append(user.get_api_data())
407 result.append(user.get_api_data())
407 return result
408 return result
408
409
409 @HasPermissionAllDecorator('hg.admin')
410 @HasPermissionAllDecorator('hg.admin')
410 def create_user(self, apiuser, username, email, password=Optional(None),
411 def create_user(self, apiuser, username, email, password=Optional(None),
411 firstname=Optional(None), lastname=Optional(None),
412 firstname=Optional(None), lastname=Optional(None),
412 active=Optional(True), admin=Optional(False),
413 active=Optional(True), admin=Optional(False),
413 ldap_dn=Optional(None)):
414 ldap_dn=Optional(None)):
414 """
415 """
415 Create new user
416 Create new user
416
417
417 :param apiuser:
418 :param apiuser:
418 :param username:
419 :param username:
419 :param email:
420 :param email:
420 :param password:
421 :param password:
421 :param firstname:
422 :param firstname:
422 :param lastname:
423 :param lastname:
423 :param active:
424 :param active:
424 :param admin:
425 :param admin:
425 :param ldap_dn:
426 :param ldap_dn:
426 """
427 """
427
428
428 if UserModel().get_by_username(username):
429 if UserModel().get_by_username(username):
429 raise JSONRPCError("user `%s` already exist" % username)
430 raise JSONRPCError("user `%s` already exist" % username)
430
431
431 if UserModel().get_by_email(email, case_insensitive=True):
432 if UserModel().get_by_email(email, case_insensitive=True):
432 raise JSONRPCError("email `%s` already exist" % email)
433 raise JSONRPCError("email `%s` already exist" % email)
433
434
434 if Optional.extract(ldap_dn):
435 if Optional.extract(ldap_dn):
435 # generate temporary password if ldap_dn
436 # generate temporary password if ldap_dn
436 password = PasswordGenerator().gen_password(length=8)
437 password = PasswordGenerator().gen_password(length=8)
437
438
438 try:
439 try:
439 user = UserModel().create_or_update(
440 user = UserModel().create_or_update(
440 username=Optional.extract(username),
441 username=Optional.extract(username),
441 password=Optional.extract(password),
442 password=Optional.extract(password),
442 email=Optional.extract(email),
443 email=Optional.extract(email),
443 firstname=Optional.extract(firstname),
444 firstname=Optional.extract(firstname),
444 lastname=Optional.extract(lastname),
445 lastname=Optional.extract(lastname),
445 active=Optional.extract(active),
446 active=Optional.extract(active),
446 admin=Optional.extract(admin),
447 admin=Optional.extract(admin),
447 ldap_dn=Optional.extract(ldap_dn)
448 ldap_dn=Optional.extract(ldap_dn)
448 )
449 )
449 Session().commit()
450 Session().commit()
450 return dict(
451 return dict(
451 msg='created new user `%s`' % username,
452 msg='created new user `%s`' % username,
452 user=user.get_api_data()
453 user=user.get_api_data()
453 )
454 )
454 except Exception:
455 except Exception:
455 log.error(traceback.format_exc())
456 log.error(traceback.format_exc())
456 raise JSONRPCError('failed to create user `%s`' % username)
457 raise JSONRPCError('failed to create user `%s`' % username)
457
458
458 @HasPermissionAllDecorator('hg.admin')
459 @HasPermissionAllDecorator('hg.admin')
459 def update_user(self, apiuser, userid, username=Optional(None),
460 def update_user(self, apiuser, userid, username=Optional(None),
460 email=Optional(None), firstname=Optional(None),
461 email=Optional(None), firstname=Optional(None),
461 lastname=Optional(None), active=Optional(None),
462 lastname=Optional(None), active=Optional(None),
462 admin=Optional(None), ldap_dn=Optional(None),
463 admin=Optional(None), ldap_dn=Optional(None),
463 password=Optional(None)):
464 password=Optional(None)):
464 """
465 """
465 Updates given user
466 Updates given user
466
467
467 :param apiuser:
468 :param apiuser:
468 :param userid:
469 :param userid:
469 :param username:
470 :param username:
470 :param email:
471 :param email:
471 :param firstname:
472 :param firstname:
472 :param lastname:
473 :param lastname:
473 :param active:
474 :param active:
474 :param admin:
475 :param admin:
475 :param ldap_dn:
476 :param ldap_dn:
476 :param password:
477 :param password:
477 """
478 """
478
479
479 user = get_user_or_error(userid)
480 user = get_user_or_error(userid)
480
481
481 # call function and store only updated arguments
482 # call function and store only updated arguments
482 updates = {}
483 updates = {}
483
484
484 def store_update(attr, name):
485 def store_update(attr, name):
485 if not isinstance(attr, Optional):
486 if not isinstance(attr, Optional):
486 updates[name] = attr
487 updates[name] = attr
487
488
488 try:
489 try:
489
490
490 store_update(username, 'username')
491 store_update(username, 'username')
491 store_update(password, 'password')
492 store_update(password, 'password')
492 store_update(email, 'email')
493 store_update(email, 'email')
493 store_update(firstname, 'name')
494 store_update(firstname, 'name')
494 store_update(lastname, 'lastname')
495 store_update(lastname, 'lastname')
495 store_update(active, 'active')
496 store_update(active, 'active')
496 store_update(admin, 'admin')
497 store_update(admin, 'admin')
497 store_update(ldap_dn, 'ldap_dn')
498 store_update(ldap_dn, 'ldap_dn')
498
499
499 user = UserModel().update_user(user, **updates)
500 user = UserModel().update_user(user, **updates)
500 Session().commit()
501 Session().commit()
501 return dict(
502 return dict(
502 msg='updated user ID:%s %s' % (user.user_id, user.username),
503 msg='updated user ID:%s %s' % (user.user_id, user.username),
503 user=user.get_api_data()
504 user=user.get_api_data()
504 )
505 )
505 except DefaultUserException:
506 except DefaultUserException:
506 log.error(traceback.format_exc())
507 log.error(traceback.format_exc())
507 raise JSONRPCError('editing default user is forbidden')
508 raise JSONRPCError('editing default user is forbidden')
508 except Exception:
509 except Exception:
509 log.error(traceback.format_exc())
510 log.error(traceback.format_exc())
510 raise JSONRPCError('failed to update user `%s`' % userid)
511 raise JSONRPCError('failed to update user `%s`' % userid)
511
512
512 @HasPermissionAllDecorator('hg.admin')
513 @HasPermissionAllDecorator('hg.admin')
513 def delete_user(self, apiuser, userid):
514 def delete_user(self, apiuser, userid):
514 """"
515 """"
515 Deletes an user
516 Deletes an user
516
517
517 :param apiuser:
518 :param apiuser:
518 :param userid:
519 :param userid:
519 """
520 """
520 user = get_user_or_error(userid)
521 user = get_user_or_error(userid)
521
522
522 try:
523 try:
523 UserModel().delete(userid)
524 UserModel().delete(userid)
524 Session().commit()
525 Session().commit()
525 return dict(
526 return dict(
526 msg='deleted user ID:%s %s' % (user.user_id, user.username),
527 msg='deleted user ID:%s %s' % (user.user_id, user.username),
527 user=None
528 user=None
528 )
529 )
529 except Exception:
530 except Exception:
530 log.error(traceback.format_exc())
531 log.error(traceback.format_exc())
531 raise JSONRPCError('failed to delete ID:%s %s' % (user.user_id,
532 raise JSONRPCError('failed to delete ID:%s %s' % (user.user_id,
532 user.username))
533 user.username))
533
534
534 @HasPermissionAllDecorator('hg.admin')
535 @HasPermissionAllDecorator('hg.admin')
535 def get_users_group(self, apiuser, usersgroupid):
536 def get_users_group(self, apiuser, usersgroupid):
536 """"
537 """"
537 Get user group by name or id
538 Get user group by name or id
538
539
539 :param apiuser:
540 :param apiuser:
540 :param usersgroupid:
541 :param usersgroupid:
541 """
542 """
542 users_group = get_users_group_or_error(usersgroupid)
543 users_group = get_users_group_or_error(usersgroupid)
543
544
544 data = users_group.get_api_data()
545 data = users_group.get_api_data()
545
546
546 members = []
547 members = []
547 for user in users_group.members:
548 for user in users_group.members:
548 user = user.user
549 user = user.user
549 members.append(user.get_api_data())
550 members.append(user.get_api_data())
550 data['members'] = members
551 data['members'] = members
551 return data
552 return data
552
553
553 @HasPermissionAllDecorator('hg.admin')
554 @HasPermissionAllDecorator('hg.admin')
554 def get_users_groups(self, apiuser):
555 def get_users_groups(self, apiuser):
555 """"
556 """"
556 Get all user groups
557 Get all user groups
557
558
558 :param apiuser:
559 :param apiuser:
559 """
560 """
560
561
561 result = []
562 result = []
562 for users_group in UserGroupModel().get_all():
563 for users_group in UserGroupModel().get_all():
563 result.append(users_group.get_api_data())
564 result.append(users_group.get_api_data())
564 return result
565 return result
565
566
566 @HasPermissionAllDecorator('hg.admin')
567 @HasPermissionAllDecorator('hg.admin')
567 def create_users_group(self, apiuser, group_name,
568 def create_users_group(self, apiuser, group_name,
568 owner=Optional(OAttr('apiuser')),
569 owner=Optional(OAttr('apiuser')),
569 active=Optional(True)):
570 active=Optional(True)):
570 """
571 """
571 Creates an new usergroup
572 Creates an new usergroup
572
573
573 :param apiuser:
574 :param apiuser:
574 :param group_name:
575 :param group_name:
575 :param owner:
576 :param owner:
576 :param active:
577 :param active:
577 """
578 """
578
579
579 if UserGroupModel().get_by_name(group_name):
580 if UserGroupModel().get_by_name(group_name):
580 raise JSONRPCError("user group `%s` already exist" % group_name)
581 raise JSONRPCError("user group `%s` already exist" % group_name)
581
582
582 try:
583 try:
583 if isinstance(owner, Optional):
584 if isinstance(owner, Optional):
584 owner = apiuser.user_id
585 owner = apiuser.user_id
585
586
586 owner = get_user_or_error(owner)
587 owner = get_user_or_error(owner)
587 active = Optional.extract(active)
588 active = Optional.extract(active)
588 ug = UserGroupModel().create(name=group_name,
589 ug = UserGroupModel().create(name=group_name,
589 owner=owner,
590 owner=owner,
590 active=active)
591 active=active)
591 Session().commit()
592 Session().commit()
592 return dict(
593 return dict(
593 msg='created new user group `%s`' % group_name,
594 msg='created new user group `%s`' % group_name,
594 users_group=ug.get_api_data()
595 users_group=ug.get_api_data()
595 )
596 )
596 except Exception:
597 except Exception:
597 log.error(traceback.format_exc())
598 log.error(traceback.format_exc())
598 raise JSONRPCError('failed to create group `%s`' % group_name)
599 raise JSONRPCError('failed to create group `%s`' % group_name)
599
600
600 @HasPermissionAllDecorator('hg.admin')
601 @HasPermissionAllDecorator('hg.admin')
601 def add_user_to_users_group(self, apiuser, usersgroupid, userid):
602 def add_user_to_users_group(self, apiuser, usersgroupid, userid):
602 """"
603 """"
603 Add a user to a user group
604 Add a user to a user group
604
605
605 :param apiuser:
606 :param apiuser:
606 :param usersgroupid:
607 :param usersgroupid:
607 :param userid:
608 :param userid:
608 """
609 """
609 user = get_user_or_error(userid)
610 user = get_user_or_error(userid)
610 users_group = get_users_group_or_error(usersgroupid)
611 users_group = get_users_group_or_error(usersgroupid)
611
612
612 try:
613 try:
613 ugm = UserGroupModel().add_user_to_group(users_group, user)
614 ugm = UserGroupModel().add_user_to_group(users_group, user)
614 success = True if ugm != True else False
615 success = True if ugm != True else False
615 msg = 'added member `%s` to user group `%s`' % (
616 msg = 'added member `%s` to user group `%s`' % (
616 user.username, users_group.users_group_name
617 user.username, users_group.users_group_name
617 )
618 )
618 msg = msg if success else 'User is already in that group'
619 msg = msg if success else 'User is already in that group'
619 Session().commit()
620 Session().commit()
620
621
621 return dict(
622 return dict(
622 success=success,
623 success=success,
623 msg=msg
624 msg=msg
624 )
625 )
625 except Exception:
626 except Exception:
626 log.error(traceback.format_exc())
627 log.error(traceback.format_exc())
627 raise JSONRPCError(
628 raise JSONRPCError(
628 'failed to add member to user group `%s`' % (
629 'failed to add member to user group `%s`' % (
629 users_group.users_group_name
630 users_group.users_group_name
630 )
631 )
631 )
632 )
632
633
633 @HasPermissionAllDecorator('hg.admin')
634 @HasPermissionAllDecorator('hg.admin')
634 def remove_user_from_users_group(self, apiuser, usersgroupid, userid):
635 def remove_user_from_users_group(self, apiuser, usersgroupid, userid):
635 """
636 """
636 Remove user from a group
637 Remove user from a group
637
638
638 :param apiuser:
639 :param apiuser:
639 :param usersgroupid:
640 :param usersgroupid:
640 :param userid:
641 :param userid:
641 """
642 """
642 user = get_user_or_error(userid)
643 user = get_user_or_error(userid)
643 users_group = get_users_group_or_error(usersgroupid)
644 users_group = get_users_group_or_error(usersgroupid)
644
645
645 try:
646 try:
646 success = UserGroupModel().remove_user_from_group(users_group,
647 success = UserGroupModel().remove_user_from_group(users_group,
647 user)
648 user)
648 msg = 'removed member `%s` from user group `%s`' % (
649 msg = 'removed member `%s` from user group `%s`' % (
649 user.username, users_group.users_group_name
650 user.username, users_group.users_group_name
650 )
651 )
651 msg = msg if success else "User wasn't in group"
652 msg = msg if success else "User wasn't in group"
652 Session().commit()
653 Session().commit()
653 return dict(success=success, msg=msg)
654 return dict(success=success, msg=msg)
654 except Exception:
655 except Exception:
655 log.error(traceback.format_exc())
656 log.error(traceback.format_exc())
656 raise JSONRPCError(
657 raise JSONRPCError(
657 'failed to remove member from user group `%s`' % (
658 'failed to remove member from user group `%s`' % (
658 users_group.users_group_name
659 users_group.users_group_name
659 )
660 )
660 )
661 )
661
662
662 def get_repo(self, apiuser, repoid):
663 def get_repo(self, apiuser, repoid):
663 """"
664 """"
664 Get repository by name
665 Get repository by name
665
666
666 :param apiuser:
667 :param apiuser:
667 :param repoid:
668 :param repoid:
668 """
669 """
669 repo = get_repo_or_error(repoid)
670 repo = get_repo_or_error(repoid)
670
671
671 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
672 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
672 # check if we have admin permission for this repo !
673 # check if we have admin permission for this repo !
673 if HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
674 if HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
674 repo_name=repo.repo_name) is False:
675 repo_name=repo.repo_name) is False:
675 raise JSONRPCError('repository `%s` does not exist' % (repoid))
676 raise JSONRPCError('repository `%s` does not exist' % (repoid))
676
677
677 members = []
678 members = []
678 followers = []
679 followers = []
679 for user in repo.repo_to_perm:
680 for user in repo.repo_to_perm:
680 perm = user.permission.permission_name
681 perm = user.permission.permission_name
681 user = user.user
682 user = user.user
682 user_data = user.get_api_data()
683 user_data = user.get_api_data()
683 user_data['type'] = "user"
684 user_data['type'] = "user"
684 user_data['permission'] = perm
685 user_data['permission'] = perm
685 members.append(user_data)
686 members.append(user_data)
686
687
687 for users_group in repo.users_group_to_perm:
688 for users_group in repo.users_group_to_perm:
688 perm = users_group.permission.permission_name
689 perm = users_group.permission.permission_name
689 users_group = users_group.users_group
690 users_group = users_group.users_group
690 users_group_data = users_group.get_api_data()
691 users_group_data = users_group.get_api_data()
691 users_group_data['type'] = "users_group"
692 users_group_data['type'] = "users_group"
692 users_group_data['permission'] = perm
693 users_group_data['permission'] = perm
693 members.append(users_group_data)
694 members.append(users_group_data)
694
695
695 for user in repo.followers:
696 for user in repo.followers:
696 followers.append(user.user.get_api_data())
697 followers.append(user.user.get_api_data())
697
698
698 data = repo.get_api_data()
699 data = repo.get_api_data()
699 data['members'] = members
700 data['members'] = members
700 data['followers'] = followers
701 data['followers'] = followers
701 return data
702 return data
702
703
703 def get_repos(self, apiuser):
704 def get_repos(self, apiuser):
704 """"
705 """"
705 Get all repositories
706 Get all repositories
706
707
707 :param apiuser:
708 :param apiuser:
708 """
709 """
709 result = []
710 result = []
710 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
711 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
711 repos = RepoModel().get_all_user_repos(user=apiuser)
712 repos = RepoModel().get_all_user_repos(user=apiuser)
712 else:
713 else:
713 repos = RepoModel().get_all()
714 repos = RepoModel().get_all()
714
715
715 for repo in repos:
716 for repo in repos:
716 result.append(repo.get_api_data())
717 result.append(repo.get_api_data())
717 return result
718 return result
718
719
719 @HasPermissionAllDecorator('hg.admin')
720 @HasPermissionAllDecorator('hg.admin')
720 def get_repo_nodes(self, apiuser, repoid, revision, root_path,
721 def get_repo_nodes(self, apiuser, repoid, revision, root_path,
721 ret_type='all'):
722 ret_type='all'):
722 """
723 """
723 returns a list of nodes and it's children
724 returns a list of nodes and it's children
724 for a given path at given revision. It's possible to specify ret_type
725 for a given path at given revision. It's possible to specify ret_type
725 to show only files or dirs
726 to show only files or dirs
726
727
727 :param apiuser:
728 :param apiuser:
728 :param repoid: name or id of repository
729 :param repoid: name or id of repository
729 :param revision: revision for which listing should be done
730 :param revision: revision for which listing should be done
730 :param root_path: path from which start displaying
731 :param root_path: path from which start displaying
731 :param ret_type: return type 'all|files|dirs' nodes
732 :param ret_type: return type 'all|files|dirs' nodes
732 """
733 """
733 repo = get_repo_or_error(repoid)
734 repo = get_repo_or_error(repoid)
734 try:
735 try:
735 _d, _f = ScmModel().get_nodes(repo, revision, root_path,
736 _d, _f = ScmModel().get_nodes(repo, revision, root_path,
736 flat=False)
737 flat=False)
737 _map = {
738 _map = {
738 'all': _d + _f,
739 'all': _d + _f,
739 'files': _f,
740 'files': _f,
740 'dirs': _d,
741 'dirs': _d,
741 }
742 }
742 return _map[ret_type]
743 return _map[ret_type]
743 except KeyError:
744 except KeyError:
744 raise JSONRPCError('ret_type must be one of %s' % _map.keys())
745 raise JSONRPCError('ret_type must be one of %s' % _map.keys())
745 except Exception:
746 except Exception:
746 log.error(traceback.format_exc())
747 log.error(traceback.format_exc())
747 raise JSONRPCError(
748 raise JSONRPCError(
748 'failed to get repo: `%s` nodes' % repo.repo_name
749 'failed to get repo: `%s` nodes' % repo.repo_name
749 )
750 )
750
751
751 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
752 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
752 def create_repo(self, apiuser, repo_name, owner=Optional(OAttr('apiuser')),
753 def create_repo(self, apiuser, repo_name, owner=Optional(OAttr('apiuser')),
753 repo_type=Optional('hg'),
754 repo_type=Optional('hg'),
754 description=Optional(''), private=Optional(False),
755 description=Optional(''), private=Optional(False),
755 clone_uri=Optional(None), landing_rev=Optional('tip'),
756 clone_uri=Optional(None), landing_rev=Optional('tip'),
756 enable_statistics=Optional(False),
757 enable_statistics=Optional(False),
757 enable_locking=Optional(False),
758 enable_locking=Optional(False),
758 enable_downloads=Optional(False)):
759 enable_downloads=Optional(False)):
759 """
760 """
760 Create repository, if clone_url is given it makes a remote clone
761 Create repository, if clone_url is given it makes a remote clone
761 if repo_name is within a group name the groups will be created
762 if repo_name is within a group name the groups will be created
762 automatically if they aren't present
763 automatically if they aren't present
763
764
764 :param apiuser:
765 :param apiuser:
765 :param repo_name:
766 :param repo_name:
766 :param onwer:
767 :param onwer:
767 :param repo_type:
768 :param repo_type:
768 :param description:
769 :param description:
769 :param private:
770 :param private:
770 :param clone_uri:
771 :param clone_uri:
771 :param landing_rev:
772 :param landing_rev:
772 """
773 """
773 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
774 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
774 if not isinstance(owner, Optional):
775 if not isinstance(owner, Optional):
775 #forbid setting owner for non-admins
776 #forbid setting owner for non-admins
776 raise JSONRPCError(
777 raise JSONRPCError(
777 'Only RhodeCode admin can specify `owner` param'
778 'Only RhodeCode admin can specify `owner` param'
778 )
779 )
779 if isinstance(owner, Optional):
780 if isinstance(owner, Optional):
780 owner = apiuser.user_id
781 owner = apiuser.user_id
781
782
782 owner = get_user_or_error(owner)
783 owner = get_user_or_error(owner)
783
784
784 if RepoModel().get_by_repo_name(repo_name):
785 if RepoModel().get_by_repo_name(repo_name):
785 raise JSONRPCError("repo `%s` already exist" % repo_name)
786 raise JSONRPCError("repo `%s` already exist" % repo_name)
786
787
787 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
788 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
788 if isinstance(private, Optional):
789 if isinstance(private, Optional):
789 private = defs.get('repo_private') or Optional.extract(private)
790 private = defs.get('repo_private') or Optional.extract(private)
790 if isinstance(repo_type, Optional):
791 if isinstance(repo_type, Optional):
791 repo_type = defs.get('repo_type')
792 repo_type = defs.get('repo_type')
792 if isinstance(enable_statistics, Optional):
793 if isinstance(enable_statistics, Optional):
793 enable_statistics = defs.get('repo_enable_statistics')
794 enable_statistics = defs.get('repo_enable_statistics')
794 if isinstance(enable_locking, Optional):
795 if isinstance(enable_locking, Optional):
795 enable_locking = defs.get('repo_enable_locking')
796 enable_locking = defs.get('repo_enable_locking')
796 if isinstance(enable_downloads, Optional):
797 if isinstance(enable_downloads, Optional):
797 enable_downloads = defs.get('repo_enable_downloads')
798 enable_downloads = defs.get('repo_enable_downloads')
798
799
799 clone_uri = Optional.extract(clone_uri)
800 clone_uri = Optional.extract(clone_uri)
800 description = Optional.extract(description)
801 description = Optional.extract(description)
801 landing_rev = Optional.extract(landing_rev)
802 landing_rev = Optional.extract(landing_rev)
802
803
803 try:
804 try:
804 # create structure of groups and return the last group
805 # create structure of groups and return the last group
805 group = map_groups(repo_name)
806 group = map_groups(repo_name)
806
807
807 repo = RepoModel().create_repo(
808 repo = RepoModel().create_repo(
808 repo_name=repo_name,
809 repo_name=repo_name,
809 repo_type=repo_type,
810 repo_type=repo_type,
810 description=description,
811 description=description,
811 owner=owner,
812 owner=owner,
812 private=private,
813 private=private,
813 clone_uri=clone_uri,
814 clone_uri=clone_uri,
814 repos_group=group,
815 repos_group=group,
815 landing_rev=landing_rev,
816 landing_rev=landing_rev,
816 enable_statistics=enable_statistics,
817 enable_statistics=enable_statistics,
817 enable_downloads=enable_downloads,
818 enable_downloads=enable_downloads,
818 enable_locking=enable_locking
819 enable_locking=enable_locking
819 )
820 )
820
821
821 Session().commit()
822 Session().commit()
822 return dict(
823 return dict(
823 msg="Created new repository `%s`" % (repo.repo_name),
824 msg="Created new repository `%s`" % (repo.repo_name),
824 repo=repo.get_api_data()
825 repo=repo.get_api_data()
825 )
826 )
826 except Exception:
827 except Exception:
827 log.error(traceback.format_exc())
828 log.error(traceback.format_exc())
828 raise JSONRPCError('failed to create repository `%s`' % repo_name)
829 raise JSONRPCError('failed to create repository `%s`' % repo_name)
829
830
830 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
831 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
831 def fork_repo(self, apiuser, repoid, fork_name, owner=Optional(OAttr('apiuser')),
832 def fork_repo(self, apiuser, repoid, fork_name, owner=Optional(OAttr('apiuser')),
832 description=Optional(''), copy_permissions=Optional(False),
833 description=Optional(''), copy_permissions=Optional(False),
833 private=Optional(False), landing_rev=Optional('tip')):
834 private=Optional(False), landing_rev=Optional('tip')):
834 repo = get_repo_or_error(repoid)
835 repo = get_repo_or_error(repoid)
835 repo_name = repo.repo_name
836 repo_name = repo.repo_name
836
837
837 _repo = RepoModel().get_by_repo_name(fork_name)
838 _repo = RepoModel().get_by_repo_name(fork_name)
838 if _repo:
839 if _repo:
839 type_ = 'fork' if _repo.fork else 'repo'
840 type_ = 'fork' if _repo.fork else 'repo'
840 raise JSONRPCError("%s `%s` already exist" % (type_, fork_name))
841 raise JSONRPCError("%s `%s` already exist" % (type_, fork_name))
841
842
842 if HasPermissionAnyApi('hg.admin')(user=apiuser):
843 if HasPermissionAnyApi('hg.admin')(user=apiuser):
843 pass
844 pass
844 elif HasRepoPermissionAnyApi('repository.admin',
845 elif HasRepoPermissionAnyApi('repository.admin',
845 'repository.write',
846 'repository.write',
846 'repository.read')(user=apiuser,
847 'repository.read')(user=apiuser,
847 repo_name=repo.repo_name):
848 repo_name=repo.repo_name):
848 if not isinstance(owner, Optional):
849 if not isinstance(owner, Optional):
849 #forbid setting owner for non-admins
850 #forbid setting owner for non-admins
850 raise JSONRPCError(
851 raise JSONRPCError(
851 'Only RhodeCode admin can specify `owner` param'
852 'Only RhodeCode admin can specify `owner` param'
852 )
853 )
853 else:
854 else:
854 raise JSONRPCError('repository `%s` does not exist' % (repoid))
855 raise JSONRPCError('repository `%s` does not exist' % (repoid))
855
856
856 if isinstance(owner, Optional):
857 if isinstance(owner, Optional):
857 owner = apiuser.user_id
858 owner = apiuser.user_id
858
859
859 owner = get_user_or_error(owner)
860 owner = get_user_or_error(owner)
860
861
861 try:
862 try:
862 # create structure of groups and return the last group
863 # create structure of groups and return the last group
863 group = map_groups(fork_name)
864 group = map_groups(fork_name)
864
865
865 form_data = dict(
866 form_data = dict(
866 repo_name=fork_name,
867 repo_name=fork_name,
867 repo_name_full=fork_name,
868 repo_name_full=fork_name,
868 repo_group=group,
869 repo_group=group,
869 repo_type=repo.repo_type,
870 repo_type=repo.repo_type,
870 description=Optional.extract(description),
871 description=Optional.extract(description),
871 private=Optional.extract(private),
872 private=Optional.extract(private),
872 copy_permissions=Optional.extract(copy_permissions),
873 copy_permissions=Optional.extract(copy_permissions),
873 landing_rev=Optional.extract(landing_rev),
874 landing_rev=Optional.extract(landing_rev),
874 update_after_clone=False,
875 update_after_clone=False,
875 fork_parent_id=repo.repo_id,
876 fork_parent_id=repo.repo_id,
876 )
877 )
877 RepoModel().create_fork(form_data, cur_user=owner)
878 RepoModel().create_fork(form_data, cur_user=owner)
878 return dict(
879 return dict(
879 msg='Created fork of `%s` as `%s`' % (repo.repo_name,
880 msg='Created fork of `%s` as `%s`' % (repo.repo_name,
880 fork_name),
881 fork_name),
881 success=True # cannot return the repo data here since fork
882 success=True # cannot return the repo data here since fork
882 # cann be done async
883 # cann be done async
883 )
884 )
884 except Exception:
885 except Exception:
885 log.error(traceback.format_exc())
886 log.error(traceback.format_exc())
886 raise JSONRPCError(
887 raise JSONRPCError(
887 'failed to fork repository `%s` as `%s`' % (repo_name,
888 'failed to fork repository `%s` as `%s`' % (repo_name,
888 fork_name)
889 fork_name)
889 )
890 )
890
891
892 # perms handled inside
891 def delete_repo(self, apiuser, repoid, forks=Optional(None)):
893 def delete_repo(self, apiuser, repoid, forks=Optional(None)):
892 """
894 """
893 Deletes a given repository
895 Deletes a given repository
894
896
895 :param apiuser:
897 :param apiuser:
896 :param repoid:
898 :param repoid:
897 :param forks: detach or delete, what do do with attached forks for repo
899 :param forks: detach or delete, what do do with attached forks for repo
898 """
900 """
899 repo = get_repo_or_error(repoid)
901 repo = get_repo_or_error(repoid)
900
902
901 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
903 if HasPermissionAnyApi('hg.admin')(user=apiuser) is False:
902 # check if we have admin permission for this repo !
904 # check if we have admin permission for this repo !
903 if HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
905 if HasRepoPermissionAnyApi('repository.admin')(user=apiuser,
904 repo_name=repo.repo_name) is False:
906 repo_name=repo.repo_name) is False:
905 raise JSONRPCError('repository `%s` does not exist' % (repoid))
907 raise JSONRPCError('repository `%s` does not exist' % (repoid))
906
908
907 try:
909 try:
908 handle_forks = Optional.extract(forks)
910 handle_forks = Optional.extract(forks)
909 _forks_msg = ''
911 _forks_msg = ''
910 _forks = [f for f in repo.forks]
912 _forks = [f for f in repo.forks]
911 if handle_forks == 'detach':
913 if handle_forks == 'detach':
912 _forks_msg = ' ' + _('Detached %s forks') % len(_forks)
914 _forks_msg = ' ' + _('Detached %s forks') % len(_forks)
913 elif handle_forks == 'delete':
915 elif handle_forks == 'delete':
914 _forks_msg = ' ' + _('Deleted %s forks') % len(_forks)
916 _forks_msg = ' ' + _('Deleted %s forks') % len(_forks)
915 elif _forks:
917 elif _forks:
916 raise JSONRPCError(
918 raise JSONRPCError(
917 'Cannot delete `%s` it still contains attached forks'
919 'Cannot delete `%s` it still contains attached forks'
918 % repo.repo_name
920 % repo.repo_name
919 )
921 )
920
922
921 RepoModel().delete(repo, forks=forks)
923 RepoModel().delete(repo, forks=forks)
922 Session().commit()
924 Session().commit()
923 return dict(
925 return dict(
924 msg='Deleted repository `%s`%s' % (repo.repo_name, _forks_msg),
926 msg='Deleted repository `%s`%s' % (repo.repo_name, _forks_msg),
925 success=True
927 success=True
926 )
928 )
927 except Exception:
929 except Exception:
928 log.error(traceback.format_exc())
930 log.error(traceback.format_exc())
929 raise JSONRPCError(
931 raise JSONRPCError(
930 'failed to delete repository `%s`' % repo.repo_name
932 'failed to delete repository `%s`' % repo.repo_name
931 )
933 )
932
934
933 @HasPermissionAllDecorator('hg.admin')
935 @HasPermissionAllDecorator('hg.admin')
934 def grant_user_permission(self, apiuser, repoid, userid, perm):
936 def grant_user_permission(self, apiuser, repoid, userid, perm):
935 """
937 """
936 Grant permission for user on given repository, or update existing one
938 Grant permission for user on given repository, or update existing one
937 if found
939 if found
938
940
939 :param repoid:
941 :param repoid:
940 :param userid:
942 :param userid:
941 :param perm:
943 :param perm:
942 """
944 """
943 repo = get_repo_or_error(repoid)
945 repo = get_repo_or_error(repoid)
944 user = get_user_or_error(userid)
946 user = get_user_or_error(userid)
945 perm = get_perm_or_error(perm)
947 perm = get_perm_or_error(perm)
946
948
947 try:
949 try:
948
950
949 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
951 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
950
952
951 Session().commit()
953 Session().commit()
952 return dict(
954 return dict(
953 msg='Granted perm: `%s` for user: `%s` in repo: `%s`' % (
955 msg='Granted perm: `%s` for user: `%s` in repo: `%s`' % (
954 perm.permission_name, user.username, repo.repo_name
956 perm.permission_name, user.username, repo.repo_name
955 ),
957 ),
956 success=True
958 success=True
957 )
959 )
958 except Exception:
960 except Exception:
959 log.error(traceback.format_exc())
961 log.error(traceback.format_exc())
960 raise JSONRPCError(
962 raise JSONRPCError(
961 'failed to edit permission for user: `%s` in repo: `%s`' % (
963 'failed to edit permission for user: `%s` in repo: `%s`' % (
962 userid, repoid
964 userid, repoid
963 )
965 )
964 )
966 )
965
967
966 @HasPermissionAllDecorator('hg.admin')
968 @HasPermissionAllDecorator('hg.admin')
967 def revoke_user_permission(self, apiuser, repoid, userid):
969 def revoke_user_permission(self, apiuser, repoid, userid):
968 """
970 """
969 Revoke permission for user on given repository
971 Revoke permission for user on given repository
970
972
971 :param apiuser:
973 :param apiuser:
972 :param repoid:
974 :param repoid:
973 :param userid:
975 :param userid:
974 """
976 """
975
977
976 repo = get_repo_or_error(repoid)
978 repo = get_repo_or_error(repoid)
977 user = get_user_or_error(userid)
979 user = get_user_or_error(userid)
978 try:
980 try:
979
981
980 RepoModel().revoke_user_permission(repo=repo, user=user)
982 RepoModel().revoke_user_permission(repo=repo, user=user)
981
983
982 Session().commit()
984 Session().commit()
983 return dict(
985 return dict(
984 msg='Revoked perm for user: `%s` in repo: `%s`' % (
986 msg='Revoked perm for user: `%s` in repo: `%s`' % (
985 user.username, repo.repo_name
987 user.username, repo.repo_name
986 ),
988 ),
987 success=True
989 success=True
988 )
990 )
989 except Exception:
991 except Exception:
990 log.error(traceback.format_exc())
992 log.error(traceback.format_exc())
991 raise JSONRPCError(
993 raise JSONRPCError(
992 'failed to edit permission for user: `%s` in repo: `%s`' % (
994 'failed to edit permission for user: `%s` in repo: `%s`' % (
993 userid, repoid
995 userid, repoid
994 )
996 )
995 )
997 )
996
998
997 @HasPermissionAllDecorator('hg.admin')
999 @HasPermissionAllDecorator('hg.admin')
998 def grant_users_group_permission(self, apiuser, repoid, usersgroupid,
1000 def grant_users_group_permission(self, apiuser, repoid, usersgroupid,
999 perm):
1001 perm):
1000 """
1002 """
1001 Grant permission for user group on given repository, or update
1003 Grant permission for user group on given repository, or update
1002 existing one if found
1004 existing one if found
1003
1005
1004 :param apiuser:
1006 :param apiuser:
1005 :param repoid:
1007 :param repoid:
1006 :param usersgroupid:
1008 :param usersgroupid:
1007 :param perm:
1009 :param perm:
1008 """
1010 """
1009 repo = get_repo_or_error(repoid)
1011 repo = get_repo_or_error(repoid)
1010 perm = get_perm_or_error(perm)
1012 perm = get_perm_or_error(perm)
1011 users_group = get_users_group_or_error(usersgroupid)
1013 users_group = get_users_group_or_error(usersgroupid)
1012
1014
1013 try:
1015 try:
1014 RepoModel().grant_users_group_permission(repo=repo,
1016 RepoModel().grant_users_group_permission(repo=repo,
1015 group_name=users_group,
1017 group_name=users_group,
1016 perm=perm)
1018 perm=perm)
1017
1019
1018 Session().commit()
1020 Session().commit()
1019 return dict(
1021 return dict(
1020 msg='Granted perm: `%s` for user group: `%s` in '
1022 msg='Granted perm: `%s` for user group: `%s` in '
1021 'repo: `%s`' % (
1023 'repo: `%s`' % (
1022 perm.permission_name, users_group.users_group_name,
1024 perm.permission_name, users_group.users_group_name,
1023 repo.repo_name
1025 repo.repo_name
1024 ),
1026 ),
1025 success=True
1027 success=True
1026 )
1028 )
1027 except Exception:
1029 except Exception:
1028 log.error(traceback.format_exc())
1030 log.error(traceback.format_exc())
1029 raise JSONRPCError(
1031 raise JSONRPCError(
1030 'failed to edit permission for user group: `%s` in '
1032 'failed to edit permission for user group: `%s` in '
1031 'repo: `%s`' % (
1033 'repo: `%s`' % (
1032 usersgroupid, repo.repo_name
1034 usersgroupid, repo.repo_name
1033 )
1035 )
1034 )
1036 )
1035
1037
1036 @HasPermissionAllDecorator('hg.admin')
1038 @HasPermissionAllDecorator('hg.admin')
1037 def revoke_users_group_permission(self, apiuser, repoid, usersgroupid):
1039 def revoke_users_group_permission(self, apiuser, repoid, usersgroupid):
1038 """
1040 """
1039 Revoke permission for user group on given repository
1041 Revoke permission for user group on given repository
1040
1042
1041 :param apiuser:
1043 :param apiuser:
1042 :param repoid:
1044 :param repoid:
1043 :param usersgroupid:
1045 :param usersgroupid:
1044 """
1046 """
1045 repo = get_repo_or_error(repoid)
1047 repo = get_repo_or_error(repoid)
1046 users_group = get_users_group_or_error(usersgroupid)
1048 users_group = get_users_group_or_error(usersgroupid)
1047
1049
1048 try:
1050 try:
1049 RepoModel().revoke_users_group_permission(repo=repo,
1051 RepoModel().revoke_users_group_permission(repo=repo,
1050 group_name=users_group)
1052 group_name=users_group)
1051
1053
1052 Session().commit()
1054 Session().commit()
1053 return dict(
1055 return dict(
1054 msg='Revoked perm for user group: `%s` in repo: `%s`' % (
1056 msg='Revoked perm for user group: `%s` in repo: `%s`' % (
1055 users_group.users_group_name, repo.repo_name
1057 users_group.users_group_name, repo.repo_name
1056 ),
1058 ),
1057 success=True
1059 success=True
1058 )
1060 )
1059 except Exception:
1061 except Exception:
1060 log.error(traceback.format_exc())
1062 log.error(traceback.format_exc())
1061 raise JSONRPCError(
1063 raise JSONRPCError(
1062 'failed to edit permission for user group: `%s` in '
1064 'failed to edit permission for user group: `%s` in '
1063 'repo: `%s`' % (
1065 'repo: `%s`' % (
1064 users_group.users_group_name, repo.repo_name
1066 users_group.users_group_name, repo.repo_name
1065 )
1067 )
1066 )
1068 )
1069
1070 def create_gist(self, apiuser, files, owner=Optional(OAttr('apiuser')),
1071 gist_type=Optional(Gist.GIST_PUBLIC),
1072 gist_lifetime=Optional(-1),
1073 gist_description=Optional('')):
1074
1075 try:
1076 if isinstance(owner, Optional):
1077 owner = apiuser.user_id
1078
1079 owner = get_user_or_error(owner)
1080 description = Optional.extract(gist_description)
1081 gist_type = Optional.extract(gist_type)
1082 gist_lifetime = Optional.extract(gist_lifetime)
1083
1084 # files: {
1085 # 'filename': {'content':'...', 'lexer': null},
1086 # 'filename2': {'content':'...', 'lexer': null}
1087 #}
1088 gist = GistModel().create(description=description,
1089 owner=owner,
1090 gist_mapping=files,
1091 gist_type=gist_type,
1092 lifetime=gist_lifetime)
1093 Session().commit()
1094 return dict(
1095 msg='created new gist',
1096 gist_url=gist.gist_url(),
1097 gist_id=gist.gist_access_id,
1098 gist_type=gist.gist_type,
1099 files=files.keys()
1100 )
1101 except Exception:
1102 log.error(traceback.format_exc())
1103 raise JSONRPCError('failed to create gist')
1104
1105 def update_gist(self, apiuser):
1106 pass
1107
1108 def delete_gist(self, apiuser):
1109 pass
@@ -1,650 +1,658 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.files
3 rhodecode.controllers.files
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Files controller for RhodeCode
6 Files controller for RhodeCode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 from __future__ import with_statement
25 from __future__ import with_statement
26 import os
26 import os
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import tempfile
29 import tempfile
30 import shutil
30 import shutil
31
31
32 from pylons import request, response, tmpl_context as c, url
32 from pylons import request, response, tmpl_context as c, url
33 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
34 from pylons.controllers.util import redirect
34 from pylons.controllers.util import redirect
35 from rhodecode.lib.utils import jsonify
35 from rhodecode.lib.utils import jsonify
36
36
37 from rhodecode.lib import diffs
37 from rhodecode.lib import diffs
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39
39
40 from rhodecode.lib.compat import OrderedDict
40 from rhodecode.lib.compat import OrderedDict
41 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str,\
41 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str,\
42 str2bool
42 str2bool
43 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
43 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
44 from rhodecode.lib.base import BaseRepoController, render
44 from rhodecode.lib.base import BaseRepoController, render
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
46 from rhodecode.lib.vcs.conf import settings
46 from rhodecode.lib.vcs.conf import settings
47 from rhodecode.lib.vcs.exceptions import RepositoryError, \
47 from rhodecode.lib.vcs.exceptions import RepositoryError, \
48 ChangesetDoesNotExistError, EmptyRepositoryError, \
48 ChangesetDoesNotExistError, EmptyRepositoryError, \
49 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,\
49 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,\
50 NodeDoesNotExistError, ChangesetError, NodeError
50 NodeDoesNotExistError, ChangesetError, NodeError
51 from rhodecode.lib.vcs.nodes import FileNode
51 from rhodecode.lib.vcs.nodes import FileNode
52
52
53 from rhodecode.model.repo import RepoModel
53 from rhodecode.model.repo import RepoModel
54 from rhodecode.model.scm import ScmModel
54 from rhodecode.model.scm import ScmModel
55 from rhodecode.model.db import Repository
55 from rhodecode.model.db import Repository
56
56
57 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
57 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
58 _context_url, get_line_ctx, get_ignore_ws
58 _context_url, get_line_ctx, get_ignore_ws
59 from webob.exc import HTTPNotFound
59 from webob.exc import HTTPNotFound
60 from rhodecode.lib.exceptions import NonRelativePathError
60
61
61
62
62 log = logging.getLogger(__name__)
63 log = logging.getLogger(__name__)
63
64
64
65
65 class FilesController(BaseRepoController):
66 class FilesController(BaseRepoController):
66
67
67 def __before__(self):
68 def __before__(self):
68 super(FilesController, self).__before__()
69 super(FilesController, self).__before__()
69 c.cut_off_limit = self.cut_off_limit
70 c.cut_off_limit = self.cut_off_limit
70
71
71 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
72 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
72 """
73 """
73 Safe way to get changeset if error occur it redirects to tip with
74 Safe way to get changeset if error occur it redirects to tip with
74 proper message
75 proper message
75
76
76 :param rev: revision to fetch
77 :param rev: revision to fetch
77 :param repo_name: repo name to redirect after
78 :param repo_name: repo name to redirect after
78 """
79 """
79
80
80 try:
81 try:
81 return c.rhodecode_repo.get_changeset(rev)
82 return c.rhodecode_repo.get_changeset(rev)
82 except EmptyRepositoryError, e:
83 except EmptyRepositoryError, e:
83 if not redirect_after:
84 if not redirect_after:
84 return None
85 return None
85 url_ = url('files_add_home',
86 url_ = url('files_add_home',
86 repo_name=c.repo_name,
87 repo_name=c.repo_name,
87 revision=0, f_path='')
88 revision=0, f_path='')
88 add_new = h.link_to(_('Click here to add new file'), url_)
89 add_new = h.link_to(_('Click here to add new file'), url_)
89 h.flash(h.literal(_('There are no files yet %s') % add_new),
90 h.flash(h.literal(_('There are no files yet %s') % add_new),
90 category='warning')
91 category='warning')
91 redirect(h.url('summary_home', repo_name=repo_name))
92 redirect(h.url('summary_home', repo_name=repo_name))
92
93
93 except RepositoryError, e: # including ChangesetDoesNotExistError
94 except RepositoryError, e: # including ChangesetDoesNotExistError
94 h.flash(str(e), category='error')
95 h.flash(str(e), category='error')
95 raise HTTPNotFound()
96 raise HTTPNotFound()
96
97
97 def __get_filenode_or_redirect(self, repo_name, cs, path):
98 def __get_filenode_or_redirect(self, repo_name, cs, path):
98 """
99 """
99 Returns file_node, if error occurs or given path is directory,
100 Returns file_node, if error occurs or given path is directory,
100 it'll redirect to top level path
101 it'll redirect to top level path
101
102
102 :param repo_name: repo_name
103 :param repo_name: repo_name
103 :param cs: given changeset
104 :param cs: given changeset
104 :param path: path to lookup
105 :param path: path to lookup
105 """
106 """
106
107
107 try:
108 try:
108 file_node = cs.get_node(path)
109 file_node = cs.get_node(path)
109 if file_node.is_dir():
110 if file_node.is_dir():
110 raise RepositoryError('given path is a directory')
111 raise RepositoryError('given path is a directory')
111 except RepositoryError, e:
112 except RepositoryError, e:
112 h.flash(str(e), category='error')
113 h.flash(str(e), category='error')
113 raise HTTPNotFound()
114 raise HTTPNotFound()
114
115
115 return file_node
116 return file_node
116
117
117 @LoginRequired()
118 @LoginRequired()
118 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
119 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
119 'repository.admin')
120 'repository.admin')
120 def index(self, repo_name, revision, f_path, annotate=False):
121 def index(self, repo_name, revision, f_path, annotate=False):
121 # redirect to given revision from form if given
122 # redirect to given revision from form if given
122 post_revision = request.POST.get('at_rev', None)
123 post_revision = request.POST.get('at_rev', None)
123 if post_revision:
124 if post_revision:
124 cs = self.__get_cs_or_redirect(post_revision, repo_name)
125 cs = self.__get_cs_or_redirect(post_revision, repo_name)
125
126
126 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
127 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
127 c.branch = request.GET.get('branch', None)
128 c.branch = request.GET.get('branch', None)
128 c.f_path = f_path
129 c.f_path = f_path
129 c.annotate = annotate
130 c.annotate = annotate
130 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
131 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
131 cur_rev = c.changeset.revision
132 cur_rev = c.changeset.revision
132
133
133 # prev link
134 # prev link
134 try:
135 try:
135 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
136 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
136 c.url_prev = url('files_home', repo_name=c.repo_name,
137 c.url_prev = url('files_home', repo_name=c.repo_name,
137 revision=prev_rev.raw_id, f_path=f_path)
138 revision=prev_rev.raw_id, f_path=f_path)
138 if c.branch:
139 if c.branch:
139 c.url_prev += '?branch=%s' % c.branch
140 c.url_prev += '?branch=%s' % c.branch
140 except (ChangesetDoesNotExistError, VCSError):
141 except (ChangesetDoesNotExistError, VCSError):
141 c.url_prev = '#'
142 c.url_prev = '#'
142
143
143 # next link
144 # next link
144 try:
145 try:
145 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
146 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
146 c.url_next = url('files_home', repo_name=c.repo_name,
147 c.url_next = url('files_home', repo_name=c.repo_name,
147 revision=next_rev.raw_id, f_path=f_path)
148 revision=next_rev.raw_id, f_path=f_path)
148 if c.branch:
149 if c.branch:
149 c.url_next += '?branch=%s' % c.branch
150 c.url_next += '?branch=%s' % c.branch
150 except (ChangesetDoesNotExistError, VCSError):
151 except (ChangesetDoesNotExistError, VCSError):
151 c.url_next = '#'
152 c.url_next = '#'
152
153
153 # files or dirs
154 # files or dirs
154 try:
155 try:
155 c.file = c.changeset.get_node(f_path)
156 c.file = c.changeset.get_node(f_path)
156
157
157 if c.file.is_file():
158 if c.file.is_file():
158 c.load_full_history = False
159 c.load_full_history = False
159 file_last_cs = c.file.last_changeset
160 file_last_cs = c.file.last_changeset
160 c.file_changeset = (c.changeset
161 c.file_changeset = (c.changeset
161 if c.changeset.revision < file_last_cs.revision
162 if c.changeset.revision < file_last_cs.revision
162 else file_last_cs)
163 else file_last_cs)
163 #determine if we're on branch head
164 #determine if we're on branch head
164 _branches = c.rhodecode_repo.branches
165 _branches = c.rhodecode_repo.branches
165 c.on_branch_head = revision in _branches.keys() + _branches.values()
166 c.on_branch_head = revision in _branches.keys() + _branches.values()
166 _hist = []
167 _hist = []
167 c.file_history = []
168 c.file_history = []
168 if c.load_full_history:
169 if c.load_full_history:
169 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
170 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
170
171
171 c.authors = []
172 c.authors = []
172 for a in set([x.author for x in _hist]):
173 for a in set([x.author for x in _hist]):
173 c.authors.append((h.email(a), h.person(a)))
174 c.authors.append((h.email(a), h.person(a)))
174 else:
175 else:
175 c.authors = c.file_history = []
176 c.authors = c.file_history = []
176 except RepositoryError, e:
177 except RepositoryError, e:
177 h.flash(str(e), category='error')
178 h.flash(str(e), category='error')
178 raise HTTPNotFound()
179 raise HTTPNotFound()
179
180
180 if request.environ.get('HTTP_X_PARTIAL_XHR'):
181 if request.environ.get('HTTP_X_PARTIAL_XHR'):
181 return render('files/files_ypjax.html')
182 return render('files/files_ypjax.html')
182
183
183 return render('files/files.html')
184 return render('files/files.html')
184
185
185 @LoginRequired()
186 @LoginRequired()
186 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
187 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
187 'repository.admin')
188 'repository.admin')
188 def history(self, repo_name, revision, f_path, annotate=False):
189 def history(self, repo_name, revision, f_path, annotate=False):
189 if request.environ.get('HTTP_X_PARTIAL_XHR'):
190 if request.environ.get('HTTP_X_PARTIAL_XHR'):
190 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
191 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
191 c.f_path = f_path
192 c.f_path = f_path
192 c.annotate = annotate
193 c.annotate = annotate
193 c.file = c.changeset.get_node(f_path)
194 c.file = c.changeset.get_node(f_path)
194 if c.file.is_file():
195 if c.file.is_file():
195 file_last_cs = c.file.last_changeset
196 file_last_cs = c.file.last_changeset
196 c.file_changeset = (c.changeset
197 c.file_changeset = (c.changeset
197 if c.changeset.revision < file_last_cs.revision
198 if c.changeset.revision < file_last_cs.revision
198 else file_last_cs)
199 else file_last_cs)
199 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
200 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
200 c.authors = []
201 c.authors = []
201 for a in set([x.author for x in _hist]):
202 for a in set([x.author for x in _hist]):
202 c.authors.append((h.email(a), h.person(a)))
203 c.authors.append((h.email(a), h.person(a)))
203 return render('files/files_history_box.html')
204 return render('files/files_history_box.html')
204
205
205 @LoginRequired()
206 @LoginRequired()
206 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
207 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
207 'repository.admin')
208 'repository.admin')
208 def rawfile(self, repo_name, revision, f_path):
209 def rawfile(self, repo_name, revision, f_path):
209 cs = self.__get_cs_or_redirect(revision, repo_name)
210 cs = self.__get_cs_or_redirect(revision, repo_name)
210 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
211 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
211
212
212 response.content_disposition = 'attachment; filename=%s' % \
213 response.content_disposition = 'attachment; filename=%s' % \
213 safe_str(f_path.split(Repository.url_sep())[-1])
214 safe_str(f_path.split(Repository.url_sep())[-1])
214
215
215 response.content_type = file_node.mimetype
216 response.content_type = file_node.mimetype
216 return file_node.content
217 return file_node.content
217
218
218 @LoginRequired()
219 @LoginRequired()
219 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
220 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
220 'repository.admin')
221 'repository.admin')
221 def raw(self, repo_name, revision, f_path):
222 def raw(self, repo_name, revision, f_path):
222 cs = self.__get_cs_or_redirect(revision, repo_name)
223 cs = self.__get_cs_or_redirect(revision, repo_name)
223 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
224 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
224
225
225 raw_mimetype_mapping = {
226 raw_mimetype_mapping = {
226 # map original mimetype to a mimetype used for "show as raw"
227 # map original mimetype to a mimetype used for "show as raw"
227 # you can also provide a content-disposition to override the
228 # you can also provide a content-disposition to override the
228 # default "attachment" disposition.
229 # default "attachment" disposition.
229 # orig_type: (new_type, new_dispo)
230 # orig_type: (new_type, new_dispo)
230
231
231 # show images inline:
232 # show images inline:
232 'image/x-icon': ('image/x-icon', 'inline'),
233 'image/x-icon': ('image/x-icon', 'inline'),
233 'image/png': ('image/png', 'inline'),
234 'image/png': ('image/png', 'inline'),
234 'image/gif': ('image/gif', 'inline'),
235 'image/gif': ('image/gif', 'inline'),
235 'image/jpeg': ('image/jpeg', 'inline'),
236 'image/jpeg': ('image/jpeg', 'inline'),
236 'image/svg+xml': ('image/svg+xml', 'inline'),
237 'image/svg+xml': ('image/svg+xml', 'inline'),
237 }
238 }
238
239
239 mimetype = file_node.mimetype
240 mimetype = file_node.mimetype
240 try:
241 try:
241 mimetype, dispo = raw_mimetype_mapping[mimetype]
242 mimetype, dispo = raw_mimetype_mapping[mimetype]
242 except KeyError:
243 except KeyError:
243 # we don't know anything special about this, handle it safely
244 # we don't know anything special about this, handle it safely
244 if file_node.is_binary:
245 if file_node.is_binary:
245 # do same as download raw for binary files
246 # do same as download raw for binary files
246 mimetype, dispo = 'application/octet-stream', 'attachment'
247 mimetype, dispo = 'application/octet-stream', 'attachment'
247 else:
248 else:
248 # do not just use the original mimetype, but force text/plain,
249 # do not just use the original mimetype, but force text/plain,
249 # otherwise it would serve text/html and that might be unsafe.
250 # otherwise it would serve text/html and that might be unsafe.
250 # Note: underlying vcs library fakes text/plain mimetype if the
251 # Note: underlying vcs library fakes text/plain mimetype if the
251 # mimetype can not be determined and it thinks it is not
252 # mimetype can not be determined and it thinks it is not
252 # binary.This might lead to erroneous text display in some
253 # binary.This might lead to erroneous text display in some
253 # cases, but helps in other cases, like with text files
254 # cases, but helps in other cases, like with text files
254 # without extension.
255 # without extension.
255 mimetype, dispo = 'text/plain', 'inline'
256 mimetype, dispo = 'text/plain', 'inline'
256
257
257 if dispo == 'attachment':
258 if dispo == 'attachment':
258 dispo = 'attachment; filename=%s' % \
259 dispo = 'attachment; filename=%s' % \
259 safe_str(f_path.split(os.sep)[-1])
260 safe_str(f_path.split(os.sep)[-1])
260
261
261 response.content_disposition = dispo
262 response.content_disposition = dispo
262 response.content_type = mimetype
263 response.content_type = mimetype
263 return file_node.content
264 return file_node.content
264
265
265 @LoginRequired()
266 @LoginRequired()
266 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
267 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
267 def edit(self, repo_name, revision, f_path):
268 def edit(self, repo_name, revision, f_path):
268 repo = c.rhodecode_db_repo
269 repo = c.rhodecode_db_repo
269 if repo.enable_locking and repo.locked[0]:
270 if repo.enable_locking and repo.locked[0]:
270 h.flash(_('This repository is has been locked by %s on %s')
271 h.flash(_('This repository is has been locked by %s on %s')
271 % (h.person_by_id(repo.locked[0]),
272 % (h.person_by_id(repo.locked[0]),
272 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
273 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
273 'warning')
274 'warning')
274 return redirect(h.url('files_home',
275 return redirect(h.url('files_home',
275 repo_name=repo_name, revision='tip'))
276 repo_name=repo_name, revision='tip'))
276
277
277 # check if revision is a branch identifier- basically we cannot
278 # check if revision is a branch identifier- basically we cannot
278 # create multiple heads via file editing
279 # create multiple heads via file editing
279 _branches = repo.scm_instance.branches
280 _branches = repo.scm_instance.branches
280 # check if revision is a branch name or branch hash
281 # check if revision is a branch name or branch hash
281 if revision not in _branches.keys() + _branches.values():
282 if revision not in _branches.keys() + _branches.values():
282 h.flash(_('You can only edit files with revision '
283 h.flash(_('You can only edit files with revision '
283 'being a valid branch '), category='warning')
284 'being a valid branch '), category='warning')
284 return redirect(h.url('files_home',
285 return redirect(h.url('files_home',
285 repo_name=repo_name, revision='tip',
286 repo_name=repo_name, revision='tip',
286 f_path=f_path))
287 f_path=f_path))
287
288
288 r_post = request.POST
289 r_post = request.POST
289
290
290 c.cs = self.__get_cs_or_redirect(revision, repo_name)
291 c.cs = self.__get_cs_or_redirect(revision, repo_name)
291 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
292 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
292
293
293 if c.file.is_binary:
294 if c.file.is_binary:
294 return redirect(url('files_home', repo_name=c.repo_name,
295 return redirect(url('files_home', repo_name=c.repo_name,
295 revision=c.cs.raw_id, f_path=f_path))
296 revision=c.cs.raw_id, f_path=f_path))
296 c.default_message = _('Edited file %s via RhodeCode') % (f_path)
297 c.default_message = _('Edited file %s via RhodeCode') % (f_path)
297 c.f_path = f_path
298 c.f_path = f_path
298
299
299 if r_post:
300 if r_post:
300
301
301 old_content = c.file.content
302 old_content = c.file.content
302 sl = old_content.splitlines(1)
303 sl = old_content.splitlines(1)
303 first_line = sl[0] if sl else ''
304 first_line = sl[0] if sl else ''
304 # modes: 0 - Unix, 1 - Mac, 2 - DOS
305 # modes: 0 - Unix, 1 - Mac, 2 - DOS
305 mode = detect_mode(first_line, 0)
306 mode = detect_mode(first_line, 0)
306 content = convert_line_endings(r_post.get('content', ''), mode)
307 content = convert_line_endings(r_post.get('content', ''), mode)
307
308
308 message = r_post.get('message') or c.default_message
309 message = r_post.get('message') or c.default_message
309 author = self.rhodecode_user.full_contact
310 author = self.rhodecode_user.full_contact
310
311
311 if content == old_content:
312 if content == old_content:
312 h.flash(_('No changes'), category='warning')
313 h.flash(_('No changes'), category='warning')
313 return redirect(url('changeset_home', repo_name=c.repo_name,
314 return redirect(url('changeset_home', repo_name=c.repo_name,
314 revision='tip'))
315 revision='tip'))
315 try:
316 try:
316 self.scm_model.commit_change(repo=c.rhodecode_repo,
317 self.scm_model.commit_change(repo=c.rhodecode_repo,
317 repo_name=repo_name, cs=c.cs,
318 repo_name=repo_name, cs=c.cs,
318 user=self.rhodecode_user.user_id,
319 user=self.rhodecode_user.user_id,
319 author=author, message=message,
320 author=author, message=message,
320 content=content, f_path=f_path)
321 content=content, f_path=f_path)
321 h.flash(_('Successfully committed to %s') % f_path,
322 h.flash(_('Successfully committed to %s') % f_path,
322 category='success')
323 category='success')
323
324
324 except Exception:
325 except Exception:
325 log.error(traceback.format_exc())
326 log.error(traceback.format_exc())
326 h.flash(_('Error occurred during commit'), category='error')
327 h.flash(_('Error occurred during commit'), category='error')
327 return redirect(url('changeset_home',
328 return redirect(url('changeset_home',
328 repo_name=c.repo_name, revision='tip'))
329 repo_name=c.repo_name, revision='tip'))
329
330
330 return render('files/files_edit.html')
331 return render('files/files_edit.html')
331
332
332 @LoginRequired()
333 @LoginRequired()
333 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
334 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
334 def add(self, repo_name, revision, f_path):
335 def add(self, repo_name, revision, f_path):
335
336
336 repo = Repository.get_by_repo_name(repo_name)
337 repo = Repository.get_by_repo_name(repo_name)
337 if repo.enable_locking and repo.locked[0]:
338 if repo.enable_locking and repo.locked[0]:
338 h.flash(_('This repository is has been locked by %s on %s')
339 h.flash(_('This repository is has been locked by %s on %s')
339 % (h.person_by_id(repo.locked[0]),
340 % (h.person_by_id(repo.locked[0]),
340 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
341 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
341 'warning')
342 'warning')
342 return redirect(h.url('files_home',
343 return redirect(h.url('files_home',
343 repo_name=repo_name, revision='tip'))
344 repo_name=repo_name, revision='tip'))
344
345
345 r_post = request.POST
346 r_post = request.POST
346 c.cs = self.__get_cs_or_redirect(revision, repo_name,
347 c.cs = self.__get_cs_or_redirect(revision, repo_name,
347 redirect_after=False)
348 redirect_after=False)
348 if c.cs is None:
349 if c.cs is None:
349 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
350 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
350 c.default_message = (_('Added file via RhodeCode'))
351 c.default_message = (_('Added file via RhodeCode'))
351 c.f_path = f_path
352 c.f_path = f_path
352
353
353 if r_post:
354 if r_post:
354 unix_mode = 0
355 unix_mode = 0
355 content = convert_line_endings(r_post.get('content', ''), unix_mode)
356 content = convert_line_endings(r_post.get('content', ''), unix_mode)
356
357
357 message = r_post.get('message') or c.default_message
358 message = r_post.get('message') or c.default_message
358 filename = r_post.get('filename')
359 filename = r_post.get('filename')
359 location = r_post.get('location', '')
360 location = r_post.get('location', '')
360 file_obj = r_post.get('upload_file', None)
361 file_obj = r_post.get('upload_file', None)
361
362
362 if file_obj is not None and hasattr(file_obj, 'filename'):
363 if file_obj is not None and hasattr(file_obj, 'filename'):
363 filename = file_obj.filename
364 filename = file_obj.filename
364 content = file_obj.file
365 content = file_obj.file
365
366
366 if not content:
367 if not content:
367 h.flash(_('No content'), category='warning')
368 h.flash(_('No content'), category='warning')
368 return redirect(url('changeset_home', repo_name=c.repo_name,
369 return redirect(url('changeset_home', repo_name=c.repo_name,
369 revision='tip'))
370 revision='tip'))
370 if not filename:
371 if not filename:
371 h.flash(_('No filename'), category='warning')
372 h.flash(_('No filename'), category='warning')
372 return redirect(url('changeset_home', repo_name=c.repo_name,
373 return redirect(url('changeset_home', repo_name=c.repo_name,
373 revision='tip'))
374 revision='tip'))
374 if location.startswith('/') or location.startswith('.') or '../' in location:
375 #strip all crap out of file, just leave the basename
375 h.flash(_('Location must be relative path and must not '
376 'contain .. in path'), category='warning')
377 return redirect(url('changeset_home', repo_name=c.repo_name,
378 revision='tip'))
379 if location:
380 location = os.path.normpath(location)
381 filename = os.path.basename(filename)
376 filename = os.path.basename(filename)
382 node_path = os.path.join(location, filename)
377 node_path = os.path.join(location, filename)
383 author = self.rhodecode_user.full_contact
378 author = self.rhodecode_user.full_contact
384
379
385 try:
380 try:
386 self.scm_model.create_node(repo=c.rhodecode_repo,
381 nodes = {
387 repo_name=repo_name, cs=c.cs,
382 node_path: {
388 user=self.rhodecode_user.user_id,
383 'content': content
389 author=author, message=message,
384 }
390 content=content, f_path=node_path)
385 }
386 self.scm_model.create_nodes(
387 user=c.rhodecode_user.user_id, repo=c.rhodecode_db_repo,
388 message=message,
389 nodes=nodes,
390 parent_cs=c.cs,
391 author=author,
392 )
393
391 h.flash(_('Successfully committed to %s') % node_path,
394 h.flash(_('Successfully committed to %s') % node_path,
392 category='success')
395 category='success')
396 except NonRelativePathError, e:
397 h.flash(_('Location must be relative path and must not '
398 'contain .. in path'), category='warning')
399 return redirect(url('changeset_home', repo_name=c.repo_name,
400 revision='tip'))
393 except (NodeError, NodeAlreadyExistsError), e:
401 except (NodeError, NodeAlreadyExistsError), e:
394 h.flash(_(e), category='error')
402 h.flash(_(e), category='error')
395 except Exception:
403 except Exception:
396 log.error(traceback.format_exc())
404 log.error(traceback.format_exc())
397 h.flash(_('Error occurred during commit'), category='error')
405 h.flash(_('Error occurred during commit'), category='error')
398 return redirect(url('changeset_home',
406 return redirect(url('changeset_home',
399 repo_name=c.repo_name, revision='tip'))
407 repo_name=c.repo_name, revision='tip'))
400
408
401 return render('files/files_add.html')
409 return render('files/files_add.html')
402
410
403 @LoginRequired()
411 @LoginRequired()
404 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
412 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
405 'repository.admin')
413 'repository.admin')
406 def archivefile(self, repo_name, fname):
414 def archivefile(self, repo_name, fname):
407
415
408 fileformat = None
416 fileformat = None
409 revision = None
417 revision = None
410 ext = None
418 ext = None
411 subrepos = request.GET.get('subrepos') == 'true'
419 subrepos = request.GET.get('subrepos') == 'true'
412
420
413 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
421 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
414 archive_spec = fname.split(ext_data[1])
422 archive_spec = fname.split(ext_data[1])
415 if len(archive_spec) == 2 and archive_spec[1] == '':
423 if len(archive_spec) == 2 and archive_spec[1] == '':
416 fileformat = a_type or ext_data[1]
424 fileformat = a_type or ext_data[1]
417 revision = archive_spec[0]
425 revision = archive_spec[0]
418 ext = ext_data[1]
426 ext = ext_data[1]
419
427
420 try:
428 try:
421 dbrepo = RepoModel().get_by_repo_name(repo_name)
429 dbrepo = RepoModel().get_by_repo_name(repo_name)
422 if not dbrepo.enable_downloads:
430 if not dbrepo.enable_downloads:
423 return _('Downloads disabled')
431 return _('Downloads disabled')
424
432
425 if c.rhodecode_repo.alias == 'hg':
433 if c.rhodecode_repo.alias == 'hg':
426 # patch and reset hooks section of UI config to not run any
434 # patch and reset hooks section of UI config to not run any
427 # hooks on fetching archives with subrepos
435 # hooks on fetching archives with subrepos
428 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
436 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
429 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
437 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
430
438
431 cs = c.rhodecode_repo.get_changeset(revision)
439 cs = c.rhodecode_repo.get_changeset(revision)
432 content_type = settings.ARCHIVE_SPECS[fileformat][0]
440 content_type = settings.ARCHIVE_SPECS[fileformat][0]
433 except ChangesetDoesNotExistError:
441 except ChangesetDoesNotExistError:
434 return _('Unknown revision %s') % revision
442 return _('Unknown revision %s') % revision
435 except EmptyRepositoryError:
443 except EmptyRepositoryError:
436 return _('Empty repository')
444 return _('Empty repository')
437 except (ImproperArchiveTypeError, KeyError):
445 except (ImproperArchiveTypeError, KeyError):
438 return _('Unknown archive type')
446 return _('Unknown archive type')
439 # archive cache
447 # archive cache
440 from rhodecode import CONFIG
448 from rhodecode import CONFIG
441 rev_name = cs.raw_id[:12]
449 rev_name = cs.raw_id[:12]
442 archive_name = '%s-%s%s' % (safe_str(repo_name.replace('/', '_')),
450 archive_name = '%s-%s%s' % (safe_str(repo_name.replace('/', '_')),
443 safe_str(rev_name), ext)
451 safe_str(rev_name), ext)
444
452
445 use_cached_archive = False # defines if we use cached version of archive
453 use_cached_archive = False # defines if we use cached version of archive
446 archive_cache_enabled = CONFIG.get('archive_cache_dir')
454 archive_cache_enabled = CONFIG.get('archive_cache_dir')
447 if not subrepos and archive_cache_enabled:
455 if not subrepos and archive_cache_enabled:
448 #check if we it's ok to write
456 #check if we it's ok to write
449 if not os.path.isdir(CONFIG['archive_cache_dir']):
457 if not os.path.isdir(CONFIG['archive_cache_dir']):
450 os.makedirs(CONFIG['archive_cache_dir'])
458 os.makedirs(CONFIG['archive_cache_dir'])
451 cached_archive_path = os.path.join(CONFIG['archive_cache_dir'], archive_name)
459 cached_archive_path = os.path.join(CONFIG['archive_cache_dir'], archive_name)
452 if os.path.isfile(cached_archive_path):
460 if os.path.isfile(cached_archive_path):
453 log.debug('Found cached archive in %s' % cached_archive_path)
461 log.debug('Found cached archive in %s' % cached_archive_path)
454 fd, archive = None, cached_archive_path
462 fd, archive = None, cached_archive_path
455 use_cached_archive = True
463 use_cached_archive = True
456 else:
464 else:
457 log.debug('Archive %s is not yet cached' % (archive_name))
465 log.debug('Archive %s is not yet cached' % (archive_name))
458
466
459 if not use_cached_archive:
467 if not use_cached_archive:
460 #generate new archive
468 #generate new archive
461 try:
469 try:
462 fd, archive = tempfile.mkstemp()
470 fd, archive = tempfile.mkstemp()
463 t = open(archive, 'wb')
471 t = open(archive, 'wb')
464 log.debug('Creating new temp archive in %s' % archive)
472 log.debug('Creating new temp archive in %s' % archive)
465 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
473 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
466 if archive_cache_enabled:
474 if archive_cache_enabled:
467 #if we generated the archive and use cache rename that
475 #if we generated the archive and use cache rename that
468 log.debug('Storing new archive in %s' % cached_archive_path)
476 log.debug('Storing new archive in %s' % cached_archive_path)
469 shutil.move(archive, cached_archive_path)
477 shutil.move(archive, cached_archive_path)
470 archive = cached_archive_path
478 archive = cached_archive_path
471 finally:
479 finally:
472 t.close()
480 t.close()
473
481
474 def get_chunked_archive(archive):
482 def get_chunked_archive(archive):
475 stream = open(archive, 'rb')
483 stream = open(archive, 'rb')
476 while True:
484 while True:
477 data = stream.read(16 * 1024)
485 data = stream.read(16 * 1024)
478 if not data:
486 if not data:
479 stream.close()
487 stream.close()
480 if fd: # fd means we used temporary file
488 if fd: # fd means we used temporary file
481 os.close(fd)
489 os.close(fd)
482 if not archive_cache_enabled:
490 if not archive_cache_enabled:
483 log.debug('Destroing temp archive %s' % archive)
491 log.debug('Destroing temp archive %s' % archive)
484 os.remove(archive)
492 os.remove(archive)
485 break
493 break
486 yield data
494 yield data
487
495
488 response.content_disposition = str('attachment; filename=%s' % (archive_name))
496 response.content_disposition = str('attachment; filename=%s' % (archive_name))
489 response.content_type = str(content_type)
497 response.content_type = str(content_type)
490 return get_chunked_archive(archive)
498 return get_chunked_archive(archive)
491
499
492 @LoginRequired()
500 @LoginRequired()
493 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
501 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
494 'repository.admin')
502 'repository.admin')
495 def diff(self, repo_name, f_path):
503 def diff(self, repo_name, f_path):
496 ignore_whitespace = request.GET.get('ignorews') == '1'
504 ignore_whitespace = request.GET.get('ignorews') == '1'
497 line_context = request.GET.get('context', 3)
505 line_context = request.GET.get('context', 3)
498 diff1 = request.GET.get('diff1', '')
506 diff1 = request.GET.get('diff1', '')
499 diff2 = request.GET.get('diff2', '')
507 diff2 = request.GET.get('diff2', '')
500 c.action = request.GET.get('diff')
508 c.action = request.GET.get('diff')
501 c.no_changes = diff1 == diff2
509 c.no_changes = diff1 == diff2
502 c.f_path = f_path
510 c.f_path = f_path
503 c.big_diff = False
511 c.big_diff = False
504 c.anchor_url = anchor_url
512 c.anchor_url = anchor_url
505 c.ignorews_url = _ignorews_url
513 c.ignorews_url = _ignorews_url
506 c.context_url = _context_url
514 c.context_url = _context_url
507 c.changes = OrderedDict()
515 c.changes = OrderedDict()
508 c.changes[diff2] = []
516 c.changes[diff2] = []
509
517
510 #special case if we want a show rev only, it's impl here
518 #special case if we want a show rev only, it's impl here
511 #to reduce JS and callbacks
519 #to reduce JS and callbacks
512
520
513 if request.GET.get('show_rev'):
521 if request.GET.get('show_rev'):
514 if str2bool(request.GET.get('annotate', 'False')):
522 if str2bool(request.GET.get('annotate', 'False')):
515 _url = url('files_annotate_home', repo_name=c.repo_name,
523 _url = url('files_annotate_home', repo_name=c.repo_name,
516 revision=diff1, f_path=c.f_path)
524 revision=diff1, f_path=c.f_path)
517 else:
525 else:
518 _url = url('files_home', repo_name=c.repo_name,
526 _url = url('files_home', repo_name=c.repo_name,
519 revision=diff1, f_path=c.f_path)
527 revision=diff1, f_path=c.f_path)
520
528
521 return redirect(_url)
529 return redirect(_url)
522 try:
530 try:
523 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
531 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
524 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
532 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
525 try:
533 try:
526 node1 = c.changeset_1.get_node(f_path)
534 node1 = c.changeset_1.get_node(f_path)
527 if node1.is_dir():
535 if node1.is_dir():
528 raise NodeError('%s path is a %s not a file'
536 raise NodeError('%s path is a %s not a file'
529 % (node1, type(node1)))
537 % (node1, type(node1)))
530 except NodeDoesNotExistError:
538 except NodeDoesNotExistError:
531 c.changeset_1 = EmptyChangeset(cs=diff1,
539 c.changeset_1 = EmptyChangeset(cs=diff1,
532 revision=c.changeset_1.revision,
540 revision=c.changeset_1.revision,
533 repo=c.rhodecode_repo)
541 repo=c.rhodecode_repo)
534 node1 = FileNode(f_path, '', changeset=c.changeset_1)
542 node1 = FileNode(f_path, '', changeset=c.changeset_1)
535 else:
543 else:
536 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
544 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
537 node1 = FileNode(f_path, '', changeset=c.changeset_1)
545 node1 = FileNode(f_path, '', changeset=c.changeset_1)
538
546
539 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
547 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
540 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
548 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
541 try:
549 try:
542 node2 = c.changeset_2.get_node(f_path)
550 node2 = c.changeset_2.get_node(f_path)
543 if node2.is_dir():
551 if node2.is_dir():
544 raise NodeError('%s path is a %s not a file'
552 raise NodeError('%s path is a %s not a file'
545 % (node2, type(node2)))
553 % (node2, type(node2)))
546 except NodeDoesNotExistError:
554 except NodeDoesNotExistError:
547 c.changeset_2 = EmptyChangeset(cs=diff2,
555 c.changeset_2 = EmptyChangeset(cs=diff2,
548 revision=c.changeset_2.revision,
556 revision=c.changeset_2.revision,
549 repo=c.rhodecode_repo)
557 repo=c.rhodecode_repo)
550 node2 = FileNode(f_path, '', changeset=c.changeset_2)
558 node2 = FileNode(f_path, '', changeset=c.changeset_2)
551 else:
559 else:
552 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
560 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
553 node2 = FileNode(f_path, '', changeset=c.changeset_2)
561 node2 = FileNode(f_path, '', changeset=c.changeset_2)
554 except (RepositoryError, NodeError):
562 except (RepositoryError, NodeError):
555 log.error(traceback.format_exc())
563 log.error(traceback.format_exc())
556 return redirect(url('files_home', repo_name=c.repo_name,
564 return redirect(url('files_home', repo_name=c.repo_name,
557 f_path=f_path))
565 f_path=f_path))
558
566
559 if c.action == 'download':
567 if c.action == 'download':
560 _diff = diffs.get_gitdiff(node1, node2,
568 _diff = diffs.get_gitdiff(node1, node2,
561 ignore_whitespace=ignore_whitespace,
569 ignore_whitespace=ignore_whitespace,
562 context=line_context)
570 context=line_context)
563 diff = diffs.DiffProcessor(_diff, format='gitdiff')
571 diff = diffs.DiffProcessor(_diff, format='gitdiff')
564
572
565 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
573 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
566 response.content_type = 'text/plain'
574 response.content_type = 'text/plain'
567 response.content_disposition = (
575 response.content_disposition = (
568 'attachment; filename=%s' % diff_name
576 'attachment; filename=%s' % diff_name
569 )
577 )
570 return diff.as_raw()
578 return diff.as_raw()
571
579
572 elif c.action == 'raw':
580 elif c.action == 'raw':
573 _diff = diffs.get_gitdiff(node1, node2,
581 _diff = diffs.get_gitdiff(node1, node2,
574 ignore_whitespace=ignore_whitespace,
582 ignore_whitespace=ignore_whitespace,
575 context=line_context)
583 context=line_context)
576 diff = diffs.DiffProcessor(_diff, format='gitdiff')
584 diff = diffs.DiffProcessor(_diff, format='gitdiff')
577 response.content_type = 'text/plain'
585 response.content_type = 'text/plain'
578 return diff.as_raw()
586 return diff.as_raw()
579
587
580 else:
588 else:
581 fid = h.FID(diff2, node2.path)
589 fid = h.FID(diff2, node2.path)
582 line_context_lcl = get_line_ctx(fid, request.GET)
590 line_context_lcl = get_line_ctx(fid, request.GET)
583 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
591 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
584
592
585 lim = request.GET.get('fulldiff') or self.cut_off_limit
593 lim = request.GET.get('fulldiff') or self.cut_off_limit
586 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
594 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
587 filenode_new=node2,
595 filenode_new=node2,
588 cut_off_limit=lim,
596 cut_off_limit=lim,
589 ignore_whitespace=ign_whitespace_lcl,
597 ignore_whitespace=ign_whitespace_lcl,
590 line_context=line_context_lcl,
598 line_context=line_context_lcl,
591 enable_comments=False)
599 enable_comments=False)
592 op = ''
600 op = ''
593 filename = node1.path
601 filename = node1.path
594 cs_changes = {
602 cs_changes = {
595 'fid': [cs1, cs2, op, filename, diff, st]
603 'fid': [cs1, cs2, op, filename, diff, st]
596 }
604 }
597 c.changes = cs_changes
605 c.changes = cs_changes
598
606
599 return render('files/file_diff.html')
607 return render('files/file_diff.html')
600
608
601 def _get_node_history(self, cs, f_path, changesets=None):
609 def _get_node_history(self, cs, f_path, changesets=None):
602 """
610 """
603 get changesets history for given node
611 get changesets history for given node
604
612
605 :param cs: changeset to calculate history
613 :param cs: changeset to calculate history
606 :param f_path: path for node to calculate history for
614 :param f_path: path for node to calculate history for
607 :param changesets: if passed don't calculate history and take
615 :param changesets: if passed don't calculate history and take
608 changesets defined in this list
616 changesets defined in this list
609 """
617 """
610 # calculate history based on tip
618 # calculate history based on tip
611 tip_cs = c.rhodecode_repo.get_changeset()
619 tip_cs = c.rhodecode_repo.get_changeset()
612 if changesets is None:
620 if changesets is None:
613 try:
621 try:
614 changesets = tip_cs.get_file_history(f_path)
622 changesets = tip_cs.get_file_history(f_path)
615 except (NodeDoesNotExistError, ChangesetError):
623 except (NodeDoesNotExistError, ChangesetError):
616 #this node is not present at tip !
624 #this node is not present at tip !
617 changesets = cs.get_file_history(f_path)
625 changesets = cs.get_file_history(f_path)
618 hist_l = []
626 hist_l = []
619
627
620 changesets_group = ([], _("Changesets"))
628 changesets_group = ([], _("Changesets"))
621 branches_group = ([], _("Branches"))
629 branches_group = ([], _("Branches"))
622 tags_group = ([], _("Tags"))
630 tags_group = ([], _("Tags"))
623 _hg = cs.repository.alias == 'hg'
631 _hg = cs.repository.alias == 'hg'
624 for chs in changesets:
632 for chs in changesets:
625 #_branch = '(%s)' % chs.branch if _hg else ''
633 #_branch = '(%s)' % chs.branch if _hg else ''
626 _branch = chs.branch
634 _branch = chs.branch
627 n_desc = 'r%s:%s (%s)' % (chs.revision, chs.short_id, _branch)
635 n_desc = 'r%s:%s (%s)' % (chs.revision, chs.short_id, _branch)
628 changesets_group[0].append((chs.raw_id, n_desc,))
636 changesets_group[0].append((chs.raw_id, n_desc,))
629 hist_l.append(changesets_group)
637 hist_l.append(changesets_group)
630
638
631 for name, chs in c.rhodecode_repo.branches.items():
639 for name, chs in c.rhodecode_repo.branches.items():
632 branches_group[0].append((chs, name),)
640 branches_group[0].append((chs, name),)
633 hist_l.append(branches_group)
641 hist_l.append(branches_group)
634
642
635 for name, chs in c.rhodecode_repo.tags.items():
643 for name, chs in c.rhodecode_repo.tags.items():
636 tags_group[0].append((chs, name),)
644 tags_group[0].append((chs, name),)
637 hist_l.append(tags_group)
645 hist_l.append(tags_group)
638
646
639 return hist_l, changesets
647 return hist_l, changesets
640
648
641 @LoginRequired()
649 @LoginRequired()
642 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
650 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
643 'repository.admin')
651 'repository.admin')
644 @jsonify
652 @jsonify
645 def nodelist(self, repo_name, revision, f_path):
653 def nodelist(self, repo_name, revision, f_path):
646 if request.environ.get('HTTP_X_PARTIAL_XHR'):
654 if request.environ.get('HTTP_X_PARTIAL_XHR'):
647 cs = self.__get_cs_or_redirect(revision, repo_name)
655 cs = self.__get_cs_or_redirect(revision, repo_name)
648 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
656 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
649 flat=False)
657 flat=False)
650 return {'nodes': _d + _f}
658 return {'nodes': _d + _f}
@@ -1,560 +1,559 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.pullrequests
3 rhodecode.controllers.pullrequests
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 pull requests controller for rhodecode for initializing pull requests
6 pull requests controller for rhodecode for initializing pull requests
7
7
8 :created_on: May 7, 2012
8 :created_on: May 7, 2012
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import logging
25 import logging
26 import traceback
26 import traceback
27 import formencode
27 import formencode
28
28
29 from webob.exc import HTTPNotFound, HTTPForbidden
29 from webob.exc import HTTPNotFound, HTTPForbidden
30 from collections import defaultdict
30 from collections import defaultdict
31 from itertools import groupby
31 from itertools import groupby
32
32
33 from pylons import request, response, session, tmpl_context as c, url
33 from pylons import request, response, session, tmpl_context as c, url
34 from pylons.controllers.util import abort, redirect
34 from pylons.controllers.util import abort, redirect
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 from rhodecode.lib.compat import json
37 from rhodecode.lib.compat import json
38 from rhodecode.lib.base import BaseRepoController, render
38 from rhodecode.lib.base import BaseRepoController, render
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
40 NotAnonymous
40 NotAnonymous
41 from rhodecode.lib.helpers import Page
41 from rhodecode.lib.helpers import Page
42 from rhodecode.lib import helpers as h
42 from rhodecode.lib import helpers as h
43 from rhodecode.lib import diffs
43 from rhodecode.lib import diffs
44 from rhodecode.lib.utils import action_logger, jsonify
44 from rhodecode.lib.utils import action_logger, jsonify
45 from rhodecode.lib.vcs.utils import safe_str
45 from rhodecode.lib.vcs.utils import safe_str
46 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
46 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48 from rhodecode.lib.diffs import LimitedDiffContainer
48 from rhodecode.lib.diffs import LimitedDiffContainer
49 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
49 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
50 ChangesetComment
50 ChangesetComment
51 from rhodecode.model.pull_request import PullRequestModel
51 from rhodecode.model.pull_request import PullRequestModel
52 from rhodecode.model.meta import Session
52 from rhodecode.model.meta import Session
53 from rhodecode.model.repo import RepoModel
53 from rhodecode.model.repo import RepoModel
54 from rhodecode.model.comment import ChangesetCommentsModel
54 from rhodecode.model.comment import ChangesetCommentsModel
55 from rhodecode.model.changeset_status import ChangesetStatusModel
55 from rhodecode.model.changeset_status import ChangesetStatusModel
56 from rhodecode.model.forms import PullRequestForm
56 from rhodecode.model.forms import PullRequestForm
57 from mercurial import scmutil
57 from mercurial import scmutil
58 from rhodecode.lib.utils2 import safe_int
58 from rhodecode.lib.utils2 import safe_int
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class PullrequestsController(BaseRepoController):
63 class PullrequestsController(BaseRepoController):
64
64
65 def __before__(self):
65 def __before__(self):
66 super(PullrequestsController, self).__before__()
66 super(PullrequestsController, self).__before__()
67 repo_model = RepoModel()
67 repo_model = RepoModel()
68 c.users_array = repo_model.get_users_js()
68 c.users_array = repo_model.get_users_js()
69 c.users_groups_array = repo_model.get_users_groups_js()
69 c.users_groups_array = repo_model.get_users_groups_js()
70
70
71 def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None):
71 def _get_repo_refs(self, repo, rev=None, branch=None, branch_rev=None):
72 """return a structure with repo's interesting changesets, suitable for
72 """return a structure with repo's interesting changesets, suitable for
73 the selectors in pullrequest.html
73 the selectors in pullrequest.html
74
74
75 rev: a revision that must be in the list somehow and selected by default
75 rev: a revision that must be in the list somehow and selected by default
76 branch: a branch that must be in the list and selected by default - even if closed
76 branch: a branch that must be in the list and selected by default - even if closed
77 branch_rev: a revision of which peers should be preferred and available."""
77 branch_rev: a revision of which peers should be preferred and available."""
78 # list named branches that has been merged to this named branch - it should probably merge back
78 # list named branches that has been merged to this named branch - it should probably merge back
79 peers = []
79 peers = []
80
80
81 if rev:
81 if rev:
82 rev = safe_str(rev)
82 rev = safe_str(rev)
83
83
84 if branch:
84 if branch:
85 branch = safe_str(branch)
85 branch = safe_str(branch)
86
86
87 if branch_rev:
87 if branch_rev:
88 branch_rev = safe_str(branch_rev)
88 branch_rev = safe_str(branch_rev)
89 # not restricting to merge() would also get branch point and be better
89 # not restricting to merge() would also get branch point and be better
90 # (especially because it would get the branch point) ... but is currently too expensive
90 # (especially because it would get the branch point) ... but is currently too expensive
91 otherbranches = {}
91 otherbranches = {}
92 for i in repo._repo.revs(
92 for i in repo._repo.revs(
93 "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)))",
93 "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)))",
94 branch_rev, branch_rev):
94 branch_rev, branch_rev):
95 cs = repo.get_changeset(i)
95 cs = repo.get_changeset(i)
96 otherbranches[cs.branch] = cs.raw_id
96 otherbranches[cs.branch] = cs.raw_id
97 for abranch, node in otherbranches.iteritems():
97 for abranch, node in otherbranches.iteritems():
98 selected = 'branch:%s:%s' % (abranch, node)
98 selected = 'branch:%s:%s' % (abranch, node)
99 peers.append((selected, abranch))
99 peers.append((selected, abranch))
100
100
101 selected = None
101 selected = None
102
102
103 branches = []
103 branches = []
104 for abranch, branchrev in repo.branches.iteritems():
104 for abranch, branchrev in repo.branches.iteritems():
105 n = 'branch:%s:%s' % (abranch, branchrev)
105 n = 'branch:%s:%s' % (abranch, branchrev)
106 branches.append((n, abranch))
106 branches.append((n, abranch))
107 if rev == branchrev:
107 if rev == branchrev:
108 selected = n
108 selected = n
109 if branch == abranch:
109 if branch == abranch:
110 selected = n
110 selected = n
111 branch = None
111 branch = None
112 if branch: # branch not in list - it is probably closed
112 if branch: # branch not in list - it is probably closed
113 revs = repo._repo.revs('max(branch(%s))', branch)
113 revs = repo._repo.revs('max(branch(%s))', branch)
114 if revs:
114 if revs:
115 cs = repo.get_changeset(revs[0])
115 cs = repo.get_changeset(revs[0])
116 selected = 'branch:%s:%s' % (branch, cs.raw_id)
116 selected = 'branch:%s:%s' % (branch, cs.raw_id)
117 branches.append((selected, branch))
117 branches.append((selected, branch))
118
118
119 bookmarks = []
119 bookmarks = []
120 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
120 for bookmark, bookmarkrev in repo.bookmarks.iteritems():
121 n = 'book:%s:%s' % (bookmark, bookmarkrev)
121 n = 'book:%s:%s' % (bookmark, bookmarkrev)
122 bookmarks.append((n, bookmark))
122 bookmarks.append((n, bookmark))
123 if rev == bookmarkrev:
123 if rev == bookmarkrev:
124 selected = n
124 selected = n
125
125
126 tags = []
126 tags = []
127 for tag, tagrev in repo.tags.iteritems():
127 for tag, tagrev in repo.tags.iteritems():
128 n = 'tag:%s:%s' % (tag, tagrev)
128 n = 'tag:%s:%s' % (tag, tagrev)
129 tags.append((n, tag))
129 tags.append((n, tag))
130 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
130 if rev == tagrev and tag != 'tip': # tip is not a real tag - and its branch is better
131 selected = n
131 selected = n
132
132
133 # prio 1: rev was selected as existing entry above
133 # prio 1: rev was selected as existing entry above
134
134
135 # prio 2: create special entry for rev; rev _must_ be used
135 # prio 2: create special entry for rev; rev _must_ be used
136 specials = []
136 specials = []
137 if rev and selected is None:
137 if rev and selected is None:
138 selected = 'rev:%s:%s' % (rev, rev)
138 selected = 'rev:%s:%s' % (rev, rev)
139 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
139 specials = [(selected, '%s: %s' % (_("Changeset"), rev[:12]))]
140
140
141 # prio 3: most recent peer branch
141 # prio 3: most recent peer branch
142 if peers and not selected:
142 if peers and not selected:
143 selected = peers[0][0][0]
143 selected = peers[0][0][0]
144
144
145 # prio 4: tip revision
145 # prio 4: tip revision
146 if not selected:
146 if not selected:
147 selected = 'tag:tip:%s' % repo.tags['tip']
147 selected = 'tag:tip:%s' % repo.tags['tip']
148
148
149 groups = [(specials, _("Special")),
149 groups = [(specials, _("Special")),
150 (peers, _("Peer branches")),
150 (peers, _("Peer branches")),
151 (bookmarks, _("Bookmarks")),
151 (bookmarks, _("Bookmarks")),
152 (branches, _("Branches")),
152 (branches, _("Branches")),
153 (tags, _("Tags")),
153 (tags, _("Tags")),
154 ]
154 ]
155 return [g for g in groups if g[0]], selected
155 return [g for g in groups if g[0]], selected
156
156
157 def _get_is_allowed_change_status(self, pull_request):
157 def _get_is_allowed_change_status(self, pull_request):
158 owner = self.rhodecode_user.user_id == pull_request.user_id
158 owner = self.rhodecode_user.user_id == pull_request.user_id
159 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
159 reviewer = self.rhodecode_user.user_id in [x.user_id for x in
160 pull_request.reviewers]
160 pull_request.reviewers]
161 return (self.rhodecode_user.admin or owner or reviewer)
161 return (self.rhodecode_user.admin or owner or reviewer)
162
162
163 def _load_compare_data(self, pull_request, enable_comments=True):
163 def _load_compare_data(self, pull_request, enable_comments=True):
164 """
164 """
165 Load context data needed for generating compare diff
165 Load context data needed for generating compare diff
166
166
167 :param pull_request:
167 :param pull_request:
168 :type pull_request:
169 """
168 """
170 org_repo = pull_request.org_repo
169 org_repo = pull_request.org_repo
171 (org_ref_type,
170 (org_ref_type,
172 org_ref_name,
171 org_ref_name,
173 org_ref_rev) = pull_request.org_ref.split(':')
172 org_ref_rev) = pull_request.org_ref.split(':')
174
173
175 other_repo = org_repo
174 other_repo = org_repo
176 (other_ref_type,
175 (other_ref_type,
177 other_ref_name,
176 other_ref_name,
178 other_ref_rev) = pull_request.other_ref.split(':')
177 other_ref_rev) = pull_request.other_ref.split(':')
179
178
180 # despite opening revisions for bookmarks/branches/tags, we always
179 # despite opening revisions for bookmarks/branches/tags, we always
181 # convert this to rev to prevent changes after bookmark or branch change
180 # convert this to rev to prevent changes after bookmark or branch change
182 org_ref = ('rev', org_ref_rev)
181 org_ref = ('rev', org_ref_rev)
183 other_ref = ('rev', other_ref_rev)
182 other_ref = ('rev', other_ref_rev)
184
183
185 c.org_repo = org_repo
184 c.org_repo = org_repo
186 c.other_repo = other_repo
185 c.other_repo = other_repo
187
186
188 c.fulldiff = fulldiff = request.GET.get('fulldiff')
187 c.fulldiff = fulldiff = request.GET.get('fulldiff')
189
188
190 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
189 c.cs_ranges = [org_repo.get_changeset(x) for x in pull_request.revisions]
191
190
192 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
191 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
193
192
194 c.org_ref = org_ref[1]
193 c.org_ref = org_ref[1]
195 c.org_ref_type = org_ref[0]
194 c.org_ref_type = org_ref[0]
196 c.other_ref = other_ref[1]
195 c.other_ref = other_ref[1]
197 c.other_ref_type = other_ref[0]
196 c.other_ref_type = other_ref[0]
198
197
199 diff_limit = self.cut_off_limit if not fulldiff else None
198 diff_limit = self.cut_off_limit if not fulldiff else None
200
199
201 # we swap org/other ref since we run a simple diff on one repo
200 # we swap org/other ref since we run a simple diff on one repo
202 log.debug('running diff between %s and %s in %s'
201 log.debug('running diff between %s and %s in %s'
203 % (other_ref, org_ref, org_repo.scm_instance.path))
202 % (other_ref, org_ref, org_repo.scm_instance.path))
204 txtdiff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1]))
203 txtdiff = org_repo.scm_instance.get_diff(rev1=safe_str(other_ref[1]), rev2=safe_str(org_ref[1]))
205
204
206 diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
205 diff_processor = diffs.DiffProcessor(txtdiff or '', format='gitdiff',
207 diff_limit=diff_limit)
206 diff_limit=diff_limit)
208 _parsed = diff_processor.prepare()
207 _parsed = diff_processor.prepare()
209
208
210 c.limited_diff = False
209 c.limited_diff = False
211 if isinstance(_parsed, LimitedDiffContainer):
210 if isinstance(_parsed, LimitedDiffContainer):
212 c.limited_diff = True
211 c.limited_diff = True
213
212
214 c.files = []
213 c.files = []
215 c.changes = {}
214 c.changes = {}
216 c.lines_added = 0
215 c.lines_added = 0
217 c.lines_deleted = 0
216 c.lines_deleted = 0
218
217
219 for f in _parsed:
218 for f in _parsed:
220 st = f['stats']
219 st = f['stats']
221 c.lines_added += st['added']
220 c.lines_added += st['added']
222 c.lines_deleted += st['deleted']
221 c.lines_deleted += st['deleted']
223 fid = h.FID('', f['filename'])
222 fid = h.FID('', f['filename'])
224 c.files.append([fid, f['operation'], f['filename'], f['stats']])
223 c.files.append([fid, f['operation'], f['filename'], f['stats']])
225 htmldiff = diff_processor.as_html(enable_comments=enable_comments,
224 htmldiff = diff_processor.as_html(enable_comments=enable_comments,
226 parsed_lines=[f])
225 parsed_lines=[f])
227 c.changes[fid] = [f['operation'], f['filename'], htmldiff]
226 c.changes[fid] = [f['operation'], f['filename'], htmldiff]
228
227
229 @LoginRequired()
228 @LoginRequired()
230 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
229 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
231 'repository.admin')
230 'repository.admin')
232 def show_all(self, repo_name):
231 def show_all(self, repo_name):
233 c.pull_requests = PullRequestModel().get_all(repo_name)
232 c.pull_requests = PullRequestModel().get_all(repo_name)
234 c.repo_name = repo_name
233 c.repo_name = repo_name
235 p = safe_int(request.GET.get('page', 1), 1)
234 p = safe_int(request.GET.get('page', 1), 1)
236
235
237 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
236 c.pullrequests_pager = Page(c.pull_requests, page=p, items_per_page=10)
238
237
239 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
238 c.pullrequest_data = render('/pullrequests/pullrequest_data.html')
240
239
241 if request.environ.get('HTTP_X_PARTIAL_XHR'):
240 if request.environ.get('HTTP_X_PARTIAL_XHR'):
242 return c.pullrequest_data
241 return c.pullrequest_data
243
242
244 return render('/pullrequests/pullrequest_show_all.html')
243 return render('/pullrequests/pullrequest_show_all.html')
245
244
246 @LoginRequired()
245 @LoginRequired()
247 @NotAnonymous()
246 @NotAnonymous()
248 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
247 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
249 'repository.admin')
248 'repository.admin')
250 def index(self):
249 def index(self):
251 org_repo = c.rhodecode_db_repo
250 org_repo = c.rhodecode_db_repo
252
251
253 if org_repo.scm_instance.alias != 'hg':
252 if org_repo.scm_instance.alias != 'hg':
254 log.error('Review not available for GIT REPOS')
253 log.error('Review not available for GIT REPOS')
255 raise HTTPNotFound
254 raise HTTPNotFound
256
255
257 try:
256 try:
258 org_repo.scm_instance.get_changeset()
257 org_repo.scm_instance.get_changeset()
259 except EmptyRepositoryError, e:
258 except EmptyRepositoryError, e:
260 h.flash(h.literal(_('There are no changesets yet')),
259 h.flash(h.literal(_('There are no changesets yet')),
261 category='warning')
260 category='warning')
262 redirect(url('summary_home', repo_name=org_repo.repo_name))
261 redirect(url('summary_home', repo_name=org_repo.repo_name))
263
262
264 org_rev = request.GET.get('rev_end')
263 org_rev = request.GET.get('rev_end')
265 # rev_start is not directly useful - its parent could however be used
264 # rev_start is not directly useful - its parent could however be used
266 # as default for other and thus give a simple compare view
265 # as default for other and thus give a simple compare view
267 #other_rev = request.POST.get('rev_start')
266 #other_rev = request.POST.get('rev_start')
268 branch = request.GET.get('branch')
267 branch = request.GET.get('branch')
269
268
270 c.org_repos = []
269 c.org_repos = []
271 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
270 c.org_repos.append((org_repo.repo_name, org_repo.repo_name))
272 c.default_org_repo = org_repo.repo_name
271 c.default_org_repo = org_repo.repo_name
273 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, rev=org_rev, branch=branch)
272 c.org_refs, c.default_org_ref = self._get_repo_refs(org_repo.scm_instance, rev=org_rev, branch=branch)
274
273
275 c.other_repos = []
274 c.other_repos = []
276 other_repos_info = {}
275 other_repos_info = {}
277
276
278 def add_other_repo(repo, branch_rev=None):
277 def add_other_repo(repo, branch_rev=None):
279 if repo.repo_name in other_repos_info: # shouldn't happen
278 if repo.repo_name in other_repos_info: # shouldn't happen
280 return
279 return
281 c.other_repos.append((repo.repo_name, repo.repo_name))
280 c.other_repos.append((repo.repo_name, repo.repo_name))
282 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
281 other_refs, selected_other_ref = self._get_repo_refs(repo.scm_instance, branch_rev=branch_rev)
283 other_repos_info[repo.repo_name] = {
282 other_repos_info[repo.repo_name] = {
284 'user': dict(user_id=repo.user.user_id,
283 'user': dict(user_id=repo.user.user_id,
285 username=repo.user.username,
284 username=repo.user.username,
286 firstname=repo.user.firstname,
285 firstname=repo.user.firstname,
287 lastname=repo.user.lastname,
286 lastname=repo.user.lastname,
288 gravatar_link=h.gravatar_url(repo.user.email, 14)),
287 gravatar_link=h.gravatar_url(repo.user.email, 14)),
289 'description': repo.description.split('\n', 1)[0],
288 'description': repo.description.split('\n', 1)[0],
290 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
289 'revs': h.select('other_ref', selected_other_ref, other_refs, class_='refs')
291 }
290 }
292
291
293 # add org repo to other so we can open pull request against peer branches on itself
292 # add org repo to other so we can open pull request against peer branches on itself
294 add_other_repo(org_repo, branch_rev=org_rev)
293 add_other_repo(org_repo, branch_rev=org_rev)
295 c.default_other_repo = org_repo.repo_name
294 c.default_other_repo = org_repo.repo_name
296
295
297 # gather forks and add to this list ... even though it is rare to
296 # gather forks and add to this list ... even though it is rare to
298 # request forks to pull from their parent
297 # request forks to pull from their parent
299 for fork in org_repo.forks:
298 for fork in org_repo.forks:
300 add_other_repo(fork)
299 add_other_repo(fork)
301
300
302 # add parents of this fork also, but only if it's not empty
301 # add parents of this fork also, but only if it's not empty
303 if org_repo.parent and org_repo.parent.scm_instance.revisions:
302 if org_repo.parent and org_repo.parent.scm_instance.revisions:
304 add_other_repo(org_repo.parent)
303 add_other_repo(org_repo.parent)
305 c.default_other_repo = org_repo.parent.repo_name
304 c.default_other_repo = org_repo.parent.repo_name
306
305
307 c.default_other_repo_info = other_repos_info[c.default_other_repo]
306 c.default_other_repo_info = other_repos_info[c.default_other_repo]
308 c.other_repos_info = json.dumps(other_repos_info)
307 c.other_repos_info = json.dumps(other_repos_info)
309
308
310 return render('/pullrequests/pullrequest.html')
309 return render('/pullrequests/pullrequest.html')
311
310
312 @LoginRequired()
311 @LoginRequired()
313 @NotAnonymous()
312 @NotAnonymous()
314 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
313 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
315 'repository.admin')
314 'repository.admin')
316 def create(self, repo_name):
315 def create(self, repo_name):
317 repo = RepoModel()._get_repo(repo_name)
316 repo = RepoModel()._get_repo(repo_name)
318 try:
317 try:
319 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
318 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
320 except formencode.Invalid, errors:
319 except formencode.Invalid, errors:
321 log.error(traceback.format_exc())
320 log.error(traceback.format_exc())
322 if errors.error_dict.get('revisions'):
321 if errors.error_dict.get('revisions'):
323 msg = 'Revisions: %s' % errors.error_dict['revisions']
322 msg = 'Revisions: %s' % errors.error_dict['revisions']
324 elif errors.error_dict.get('pullrequest_title'):
323 elif errors.error_dict.get('pullrequest_title'):
325 msg = _('Pull request requires a title with min. 3 chars')
324 msg = _('Pull request requires a title with min. 3 chars')
326 else:
325 else:
327 msg = _('Error creating pull request')
326 msg = _('Error creating pull request')
328
327
329 h.flash(msg, 'error')
328 h.flash(msg, 'error')
330 return redirect(url('pullrequest_home', repo_name=repo_name))
329 return redirect(url('pullrequest_home', repo_name=repo_name))
331
330
332 org_repo = _form['org_repo']
331 org_repo = _form['org_repo']
333 org_ref = 'rev:merge:%s' % _form['merge_rev']
332 org_ref = 'rev:merge:%s' % _form['merge_rev']
334 other_repo = _form['other_repo']
333 other_repo = _form['other_repo']
335 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
334 other_ref = 'rev:ancestor:%s' % _form['ancestor_rev']
336 revisions = [x for x in reversed(_form['revisions'])]
335 revisions = [x for x in reversed(_form['revisions'])]
337 reviewers = _form['review_members']
336 reviewers = _form['review_members']
338
337
339 title = _form['pullrequest_title']
338 title = _form['pullrequest_title']
340 description = _form['pullrequest_desc']
339 description = _form['pullrequest_desc']
341 try:
340 try:
342 pull_request = PullRequestModel().create(
341 pull_request = PullRequestModel().create(
343 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
342 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
344 other_ref, revisions, reviewers, title, description
343 other_ref, revisions, reviewers, title, description
345 )
344 )
346 Session().commit()
345 Session().commit()
347 h.flash(_('Successfully opened new pull request'),
346 h.flash(_('Successfully opened new pull request'),
348 category='success')
347 category='success')
349 except Exception:
348 except Exception:
350 h.flash(_('Error occurred during sending pull request'),
349 h.flash(_('Error occurred during sending pull request'),
351 category='error')
350 category='error')
352 log.error(traceback.format_exc())
351 log.error(traceback.format_exc())
353 return redirect(url('pullrequest_home', repo_name=repo_name))
352 return redirect(url('pullrequest_home', repo_name=repo_name))
354
353
355 return redirect(url('pullrequest_show', repo_name=other_repo,
354 return redirect(url('pullrequest_show', repo_name=other_repo,
356 pull_request_id=pull_request.pull_request_id))
355 pull_request_id=pull_request.pull_request_id))
357
356
358 @LoginRequired()
357 @LoginRequired()
359 @NotAnonymous()
358 @NotAnonymous()
360 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
359 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
361 'repository.admin')
360 'repository.admin')
362 @jsonify
361 @jsonify
363 def update(self, repo_name, pull_request_id):
362 def update(self, repo_name, pull_request_id):
364 pull_request = PullRequest.get_or_404(pull_request_id)
363 pull_request = PullRequest.get_or_404(pull_request_id)
365 if pull_request.is_closed():
364 if pull_request.is_closed():
366 raise HTTPForbidden()
365 raise HTTPForbidden()
367 #only owner or admin can update it
366 #only owner or admin can update it
368 owner = pull_request.author.user_id == c.rhodecode_user.user_id
367 owner = pull_request.author.user_id == c.rhodecode_user.user_id
369 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
368 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
370 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
369 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
371 request.POST.get('reviewers_ids', '').split(',')))
370 request.POST.get('reviewers_ids', '').split(',')))
372
371
373 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
372 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
374 Session().commit()
373 Session().commit()
375 return True
374 return True
376 raise HTTPForbidden()
375 raise HTTPForbidden()
377
376
378 @LoginRequired()
377 @LoginRequired()
379 @NotAnonymous()
378 @NotAnonymous()
380 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
379 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
381 'repository.admin')
380 'repository.admin')
382 @jsonify
381 @jsonify
383 def delete(self, repo_name, pull_request_id):
382 def delete(self, repo_name, pull_request_id):
384 pull_request = PullRequest.get_or_404(pull_request_id)
383 pull_request = PullRequest.get_or_404(pull_request_id)
385 #only owner can delete it !
384 #only owner can delete it !
386 if pull_request.author.user_id == c.rhodecode_user.user_id:
385 if pull_request.author.user_id == c.rhodecode_user.user_id:
387 PullRequestModel().delete(pull_request)
386 PullRequestModel().delete(pull_request)
388 Session().commit()
387 Session().commit()
389 h.flash(_('Successfully deleted pull request'),
388 h.flash(_('Successfully deleted pull request'),
390 category='success')
389 category='success')
391 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
390 return redirect(url('admin_settings_my_account', anchor='pullrequests'))
392 raise HTTPForbidden()
391 raise HTTPForbidden()
393
392
394 @LoginRequired()
393 @LoginRequired()
395 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
394 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
396 'repository.admin')
395 'repository.admin')
397 def show(self, repo_name, pull_request_id):
396 def show(self, repo_name, pull_request_id):
398 repo_model = RepoModel()
397 repo_model = RepoModel()
399 c.users_array = repo_model.get_users_js()
398 c.users_array = repo_model.get_users_js()
400 c.users_groups_array = repo_model.get_users_groups_js()
399 c.users_groups_array = repo_model.get_users_groups_js()
401 c.pull_request = PullRequest.get_or_404(pull_request_id)
400 c.pull_request = PullRequest.get_or_404(pull_request_id)
402 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
401 c.allowed_to_change_status = self._get_is_allowed_change_status(c.pull_request)
403 cc_model = ChangesetCommentsModel()
402 cc_model = ChangesetCommentsModel()
404 cs_model = ChangesetStatusModel()
403 cs_model = ChangesetStatusModel()
405 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
404 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
406 pull_request=c.pull_request,
405 pull_request=c.pull_request,
407 with_revisions=True)
406 with_revisions=True)
408
407
409 cs_statuses = defaultdict(list)
408 cs_statuses = defaultdict(list)
410 for st in _cs_statuses:
409 for st in _cs_statuses:
411 cs_statuses[st.author.username] += [st]
410 cs_statuses[st.author.username] += [st]
412
411
413 c.pull_request_reviewers = []
412 c.pull_request_reviewers = []
414 c.pull_request_pending_reviewers = []
413 c.pull_request_pending_reviewers = []
415 for o in c.pull_request.reviewers:
414 for o in c.pull_request.reviewers:
416 st = cs_statuses.get(o.user.username, None)
415 st = cs_statuses.get(o.user.username, None)
417 if st:
416 if st:
418 sorter = lambda k: k.version
417 sorter = lambda k: k.version
419 st = [(x, list(y)[0])
418 st = [(x, list(y)[0])
420 for x, y in (groupby(sorted(st, key=sorter), sorter))]
419 for x, y in (groupby(sorted(st, key=sorter), sorter))]
421 else:
420 else:
422 c.pull_request_pending_reviewers.append(o.user)
421 c.pull_request_pending_reviewers.append(o.user)
423 c.pull_request_reviewers.append([o.user, st])
422 c.pull_request_reviewers.append([o.user, st])
424
423
425 # pull_requests repo_name we opened it against
424 # pull_requests repo_name we opened it against
426 # ie. other_repo must match
425 # ie. other_repo must match
427 if repo_name != c.pull_request.other_repo.repo_name:
426 if repo_name != c.pull_request.other_repo.repo_name:
428 raise HTTPNotFound
427 raise HTTPNotFound
429
428
430 # load compare data into template context
429 # load compare data into template context
431 enable_comments = not c.pull_request.is_closed()
430 enable_comments = not c.pull_request.is_closed()
432 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
431 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
433
432
434 # inline comments
433 # inline comments
435 c.inline_cnt = 0
434 c.inline_cnt = 0
436 c.inline_comments = cc_model.get_inline_comments(
435 c.inline_comments = cc_model.get_inline_comments(
437 c.rhodecode_db_repo.repo_id,
436 c.rhodecode_db_repo.repo_id,
438 pull_request=pull_request_id)
437 pull_request=pull_request_id)
439 # count inline comments
438 # count inline comments
440 for __, lines in c.inline_comments:
439 for __, lines in c.inline_comments:
441 for comments in lines.values():
440 for comments in lines.values():
442 c.inline_cnt += len(comments)
441 c.inline_cnt += len(comments)
443 # comments
442 # comments
444 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
443 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
445 pull_request=pull_request_id)
444 pull_request=pull_request_id)
446
445
447 try:
446 try:
448 cur_status = c.statuses[c.pull_request.revisions[0]][0]
447 cur_status = c.statuses[c.pull_request.revisions[0]][0]
449 except Exception:
448 except Exception:
450 log.error(traceback.format_exc())
449 log.error(traceback.format_exc())
451 cur_status = 'undefined'
450 cur_status = 'undefined'
452 if c.pull_request.is_closed() and 0:
451 if c.pull_request.is_closed() and 0:
453 c.current_changeset_status = cur_status
452 c.current_changeset_status = cur_status
454 else:
453 else:
455 # changeset(pull-request) status calulation based on reviewers
454 # changeset(pull-request) status calulation based on reviewers
456 c.current_changeset_status = cs_model.calculate_status(
455 c.current_changeset_status = cs_model.calculate_status(
457 c.pull_request_reviewers,
456 c.pull_request_reviewers,
458 )
457 )
459 c.changeset_statuses = ChangesetStatus.STATUSES
458 c.changeset_statuses = ChangesetStatus.STATUSES
460
459
461 c.as_form = False
460 c.as_form = False
462 c.ancestor = None # there is one - but right here we don't know which
461 c.ancestor = None # there is one - but right here we don't know which
463 return render('/pullrequests/pullrequest_show.html')
462 return render('/pullrequests/pullrequest_show.html')
464
463
465 @LoginRequired()
464 @LoginRequired()
466 @NotAnonymous()
465 @NotAnonymous()
467 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
466 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
468 'repository.admin')
467 'repository.admin')
469 @jsonify
468 @jsonify
470 def comment(self, repo_name, pull_request_id):
469 def comment(self, repo_name, pull_request_id):
471 pull_request = PullRequest.get_or_404(pull_request_id)
470 pull_request = PullRequest.get_or_404(pull_request_id)
472 if pull_request.is_closed():
471 if pull_request.is_closed():
473 raise HTTPForbidden()
472 raise HTTPForbidden()
474
473
475 status = request.POST.get('changeset_status')
474 status = request.POST.get('changeset_status')
476 change_status = request.POST.get('change_changeset_status')
475 change_status = request.POST.get('change_changeset_status')
477 text = request.POST.get('text')
476 text = request.POST.get('text')
478 close_pr = request.POST.get('save_close')
477 close_pr = request.POST.get('save_close')
479
478
480 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
479 allowed_to_change_status = self._get_is_allowed_change_status(pull_request)
481 if status and change_status and allowed_to_change_status:
480 if status and change_status and allowed_to_change_status:
482 _def = (_('Status change -> %s')
481 _def = (_('Status change -> %s')
483 % ChangesetStatus.get_status_lbl(status))
482 % ChangesetStatus.get_status_lbl(status))
484 if close_pr:
483 if close_pr:
485 _def = _('Closing with') + ' ' + _def
484 _def = _('Closing with') + ' ' + _def
486 text = text or _def
485 text = text or _def
487 comm = ChangesetCommentsModel().create(
486 comm = ChangesetCommentsModel().create(
488 text=text,
487 text=text,
489 repo=c.rhodecode_db_repo.repo_id,
488 repo=c.rhodecode_db_repo.repo_id,
490 user=c.rhodecode_user.user_id,
489 user=c.rhodecode_user.user_id,
491 pull_request=pull_request_id,
490 pull_request=pull_request_id,
492 f_path=request.POST.get('f_path'),
491 f_path=request.POST.get('f_path'),
493 line_no=request.POST.get('line'),
492 line_no=request.POST.get('line'),
494 status_change=(ChangesetStatus.get_status_lbl(status)
493 status_change=(ChangesetStatus.get_status_lbl(status)
495 if status and change_status
494 if status and change_status
496 and allowed_to_change_status else None),
495 and allowed_to_change_status else None),
497 closing_pr=close_pr
496 closing_pr=close_pr
498 )
497 )
499
498
500 action_logger(self.rhodecode_user,
499 action_logger(self.rhodecode_user,
501 'user_commented_pull_request:%s' % pull_request_id,
500 'user_commented_pull_request:%s' % pull_request_id,
502 c.rhodecode_db_repo, self.ip_addr, self.sa)
501 c.rhodecode_db_repo, self.ip_addr, self.sa)
503
502
504 if allowed_to_change_status:
503 if allowed_to_change_status:
505 # get status if set !
504 # get status if set !
506 if status and change_status:
505 if status and change_status:
507 ChangesetStatusModel().set_status(
506 ChangesetStatusModel().set_status(
508 c.rhodecode_db_repo.repo_id,
507 c.rhodecode_db_repo.repo_id,
509 status,
508 status,
510 c.rhodecode_user.user_id,
509 c.rhodecode_user.user_id,
511 comm,
510 comm,
512 pull_request=pull_request_id
511 pull_request=pull_request_id
513 )
512 )
514
513
515 if close_pr:
514 if close_pr:
516 if status in ['rejected', 'approved']:
515 if status in ['rejected', 'approved']:
517 PullRequestModel().close_pull_request(pull_request_id)
516 PullRequestModel().close_pull_request(pull_request_id)
518 action_logger(self.rhodecode_user,
517 action_logger(self.rhodecode_user,
519 'user_closed_pull_request:%s' % pull_request_id,
518 'user_closed_pull_request:%s' % pull_request_id,
520 c.rhodecode_db_repo, self.ip_addr, self.sa)
519 c.rhodecode_db_repo, self.ip_addr, self.sa)
521 else:
520 else:
522 h.flash(_('Closing pull request on other statuses than '
521 h.flash(_('Closing pull request on other statuses than '
523 'rejected or approved forbidden'),
522 'rejected or approved forbidden'),
524 category='warning')
523 category='warning')
525
524
526 Session().commit()
525 Session().commit()
527
526
528 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
527 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
529 return redirect(h.url('pullrequest_show', repo_name=repo_name,
528 return redirect(h.url('pullrequest_show', repo_name=repo_name,
530 pull_request_id=pull_request_id))
529 pull_request_id=pull_request_id))
531
530
532 data = {
531 data = {
533 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
532 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
534 }
533 }
535 if comm:
534 if comm:
536 c.co = comm
535 c.co = comm
537 data.update(comm.get_dict())
536 data.update(comm.get_dict())
538 data.update({'rendered_text':
537 data.update({'rendered_text':
539 render('changeset/changeset_comment_block.html')})
538 render('changeset/changeset_comment_block.html')})
540
539
541 return data
540 return data
542
541
543 @LoginRequired()
542 @LoginRequired()
544 @NotAnonymous()
543 @NotAnonymous()
545 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
544 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
546 'repository.admin')
545 'repository.admin')
547 @jsonify
546 @jsonify
548 def delete_comment(self, repo_name, comment_id):
547 def delete_comment(self, repo_name, comment_id):
549 co = ChangesetComment.get(comment_id)
548 co = ChangesetComment.get(comment_id)
550 if co.pull_request.is_closed():
549 if co.pull_request.is_closed():
551 #don't allow deleting comments on closed pull request
550 #don't allow deleting comments on closed pull request
552 raise HTTPForbidden()
551 raise HTTPForbidden()
553
552
554 owner = co.author.user_id == c.rhodecode_user.user_id
553 owner = co.author.user_id == c.rhodecode_user.user_id
555 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
554 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
556 ChangesetCommentsModel().delete(comment=co)
555 ChangesetCommentsModel().delete(comment=co)
557 Session().commit()
556 Session().commit()
558 return True
557 return True
559 else:
558 else:
560 raise HTTPForbidden()
559 raise HTTPForbidden()
@@ -1,726 +1,725 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.db_manage
3 rhodecode.lib.db_manage
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Database creation, and setup module for RhodeCode. Used for creation
6 Database creation, and setup module for RhodeCode. Used for creation
7 of database as well as for migration operations
7 of database as well as for migration operations
8
8
9 :created_on: Apr 10, 2010
9 :created_on: Apr 10, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import uuid
29 import uuid
30 import logging
30 import logging
31 from os.path import dirname as dn, join as jn
31 from os.path import dirname as dn, join as jn
32
32
33 from rhodecode import __dbversion__, __py_version__
33 from rhodecode import __dbversion__, __py_version__
34
34
35 from rhodecode.model.user import UserModel
35 from rhodecode.model.user import UserModel
36 from rhodecode.lib.utils import ask_ok
36 from rhodecode.lib.utils import ask_ok
37 from rhodecode.model import init_model
37 from rhodecode.model import init_model
38 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
38 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
39 RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup, \
39 RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup, \
40 UserRepoGroupToPerm, CacheInvalidation, UserGroup
40 UserRepoGroupToPerm, CacheInvalidation, UserGroup
41
41
42 from sqlalchemy.engine import create_engine
42 from sqlalchemy.engine import create_engine
43 from rhodecode.model.repos_group import ReposGroupModel
43 from rhodecode.model.repos_group import ReposGroupModel
44 #from rhodecode.model import meta
44 #from rhodecode.model import meta
45 from rhodecode.model.meta import Session, Base
45 from rhodecode.model.meta import Session, Base
46 from rhodecode.model.repo import RepoModel
46 from rhodecode.model.repo import RepoModel
47 from rhodecode.model.permission import PermissionModel
47 from rhodecode.model.permission import PermissionModel
48 from rhodecode.model.users_group import UserGroupModel
48 from rhodecode.model.users_group import UserGroupModel
49
49
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 def notify(msg):
54 def notify(msg):
55 """
55 """
56 Notification for migrations messages
56 Notification for migrations messages
57 """
57 """
58 ml = len(msg) + (4 * 2)
58 ml = len(msg) + (4 * 2)
59 print >> sys.stdout, ('*** %s ***\n%s' % (msg, '*' * ml)).upper()
59 print >> sys.stdout, ('*** %s ***\n%s' % (msg, '*' * ml)).upper()
60
60
61
61
62 class DbManage(object):
62 class DbManage(object):
63 def __init__(self, log_sql, dbconf, root, tests=False, cli_args={}):
63 def __init__(self, log_sql, dbconf, root, tests=False, cli_args={}):
64 self.dbname = dbconf.split('/')[-1]
64 self.dbname = dbconf.split('/')[-1]
65 self.tests = tests
65 self.tests = tests
66 self.root = root
66 self.root = root
67 self.dburi = dbconf
67 self.dburi = dbconf
68 self.log_sql = log_sql
68 self.log_sql = log_sql
69 self.db_exists = False
69 self.db_exists = False
70 self.cli_args = cli_args
70 self.cli_args = cli_args
71 self.init_db()
71 self.init_db()
72
72
73 force_ask = self.cli_args.get('force_ask')
73 force_ask = self.cli_args.get('force_ask')
74 if force_ask is not None:
74 if force_ask is not None:
75 global ask_ok
75 global ask_ok
76 ask_ok = lambda *args, **kwargs: force_ask
76 ask_ok = lambda *args, **kwargs: force_ask
77
77
78 def init_db(self):
78 def init_db(self):
79 engine = create_engine(self.dburi, echo=self.log_sql)
79 engine = create_engine(self.dburi, echo=self.log_sql)
80 init_model(engine)
80 init_model(engine)
81 self.sa = Session()
81 self.sa = Session()
82
82
83 def create_tables(self, override=False):
83 def create_tables(self, override=False):
84 """
84 """
85 Create a auth database
85 Create a auth database
86 """
86 """
87
87
88 log.info("Any existing database is going to be destroyed")
88 log.info("Any existing database is going to be destroyed")
89 if self.tests:
89 if self.tests:
90 destroy = True
90 destroy = True
91 else:
91 else:
92 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
92 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
93 if not destroy:
93 if not destroy:
94 sys.exit('Nothing tables created')
94 sys.exit('Nothing tables created')
95 if destroy:
95 if destroy:
96 Base.metadata.drop_all()
96 Base.metadata.drop_all()
97
97
98 checkfirst = not override
98 checkfirst = not override
99 Base.metadata.create_all(checkfirst=checkfirst)
99 Base.metadata.create_all(checkfirst=checkfirst)
100 log.info('Created tables for %s' % self.dbname)
100 log.info('Created tables for %s' % self.dbname)
101
101
102 def set_db_version(self):
102 def set_db_version(self):
103 ver = DbMigrateVersion()
103 ver = DbMigrateVersion()
104 ver.version = __dbversion__
104 ver.version = __dbversion__
105 ver.repository_id = 'rhodecode_db_migrations'
105 ver.repository_id = 'rhodecode_db_migrations'
106 ver.repository_path = 'versions'
106 ver.repository_path = 'versions'
107 self.sa.add(ver)
107 self.sa.add(ver)
108 log.info('db version set to: %s' % __dbversion__)
108 log.info('db version set to: %s' % __dbversion__)
109
109
110 def upgrade(self):
110 def upgrade(self):
111 """
111 """
112 Upgrades given database schema to given revision following
112 Upgrades given database schema to given revision following
113 all needed steps, to perform the upgrade
113 all needed steps, to perform the upgrade
114
114
115 """
115 """
116
116
117 from rhodecode.lib.dbmigrate.migrate.versioning import api
117 from rhodecode.lib.dbmigrate.migrate.versioning import api
118 from rhodecode.lib.dbmigrate.migrate.exceptions import \
118 from rhodecode.lib.dbmigrate.migrate.exceptions import \
119 DatabaseNotControlledError
119 DatabaseNotControlledError
120
120
121 if 'sqlite' in self.dburi:
121 if 'sqlite' in self.dburi:
122 print (
122 print (
123 '********************** WARNING **********************\n'
123 '********************** WARNING **********************\n'
124 'Make sure your version of sqlite is at least 3.7.X. \n'
124 'Make sure your version of sqlite is at least 3.7.X. \n'
125 'Earlier versions are known to fail on some migrations\n'
125 'Earlier versions are known to fail on some migrations\n'
126 '*****************************************************\n'
126 '*****************************************************\n'
127 )
127 )
128 upgrade = ask_ok('You are about to perform database upgrade, make '
128 upgrade = ask_ok('You are about to perform database upgrade, make '
129 'sure You backed up your database before. '
129 'sure You backed up your database before. '
130 'Continue ? [y/n]')
130 'Continue ? [y/n]')
131 if not upgrade:
131 if not upgrade:
132 sys.exit('No upgrade performed')
132 sys.exit('No upgrade performed')
133
133
134 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
134 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
135 'rhodecode/lib/dbmigrate')
135 'rhodecode/lib/dbmigrate')
136 db_uri = self.dburi
136 db_uri = self.dburi
137
137
138 try:
138 try:
139 curr_version = api.db_version(db_uri, repository_path)
139 curr_version = api.db_version(db_uri, repository_path)
140 msg = ('Found current database under version'
140 msg = ('Found current database under version'
141 ' control with version %s' % curr_version)
141 ' control with version %s' % curr_version)
142
142
143 except (RuntimeError, DatabaseNotControlledError):
143 except (RuntimeError, DatabaseNotControlledError):
144 curr_version = 1
144 curr_version = 1
145 msg = ('Current database is not under version control. Setting'
145 msg = ('Current database is not under version control. Setting'
146 ' as version %s' % curr_version)
146 ' as version %s' % curr_version)
147 api.version_control(db_uri, repository_path, curr_version)
147 api.version_control(db_uri, repository_path, curr_version)
148
148
149 notify(msg)
149 notify(msg)
150
150
151 if curr_version == __dbversion__:
151 if curr_version == __dbversion__:
152 sys.exit('This database is already at the newest version')
152 sys.exit('This database is already at the newest version')
153
153
154 # clear cache keys
154 # clear cache keys
155 log.info("Clearing cache keys now...")
155 log.info("Clearing cache keys now...")
156 CacheInvalidation.clear_cache()
156 CacheInvalidation.clear_cache()
157
157
158 #======================================================================
158 #======================================================================
159 # UPGRADE STEPS
159 # UPGRADE STEPS
160 #======================================================================
160 #======================================================================
161
161
162 class UpgradeSteps(object):
162 class UpgradeSteps(object):
163 """
163 """
164 Those steps follow schema versions so for example schema
164 Those steps follow schema versions so for example schema
165 for example schema with seq 002 == step_2 and so on.
165 for example schema with seq 002 == step_2 and so on.
166 """
166 """
167
167
168 def __init__(self, klass):
168 def __init__(self, klass):
169 self.klass = klass
169 self.klass = klass
170
170
171 def step_0(self):
171 def step_0(self):
172 # step 0 is the schema upgrade, and than follow proper upgrades
172 # step 0 is the schema upgrade, and than follow proper upgrades
173 notify('attempting to do database upgrade from '
173 notify('attempting to do database upgrade from '
174 'version %s to version %s' %(curr_version, __dbversion__))
174 'version %s to version %s' %(curr_version, __dbversion__))
175 api.upgrade(db_uri, repository_path, __dbversion__)
175 api.upgrade(db_uri, repository_path, __dbversion__)
176 notify('Schema upgrade completed')
176 notify('Schema upgrade completed')
177
177
178 def step_1(self):
178 def step_1(self):
179 pass
179 pass
180
180
181 def step_2(self):
181 def step_2(self):
182 notify('Patching repo paths for newer version of RhodeCode')
182 notify('Patching repo paths for newer version of RhodeCode')
183 self.klass.fix_repo_paths()
183 self.klass.fix_repo_paths()
184
184
185 notify('Patching default user of RhodeCode')
185 notify('Patching default user of RhodeCode')
186 self.klass.fix_default_user()
186 self.klass.fix_default_user()
187
187
188 log.info('Changing ui settings')
188 log.info('Changing ui settings')
189 self.klass.create_ui_settings()
189 self.klass.create_ui_settings()
190
190
191 def step_3(self):
191 def step_3(self):
192 notify('Adding additional settings into RhodeCode db')
192 notify('Adding additional settings into RhodeCode db')
193 self.klass.fix_settings()
193 self.klass.fix_settings()
194 notify('Adding ldap defaults')
194 notify('Adding ldap defaults')
195 self.klass.create_ldap_options(skip_existing=True)
195 self.klass.create_ldap_options(skip_existing=True)
196
196
197 def step_4(self):
197 def step_4(self):
198 notify('create permissions and fix groups')
198 notify('create permissions and fix groups')
199 self.klass.create_permissions()
199 self.klass.create_permissions()
200 self.klass.fixup_groups()
200 self.klass.fixup_groups()
201
201
202 def step_5(self):
202 def step_5(self):
203 pass
203 pass
204
204
205 def step_6(self):
205 def step_6(self):
206
206
207 notify('re-checking permissions')
207 notify('re-checking permissions')
208 self.klass.create_permissions()
208 self.klass.create_permissions()
209
209
210 notify('installing new UI options')
210 notify('installing new UI options')
211 sett4 = RhodeCodeSetting('show_public_icon', True)
211 sett4 = RhodeCodeSetting('show_public_icon', True)
212 Session().add(sett4)
212 Session().add(sett4)
213 sett5 = RhodeCodeSetting('show_private_icon', True)
213 sett5 = RhodeCodeSetting('show_private_icon', True)
214 Session().add(sett5)
214 Session().add(sett5)
215 sett6 = RhodeCodeSetting('stylify_metatags', False)
215 sett6 = RhodeCodeSetting('stylify_metatags', False)
216 Session().add(sett6)
216 Session().add(sett6)
217
217
218 notify('fixing old PULL hook')
218 notify('fixing old PULL hook')
219 _pull = RhodeCodeUi.get_by_key('preoutgoing.pull_logger')
219 _pull = RhodeCodeUi.get_by_key('preoutgoing.pull_logger')
220 if _pull:
220 if _pull:
221 _pull.ui_key = RhodeCodeUi.HOOK_PULL
221 _pull.ui_key = RhodeCodeUi.HOOK_PULL
222 Session().add(_pull)
222 Session().add(_pull)
223
223
224 notify('fixing old PUSH hook')
224 notify('fixing old PUSH hook')
225 _push = RhodeCodeUi.get_by_key('pretxnchangegroup.push_logger')
225 _push = RhodeCodeUi.get_by_key('pretxnchangegroup.push_logger')
226 if _push:
226 if _push:
227 _push.ui_key = RhodeCodeUi.HOOK_PUSH
227 _push.ui_key = RhodeCodeUi.HOOK_PUSH
228 Session().add(_push)
228 Session().add(_push)
229
229
230 notify('installing new pre-push hook')
230 notify('installing new pre-push hook')
231 hooks4 = RhodeCodeUi()
231 hooks4 = RhodeCodeUi()
232 hooks4.ui_section = 'hooks'
232 hooks4.ui_section = 'hooks'
233 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
233 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
234 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
234 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
235 Session().add(hooks4)
235 Session().add(hooks4)
236
236
237 notify('installing new pre-pull hook')
237 notify('installing new pre-pull hook')
238 hooks6 = RhodeCodeUi()
238 hooks6 = RhodeCodeUi()
239 hooks6.ui_section = 'hooks'
239 hooks6.ui_section = 'hooks'
240 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
240 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
241 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
241 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
242 Session().add(hooks6)
242 Session().add(hooks6)
243
243
244 notify('installing hgsubversion option')
244 notify('installing hgsubversion option')
245 # enable hgsubversion disabled by default
245 # enable hgsubversion disabled by default
246 hgsubversion = RhodeCodeUi()
246 hgsubversion = RhodeCodeUi()
247 hgsubversion.ui_section = 'extensions'
247 hgsubversion.ui_section = 'extensions'
248 hgsubversion.ui_key = 'hgsubversion'
248 hgsubversion.ui_key = 'hgsubversion'
249 hgsubversion.ui_value = ''
249 hgsubversion.ui_value = ''
250 hgsubversion.ui_active = False
250 hgsubversion.ui_active = False
251 Session().add(hgsubversion)
251 Session().add(hgsubversion)
252
252
253 notify('installing hg git option')
253 notify('installing hg git option')
254 # enable hggit disabled by default
254 # enable hggit disabled by default
255 hggit = RhodeCodeUi()
255 hggit = RhodeCodeUi()
256 hggit.ui_section = 'extensions'
256 hggit.ui_section = 'extensions'
257 hggit.ui_key = 'hggit'
257 hggit.ui_key = 'hggit'
258 hggit.ui_value = ''
258 hggit.ui_value = ''
259 hggit.ui_active = False
259 hggit.ui_active = False
260 Session().add(hggit)
260 Session().add(hggit)
261
261
262 notify('re-check default permissions')
262 notify('re-check default permissions')
263 default_user = User.get_by_username(User.DEFAULT_USER)
263 default_user = User.get_by_username(User.DEFAULT_USER)
264 perm = Permission.get_by_key('hg.fork.repository')
264 perm = Permission.get_by_key('hg.fork.repository')
265 reg_perm = UserToPerm()
265 reg_perm = UserToPerm()
266 reg_perm.user = default_user
266 reg_perm.user = default_user
267 reg_perm.permission = perm
267 reg_perm.permission = perm
268 Session().add(reg_perm)
268 Session().add(reg_perm)
269
269
270 def step_7(self):
270 def step_7(self):
271 perm_fixes = self.klass.reset_permissions(User.DEFAULT_USER)
271 perm_fixes = self.klass.reset_permissions(User.DEFAULT_USER)
272 Session().commit()
272 Session().commit()
273 if perm_fixes:
273 if perm_fixes:
274 notify('There was an inconsistent state of permissions '
274 notify('There was an inconsistent state of permissions '
275 'detected for default user. Permissions are now '
275 'detected for default user. Permissions are now '
276 'reset to the default value for default user. '
276 'reset to the default value for default user. '
277 'Please validate and check default permissions '
277 'Please validate and check default permissions '
278 'in admin panel')
278 'in admin panel')
279
279
280 def step_8(self):
280 def step_8(self):
281 self.klass.create_permissions()
281 self.klass.create_permissions()
282 self.klass.populate_default_permissions()
282 self.klass.populate_default_permissions()
283 self.klass.create_default_options(skip_existing=True)
283 self.klass.create_default_options(skip_existing=True)
284 Session().commit()
284 Session().commit()
285
285
286 def step_9(self):
286 def step_9(self):
287 perm_fixes = self.klass.reset_permissions(User.DEFAULT_USER)
287 perm_fixes = self.klass.reset_permissions(User.DEFAULT_USER)
288 Session().commit()
288 Session().commit()
289 if perm_fixes:
289 if perm_fixes:
290 notify('There was an inconsistent state of permissions '
290 notify('There was an inconsistent state of permissions '
291 'detected for default user. Permissions are now '
291 'detected for default user. Permissions are now '
292 'reset to the default value for default user. '
292 'reset to the default value for default user. '
293 'Please validate and check default permissions '
293 'Please validate and check default permissions '
294 'in admin panel')
294 'in admin panel')
295
295
296 def step_10(self):
296 def step_10(self):
297 pass
297 pass
298
298
299 def step_11(self):
299 def step_11(self):
300 self.klass.update_repo_info()
300 self.klass.update_repo_info()
301
301
302 def step_12(self):
302 def step_12(self):
303 self.klass.create_permissions()
303 self.klass.create_permissions()
304 Session().commit()
304 Session().commit()
305
305
306 self.klass.populate_default_permissions()
306 self.klass.populate_default_permissions()
307 Session().commit()
307 Session().commit()
308
308
309 #fix all usergroups
309 #fix all usergroups
310 ug_model = UserGroupModel()
310 ug_model = UserGroupModel()
311 for ug in UserGroup.get_all():
311 for ug in UserGroup.get_all():
312 perm_obj = ug_model._create_default_perms(ug)
312 perm_obj = ug_model._create_default_perms(ug)
313 Session().add(perm_obj)
313 Session().add(perm_obj)
314 Session().commit()
314 Session().commit()
315
315
316 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
316 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
317
317
318 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
318 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
319 _step = None
319 _step = None
320 for step in upgrade_steps:
320 for step in upgrade_steps:
321 notify('performing upgrade step %s' % step)
321 notify('performing upgrade step %s' % step)
322 getattr(UpgradeSteps(self), 'step_%s' % step)()
322 getattr(UpgradeSteps(self), 'step_%s' % step)()
323 self.sa.commit()
323 self.sa.commit()
324 _step = step
324 _step = step
325
325
326 notify('upgrade to version %s successful' % _step)
326 notify('upgrade to version %s successful' % _step)
327
327
328 def fix_repo_paths(self):
328 def fix_repo_paths(self):
329 """
329 """
330 Fixes a old rhodecode version path into new one without a '*'
330 Fixes a old rhodecode version path into new one without a '*'
331 """
331 """
332
332
333 paths = self.sa.query(RhodeCodeUi)\
333 paths = self.sa.query(RhodeCodeUi)\
334 .filter(RhodeCodeUi.ui_key == '/')\
334 .filter(RhodeCodeUi.ui_key == '/')\
335 .scalar()
335 .scalar()
336
336
337 paths.ui_value = paths.ui_value.replace('*', '')
337 paths.ui_value = paths.ui_value.replace('*', '')
338
338
339 try:
339 try:
340 self.sa.add(paths)
340 self.sa.add(paths)
341 self.sa.commit()
341 self.sa.commit()
342 except Exception:
342 except Exception:
343 self.sa.rollback()
343 self.sa.rollback()
344 raise
344 raise
345
345
346 def fix_default_user(self):
346 def fix_default_user(self):
347 """
347 """
348 Fixes a old default user with some 'nicer' default values,
348 Fixes a old default user with some 'nicer' default values,
349 used mostly for anonymous access
349 used mostly for anonymous access
350 """
350 """
351 def_user = self.sa.query(User)\
351 def_user = self.sa.query(User)\
352 .filter(User.username == 'default')\
352 .filter(User.username == 'default')\
353 .one()
353 .one()
354
354
355 def_user.name = 'Anonymous'
355 def_user.name = 'Anonymous'
356 def_user.lastname = 'User'
356 def_user.lastname = 'User'
357 def_user.email = 'anonymous@rhodecode.org'
357 def_user.email = 'anonymous@rhodecode.org'
358
358
359 try:
359 try:
360 self.sa.add(def_user)
360 self.sa.add(def_user)
361 self.sa.commit()
361 self.sa.commit()
362 except Exception:
362 except Exception:
363 self.sa.rollback()
363 self.sa.rollback()
364 raise
364 raise
365
365
366 def fix_settings(self):
366 def fix_settings(self):
367 """
367 """
368 Fixes rhodecode settings adds ga_code key for google analytics
368 Fixes rhodecode settings adds ga_code key for google analytics
369 """
369 """
370
370
371 hgsettings3 = RhodeCodeSetting('ga_code', '')
371 hgsettings3 = RhodeCodeSetting('ga_code', '')
372
372
373 try:
373 try:
374 self.sa.add(hgsettings3)
374 self.sa.add(hgsettings3)
375 self.sa.commit()
375 self.sa.commit()
376 except Exception:
376 except Exception:
377 self.sa.rollback()
377 self.sa.rollback()
378 raise
378 raise
379
379
380 def admin_prompt(self, second=False):
380 def admin_prompt(self, second=False):
381 if not self.tests:
381 if not self.tests:
382 import getpass
382 import getpass
383
383
384 # defaults
384 # defaults
385 defaults = self.cli_args
385 defaults = self.cli_args
386 username = defaults.get('username')
386 username = defaults.get('username')
387 password = defaults.get('password')
387 password = defaults.get('password')
388 email = defaults.get('email')
388 email = defaults.get('email')
389
389
390 def get_password():
390 def get_password():
391 password = getpass.getpass('Specify admin password '
391 password = getpass.getpass('Specify admin password '
392 '(min 6 chars):')
392 '(min 6 chars):')
393 confirm = getpass.getpass('Confirm password:')
393 confirm = getpass.getpass('Confirm password:')
394
394
395 if password != confirm:
395 if password != confirm:
396 log.error('passwords mismatch')
396 log.error('passwords mismatch')
397 return False
397 return False
398 if len(password) < 6:
398 if len(password) < 6:
399 log.error('password is to short use at least 6 characters')
399 log.error('password is to short use at least 6 characters')
400 return False
400 return False
401
401
402 return password
402 return password
403 if username is None:
403 if username is None:
404 username = raw_input('Specify admin username:')
404 username = raw_input('Specify admin username:')
405 if password is None:
405 if password is None:
406 password = get_password()
406 password = get_password()
407 if not password:
407 if not password:
408 #second try
408 #second try
409 password = get_password()
409 password = get_password()
410 if not password:
410 if not password:
411 sys.exit()
411 sys.exit()
412 if email is None:
412 if email is None:
413 email = raw_input('Specify admin email:')
413 email = raw_input('Specify admin email:')
414 self.create_user(username, password, email, True)
414 self.create_user(username, password, email, True)
415 else:
415 else:
416 log.info('creating admin and regular test users')
416 log.info('creating admin and regular test users')
417 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
417 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
418 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
418 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
419 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
419 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
420 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
420 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
421 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
421 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
422
422
423 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
423 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
424 TEST_USER_ADMIN_EMAIL, True)
424 TEST_USER_ADMIN_EMAIL, True)
425
425
426 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
426 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
427 TEST_USER_REGULAR_EMAIL, False)
427 TEST_USER_REGULAR_EMAIL, False)
428
428
429 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
429 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
430 TEST_USER_REGULAR2_EMAIL, False)
430 TEST_USER_REGULAR2_EMAIL, False)
431
431
432 def create_ui_settings(self):
432 def create_ui_settings(self):
433 """
433 """
434 Creates ui settings, fills out hooks
434 Creates ui settings, fills out hooks
435 and disables dotencode
435 and disables dotencode
436 """
436 """
437
437
438 #HOOKS
438 #HOOKS
439 hooks1_key = RhodeCodeUi.HOOK_UPDATE
439 hooks1_key = RhodeCodeUi.HOOK_UPDATE
440 hooks1_ = self.sa.query(RhodeCodeUi)\
440 hooks1_ = self.sa.query(RhodeCodeUi)\
441 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
441 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
442
442
443 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
443 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
444 hooks1.ui_section = 'hooks'
444 hooks1.ui_section = 'hooks'
445 hooks1.ui_key = hooks1_key
445 hooks1.ui_key = hooks1_key
446 hooks1.ui_value = 'hg update >&2'
446 hooks1.ui_value = 'hg update >&2'
447 hooks1.ui_active = False
447 hooks1.ui_active = False
448 self.sa.add(hooks1)
448 self.sa.add(hooks1)
449
449
450 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
450 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
451 hooks2_ = self.sa.query(RhodeCodeUi)\
451 hooks2_ = self.sa.query(RhodeCodeUi)\
452 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
452 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
453 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
453 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
454 hooks2.ui_section = 'hooks'
454 hooks2.ui_section = 'hooks'
455 hooks2.ui_key = hooks2_key
455 hooks2.ui_key = hooks2_key
456 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
456 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
457 self.sa.add(hooks2)
457 self.sa.add(hooks2)
458
458
459 hooks3 = RhodeCodeUi()
459 hooks3 = RhodeCodeUi()
460 hooks3.ui_section = 'hooks'
460 hooks3.ui_section = 'hooks'
461 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
461 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
462 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
462 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
463 self.sa.add(hooks3)
463 self.sa.add(hooks3)
464
464
465 hooks4 = RhodeCodeUi()
465 hooks4 = RhodeCodeUi()
466 hooks4.ui_section = 'hooks'
466 hooks4.ui_section = 'hooks'
467 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
467 hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
468 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
468 hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
469 self.sa.add(hooks4)
469 self.sa.add(hooks4)
470
470
471 hooks5 = RhodeCodeUi()
471 hooks5 = RhodeCodeUi()
472 hooks5.ui_section = 'hooks'
472 hooks5.ui_section = 'hooks'
473 hooks5.ui_key = RhodeCodeUi.HOOK_PULL
473 hooks5.ui_key = RhodeCodeUi.HOOK_PULL
474 hooks5.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
474 hooks5.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
475 self.sa.add(hooks5)
475 self.sa.add(hooks5)
476
476
477 hooks6 = RhodeCodeUi()
477 hooks6 = RhodeCodeUi()
478 hooks6.ui_section = 'hooks'
478 hooks6.ui_section = 'hooks'
479 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
479 hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
480 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
480 hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
481 self.sa.add(hooks6)
481 self.sa.add(hooks6)
482
482
483 # enable largefiles
483 # enable largefiles
484 largefiles = RhodeCodeUi()
484 largefiles = RhodeCodeUi()
485 largefiles.ui_section = 'extensions'
485 largefiles.ui_section = 'extensions'
486 largefiles.ui_key = 'largefiles'
486 largefiles.ui_key = 'largefiles'
487 largefiles.ui_value = ''
487 largefiles.ui_value = ''
488 self.sa.add(largefiles)
488 self.sa.add(largefiles)
489
489
490 # enable hgsubversion disabled by default
490 # enable hgsubversion disabled by default
491 hgsubversion = RhodeCodeUi()
491 hgsubversion = RhodeCodeUi()
492 hgsubversion.ui_section = 'extensions'
492 hgsubversion.ui_section = 'extensions'
493 hgsubversion.ui_key = 'hgsubversion'
493 hgsubversion.ui_key = 'hgsubversion'
494 hgsubversion.ui_value = ''
494 hgsubversion.ui_value = ''
495 hgsubversion.ui_active = False
495 hgsubversion.ui_active = False
496 self.sa.add(hgsubversion)
496 self.sa.add(hgsubversion)
497
497
498 # enable hggit disabled by default
498 # enable hggit disabled by default
499 hggit = RhodeCodeUi()
499 hggit = RhodeCodeUi()
500 hggit.ui_section = 'extensions'
500 hggit.ui_section = 'extensions'
501 hggit.ui_key = 'hggit'
501 hggit.ui_key = 'hggit'
502 hggit.ui_value = ''
502 hggit.ui_value = ''
503 hggit.ui_active = False
503 hggit.ui_active = False
504 self.sa.add(hggit)
504 self.sa.add(hggit)
505
505
506 def create_ldap_options(self, skip_existing=False):
506 def create_ldap_options(self, skip_existing=False):
507 """Creates ldap settings"""
507 """Creates ldap settings"""
508
508
509 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
509 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
510 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
510 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
511 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
511 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
512 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
512 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
513 ('ldap_filter', ''), ('ldap_search_scope', ''),
513 ('ldap_filter', ''), ('ldap_search_scope', ''),
514 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
514 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
515 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
515 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
516
516
517 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
517 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
518 log.debug('Skipping option %s' % k)
518 log.debug('Skipping option %s' % k)
519 continue
519 continue
520 setting = RhodeCodeSetting(k, v)
520 setting = RhodeCodeSetting(k, v)
521 self.sa.add(setting)
521 self.sa.add(setting)
522
522
523 def create_default_options(self, skip_existing=False):
523 def create_default_options(self, skip_existing=False):
524 """Creates default settings"""
524 """Creates default settings"""
525
525
526 for k, v in [
526 for k, v in [
527 ('default_repo_enable_locking', False),
527 ('default_repo_enable_locking', False),
528 ('default_repo_enable_downloads', False),
528 ('default_repo_enable_downloads', False),
529 ('default_repo_enable_statistics', False),
529 ('default_repo_enable_statistics', False),
530 ('default_repo_private', False),
530 ('default_repo_private', False),
531 ('default_repo_type', 'hg')]:
531 ('default_repo_type', 'hg')]:
532
532
533 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
533 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
534 log.debug('Skipping option %s' % k)
534 log.debug('Skipping option %s' % k)
535 continue
535 continue
536 setting = RhodeCodeSetting(k, v)
536 setting = RhodeCodeSetting(k, v)
537 self.sa.add(setting)
537 self.sa.add(setting)
538
538
539 def fixup_groups(self):
539 def fixup_groups(self):
540 def_usr = User.get_default_user()
540 def_usr = User.get_default_user()
541 for g in RepoGroup.query().all():
541 for g in RepoGroup.query().all():
542 g.group_name = g.get_new_name(g.name)
542 g.group_name = g.get_new_name(g.name)
543 self.sa.add(g)
543 self.sa.add(g)
544 # get default perm
544 # get default perm
545 default = UserRepoGroupToPerm.query()\
545 default = UserRepoGroupToPerm.query()\
546 .filter(UserRepoGroupToPerm.group == g)\
546 .filter(UserRepoGroupToPerm.group == g)\
547 .filter(UserRepoGroupToPerm.user == def_usr)\
547 .filter(UserRepoGroupToPerm.user == def_usr)\
548 .scalar()
548 .scalar()
549
549
550 if default is None:
550 if default is None:
551 log.debug('missing default permission for group %s adding' % g)
551 log.debug('missing default permission for group %s adding' % g)
552 perm_obj = ReposGroupModel()._create_default_perms(g)
552 perm_obj = ReposGroupModel()._create_default_perms(g)
553 self.sa.add(perm_obj)
553 self.sa.add(perm_obj)
554
554
555 def reset_permissions(self, username):
555 def reset_permissions(self, username):
556 """
556 """
557 Resets permissions to default state, usefull when old systems had
557 Resets permissions to default state, usefull when old systems had
558 bad permissions, we must clean them up
558 bad permissions, we must clean them up
559
559
560 :param username:
560 :param username:
561 :type username:
562 """
561 """
563 default_user = User.get_by_username(username)
562 default_user = User.get_by_username(username)
564 if not default_user:
563 if not default_user:
565 return
564 return
566
565
567 u2p = UserToPerm.query()\
566 u2p = UserToPerm.query()\
568 .filter(UserToPerm.user == default_user).all()
567 .filter(UserToPerm.user == default_user).all()
569 fixed = False
568 fixed = False
570 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
569 if len(u2p) != len(Permission.DEFAULT_USER_PERMISSIONS):
571 for p in u2p:
570 for p in u2p:
572 Session().delete(p)
571 Session().delete(p)
573 fixed = True
572 fixed = True
574 self.populate_default_permissions()
573 self.populate_default_permissions()
575 return fixed
574 return fixed
576
575
577 def update_repo_info(self):
576 def update_repo_info(self):
578 RepoModel.update_repoinfo()
577 RepoModel.update_repoinfo()
579
578
580 def config_prompt(self, test_repo_path='', retries=3):
579 def config_prompt(self, test_repo_path='', retries=3):
581 defaults = self.cli_args
580 defaults = self.cli_args
582 _path = defaults.get('repos_location')
581 _path = defaults.get('repos_location')
583 if retries == 3:
582 if retries == 3:
584 log.info('Setting up repositories config')
583 log.info('Setting up repositories config')
585
584
586 if _path is not None:
585 if _path is not None:
587 path = _path
586 path = _path
588 elif not self.tests and not test_repo_path:
587 elif not self.tests and not test_repo_path:
589 path = raw_input(
588 path = raw_input(
590 'Enter a valid absolute path to store repositories. '
589 'Enter a valid absolute path to store repositories. '
591 'All repositories in that path will be added automatically:'
590 'All repositories in that path will be added automatically:'
592 )
591 )
593 else:
592 else:
594 path = test_repo_path
593 path = test_repo_path
595 path_ok = True
594 path_ok = True
596
595
597 # check proper dir
596 # check proper dir
598 if not os.path.isdir(path):
597 if not os.path.isdir(path):
599 path_ok = False
598 path_ok = False
600 log.error('Given path %s is not a valid directory' % path)
599 log.error('Given path %s is not a valid directory' % path)
601
600
602 elif not os.path.isabs(path):
601 elif not os.path.isabs(path):
603 path_ok = False
602 path_ok = False
604 log.error('Given path %s is not an absolute path' % path)
603 log.error('Given path %s is not an absolute path' % path)
605
604
606 # check write access
605 # check write access
607 elif not os.access(path, os.W_OK) and path_ok:
606 elif not os.access(path, os.W_OK) and path_ok:
608 path_ok = False
607 path_ok = False
609 log.error('No write permission to given path %s' % path)
608 log.error('No write permission to given path %s' % path)
610
609
611 if retries == 0:
610 if retries == 0:
612 sys.exit('max retries reached')
611 sys.exit('max retries reached')
613 if not path_ok:
612 if not path_ok:
614 retries -= 1
613 retries -= 1
615 return self.config_prompt(test_repo_path, retries)
614 return self.config_prompt(test_repo_path, retries)
616
615
617 real_path = os.path.normpath(os.path.realpath(path))
616 real_path = os.path.normpath(os.path.realpath(path))
618
617
619 if real_path != os.path.normpath(path):
618 if real_path != os.path.normpath(path):
620 if not ask_ok(('Path looks like a symlink, Rhodecode will store '
619 if not ask_ok(('Path looks like a symlink, Rhodecode will store '
621 'given path as %s ? [y/n]') % (real_path)):
620 'given path as %s ? [y/n]') % (real_path)):
622 log.error('Canceled by user')
621 log.error('Canceled by user')
623 sys.exit(-1)
622 sys.exit(-1)
624
623
625 return real_path
624 return real_path
626
625
627 def create_settings(self, path):
626 def create_settings(self, path):
628
627
629 self.create_ui_settings()
628 self.create_ui_settings()
630
629
631 #HG UI OPTIONS
630 #HG UI OPTIONS
632 web1 = RhodeCodeUi()
631 web1 = RhodeCodeUi()
633 web1.ui_section = 'web'
632 web1.ui_section = 'web'
634 web1.ui_key = 'push_ssl'
633 web1.ui_key = 'push_ssl'
635 web1.ui_value = 'false'
634 web1.ui_value = 'false'
636
635
637 web2 = RhodeCodeUi()
636 web2 = RhodeCodeUi()
638 web2.ui_section = 'web'
637 web2.ui_section = 'web'
639 web2.ui_key = 'allow_archive'
638 web2.ui_key = 'allow_archive'
640 web2.ui_value = 'gz zip bz2'
639 web2.ui_value = 'gz zip bz2'
641
640
642 web3 = RhodeCodeUi()
641 web3 = RhodeCodeUi()
643 web3.ui_section = 'web'
642 web3.ui_section = 'web'
644 web3.ui_key = 'allow_push'
643 web3.ui_key = 'allow_push'
645 web3.ui_value = '*'
644 web3.ui_value = '*'
646
645
647 web4 = RhodeCodeUi()
646 web4 = RhodeCodeUi()
648 web4.ui_section = 'web'
647 web4.ui_section = 'web'
649 web4.ui_key = 'baseurl'
648 web4.ui_key = 'baseurl'
650 web4.ui_value = '/'
649 web4.ui_value = '/'
651
650
652 paths = RhodeCodeUi()
651 paths = RhodeCodeUi()
653 paths.ui_section = 'paths'
652 paths.ui_section = 'paths'
654 paths.ui_key = '/'
653 paths.ui_key = '/'
655 paths.ui_value = path
654 paths.ui_value = path
656
655
657 phases = RhodeCodeUi()
656 phases = RhodeCodeUi()
658 phases.ui_section = 'phases'
657 phases.ui_section = 'phases'
659 phases.ui_key = 'publish'
658 phases.ui_key = 'publish'
660 phases.ui_value = False
659 phases.ui_value = False
661
660
662 sett1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
661 sett1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
663 sett2 = RhodeCodeSetting('title', 'RhodeCode')
662 sett2 = RhodeCodeSetting('title', 'RhodeCode')
664 sett3 = RhodeCodeSetting('ga_code', '')
663 sett3 = RhodeCodeSetting('ga_code', '')
665
664
666 sett4 = RhodeCodeSetting('show_public_icon', True)
665 sett4 = RhodeCodeSetting('show_public_icon', True)
667 sett5 = RhodeCodeSetting('show_private_icon', True)
666 sett5 = RhodeCodeSetting('show_private_icon', True)
668 sett6 = RhodeCodeSetting('stylify_metatags', False)
667 sett6 = RhodeCodeSetting('stylify_metatags', False)
669
668
670 self.sa.add(web1)
669 self.sa.add(web1)
671 self.sa.add(web2)
670 self.sa.add(web2)
672 self.sa.add(web3)
671 self.sa.add(web3)
673 self.sa.add(web4)
672 self.sa.add(web4)
674 self.sa.add(paths)
673 self.sa.add(paths)
675 self.sa.add(sett1)
674 self.sa.add(sett1)
676 self.sa.add(sett2)
675 self.sa.add(sett2)
677 self.sa.add(sett3)
676 self.sa.add(sett3)
678 self.sa.add(sett4)
677 self.sa.add(sett4)
679 self.sa.add(sett5)
678 self.sa.add(sett5)
680 self.sa.add(sett6)
679 self.sa.add(sett6)
681
680
682 self.create_ldap_options()
681 self.create_ldap_options()
683 self.create_default_options()
682 self.create_default_options()
684
683
685 log.info('created ui config')
684 log.info('created ui config')
686
685
687 def create_user(self, username, password, email='', admin=False):
686 def create_user(self, username, password, email='', admin=False):
688 log.info('creating user %s' % username)
687 log.info('creating user %s' % username)
689 UserModel().create_or_update(username, password, email,
688 UserModel().create_or_update(username, password, email,
690 firstname='RhodeCode', lastname='Admin',
689 firstname='RhodeCode', lastname='Admin',
691 active=True, admin=admin)
690 active=True, admin=admin)
692
691
693 def create_default_user(self):
692 def create_default_user(self):
694 log.info('creating default user')
693 log.info('creating default user')
695 # create default user for handling default permissions.
694 # create default user for handling default permissions.
696 UserModel().create_or_update(username='default',
695 UserModel().create_or_update(username='default',
697 password=str(uuid.uuid1())[:8],
696 password=str(uuid.uuid1())[:8],
698 email='anonymous@rhodecode.org',
697 email='anonymous@rhodecode.org',
699 firstname='Anonymous', lastname='User')
698 firstname='Anonymous', lastname='User')
700
699
701 def create_permissions(self):
700 def create_permissions(self):
702 """
701 """
703 Creates all permissions defined in the system
702 Creates all permissions defined in the system
704 """
703 """
705 # module.(access|create|change|delete)_[name]
704 # module.(access|create|change|delete)_[name]
706 # module.(none|read|write|admin)
705 # module.(none|read|write|admin)
707 log.info('creating permissions')
706 log.info('creating permissions')
708 PermissionModel(self.sa).create_permissions()
707 PermissionModel(self.sa).create_permissions()
709
708
710 def populate_default_permissions(self):
709 def populate_default_permissions(self):
711 """
710 """
712 Populate default permissions. It will create only the default
711 Populate default permissions. It will create only the default
713 permissions that are missing, and not alter already defined ones
712 permissions that are missing, and not alter already defined ones
714 """
713 """
715 log.info('creating default user permissions')
714 log.info('creating default user permissions')
716 PermissionModel(self.sa).create_default_permissions(user=User.DEFAULT_USER)
715 PermissionModel(self.sa).create_default_permissions(user=User.DEFAULT_USER)
717
716
718 @staticmethod
717 @staticmethod
719 def check_waitress():
718 def check_waitress():
720 """
719 """
721 Function executed at the end of setup
720 Function executed at the end of setup
722 """
721 """
723 if not __py_version__ >= (2, 6):
722 if not __py_version__ >= (2, 6):
724 notify('Python2.5 detected, please switch '
723 notify('Python2.5 detected, please switch '
725 'egg:waitress#main -> egg:Paste#http '
724 'egg:waitress#main -> egg:Paste#http '
726 'in your .ini file')
725 'in your .ini file')
@@ -1,62 +1,69 b''
1 import logging
1 import logging
2 import datetime
2 import datetime
3
3
4 from sqlalchemy import *
4 from sqlalchemy import *
5 from sqlalchemy.exc import DatabaseError
5 from sqlalchemy.exc import DatabaseError
6 from sqlalchemy.orm import relation, backref, class_mapper, joinedload
6 from sqlalchemy.orm import relation, backref, class_mapper, joinedload
7 from sqlalchemy.orm.session import Session
7 from sqlalchemy.orm.session import Session
8 from sqlalchemy.ext.declarative import declarative_base
8 from sqlalchemy.ext.declarative import declarative_base
9
9
10 from rhodecode.lib.dbmigrate.migrate import *
10 from rhodecode.lib.dbmigrate.migrate import *
11 from rhodecode.lib.dbmigrate.migrate.changeset import *
11 from rhodecode.lib.dbmigrate.migrate.changeset import *
12
12
13 from rhodecode.model.meta import Base
13 from rhodecode.model.meta import Base
14 from rhodecode.model import meta
14 from rhodecode.model import meta
15 from rhodecode.lib.dbmigrate.versions import _reset_base
15 from rhodecode.lib.dbmigrate.versions import _reset_base
16
16
17 log = logging.getLogger(__name__)
17 log = logging.getLogger(__name__)
18
18
19
19
20 def upgrade(migrate_engine):
20 def upgrade(migrate_engine):
21 """
21 """
22 Upgrade operations go here.
22 Upgrade operations go here.
23 Don't create your own engine; bind migrate_engine to your metadata
23 Don't create your own engine; bind migrate_engine to your metadata
24 """
24 """
25 _reset_base(migrate_engine)
25 _reset_base(migrate_engine)
26
26
27 #==========================================================================
27 #==========================================================================
28 # UserUserGroupToPerm
28 # UserUserGroupToPerm
29 #==========================================================================
29 #==========================================================================
30 from rhodecode.lib.dbmigrate.schema.db_1_7_0 import UserUserGroupToPerm
30 from rhodecode.lib.dbmigrate.schema.db_1_7_0 import UserUserGroupToPerm
31 tbl = UserUserGroupToPerm.__table__
31 tbl = UserUserGroupToPerm.__table__
32 tbl.create()
32 tbl.create()
33
33
34 #==========================================================================
34 #==========================================================================
35 # UserGroupUserGroupToPerm
35 # UserGroupUserGroupToPerm
36 #==========================================================================
36 #==========================================================================
37 from rhodecode.lib.dbmigrate.schema.db_1_7_0 import UserGroupUserGroupToPerm
37 from rhodecode.lib.dbmigrate.schema.db_1_7_0 import UserGroupUserGroupToPerm
38 tbl = UserGroupUserGroupToPerm.__table__
38 tbl = UserGroupUserGroupToPerm.__table__
39 tbl.create()
39 tbl.create()
40
40
41 #==========================================================================
41 #==========================================================================
42 # Gist
43 #==========================================================================
44 from rhodecode.lib.dbmigrate.schema.db_1_7_0 import Gist
45 tbl = Gist.__table__
46 tbl.create()
47
48 #==========================================================================
42 # UserGroup
49 # UserGroup
43 #==========================================================================
50 #==========================================================================
44 from rhodecode.lib.dbmigrate.schema.db_1_7_0 import UserGroup
51 from rhodecode.lib.dbmigrate.schema.db_1_7_0 import UserGroup
45 tbl = UserGroup.__table__
52 tbl = UserGroup.__table__
46 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=False, default=None)
53 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=False, default=None)
47 # create username column
54 # create username column
48 user_id.create(table=tbl)
55 user_id.create(table=tbl)
49
56
50 #==========================================================================
57 #==========================================================================
51 # UserGroup
58 # RepoGroup
52 #==========================================================================
59 #==========================================================================
53 from rhodecode.lib.dbmigrate.schema.db_1_7_0 import RepoGroup
60 from rhodecode.lib.dbmigrate.schema.db_1_7_0 import RepoGroup
54 tbl = RepoGroup.__table__
61 tbl = RepoGroup.__table__
55 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=False, default=None)
62 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=False, default=None)
56 # create username column
63 # create username column
57 user_id.create(table=tbl)
64 user_id.create(table=tbl)
58
65
59
66
60 def downgrade(migrate_engine):
67 def downgrade(migrate_engine):
61 meta = MetaData()
68 meta = MetaData()
62 meta.bind = migrate_engine
69 meta.bind = migrate_engine
@@ -1,713 +1,711 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.diffs
3 rhodecode.lib.diffs
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Set of diffing helpers, previously part of vcs
6 Set of diffing helpers, previously part of vcs
7
7
8
8
9 :created_on: Dec 4, 2011
9 :created_on: Dec 4, 2011
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :original copyright: 2007-2008 by Armin Ronacher
12 :original copyright: 2007-2008 by Armin Ronacher
13 :license: GPLv3, see COPYING for more details.
13 :license: GPLv3, see COPYING for more details.
14 """
14 """
15 # This program is free software: you can redistribute it and/or modify
15 # This program is free software: you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation, either version 3 of the License, or
17 # the Free Software Foundation, either version 3 of the License, or
18 # (at your option) any later version.
18 # (at your option) any later version.
19 #
19 #
20 # This program is distributed in the hope that it will be useful,
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
23 # GNU General Public License for more details.
24 #
24 #
25 # You should have received a copy of the GNU General Public License
25 # You should have received a copy of the GNU General Public License
26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27
27
28 import re
28 import re
29 import difflib
29 import difflib
30 import logging
30 import logging
31
31
32 from itertools import tee, imap
32 from itertools import tee, imap
33
33
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35
35
36 from rhodecode.lib.vcs.exceptions import VCSError
36 from rhodecode.lib.vcs.exceptions import VCSError
37 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
37 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
38 from rhodecode.lib.vcs.backends.base import EmptyChangeset
38 from rhodecode.lib.vcs.backends.base import EmptyChangeset
39 from rhodecode.lib.helpers import escape
39 from rhodecode.lib.helpers import escape
40 from rhodecode.lib.utils2 import safe_unicode, safe_str
40 from rhodecode.lib.utils2 import safe_unicode, safe_str
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 def wrap_to_table(str_):
45 def wrap_to_table(str_):
46 return '''<table class="code-difftable">
46 return '''<table class="code-difftable">
47 <tr class="line no-comment">
47 <tr class="line no-comment">
48 <td class="lineno new"></td>
48 <td class="lineno new"></td>
49 <td class="code no-comment"><pre>%s</pre></td>
49 <td class="code no-comment"><pre>%s</pre></td>
50 </tr>
50 </tr>
51 </table>''' % str_
51 </table>''' % str_
52
52
53
53
54 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
54 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
55 ignore_whitespace=True, line_context=3,
55 ignore_whitespace=True, line_context=3,
56 enable_comments=False):
56 enable_comments=False):
57 """
57 """
58 returns a wrapped diff into a table, checks for cut_off_limit and presents
58 returns a wrapped diff into a table, checks for cut_off_limit and presents
59 proper message
59 proper message
60 """
60 """
61
61
62 if filenode_old is None:
62 if filenode_old is None:
63 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
63 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
64
64
65 if filenode_old.is_binary or filenode_new.is_binary:
65 if filenode_old.is_binary or filenode_new.is_binary:
66 diff = wrap_to_table(_('Binary file'))
66 diff = wrap_to_table(_('Binary file'))
67 stats = (0, 0)
67 stats = (0, 0)
68 size = 0
68 size = 0
69
69
70 elif cut_off_limit != -1 and (cut_off_limit is None or
70 elif cut_off_limit != -1 and (cut_off_limit is None or
71 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
71 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
72
72
73 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
73 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
74 ignore_whitespace=ignore_whitespace,
74 ignore_whitespace=ignore_whitespace,
75 context=line_context)
75 context=line_context)
76 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
76 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
77
77
78 diff = diff_processor.as_html(enable_comments=enable_comments)
78 diff = diff_processor.as_html(enable_comments=enable_comments)
79 stats = diff_processor.stat()
79 stats = diff_processor.stat()
80 size = len(diff or '')
80 size = len(diff or '')
81 else:
81 else:
82 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
82 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
83 'diff menu to display this diff'))
83 'diff menu to display this diff'))
84 stats = (0, 0)
84 stats = (0, 0)
85 size = 0
85 size = 0
86 if not diff:
86 if not diff:
87 submodules = filter(lambda o: isinstance(o, SubModuleNode),
87 submodules = filter(lambda o: isinstance(o, SubModuleNode),
88 [filenode_new, filenode_old])
88 [filenode_new, filenode_old])
89 if submodules:
89 if submodules:
90 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
90 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
91 else:
91 else:
92 diff = wrap_to_table(_('No changes detected'))
92 diff = wrap_to_table(_('No changes detected'))
93
93
94 cs1 = filenode_old.changeset.raw_id
94 cs1 = filenode_old.changeset.raw_id
95 cs2 = filenode_new.changeset.raw_id
95 cs2 = filenode_new.changeset.raw_id
96
96
97 return size, cs1, cs2, diff, stats
97 return size, cs1, cs2, diff, stats
98
98
99
99
100 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
100 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
101 """
101 """
102 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
102 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
103
103
104 :param ignore_whitespace: ignore whitespaces in diff
104 :param ignore_whitespace: ignore whitespaces in diff
105 """
105 """
106 # make sure we pass in default context
106 # make sure we pass in default context
107 context = context or 3
107 context = context or 3
108 submodules = filter(lambda o: isinstance(o, SubModuleNode),
108 submodules = filter(lambda o: isinstance(o, SubModuleNode),
109 [filenode_new, filenode_old])
109 [filenode_new, filenode_old])
110 if submodules:
110 if submodules:
111 return ''
111 return ''
112
112
113 for filenode in (filenode_old, filenode_new):
113 for filenode in (filenode_old, filenode_new):
114 if not isinstance(filenode, FileNode):
114 if not isinstance(filenode, FileNode):
115 raise VCSError("Given object should be FileNode object, not %s"
115 raise VCSError("Given object should be FileNode object, not %s"
116 % filenode.__class__)
116 % filenode.__class__)
117
117
118 repo = filenode_new.changeset.repository
118 repo = filenode_new.changeset.repository
119 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
119 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
120 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
120 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
121
121
122 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
122 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
123 ignore_whitespace, context)
123 ignore_whitespace, context)
124 return vcs_gitdiff
124 return vcs_gitdiff
125
125
126 NEW_FILENODE = 1
126 NEW_FILENODE = 1
127 DEL_FILENODE = 2
127 DEL_FILENODE = 2
128 MOD_FILENODE = 3
128 MOD_FILENODE = 3
129 RENAMED_FILENODE = 4
129 RENAMED_FILENODE = 4
130 CHMOD_FILENODE = 5
130 CHMOD_FILENODE = 5
131 BIN_FILENODE = 6
131 BIN_FILENODE = 6
132
132
133
133
134 class DiffLimitExceeded(Exception):
134 class DiffLimitExceeded(Exception):
135 pass
135 pass
136
136
137
137
138 class LimitedDiffContainer(object):
138 class LimitedDiffContainer(object):
139
139
140 def __init__(self, diff_limit, cur_diff_size, diff):
140 def __init__(self, diff_limit, cur_diff_size, diff):
141 self.diff = diff
141 self.diff = diff
142 self.diff_limit = diff_limit
142 self.diff_limit = diff_limit
143 self.cur_diff_size = cur_diff_size
143 self.cur_diff_size = cur_diff_size
144
144
145 def __iter__(self):
145 def __iter__(self):
146 for l in self.diff:
146 for l in self.diff:
147 yield l
147 yield l
148
148
149
149
150 class DiffProcessor(object):
150 class DiffProcessor(object):
151 """
151 """
152 Give it a unified or git diff and it returns a list of the files that were
152 Give it a unified or git diff and it returns a list of the files that were
153 mentioned in the diff together with a dict of meta information that
153 mentioned in the diff together with a dict of meta information that
154 can be used to render it in a HTML template.
154 can be used to render it in a HTML template.
155 """
155 """
156 _chunk_re = re.compile(r'^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
156 _chunk_re = re.compile(r'^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
157 _newline_marker = re.compile(r'^\\ No newline at end of file')
157 _newline_marker = re.compile(r'^\\ No newline at end of file')
158 _git_header_re = re.compile(r"""
158 _git_header_re = re.compile(r"""
159 #^diff[ ]--git
159 #^diff[ ]--git
160 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
160 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
161 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%\n
161 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%\n
162 ^rename[ ]from[ ](?P<rename_from>\S+)\n
162 ^rename[ ]from[ ](?P<rename_from>\S+)\n
163 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
163 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
164 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
164 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
165 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
165 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
166 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
166 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
167 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
167 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
168 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
168 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
169 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
169 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
170 (?:^(?P<bin_patch>GIT[ ]binary[ ]patch)(?:\n|$))?
170 (?:^(?P<bin_patch>GIT[ ]binary[ ]patch)(?:\n|$))?
171 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
171 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
172 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
172 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
173 """, re.VERBOSE | re.MULTILINE)
173 """, re.VERBOSE | re.MULTILINE)
174 _hg_header_re = re.compile(r"""
174 _hg_header_re = re.compile(r"""
175 #^diff[ ]--git
175 #^diff[ ]--git
176 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
176 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
177 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
177 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
178 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
178 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
179 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%(?:\n|$))?
179 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%(?:\n|$))?
180 (?:^rename[ ]from[ ](?P<rename_from>\S+)\n
180 (?:^rename[ ]from[ ](?P<rename_from>\S+)\n
181 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
181 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
182 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
182 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
183 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
183 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
184 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
184 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
185 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
185 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
186 (?:^(?P<bin_patch>GIT[ ]binary[ ]patch)(?:\n|$))?
186 (?:^(?P<bin_patch>GIT[ ]binary[ ]patch)(?:\n|$))?
187 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
187 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
188 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
188 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
189 """, re.VERBOSE | re.MULTILINE)
189 """, re.VERBOSE | re.MULTILINE)
190
190
191 #used for inline highlighter word split
191 #used for inline highlighter word split
192 _token_re = re.compile(r'()(&gt;|&lt;|&amp;|\W+?)')
192 _token_re = re.compile(r'()(&gt;|&lt;|&amp;|\W+?)')
193
193
194 def __init__(self, diff, vcs='hg', format='gitdiff', diff_limit=None):
194 def __init__(self, diff, vcs='hg', format='gitdiff', diff_limit=None):
195 """
195 """
196 :param diff: a text in diff format
196 :param diff: a text in diff format
197 :param vcs: type of version controll hg or git
197 :param vcs: type of version controll hg or git
198 :param format: format of diff passed, `udiff` or `gitdiff`
198 :param format: format of diff passed, `udiff` or `gitdiff`
199 :param diff_limit: define the size of diff that is considered "big"
199 :param diff_limit: define the size of diff that is considered "big"
200 based on that parameter cut off will be triggered, set to None
200 based on that parameter cut off will be triggered, set to None
201 to show full diff
201 to show full diff
202 """
202 """
203 if not isinstance(diff, basestring):
203 if not isinstance(diff, basestring):
204 raise Exception('Diff must be a basestring got %s instead' % type(diff))
204 raise Exception('Diff must be a basestring got %s instead' % type(diff))
205
205
206 self._diff = diff
206 self._diff = diff
207 self._format = format
207 self._format = format
208 self.adds = 0
208 self.adds = 0
209 self.removes = 0
209 self.removes = 0
210 # calculate diff size
210 # calculate diff size
211 self.diff_size = len(diff)
211 self.diff_size = len(diff)
212 self.diff_limit = diff_limit
212 self.diff_limit = diff_limit
213 self.cur_diff_size = 0
213 self.cur_diff_size = 0
214 self.parsed = False
214 self.parsed = False
215 self.parsed_diff = []
215 self.parsed_diff = []
216 self.vcs = vcs
216 self.vcs = vcs
217
217
218 if format == 'gitdiff':
218 if format == 'gitdiff':
219 self.differ = self._highlight_line_difflib
219 self.differ = self._highlight_line_difflib
220 self._parser = self._parse_gitdiff
220 self._parser = self._parse_gitdiff
221 else:
221 else:
222 self.differ = self._highlight_line_udiff
222 self.differ = self._highlight_line_udiff
223 self._parser = self._parse_udiff
223 self._parser = self._parse_udiff
224
224
225 def _copy_iterator(self):
225 def _copy_iterator(self):
226 """
226 """
227 make a fresh copy of generator, we should not iterate thru
227 make a fresh copy of generator, we should not iterate thru
228 an original as it's needed for repeating operations on
228 an original as it's needed for repeating operations on
229 this instance of DiffProcessor
229 this instance of DiffProcessor
230 """
230 """
231 self.__udiff, iterator_copy = tee(self.__udiff)
231 self.__udiff, iterator_copy = tee(self.__udiff)
232 return iterator_copy
232 return iterator_copy
233
233
234 def _escaper(self, string):
234 def _escaper(self, string):
235 """
235 """
236 Escaper for diff escapes special chars and checks the diff limit
236 Escaper for diff escapes special chars and checks the diff limit
237
237
238 :param string:
238 :param string:
239 :type string:
240 """
239 """
241
240
242 self.cur_diff_size += len(string)
241 self.cur_diff_size += len(string)
243
242
244 # escaper get's iterated on each .next() call and it checks if each
243 # escaper get's iterated on each .next() call and it checks if each
245 # parsed line doesn't exceed the diff limit
244 # parsed line doesn't exceed the diff limit
246 if self.diff_limit is not None and self.cur_diff_size > self.diff_limit:
245 if self.diff_limit is not None and self.cur_diff_size > self.diff_limit:
247 raise DiffLimitExceeded('Diff Limit Exceeded')
246 raise DiffLimitExceeded('Diff Limit Exceeded')
248
247
249 return safe_unicode(string).replace('&', '&amp;')\
248 return safe_unicode(string).replace('&', '&amp;')\
250 .replace('<', '&lt;')\
249 .replace('<', '&lt;')\
251 .replace('>', '&gt;')
250 .replace('>', '&gt;')
252
251
253 def _line_counter(self, l):
252 def _line_counter(self, l):
254 """
253 """
255 Checks each line and bumps total adds/removes for this diff
254 Checks each line and bumps total adds/removes for this diff
256
255
257 :param l:
256 :param l:
258 """
257 """
259 if l.startswith('+') and not l.startswith('+++'):
258 if l.startswith('+') and not l.startswith('+++'):
260 self.adds += 1
259 self.adds += 1
261 elif l.startswith('-') and not l.startswith('---'):
260 elif l.startswith('-') and not l.startswith('---'):
262 self.removes += 1
261 self.removes += 1
263 return safe_unicode(l)
262 return safe_unicode(l)
264
263
265 def _highlight_line_difflib(self, line, next_):
264 def _highlight_line_difflib(self, line, next_):
266 """
265 """
267 Highlight inline changes in both lines.
266 Highlight inline changes in both lines.
268 """
267 """
269
268
270 if line['action'] == 'del':
269 if line['action'] == 'del':
271 old, new = line, next_
270 old, new = line, next_
272 else:
271 else:
273 old, new = next_, line
272 old, new = next_, line
274
273
275 oldwords = self._token_re.split(old['line'])
274 oldwords = self._token_re.split(old['line'])
276 newwords = self._token_re.split(new['line'])
275 newwords = self._token_re.split(new['line'])
277 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
276 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
278
277
279 oldfragments, newfragments = [], []
278 oldfragments, newfragments = [], []
280 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
279 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
281 oldfrag = ''.join(oldwords[i1:i2])
280 oldfrag = ''.join(oldwords[i1:i2])
282 newfrag = ''.join(newwords[j1:j2])
281 newfrag = ''.join(newwords[j1:j2])
283 if tag != 'equal':
282 if tag != 'equal':
284 if oldfrag:
283 if oldfrag:
285 oldfrag = '<del>%s</del>' % oldfrag
284 oldfrag = '<del>%s</del>' % oldfrag
286 if newfrag:
285 if newfrag:
287 newfrag = '<ins>%s</ins>' % newfrag
286 newfrag = '<ins>%s</ins>' % newfrag
288 oldfragments.append(oldfrag)
287 oldfragments.append(oldfrag)
289 newfragments.append(newfrag)
288 newfragments.append(newfrag)
290
289
291 old['line'] = "".join(oldfragments)
290 old['line'] = "".join(oldfragments)
292 new['line'] = "".join(newfragments)
291 new['line'] = "".join(newfragments)
293
292
294 def _highlight_line_udiff(self, line, next_):
293 def _highlight_line_udiff(self, line, next_):
295 """
294 """
296 Highlight inline changes in both lines.
295 Highlight inline changes in both lines.
297 """
296 """
298 start = 0
297 start = 0
299 limit = min(len(line['line']), len(next_['line']))
298 limit = min(len(line['line']), len(next_['line']))
300 while start < limit and line['line'][start] == next_['line'][start]:
299 while start < limit and line['line'][start] == next_['line'][start]:
301 start += 1
300 start += 1
302 end = -1
301 end = -1
303 limit -= start
302 limit -= start
304 while -end <= limit and line['line'][end] == next_['line'][end]:
303 while -end <= limit and line['line'][end] == next_['line'][end]:
305 end -= 1
304 end -= 1
306 end += 1
305 end += 1
307 if start or end:
306 if start or end:
308 def do(l):
307 def do(l):
309 last = end + len(l['line'])
308 last = end + len(l['line'])
310 if l['action'] == 'add':
309 if l['action'] == 'add':
311 tag = 'ins'
310 tag = 'ins'
312 else:
311 else:
313 tag = 'del'
312 tag = 'del'
314 l['line'] = '%s<%s>%s</%s>%s' % (
313 l['line'] = '%s<%s>%s</%s>%s' % (
315 l['line'][:start],
314 l['line'][:start],
316 tag,
315 tag,
317 l['line'][start:last],
316 l['line'][start:last],
318 tag,
317 tag,
319 l['line'][last:]
318 l['line'][last:]
320 )
319 )
321 do(line)
320 do(line)
322 do(next_)
321 do(next_)
323
322
324 def _get_header(self, diff_chunk):
323 def _get_header(self, diff_chunk):
325 """
324 """
326 parses the diff header, and returns parts, and leftover diff
325 parses the diff header, and returns parts, and leftover diff
327 parts consists of 14 elements::
326 parts consists of 14 elements::
328
327
329 a_path, b_path, similarity_index, rename_from, rename_to,
328 a_path, b_path, similarity_index, rename_from, rename_to,
330 old_mode, new_mode, new_file_mode, deleted_file_mode,
329 old_mode, new_mode, new_file_mode, deleted_file_mode,
331 a_blob_id, b_blob_id, b_mode, a_file, b_file
330 a_blob_id, b_blob_id, b_mode, a_file, b_file
332
331
333 :param diff_chunk:
332 :param diff_chunk:
334 :type diff_chunk:
335 """
333 """
336
334
337 if self.vcs == 'git':
335 if self.vcs == 'git':
338 match = self._git_header_re.match(diff_chunk)
336 match = self._git_header_re.match(diff_chunk)
339 diff = diff_chunk[match.end():]
337 diff = diff_chunk[match.end():]
340 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
338 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
341 elif self.vcs == 'hg':
339 elif self.vcs == 'hg':
342 match = self._hg_header_re.match(diff_chunk)
340 match = self._hg_header_re.match(diff_chunk)
343 diff = diff_chunk[match.end():]
341 diff = diff_chunk[match.end():]
344 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
342 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
345 else:
343 else:
346 raise Exception('VCS type %s is not supported' % self.vcs)
344 raise Exception('VCS type %s is not supported' % self.vcs)
347
345
348 def _clean_line(self, line, command):
346 def _clean_line(self, line, command):
349 if command in ['+', '-', ' ']:
347 if command in ['+', '-', ' ']:
350 #only modify the line if it's actually a diff thing
348 #only modify the line if it's actually a diff thing
351 line = line[1:]
349 line = line[1:]
352 return line
350 return line
353
351
354 def _parse_gitdiff(self, inline_diff=True):
352 def _parse_gitdiff(self, inline_diff=True):
355 _files = []
353 _files = []
356 diff_container = lambda arg: arg
354 diff_container = lambda arg: arg
357
355
358 ##split the diff in chunks of separate --git a/file b/file chunks
356 ##split the diff in chunks of separate --git a/file b/file chunks
359 for raw_diff in ('\n' + self._diff).split('\ndiff --git')[1:]:
357 for raw_diff in ('\n' + self._diff).split('\ndiff --git')[1:]:
360 head, diff = self._get_header(raw_diff)
358 head, diff = self._get_header(raw_diff)
361
359
362 op = None
360 op = None
363 stats = {
361 stats = {
364 'added': 0,
362 'added': 0,
365 'deleted': 0,
363 'deleted': 0,
366 'binary': False,
364 'binary': False,
367 'ops': {},
365 'ops': {},
368 }
366 }
369
367
370 if head['deleted_file_mode']:
368 if head['deleted_file_mode']:
371 op = 'D'
369 op = 'D'
372 stats['binary'] = True
370 stats['binary'] = True
373 stats['ops'][DEL_FILENODE] = 'deleted file'
371 stats['ops'][DEL_FILENODE] = 'deleted file'
374
372
375 elif head['new_file_mode']:
373 elif head['new_file_mode']:
376 op = 'A'
374 op = 'A'
377 stats['binary'] = True
375 stats['binary'] = True
378 stats['ops'][NEW_FILENODE] = 'new file %s' % head['new_file_mode']
376 stats['ops'][NEW_FILENODE] = 'new file %s' % head['new_file_mode']
379 else: # modify operation, can be cp, rename, chmod
377 else: # modify operation, can be cp, rename, chmod
380 # CHMOD
378 # CHMOD
381 if head['new_mode'] and head['old_mode']:
379 if head['new_mode'] and head['old_mode']:
382 op = 'M'
380 op = 'M'
383 stats['binary'] = True
381 stats['binary'] = True
384 stats['ops'][CHMOD_FILENODE] = ('modified file chmod %s => %s'
382 stats['ops'][CHMOD_FILENODE] = ('modified file chmod %s => %s'
385 % (head['old_mode'], head['new_mode']))
383 % (head['old_mode'], head['new_mode']))
386 # RENAME
384 # RENAME
387 if (head['rename_from'] and head['rename_to']
385 if (head['rename_from'] and head['rename_to']
388 and head['rename_from'] != head['rename_to']):
386 and head['rename_from'] != head['rename_to']):
389 op = 'M'
387 op = 'M'
390 stats['binary'] = True
388 stats['binary'] = True
391 stats['ops'][RENAMED_FILENODE] = ('file renamed from %s to %s'
389 stats['ops'][RENAMED_FILENODE] = ('file renamed from %s to %s'
392 % (head['rename_from'], head['rename_to']))
390 % (head['rename_from'], head['rename_to']))
393
391
394 # FALL BACK: detect missed old style add or remove
392 # FALL BACK: detect missed old style add or remove
395 if op is None:
393 if op is None:
396 if not head['a_file'] and head['b_file']:
394 if not head['a_file'] and head['b_file']:
397 op = 'A'
395 op = 'A'
398 stats['binary'] = True
396 stats['binary'] = True
399 stats['ops'][NEW_FILENODE] = 'new file'
397 stats['ops'][NEW_FILENODE] = 'new file'
400
398
401 elif head['a_file'] and not head['b_file']:
399 elif head['a_file'] and not head['b_file']:
402 op = 'D'
400 op = 'D'
403 stats['binary'] = True
401 stats['binary'] = True
404 stats['ops'][DEL_FILENODE] = 'deleted file'
402 stats['ops'][DEL_FILENODE] = 'deleted file'
405
403
406 # it's not ADD not DELETE
404 # it's not ADD not DELETE
407 if op is None:
405 if op is None:
408 op = 'M'
406 op = 'M'
409 stats['binary'] = True
407 stats['binary'] = True
410 stats['ops'][MOD_FILENODE] = 'modified file'
408 stats['ops'][MOD_FILENODE] = 'modified file'
411
409
412 # a real non-binary diff
410 # a real non-binary diff
413 if head['a_file'] or head['b_file']:
411 if head['a_file'] or head['b_file']:
414 try:
412 try:
415 chunks, _stats = self._parse_lines(diff)
413 chunks, _stats = self._parse_lines(diff)
416 stats['binary'] = False
414 stats['binary'] = False
417 stats['added'] = _stats[0]
415 stats['added'] = _stats[0]
418 stats['deleted'] = _stats[1]
416 stats['deleted'] = _stats[1]
419 # explicit mark that it's a modified file
417 # explicit mark that it's a modified file
420 if op == 'M':
418 if op == 'M':
421 stats['ops'][MOD_FILENODE] = 'modified file'
419 stats['ops'][MOD_FILENODE] = 'modified file'
422
420
423 except DiffLimitExceeded:
421 except DiffLimitExceeded:
424 diff_container = lambda _diff: \
422 diff_container = lambda _diff: \
425 LimitedDiffContainer(self.diff_limit,
423 LimitedDiffContainer(self.diff_limit,
426 self.cur_diff_size, _diff)
424 self.cur_diff_size, _diff)
427 break
425 break
428 else: # GIT binary patch (or empty diff)
426 else: # GIT binary patch (or empty diff)
429 # GIT Binary patch
427 # GIT Binary patch
430 if head['bin_patch']:
428 if head['bin_patch']:
431 stats['ops'][BIN_FILENODE] = 'binary diff not shown'
429 stats['ops'][BIN_FILENODE] = 'binary diff not shown'
432 chunks = []
430 chunks = []
433
431
434 chunks.insert(0, [{
432 chunks.insert(0, [{
435 'old_lineno': '',
433 'old_lineno': '',
436 'new_lineno': '',
434 'new_lineno': '',
437 'action': 'context',
435 'action': 'context',
438 'line': msg,
436 'line': msg,
439 } for _op, msg in stats['ops'].iteritems()
437 } for _op, msg in stats['ops'].iteritems()
440 if _op not in [MOD_FILENODE]])
438 if _op not in [MOD_FILENODE]])
441
439
442 _files.append({
440 _files.append({
443 'filename': head['b_path'],
441 'filename': head['b_path'],
444 'old_revision': head['a_blob_id'],
442 'old_revision': head['a_blob_id'],
445 'new_revision': head['b_blob_id'],
443 'new_revision': head['b_blob_id'],
446 'chunks': chunks,
444 'chunks': chunks,
447 'operation': op,
445 'operation': op,
448 'stats': stats,
446 'stats': stats,
449 })
447 })
450
448
451 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
449 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
452
450
453 if not inline_diff:
451 if not inline_diff:
454 return diff_container(sorted(_files, key=sorter))
452 return diff_container(sorted(_files, key=sorter))
455
453
456 # highlight inline changes
454 # highlight inline changes
457 for diff_data in _files:
455 for diff_data in _files:
458 for chunk in diff_data['chunks']:
456 for chunk in diff_data['chunks']:
459 lineiter = iter(chunk)
457 lineiter = iter(chunk)
460 try:
458 try:
461 while 1:
459 while 1:
462 line = lineiter.next()
460 line = lineiter.next()
463 if line['action'] not in ['unmod', 'context']:
461 if line['action'] not in ['unmod', 'context']:
464 nextline = lineiter.next()
462 nextline = lineiter.next()
465 if nextline['action'] in ['unmod', 'context'] or \
463 if nextline['action'] in ['unmod', 'context'] or \
466 nextline['action'] == line['action']:
464 nextline['action'] == line['action']:
467 continue
465 continue
468 self.differ(line, nextline)
466 self.differ(line, nextline)
469 except StopIteration:
467 except StopIteration:
470 pass
468 pass
471
469
472 return diff_container(sorted(_files, key=sorter))
470 return diff_container(sorted(_files, key=sorter))
473
471
474 def _parse_udiff(self, inline_diff=True):
472 def _parse_udiff(self, inline_diff=True):
475 raise NotImplementedError()
473 raise NotImplementedError()
476
474
477 def _parse_lines(self, diff):
475 def _parse_lines(self, diff):
478 """
476 """
479 Parse the diff an return data for the template.
477 Parse the diff an return data for the template.
480 """
478 """
481
479
482 lineiter = iter(diff)
480 lineiter = iter(diff)
483 stats = [0, 0]
481 stats = [0, 0]
484
482
485 try:
483 try:
486 chunks = []
484 chunks = []
487 line = lineiter.next()
485 line = lineiter.next()
488
486
489 while line:
487 while line:
490 lines = []
488 lines = []
491 chunks.append(lines)
489 chunks.append(lines)
492
490
493 match = self._chunk_re.match(line)
491 match = self._chunk_re.match(line)
494
492
495 if not match:
493 if not match:
496 break
494 break
497
495
498 gr = match.groups()
496 gr = match.groups()
499 (old_line, old_end,
497 (old_line, old_end,
500 new_line, new_end) = [int(x or 1) for x in gr[:-1]]
498 new_line, new_end) = [int(x or 1) for x in gr[:-1]]
501 old_line -= 1
499 old_line -= 1
502 new_line -= 1
500 new_line -= 1
503
501
504 context = len(gr) == 5
502 context = len(gr) == 5
505 old_end += old_line
503 old_end += old_line
506 new_end += new_line
504 new_end += new_line
507
505
508 if context:
506 if context:
509 # skip context only if it's first line
507 # skip context only if it's first line
510 if int(gr[0]) > 1:
508 if int(gr[0]) > 1:
511 lines.append({
509 lines.append({
512 'old_lineno': '...',
510 'old_lineno': '...',
513 'new_lineno': '...',
511 'new_lineno': '...',
514 'action': 'context',
512 'action': 'context',
515 'line': line,
513 'line': line,
516 })
514 })
517
515
518 line = lineiter.next()
516 line = lineiter.next()
519
517
520 while old_line < old_end or new_line < new_end:
518 while old_line < old_end or new_line < new_end:
521 command = ' '
519 command = ' '
522 if line:
520 if line:
523 command = line[0]
521 command = line[0]
524
522
525 affects_old = affects_new = False
523 affects_old = affects_new = False
526
524
527 # ignore those if we don't expect them
525 # ignore those if we don't expect them
528 if command in '#@':
526 if command in '#@':
529 continue
527 continue
530 elif command == '+':
528 elif command == '+':
531 affects_new = True
529 affects_new = True
532 action = 'add'
530 action = 'add'
533 stats[0] += 1
531 stats[0] += 1
534 elif command == '-':
532 elif command == '-':
535 affects_old = True
533 affects_old = True
536 action = 'del'
534 action = 'del'
537 stats[1] += 1
535 stats[1] += 1
538 else:
536 else:
539 affects_old = affects_new = True
537 affects_old = affects_new = True
540 action = 'unmod'
538 action = 'unmod'
541
539
542 if not self._newline_marker.match(line):
540 if not self._newline_marker.match(line):
543 old_line += affects_old
541 old_line += affects_old
544 new_line += affects_new
542 new_line += affects_new
545 lines.append({
543 lines.append({
546 'old_lineno': affects_old and old_line or '',
544 'old_lineno': affects_old and old_line or '',
547 'new_lineno': affects_new and new_line or '',
545 'new_lineno': affects_new and new_line or '',
548 'action': action,
546 'action': action,
549 'line': self._clean_line(line, command)
547 'line': self._clean_line(line, command)
550 })
548 })
551
549
552 line = lineiter.next()
550 line = lineiter.next()
553
551
554 if self._newline_marker.match(line):
552 if self._newline_marker.match(line):
555 # we need to append to lines, since this is not
553 # we need to append to lines, since this is not
556 # counted in the line specs of diff
554 # counted in the line specs of diff
557 lines.append({
555 lines.append({
558 'old_lineno': '...',
556 'old_lineno': '...',
559 'new_lineno': '...',
557 'new_lineno': '...',
560 'action': 'context',
558 'action': 'context',
561 'line': self._clean_line(line, command)
559 'line': self._clean_line(line, command)
562 })
560 })
563
561
564 except StopIteration:
562 except StopIteration:
565 pass
563 pass
566 return chunks, stats
564 return chunks, stats
567
565
568 def _safe_id(self, idstring):
566 def _safe_id(self, idstring):
569 """Make a string safe for including in an id attribute.
567 """Make a string safe for including in an id attribute.
570
568
571 The HTML spec says that id attributes 'must begin with
569 The HTML spec says that id attributes 'must begin with
572 a letter ([A-Za-z]) and may be followed by any number
570 a letter ([A-Za-z]) and may be followed by any number
573 of letters, digits ([0-9]), hyphens ("-"), underscores
571 of letters, digits ([0-9]), hyphens ("-"), underscores
574 ("_"), colons (":"), and periods (".")'. These regexps
572 ("_"), colons (":"), and periods (".")'. These regexps
575 are slightly over-zealous, in that they remove colons
573 are slightly over-zealous, in that they remove colons
576 and periods unnecessarily.
574 and periods unnecessarily.
577
575
578 Whitespace is transformed into underscores, and then
576 Whitespace is transformed into underscores, and then
579 anything which is not a hyphen or a character that
577 anything which is not a hyphen or a character that
580 matches \w (alphanumerics and underscore) is removed.
578 matches \w (alphanumerics and underscore) is removed.
581
579
582 """
580 """
583 # Transform all whitespace to underscore
581 # Transform all whitespace to underscore
584 idstring = re.sub(r'\s', "_", '%s' % idstring)
582 idstring = re.sub(r'\s', "_", '%s' % idstring)
585 # Remove everything that is not a hyphen or a member of \w
583 # Remove everything that is not a hyphen or a member of \w
586 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
584 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
587 return idstring
585 return idstring
588
586
589 def prepare(self, inline_diff=True):
587 def prepare(self, inline_diff=True):
590 """
588 """
591 Prepare the passed udiff for HTML rendering. It'l return a list
589 Prepare the passed udiff for HTML rendering. It'l return a list
592 of dicts with diff information
590 of dicts with diff information
593 """
591 """
594 parsed = self._parser(inline_diff=inline_diff)
592 parsed = self._parser(inline_diff=inline_diff)
595 self.parsed = True
593 self.parsed = True
596 self.parsed_diff = parsed
594 self.parsed_diff = parsed
597 return parsed
595 return parsed
598
596
599 def as_raw(self, diff_lines=None):
597 def as_raw(self, diff_lines=None):
600 """
598 """
601 Returns raw string diff
599 Returns raw string diff
602 """
600 """
603 return self._diff
601 return self._diff
604 #return u''.join(imap(self._line_counter, self._diff.splitlines(1)))
602 #return u''.join(imap(self._line_counter, self._diff.splitlines(1)))
605
603
606 def as_html(self, table_class='code-difftable', line_class='line',
604 def as_html(self, table_class='code-difftable', line_class='line',
607 old_lineno_class='lineno old', new_lineno_class='lineno new',
605 old_lineno_class='lineno old', new_lineno_class='lineno new',
608 code_class='code', enable_comments=False, parsed_lines=None):
606 code_class='code', enable_comments=False, parsed_lines=None):
609 """
607 """
610 Return given diff as html table with customized css classes
608 Return given diff as html table with customized css classes
611 """
609 """
612 def _link_to_if(condition, label, url):
610 def _link_to_if(condition, label, url):
613 """
611 """
614 Generates a link if condition is meet or just the label if not.
612 Generates a link if condition is meet or just the label if not.
615 """
613 """
616
614
617 if condition:
615 if condition:
618 return '''<a href="%(url)s">%(label)s</a>''' % {
616 return '''<a href="%(url)s">%(label)s</a>''' % {
619 'url': url,
617 'url': url,
620 'label': label
618 'label': label
621 }
619 }
622 else:
620 else:
623 return label
621 return label
624 if not self.parsed:
622 if not self.parsed:
625 self.prepare()
623 self.prepare()
626
624
627 diff_lines = self.parsed_diff
625 diff_lines = self.parsed_diff
628 if parsed_lines:
626 if parsed_lines:
629 diff_lines = parsed_lines
627 diff_lines = parsed_lines
630
628
631 _html_empty = True
629 _html_empty = True
632 _html = []
630 _html = []
633 _html.append('''<table class="%(table_class)s">\n''' % {
631 _html.append('''<table class="%(table_class)s">\n''' % {
634 'table_class': table_class
632 'table_class': table_class
635 })
633 })
636
634
637 for diff in diff_lines:
635 for diff in diff_lines:
638 for line in diff['chunks']:
636 for line in diff['chunks']:
639 _html_empty = False
637 _html_empty = False
640 for change in line:
638 for change in line:
641 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
639 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
642 'lc': line_class,
640 'lc': line_class,
643 'action': change['action']
641 'action': change['action']
644 })
642 })
645 anchor_old_id = ''
643 anchor_old_id = ''
646 anchor_new_id = ''
644 anchor_new_id = ''
647 anchor_old = "%(filename)s_o%(oldline_no)s" % {
645 anchor_old = "%(filename)s_o%(oldline_no)s" % {
648 'filename': self._safe_id(diff['filename']),
646 'filename': self._safe_id(diff['filename']),
649 'oldline_no': change['old_lineno']
647 'oldline_no': change['old_lineno']
650 }
648 }
651 anchor_new = "%(filename)s_n%(oldline_no)s" % {
649 anchor_new = "%(filename)s_n%(oldline_no)s" % {
652 'filename': self._safe_id(diff['filename']),
650 'filename': self._safe_id(diff['filename']),
653 'oldline_no': change['new_lineno']
651 'oldline_no': change['new_lineno']
654 }
652 }
655 cond_old = (change['old_lineno'] != '...' and
653 cond_old = (change['old_lineno'] != '...' and
656 change['old_lineno'])
654 change['old_lineno'])
657 cond_new = (change['new_lineno'] != '...' and
655 cond_new = (change['new_lineno'] != '...' and
658 change['new_lineno'])
656 change['new_lineno'])
659 if cond_old:
657 if cond_old:
660 anchor_old_id = 'id="%s"' % anchor_old
658 anchor_old_id = 'id="%s"' % anchor_old
661 if cond_new:
659 if cond_new:
662 anchor_new_id = 'id="%s"' % anchor_new
660 anchor_new_id = 'id="%s"' % anchor_new
663 ###########################################################
661 ###########################################################
664 # OLD LINE NUMBER
662 # OLD LINE NUMBER
665 ###########################################################
663 ###########################################################
666 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
664 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
667 'a_id': anchor_old_id,
665 'a_id': anchor_old_id,
668 'olc': old_lineno_class
666 'olc': old_lineno_class
669 })
667 })
670
668
671 _html.append('''%(link)s''' % {
669 _html.append('''%(link)s''' % {
672 'link': _link_to_if(True, change['old_lineno'],
670 'link': _link_to_if(True, change['old_lineno'],
673 '#%s' % anchor_old)
671 '#%s' % anchor_old)
674 })
672 })
675 _html.append('''</td>\n''')
673 _html.append('''</td>\n''')
676 ###########################################################
674 ###########################################################
677 # NEW LINE NUMBER
675 # NEW LINE NUMBER
678 ###########################################################
676 ###########################################################
679
677
680 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
678 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
681 'a_id': anchor_new_id,
679 'a_id': anchor_new_id,
682 'nlc': new_lineno_class
680 'nlc': new_lineno_class
683 })
681 })
684
682
685 _html.append('''%(link)s''' % {
683 _html.append('''%(link)s''' % {
686 'link': _link_to_if(True, change['new_lineno'],
684 'link': _link_to_if(True, change['new_lineno'],
687 '#%s' % anchor_new)
685 '#%s' % anchor_new)
688 })
686 })
689 _html.append('''</td>\n''')
687 _html.append('''</td>\n''')
690 ###########################################################
688 ###########################################################
691 # CODE
689 # CODE
692 ###########################################################
690 ###########################################################
693 comments = '' if enable_comments else 'no-comment'
691 comments = '' if enable_comments else 'no-comment'
694 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
692 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
695 'cc': code_class,
693 'cc': code_class,
696 'inc': comments
694 'inc': comments
697 })
695 })
698 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
696 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
699 'code': change['line']
697 'code': change['line']
700 })
698 })
701
699
702 _html.append('''\t</td>''')
700 _html.append('''\t</td>''')
703 _html.append('''\n</tr>\n''')
701 _html.append('''\n</tr>\n''')
704 _html.append('''</table>''')
702 _html.append('''</table>''')
705 if _html_empty:
703 if _html_empty:
706 return None
704 return None
707 return ''.join(_html)
705 return ''.join(_html)
708
706
709 def stat(self):
707 def stat(self):
710 """
708 """
711 Returns tuple of added, and removed lines for this instance
709 Returns tuple of added, and removed lines for this instance
712 """
710 """
713 return self.adds, self.removes
711 return self.adds, self.removes
@@ -1,84 +1,88 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.exceptions
3 rhodecode.lib.exceptions
4 ~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Set of custom exceptions used in RhodeCode
6 Set of custom exceptions used in RhodeCode
7
7
8 :created_on: Nov 17, 2010
8 :created_on: Nov 17, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 from webob.exc import HTTPClientError
26 from webob.exc import HTTPClientError
27
27
28
28
29 class LdapUsernameError(Exception):
29 class LdapUsernameError(Exception):
30 pass
30 pass
31
31
32
32
33 class LdapPasswordError(Exception):
33 class LdapPasswordError(Exception):
34 pass
34 pass
35
35
36
36
37 class LdapConnectionError(Exception):
37 class LdapConnectionError(Exception):
38 pass
38 pass
39
39
40
40
41 class LdapImportError(Exception):
41 class LdapImportError(Exception):
42 pass
42 pass
43
43
44
44
45 class DefaultUserException(Exception):
45 class DefaultUserException(Exception):
46 pass
46 pass
47
47
48
48
49 class UserOwnsReposException(Exception):
49 class UserOwnsReposException(Exception):
50 pass
50 pass
51
51
52
52
53 class UserGroupsAssignedException(Exception):
53 class UserGroupsAssignedException(Exception):
54 pass
54 pass
55
55
56
56
57 class StatusChangeOnClosedPullRequestError(Exception):
57 class StatusChangeOnClosedPullRequestError(Exception):
58 pass
58 pass
59
59
60
60
61 class AttachedForksError(Exception):
61 class AttachedForksError(Exception):
62 pass
62 pass
63
63
64
64
65 class RepoGroupAssignmentError(Exception):
65 class RepoGroupAssignmentError(Exception):
66 pass
66 pass
67
67
68
68
69 class NonRelativePathError(Exception):
70 pass
71
72
69 class HTTPLockedRC(HTTPClientError):
73 class HTTPLockedRC(HTTPClientError):
70 """
74 """
71 Special Exception For locked Repos in RhodeCode, the return code can
75 Special Exception For locked Repos in RhodeCode, the return code can
72 be overwritten by _code keyword argument passed into constructors
76 be overwritten by _code keyword argument passed into constructors
73 """
77 """
74 code = 423
78 code = 423
75 title = explanation = 'Repository Locked'
79 title = explanation = 'Repository Locked'
76
80
77 def __init__(self, reponame, username, *args, **kwargs):
81 def __init__(self, reponame, username, *args, **kwargs):
78 from rhodecode import CONFIG
82 from rhodecode import CONFIG
79 from rhodecode.lib.utils2 import safe_int
83 from rhodecode.lib.utils2 import safe_int
80 _code = CONFIG.get('lock_ret_code')
84 _code = CONFIG.get('lock_ret_code')
81 self.code = safe_int(_code, self.code)
85 self.code = safe_int(_code, self.code)
82 self.title = self.explanation = ('Repository `%s` locked by '
86 self.title = self.explanation = ('Repository `%s` locked by '
83 'user `%s`' % (reponame, username))
87 'user `%s`' % (reponame, username))
84 super(HTTPLockedRC, self).__init__(*args, **kwargs)
88 super(HTTPLockedRC, self).__init__(*args, **kwargs)
@@ -1,390 +1,387 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.hooks
3 rhodecode.lib.hooks
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Hooks runned by rhodecode
6 Hooks runned by rhodecode
7
7
8 :created_on: Aug 6, 2010
8 :created_on: Aug 6, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import sys
26 import sys
27 import time
27 import time
28 import binascii
28 import binascii
29 import traceback
29 import traceback
30 from inspect import isfunction
30 from inspect import isfunction
31
31
32 from mercurial.scmutil import revrange
32 from mercurial.scmutil import revrange
33 from mercurial.node import nullrev
33 from mercurial.node import nullrev
34
34
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib.utils import action_logger
36 from rhodecode.lib.utils import action_logger
37 from rhodecode.lib.vcs.backends.base import EmptyChangeset
37 from rhodecode.lib.vcs.backends.base import EmptyChangeset
38 from rhodecode.lib.compat import json
38 from rhodecode.lib.compat import json
39 from rhodecode.lib.exceptions import HTTPLockedRC
39 from rhodecode.lib.exceptions import HTTPLockedRC
40 from rhodecode.lib.utils2 import safe_str, _extract_extras
40 from rhodecode.lib.utils2 import safe_str, _extract_extras
41 from rhodecode.model.db import Repository, User
41 from rhodecode.model.db import Repository, User
42
42
43
43
44 def _get_scm_size(alias, root_path):
44 def _get_scm_size(alias, root_path):
45
45
46 if not alias.startswith('.'):
46 if not alias.startswith('.'):
47 alias += '.'
47 alias += '.'
48
48
49 size_scm, size_root = 0, 0
49 size_scm, size_root = 0, 0
50 for path, dirs, files in os.walk(safe_str(root_path)):
50 for path, dirs, files in os.walk(safe_str(root_path)):
51 if path.find(alias) != -1:
51 if path.find(alias) != -1:
52 for f in files:
52 for f in files:
53 try:
53 try:
54 size_scm += os.path.getsize(os.path.join(path, f))
54 size_scm += os.path.getsize(os.path.join(path, f))
55 except OSError:
55 except OSError:
56 pass
56 pass
57 else:
57 else:
58 for f in files:
58 for f in files:
59 try:
59 try:
60 size_root += os.path.getsize(os.path.join(path, f))
60 size_root += os.path.getsize(os.path.join(path, f))
61 except OSError:
61 except OSError:
62 pass
62 pass
63
63
64 size_scm_f = h.format_byte_size(size_scm)
64 size_scm_f = h.format_byte_size(size_scm)
65 size_root_f = h.format_byte_size(size_root)
65 size_root_f = h.format_byte_size(size_root)
66 size_total_f = h.format_byte_size(size_root + size_scm)
66 size_total_f = h.format_byte_size(size_root + size_scm)
67
67
68 return size_scm_f, size_root_f, size_total_f
68 return size_scm_f, size_root_f, size_total_f
69
69
70
70
71 def repo_size(ui, repo, hooktype=None, **kwargs):
71 def repo_size(ui, repo, hooktype=None, **kwargs):
72 """
72 """
73 Presents size of repository after push
73 Presents size of repository after push
74
74
75 :param ui:
75 :param ui:
76 :param repo:
76 :param repo:
77 :param hooktype:
77 :param hooktype:
78 """
78 """
79
79
80 size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
80 size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
81
81
82 last_cs = repo[len(repo) - 1]
82 last_cs = repo[len(repo) - 1]
83
83
84 msg = ('Repository size .hg:%s repo:%s total:%s\n'
84 msg = ('Repository size .hg:%s repo:%s total:%s\n'
85 'Last revision is now r%s:%s\n') % (
85 'Last revision is now r%s:%s\n') % (
86 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
86 size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
87 )
87 )
88
88
89 sys.stdout.write(msg)
89 sys.stdout.write(msg)
90
90
91
91
92 def pre_push(ui, repo, **kwargs):
92 def pre_push(ui, repo, **kwargs):
93 # pre push function, currently used to ban pushing when
93 # pre push function, currently used to ban pushing when
94 # repository is locked
94 # repository is locked
95 ex = _extract_extras()
95 ex = _extract_extras()
96
96
97 usr = User.get_by_username(ex.username)
97 usr = User.get_by_username(ex.username)
98 if ex.locked_by[0] and usr.user_id != int(ex.locked_by[0]):
98 if ex.locked_by[0] and usr.user_id != int(ex.locked_by[0]):
99 locked_by = User.get(ex.locked_by[0]).username
99 locked_by = User.get(ex.locked_by[0]).username
100 # this exception is interpreted in git/hg middlewares and based
100 # this exception is interpreted in git/hg middlewares and based
101 # on that proper return code is server to client
101 # on that proper return code is server to client
102 _http_ret = HTTPLockedRC(ex.repository, locked_by)
102 _http_ret = HTTPLockedRC(ex.repository, locked_by)
103 if str(_http_ret.code).startswith('2'):
103 if str(_http_ret.code).startswith('2'):
104 #2xx Codes don't raise exceptions
104 #2xx Codes don't raise exceptions
105 sys.stdout.write(_http_ret.title)
105 sys.stdout.write(_http_ret.title)
106 else:
106 else:
107 raise _http_ret
107 raise _http_ret
108
108
109
109
110 def pre_pull(ui, repo, **kwargs):
110 def pre_pull(ui, repo, **kwargs):
111 # pre push function, currently used to ban pushing when
111 # pre push function, currently used to ban pushing when
112 # repository is locked
112 # repository is locked
113 ex = _extract_extras()
113 ex = _extract_extras()
114 if ex.locked_by[0]:
114 if ex.locked_by[0]:
115 locked_by = User.get(ex.locked_by[0]).username
115 locked_by = User.get(ex.locked_by[0]).username
116 # this exception is interpreted in git/hg middlewares and based
116 # this exception is interpreted in git/hg middlewares and based
117 # on that proper return code is server to client
117 # on that proper return code is server to client
118 _http_ret = HTTPLockedRC(ex.repository, locked_by)
118 _http_ret = HTTPLockedRC(ex.repository, locked_by)
119 if str(_http_ret.code).startswith('2'):
119 if str(_http_ret.code).startswith('2'):
120 #2xx Codes don't raise exceptions
120 #2xx Codes don't raise exceptions
121 sys.stdout.write(_http_ret.title)
121 sys.stdout.write(_http_ret.title)
122 else:
122 else:
123 raise _http_ret
123 raise _http_ret
124
124
125
125
126 def log_pull_action(ui, repo, **kwargs):
126 def log_pull_action(ui, repo, **kwargs):
127 """
127 """
128 Logs user last pull action
128 Logs user last pull action
129
129
130 :param ui:
130 :param ui:
131 :param repo:
131 :param repo:
132 """
132 """
133 ex = _extract_extras()
133 ex = _extract_extras()
134
134
135 user = User.get_by_username(ex.username)
135 user = User.get_by_username(ex.username)
136 action = 'pull'
136 action = 'pull'
137 action_logger(user, action, ex.repository, ex.ip, commit=True)
137 action_logger(user, action, ex.repository, ex.ip, commit=True)
138 # extension hook call
138 # extension hook call
139 from rhodecode import EXTENSIONS
139 from rhodecode import EXTENSIONS
140 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
140 callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
141 if isfunction(callback):
141 if isfunction(callback):
142 kw = {}
142 kw = {}
143 kw.update(ex)
143 kw.update(ex)
144 callback(**kw)
144 callback(**kw)
145
145
146 if ex.make_lock is not None and ex.make_lock:
146 if ex.make_lock is not None and ex.make_lock:
147 Repository.lock(Repository.get_by_repo_name(ex.repository), user.user_id)
147 Repository.lock(Repository.get_by_repo_name(ex.repository), user.user_id)
148 #msg = 'Made lock on repo `%s`' % repository
148 #msg = 'Made lock on repo `%s`' % repository
149 #sys.stdout.write(msg)
149 #sys.stdout.write(msg)
150
150
151 if ex.locked_by[0]:
151 if ex.locked_by[0]:
152 locked_by = User.get(ex.locked_by[0]).username
152 locked_by = User.get(ex.locked_by[0]).username
153 _http_ret = HTTPLockedRC(ex.repository, locked_by)
153 _http_ret = HTTPLockedRC(ex.repository, locked_by)
154 if str(_http_ret.code).startswith('2'):
154 if str(_http_ret.code).startswith('2'):
155 #2xx Codes don't raise exceptions
155 #2xx Codes don't raise exceptions
156 sys.stdout.write(_http_ret.title)
156 sys.stdout.write(_http_ret.title)
157 return 0
157 return 0
158
158
159
159
160 def log_push_action(ui, repo, **kwargs):
160 def log_push_action(ui, repo, **kwargs):
161 """
161 """
162 Maps user last push action to new changeset id, from mercurial
162 Maps user last push action to new changeset id, from mercurial
163
163
164 :param ui:
164 :param ui:
165 :param repo: repo object containing the `ui` object
165 :param repo: repo object containing the `ui` object
166 """
166 """
167
167
168 ex = _extract_extras()
168 ex = _extract_extras()
169
169
170 action = ex.action + ':%s'
170 action = ex.action + ':%s'
171
171
172 if ex.scm == 'hg':
172 if ex.scm == 'hg':
173 node = kwargs['node']
173 node = kwargs['node']
174
174
175 def get_revs(repo, rev_opt):
175 def get_revs(repo, rev_opt):
176 if rev_opt:
176 if rev_opt:
177 revs = revrange(repo, rev_opt)
177 revs = revrange(repo, rev_opt)
178
178
179 if len(revs) == 0:
179 if len(revs) == 0:
180 return (nullrev, nullrev)
180 return (nullrev, nullrev)
181 return (max(revs), min(revs))
181 return (max(revs), min(revs))
182 else:
182 else:
183 return (len(repo) - 1, 0)
183 return (len(repo) - 1, 0)
184
184
185 stop, start = get_revs(repo, [node + ':'])
185 stop, start = get_revs(repo, [node + ':'])
186 h = binascii.hexlify
186 h = binascii.hexlify
187 revs = [h(repo[r].node()) for r in xrange(start, stop + 1)]
187 revs = [h(repo[r].node()) for r in xrange(start, stop + 1)]
188 elif ex.scm == 'git':
188 elif ex.scm == 'git':
189 revs = kwargs.get('_git_revs', [])
189 revs = kwargs.get('_git_revs', [])
190 if '_git_revs' in kwargs:
190 if '_git_revs' in kwargs:
191 kwargs.pop('_git_revs')
191 kwargs.pop('_git_revs')
192
192
193 action = action % ','.join(revs)
193 action = action % ','.join(revs)
194
194
195 action_logger(ex.username, action, ex.repository, ex.ip, commit=True)
195 action_logger(ex.username, action, ex.repository, ex.ip, commit=True)
196
196
197 # extension hook call
197 # extension hook call
198 from rhodecode import EXTENSIONS
198 from rhodecode import EXTENSIONS
199 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
199 callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
200 if isfunction(callback):
200 if isfunction(callback):
201 kw = {'pushed_revs': revs}
201 kw = {'pushed_revs': revs}
202 kw.update(ex)
202 kw.update(ex)
203 callback(**kw)
203 callback(**kw)
204
204
205 if ex.make_lock is not None and not ex.make_lock:
205 if ex.make_lock is not None and not ex.make_lock:
206 Repository.unlock(Repository.get_by_repo_name(ex.repository))
206 Repository.unlock(Repository.get_by_repo_name(ex.repository))
207 msg = 'Released lock on repo `%s`\n' % ex.repository
207 msg = 'Released lock on repo `%s`\n' % ex.repository
208 sys.stdout.write(msg)
208 sys.stdout.write(msg)
209
209
210 if ex.locked_by[0]:
210 if ex.locked_by[0]:
211 locked_by = User.get(ex.locked_by[0]).username
211 locked_by = User.get(ex.locked_by[0]).username
212 _http_ret = HTTPLockedRC(ex.repository, locked_by)
212 _http_ret = HTTPLockedRC(ex.repository, locked_by)
213 if str(_http_ret.code).startswith('2'):
213 if str(_http_ret.code).startswith('2'):
214 #2xx Codes don't raise exceptions
214 #2xx Codes don't raise exceptions
215 sys.stdout.write(_http_ret.title)
215 sys.stdout.write(_http_ret.title)
216
216
217 return 0
217 return 0
218
218
219
219
220 def log_create_repository(repository_dict, created_by, **kwargs):
220 def log_create_repository(repository_dict, created_by, **kwargs):
221 """
221 """
222 Post create repository Hook. This is a dummy function for admins to re-use
222 Post create repository Hook. This is a dummy function for admins to re-use
223 if needed. It's taken from rhodecode-extensions module and executed
223 if needed. It's taken from rhodecode-extensions module and executed
224 if present
224 if present
225
225
226 :param repository: dict dump of repository object
226 :param repository: dict dump of repository object
227 :param created_by: username who created repository
227 :param created_by: username who created repository
228
228
229 available keys of repository_dict:
229 available keys of repository_dict:
230
230
231 'repo_type',
231 'repo_type',
232 'description',
232 'description',
233 'private',
233 'private',
234 'created_on',
234 'created_on',
235 'enable_downloads',
235 'enable_downloads',
236 'repo_id',
236 'repo_id',
237 'user_id',
237 'user_id',
238 'enable_statistics',
238 'enable_statistics',
239 'clone_uri',
239 'clone_uri',
240 'fork_id',
240 'fork_id',
241 'group_id',
241 'group_id',
242 'repo_name'
242 'repo_name'
243
243
244 """
244 """
245 from rhodecode import EXTENSIONS
245 from rhodecode import EXTENSIONS
246 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
246 callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
247 if isfunction(callback):
247 if isfunction(callback):
248 kw = {}
248 kw = {}
249 kw.update(repository_dict)
249 kw.update(repository_dict)
250 kw.update({'created_by': created_by})
250 kw.update({'created_by': created_by})
251 kw.update(kwargs)
251 kw.update(kwargs)
252 return callback(**kw)
252 return callback(**kw)
253
253
254 return 0
254 return 0
255
255
256
256
257 def log_delete_repository(repository_dict, deleted_by, **kwargs):
257 def log_delete_repository(repository_dict, deleted_by, **kwargs):
258 """
258 """
259 Post delete repository Hook. This is a dummy function for admins to re-use
259 Post delete repository Hook. This is a dummy function for admins to re-use
260 if needed. It's taken from rhodecode-extensions module and executed
260 if needed. It's taken from rhodecode-extensions module and executed
261 if present
261 if present
262
262
263 :param repository: dict dump of repository object
263 :param repository: dict dump of repository object
264 :param deleted_by: username who deleted the repository
264 :param deleted_by: username who deleted the repository
265
265
266 available keys of repository_dict:
266 available keys of repository_dict:
267
267
268 'repo_type',
268 'repo_type',
269 'description',
269 'description',
270 'private',
270 'private',
271 'created_on',
271 'created_on',
272 'enable_downloads',
272 'enable_downloads',
273 'repo_id',
273 'repo_id',
274 'user_id',
274 'user_id',
275 'enable_statistics',
275 'enable_statistics',
276 'clone_uri',
276 'clone_uri',
277 'fork_id',
277 'fork_id',
278 'group_id',
278 'group_id',
279 'repo_name'
279 'repo_name'
280
280
281 """
281 """
282 from rhodecode import EXTENSIONS
282 from rhodecode import EXTENSIONS
283 callback = getattr(EXTENSIONS, 'DELETE_REPO_HOOK', None)
283 callback = getattr(EXTENSIONS, 'DELETE_REPO_HOOK', None)
284 if isfunction(callback):
284 if isfunction(callback):
285 kw = {}
285 kw = {}
286 kw.update(repository_dict)
286 kw.update(repository_dict)
287 kw.update({'deleted_by': deleted_by,
287 kw.update({'deleted_by': deleted_by,
288 'deleted_on': time.time()})
288 'deleted_on': time.time()})
289 kw.update(kwargs)
289 kw.update(kwargs)
290 return callback(**kw)
290 return callback(**kw)
291
291
292 return 0
292 return 0
293
293
294
294
295 handle_git_pre_receive = (lambda repo_path, revs, env:
295 handle_git_pre_receive = (lambda repo_path, revs, env:
296 handle_git_receive(repo_path, revs, env, hook_type='pre'))
296 handle_git_receive(repo_path, revs, env, hook_type='pre'))
297 handle_git_post_receive = (lambda repo_path, revs, env:
297 handle_git_post_receive = (lambda repo_path, revs, env:
298 handle_git_receive(repo_path, revs, env, hook_type='post'))
298 handle_git_receive(repo_path, revs, env, hook_type='post'))
299
299
300
300
301 def handle_git_receive(repo_path, revs, env, hook_type='post'):
301 def handle_git_receive(repo_path, revs, env, hook_type='post'):
302 """
302 """
303 A really hacky method that is runned by git post-receive hook and logs
303 A really hacky method that is runned by git post-receive hook and logs
304 an push action together with pushed revisions. It's executed by subprocess
304 an push action together with pushed revisions. It's executed by subprocess
305 thus needs all info to be able to create a on the fly pylons enviroment,
305 thus needs all info to be able to create a on the fly pylons enviroment,
306 connect to database and run the logging code. Hacky as sh*t but works.
306 connect to database and run the logging code. Hacky as sh*t but works.
307
307
308 :param repo_path:
308 :param repo_path:
309 :type repo_path:
310 :param revs:
309 :param revs:
311 :type revs:
312 :param env:
310 :param env:
313 :type env:
314 """
311 """
315 from paste.deploy import appconfig
312 from paste.deploy import appconfig
316 from sqlalchemy import engine_from_config
313 from sqlalchemy import engine_from_config
317 from rhodecode.config.environment import load_environment
314 from rhodecode.config.environment import load_environment
318 from rhodecode.model import init_model
315 from rhodecode.model import init_model
319 from rhodecode.model.db import RhodeCodeUi
316 from rhodecode.model.db import RhodeCodeUi
320 from rhodecode.lib.utils import make_ui
317 from rhodecode.lib.utils import make_ui
321 extras = _extract_extras(env)
318 extras = _extract_extras(env)
322
319
323 path, ini_name = os.path.split(extras['config'])
320 path, ini_name = os.path.split(extras['config'])
324 conf = appconfig('config:%s' % ini_name, relative_to=path)
321 conf = appconfig('config:%s' % ini_name, relative_to=path)
325 load_environment(conf.global_conf, conf.local_conf)
322 load_environment(conf.global_conf, conf.local_conf)
326
323
327 engine = engine_from_config(conf, 'sqlalchemy.db1.')
324 engine = engine_from_config(conf, 'sqlalchemy.db1.')
328 init_model(engine)
325 init_model(engine)
329
326
330 baseui = make_ui('db')
327 baseui = make_ui('db')
331 # fix if it's not a bare repo
328 # fix if it's not a bare repo
332 if repo_path.endswith(os.sep + '.git'):
329 if repo_path.endswith(os.sep + '.git'):
333 repo_path = repo_path[:-5]
330 repo_path = repo_path[:-5]
334
331
335 repo = Repository.get_by_full_path(repo_path)
332 repo = Repository.get_by_full_path(repo_path)
336 if not repo:
333 if not repo:
337 raise OSError('Repository %s not found in database'
334 raise OSError('Repository %s not found in database'
338 % (safe_str(repo_path)))
335 % (safe_str(repo_path)))
339
336
340 _hooks = dict(baseui.configitems('hooks')) or {}
337 _hooks = dict(baseui.configitems('hooks')) or {}
341
338
342 if hook_type == 'pre':
339 if hook_type == 'pre':
343 repo = repo.scm_instance
340 repo = repo.scm_instance
344 else:
341 else:
345 #post push shouldn't use the cached instance never
342 #post push shouldn't use the cached instance never
346 repo = repo.scm_instance_no_cache()
343 repo = repo.scm_instance_no_cache()
347
344
348 if hook_type == 'pre':
345 if hook_type == 'pre':
349 pre_push(baseui, repo)
346 pre_push(baseui, repo)
350
347
351 # if push hook is enabled via web interface
348 # if push hook is enabled via web interface
352 elif hook_type == 'post' and _hooks.get(RhodeCodeUi.HOOK_PUSH):
349 elif hook_type == 'post' and _hooks.get(RhodeCodeUi.HOOK_PUSH):
353
350
354 rev_data = []
351 rev_data = []
355 for l in revs:
352 for l in revs:
356 old_rev, new_rev, ref = l.split(' ')
353 old_rev, new_rev, ref = l.split(' ')
357 _ref_data = ref.split('/')
354 _ref_data = ref.split('/')
358 if _ref_data[1] in ['tags', 'heads']:
355 if _ref_data[1] in ['tags', 'heads']:
359 rev_data.append({'old_rev': old_rev,
356 rev_data.append({'old_rev': old_rev,
360 'new_rev': new_rev,
357 'new_rev': new_rev,
361 'ref': ref,
358 'ref': ref,
362 'type': _ref_data[1],
359 'type': _ref_data[1],
363 'name': _ref_data[2].strip()})
360 'name': _ref_data[2].strip()})
364
361
365 git_revs = []
362 git_revs = []
366 for push_ref in rev_data:
363 for push_ref in rev_data:
367 _type = push_ref['type']
364 _type = push_ref['type']
368 if _type == 'heads':
365 if _type == 'heads':
369 if push_ref['old_rev'] == EmptyChangeset().raw_id:
366 if push_ref['old_rev'] == EmptyChangeset().raw_id:
370 cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'"
367 cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'"
371 heads = repo.run_git_command(cmd)[0]
368 heads = repo.run_git_command(cmd)[0]
372 heads = heads.replace(push_ref['ref'], '')
369 heads = heads.replace(push_ref['ref'], '')
373 heads = ' '.join(map(lambda c: c.strip('\n').strip(),
370 heads = ' '.join(map(lambda c: c.strip('\n').strip(),
374 heads.splitlines()))
371 heads.splitlines()))
375 cmd = (('log %(new_rev)s' % push_ref) +
372 cmd = (('log %(new_rev)s' % push_ref) +
376 ' --reverse --pretty=format:"%H" --not ' + heads)
373 ' --reverse --pretty=format:"%H" --not ' + heads)
377 git_revs += repo.run_git_command(cmd)[0].splitlines()
374 git_revs += repo.run_git_command(cmd)[0].splitlines()
378
375
379 elif push_ref['new_rev'] == EmptyChangeset().raw_id:
376 elif push_ref['new_rev'] == EmptyChangeset().raw_id:
380 #delete branch case
377 #delete branch case
381 git_revs += ['delete_branch=>%s' % push_ref['name']]
378 git_revs += ['delete_branch=>%s' % push_ref['name']]
382 else:
379 else:
383 cmd = (('log %(old_rev)s..%(new_rev)s' % push_ref) +
380 cmd = (('log %(old_rev)s..%(new_rev)s' % push_ref) +
384 ' --reverse --pretty=format:"%H"')
381 ' --reverse --pretty=format:"%H"')
385 git_revs += repo.run_git_command(cmd)[0].splitlines()
382 git_revs += repo.run_git_command(cmd)[0].splitlines()
386
383
387 elif _type == 'tags':
384 elif _type == 'tags':
388 git_revs += ['tag=>%s' % push_ref['name']]
385 git_revs += ['tag=>%s' % push_ref['name']]
389
386
390 log_push_action(baseui, repo, _git_revs=git_revs)
387 log_push_action(baseui, repo, _git_revs=git_revs)
@@ -1,201 +1,200 b''
1 import os
1 import os
2 import socket
2 import socket
3 import logging
3 import logging
4 import subprocess
4 import subprocess
5 import traceback
5 import traceback
6
6
7 from webob import Request, Response, exc
7 from webob import Request, Response, exc
8
8
9 import rhodecode
9 import rhodecode
10 from rhodecode.lib.vcs import subprocessio
10 from rhodecode.lib.vcs import subprocessio
11
11
12 log = logging.getLogger(__name__)
12 log = logging.getLogger(__name__)
13
13
14
14
15 class FileWrapper(object):
15 class FileWrapper(object):
16
16
17 def __init__(self, fd, content_length):
17 def __init__(self, fd, content_length):
18 self.fd = fd
18 self.fd = fd
19 self.content_length = content_length
19 self.content_length = content_length
20 self.remain = content_length
20 self.remain = content_length
21
21
22 def read(self, size):
22 def read(self, size):
23 if size <= self.remain:
23 if size <= self.remain:
24 try:
24 try:
25 data = self.fd.read(size)
25 data = self.fd.read(size)
26 except socket.error:
26 except socket.error:
27 raise IOError(self)
27 raise IOError(self)
28 self.remain -= size
28 self.remain -= size
29 elif self.remain:
29 elif self.remain:
30 data = self.fd.read(self.remain)
30 data = self.fd.read(self.remain)
31 self.remain = 0
31 self.remain = 0
32 else:
32 else:
33 data = None
33 data = None
34 return data
34 return data
35
35
36 def __repr__(self):
36 def __repr__(self):
37 return '<FileWrapper %s len: %s, read: %s>' % (
37 return '<FileWrapper %s len: %s, read: %s>' % (
38 self.fd, self.content_length, self.content_length - self.remain
38 self.fd, self.content_length, self.content_length - self.remain
39 )
39 )
40
40
41
41
42 class GitRepository(object):
42 class GitRepository(object):
43 git_folder_signature = set(['config', 'head', 'info', 'objects', 'refs'])
43 git_folder_signature = set(['config', 'head', 'info', 'objects', 'refs'])
44 commands = ['git-upload-pack', 'git-receive-pack']
44 commands = ['git-upload-pack', 'git-receive-pack']
45
45
46 def __init__(self, repo_name, content_path, extras):
46 def __init__(self, repo_name, content_path, extras):
47 files = set([f.lower() for f in os.listdir(content_path)])
47 files = set([f.lower() for f in os.listdir(content_path)])
48 if not (self.git_folder_signature.intersection(files)
48 if not (self.git_folder_signature.intersection(files)
49 == self.git_folder_signature):
49 == self.git_folder_signature):
50 raise OSError('%s missing git signature' % content_path)
50 raise OSError('%s missing git signature' % content_path)
51 self.content_path = content_path
51 self.content_path = content_path
52 self.valid_accepts = ['application/x-%s-result' %
52 self.valid_accepts = ['application/x-%s-result' %
53 c for c in self.commands]
53 c for c in self.commands]
54 self.repo_name = repo_name
54 self.repo_name = repo_name
55 self.extras = extras
55 self.extras = extras
56
56
57 def _get_fixedpath(self, path):
57 def _get_fixedpath(self, path):
58 """
58 """
59 Small fix for repo_path
59 Small fix for repo_path
60
60
61 :param path:
61 :param path:
62 :type path:
63 """
62 """
64 return path.split(self.repo_name, 1)[-1].strip('/')
63 return path.split(self.repo_name, 1)[-1].strip('/')
65
64
66 def inforefs(self, request, environ):
65 def inforefs(self, request, environ):
67 """
66 """
68 WSGI Response producer for HTTP GET Git Smart
67 WSGI Response producer for HTTP GET Git Smart
69 HTTP /info/refs request.
68 HTTP /info/refs request.
70 """
69 """
71
70
72 git_command = request.GET.get('service')
71 git_command = request.GET.get('service')
73 if git_command not in self.commands:
72 if git_command not in self.commands:
74 log.debug('command %s not allowed' % git_command)
73 log.debug('command %s not allowed' % git_command)
75 return exc.HTTPMethodNotAllowed()
74 return exc.HTTPMethodNotAllowed()
76
75
77 # note to self:
76 # note to self:
78 # please, resist the urge to add '\n' to git capture and increment
77 # please, resist the urge to add '\n' to git capture and increment
79 # line count by 1.
78 # line count by 1.
80 # The code in Git client not only does NOT need '\n', but actually
79 # The code in Git client not only does NOT need '\n', but actually
81 # blows up if you sprinkle "flush" (0000) as "0001\n".
80 # blows up if you sprinkle "flush" (0000) as "0001\n".
82 # It reads binary, per number of bytes specified.
81 # It reads binary, per number of bytes specified.
83 # if you do add '\n' as part of data, count it.
82 # if you do add '\n' as part of data, count it.
84 server_advert = '# service=%s' % git_command
83 server_advert = '# service=%s' % git_command
85 packet_len = str(hex(len(server_advert) + 4)[2:].rjust(4, '0')).lower()
84 packet_len = str(hex(len(server_advert) + 4)[2:].rjust(4, '0')).lower()
86 _git_path = rhodecode.CONFIG.get('git_path', 'git')
85 _git_path = rhodecode.CONFIG.get('git_path', 'git')
87 try:
86 try:
88 out = subprocessio.SubprocessIOChunker(
87 out = subprocessio.SubprocessIOChunker(
89 r'%s %s --stateless-rpc --advertise-refs "%s"' % (
88 r'%s %s --stateless-rpc --advertise-refs "%s"' % (
90 _git_path, git_command[4:], self.content_path),
89 _git_path, git_command[4:], self.content_path),
91 starting_values=[
90 starting_values=[
92 packet_len + server_advert + '0000'
91 packet_len + server_advert + '0000'
93 ]
92 ]
94 )
93 )
95 except EnvironmentError, e:
94 except EnvironmentError, e:
96 log.error(traceback.format_exc())
95 log.error(traceback.format_exc())
97 raise exc.HTTPExpectationFailed()
96 raise exc.HTTPExpectationFailed()
98 resp = Response()
97 resp = Response()
99 resp.content_type = 'application/x-%s-advertisement' % str(git_command)
98 resp.content_type = 'application/x-%s-advertisement' % str(git_command)
100 resp.charset = None
99 resp.charset = None
101 resp.app_iter = out
100 resp.app_iter = out
102 return resp
101 return resp
103
102
104 def backend(self, request, environ):
103 def backend(self, request, environ):
105 """
104 """
106 WSGI Response producer for HTTP POST Git Smart HTTP requests.
105 WSGI Response producer for HTTP POST Git Smart HTTP requests.
107 Reads commands and data from HTTP POST's body.
106 Reads commands and data from HTTP POST's body.
108 returns an iterator obj with contents of git command's
107 returns an iterator obj with contents of git command's
109 response to stdout
108 response to stdout
110 """
109 """
111 git_command = self._get_fixedpath(request.path_info)
110 git_command = self._get_fixedpath(request.path_info)
112 if git_command not in self.commands:
111 if git_command not in self.commands:
113 log.debug('command %s not allowed' % git_command)
112 log.debug('command %s not allowed' % git_command)
114 return exc.HTTPMethodNotAllowed()
113 return exc.HTTPMethodNotAllowed()
115
114
116 if 'CONTENT_LENGTH' in environ:
115 if 'CONTENT_LENGTH' in environ:
117 inputstream = FileWrapper(environ['wsgi.input'],
116 inputstream = FileWrapper(environ['wsgi.input'],
118 request.content_length)
117 request.content_length)
119 else:
118 else:
120 inputstream = environ['wsgi.input']
119 inputstream = environ['wsgi.input']
121
120
122 try:
121 try:
123 gitenv = os.environ
122 gitenv = os.environ
124 # forget all configs
123 # forget all configs
125 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
124 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
126 opts = dict(
125 opts = dict(
127 env=gitenv,
126 env=gitenv,
128 cwd=os.getcwd()
127 cwd=os.getcwd()
129 )
128 )
130 cmd = r'git %s --stateless-rpc "%s"' % (git_command[4:],
129 cmd = r'git %s --stateless-rpc "%s"' % (git_command[4:],
131 self.content_path),
130 self.content_path),
132 log.debug('handling cmd %s' % cmd)
131 log.debug('handling cmd %s' % cmd)
133 out = subprocessio.SubprocessIOChunker(
132 out = subprocessio.SubprocessIOChunker(
134 cmd,
133 cmd,
135 inputstream=inputstream,
134 inputstream=inputstream,
136 **opts
135 **opts
137 )
136 )
138 except EnvironmentError, e:
137 except EnvironmentError, e:
139 log.error(traceback.format_exc())
138 log.error(traceback.format_exc())
140 raise exc.HTTPExpectationFailed()
139 raise exc.HTTPExpectationFailed()
141
140
142 if git_command in [u'git-receive-pack']:
141 if git_command in [u'git-receive-pack']:
143 # updating refs manually after each push.
142 # updating refs manually after each push.
144 # Needed for pre-1.7.0.4 git clients using regular HTTP mode.
143 # Needed for pre-1.7.0.4 git clients using regular HTTP mode.
145 _git_path = rhodecode.CONFIG.get('git_path', 'git')
144 _git_path = rhodecode.CONFIG.get('git_path', 'git')
146 cmd = (u'%s --git-dir "%s" '
145 cmd = (u'%s --git-dir "%s" '
147 'update-server-info' % (_git_path, self.content_path))
146 'update-server-info' % (_git_path, self.content_path))
148 log.debug('handling cmd %s' % cmd)
147 log.debug('handling cmd %s' % cmd)
149 subprocess.call(cmd, shell=True)
148 subprocess.call(cmd, shell=True)
150
149
151 resp = Response()
150 resp = Response()
152 resp.content_type = 'application/x-%s-result' % git_command.encode('utf8')
151 resp.content_type = 'application/x-%s-result' % git_command.encode('utf8')
153 resp.charset = None
152 resp.charset = None
154 resp.app_iter = out
153 resp.app_iter = out
155 return resp
154 return resp
156
155
157 def __call__(self, environ, start_response):
156 def __call__(self, environ, start_response):
158 request = Request(environ)
157 request = Request(environ)
159 _path = self._get_fixedpath(request.path_info)
158 _path = self._get_fixedpath(request.path_info)
160 if _path.startswith('info/refs'):
159 if _path.startswith('info/refs'):
161 app = self.inforefs
160 app = self.inforefs
162 elif [a for a in self.valid_accepts if a in request.accept]:
161 elif [a for a in self.valid_accepts if a in request.accept]:
163 app = self.backend
162 app = self.backend
164 try:
163 try:
165 resp = app(request, environ)
164 resp = app(request, environ)
166 except exc.HTTPException, e:
165 except exc.HTTPException, e:
167 resp = e
166 resp = e
168 log.error(traceback.format_exc())
167 log.error(traceback.format_exc())
169 except Exception, e:
168 except Exception, e:
170 log.error(traceback.format_exc())
169 log.error(traceback.format_exc())
171 resp = exc.HTTPInternalServerError()
170 resp = exc.HTTPInternalServerError()
172 return resp(environ, start_response)
171 return resp(environ, start_response)
173
172
174
173
175 class GitDirectory(object):
174 class GitDirectory(object):
176
175
177 def __init__(self, repo_root, repo_name, extras):
176 def __init__(self, repo_root, repo_name, extras):
178 repo_location = os.path.join(repo_root, repo_name)
177 repo_location = os.path.join(repo_root, repo_name)
179 if not os.path.isdir(repo_location):
178 if not os.path.isdir(repo_location):
180 raise OSError(repo_location)
179 raise OSError(repo_location)
181
180
182 self.content_path = repo_location
181 self.content_path = repo_location
183 self.repo_name = repo_name
182 self.repo_name = repo_name
184 self.repo_location = repo_location
183 self.repo_location = repo_location
185 self.extras = extras
184 self.extras = extras
186
185
187 def __call__(self, environ, start_response):
186 def __call__(self, environ, start_response):
188 content_path = self.content_path
187 content_path = self.content_path
189 try:
188 try:
190 app = GitRepository(self.repo_name, content_path, self.extras)
189 app = GitRepository(self.repo_name, content_path, self.extras)
191 except (AssertionError, OSError):
190 except (AssertionError, OSError):
192 content_path = os.path.join(content_path, '.git')
191 content_path = os.path.join(content_path, '.git')
193 if os.path.isdir(content_path):
192 if os.path.isdir(content_path):
194 app = GitRepository(self.repo_name, content_path, self.extras)
193 app = GitRepository(self.repo_name, content_path, self.extras)
195 else:
194 else:
196 return exc.HTTPNotFound()(environ, start_response)
195 return exc.HTTPNotFound()(environ, start_response)
197 return app(environ, start_response)
196 return app(environ, start_response)
198
197
199
198
200 def make_wsgi_app(repo_name, repo_root, extras):
199 def make_wsgi_app(repo_name, repo_root, extras):
201 return GitDirectory(repo_root, repo_name, extras)
200 return GitDirectory(repo_root, repo_name, extras)
@@ -1,609 +1,646 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Some simple helper functions
6 Some simple helper functions
7
7
8 :created_on: Jan 5, 2011
8 :created_on: Jan 5, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import re
27 import re
28 import sys
28 import sys
29 import time
29 import time
30 import uuid
30 import datetime
31 import datetime
31 import traceback
32 import traceback
32 import webob
33 import webob
33
34
34 from pylons.i18n.translation import _, ungettext
35 from pylons.i18n.translation import _, ungettext
35 from rhodecode.lib.vcs.utils.lazy import LazyProperty
36 from rhodecode.lib.vcs.utils.lazy import LazyProperty
36 from rhodecode.lib.compat import json
37 from rhodecode.lib.compat import json
37
38
38
39
39 def __get_lem():
40 def __get_lem():
40 """
41 """
41 Get language extension map based on what's inside pygments lexers
42 Get language extension map based on what's inside pygments lexers
42 """
43 """
43 from pygments import lexers
44 from pygments import lexers
44 from string import lower
45 from string import lower
45 from collections import defaultdict
46 from collections import defaultdict
46
47
47 d = defaultdict(lambda: [])
48 d = defaultdict(lambda: [])
48
49
49 def __clean(s):
50 def __clean(s):
50 s = s.lstrip('*')
51 s = s.lstrip('*')
51 s = s.lstrip('.')
52 s = s.lstrip('.')
52
53
53 if s.find('[') != -1:
54 if s.find('[') != -1:
54 exts = []
55 exts = []
55 start, stop = s.find('['), s.find(']')
56 start, stop = s.find('['), s.find(']')
56
57
57 for suffix in s[start + 1:stop]:
58 for suffix in s[start + 1:stop]:
58 exts.append(s[:s.find('[')] + suffix)
59 exts.append(s[:s.find('[')] + suffix)
59 return map(lower, exts)
60 return map(lower, exts)
60 else:
61 else:
61 return map(lower, [s])
62 return map(lower, [s])
62
63
63 for lx, t in sorted(lexers.LEXERS.items()):
64 for lx, t in sorted(lexers.LEXERS.items()):
64 m = map(__clean, t[-2])
65 m = map(__clean, t[-2])
65 if m:
66 if m:
66 m = reduce(lambda x, y: x + y, m)
67 m = reduce(lambda x, y: x + y, m)
67 for ext in m:
68 for ext in m:
68 desc = lx.replace('Lexer', '')
69 desc = lx.replace('Lexer', '')
69 d[ext].append(desc)
70 d[ext].append(desc)
70
71
71 return dict(d)
72 return dict(d)
72
73
73
74
74 def str2bool(_str):
75 def str2bool(_str):
75 """
76 """
76 returs True/False value from given string, it tries to translate the
77 returs True/False value from given string, it tries to translate the
77 string into boolean
78 string into boolean
78
79
79 :param _str: string value to translate into boolean
80 :param _str: string value to translate into boolean
80 :rtype: boolean
81 :rtype: boolean
81 :returns: boolean from given string
82 :returns: boolean from given string
82 """
83 """
83 if _str is None:
84 if _str is None:
84 return False
85 return False
85 if _str in (True, False):
86 if _str in (True, False):
86 return _str
87 return _str
87 _str = str(_str).strip().lower()
88 _str = str(_str).strip().lower()
88 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
89 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
89
90
90
91
91 def aslist(obj, sep=None, strip=True):
92 def aslist(obj, sep=None, strip=True):
92 """
93 """
93 Returns given string separated by sep as list
94 Returns given string separated by sep as list
94
95
95 :param obj:
96 :param obj:
96 :param sep:
97 :param sep:
97 :param strip:
98 :param strip:
98 """
99 """
99 if isinstance(obj, (basestring)):
100 if isinstance(obj, (basestring)):
100 lst = obj.split(sep)
101 lst = obj.split(sep)
101 if strip:
102 if strip:
102 lst = [v.strip() for v in lst]
103 lst = [v.strip() for v in lst]
103 return lst
104 return lst
104 elif isinstance(obj, (list, tuple)):
105 elif isinstance(obj, (list, tuple)):
105 return obj
106 return obj
106 elif obj is None:
107 elif obj is None:
107 return []
108 return []
108 else:
109 else:
109 return [obj]
110 return [obj]
110
111
111
112
112 def convert_line_endings(line, mode):
113 def convert_line_endings(line, mode):
113 """
114 """
114 Converts a given line "line end" accordingly to given mode
115 Converts a given line "line end" accordingly to given mode
115
116
116 Available modes are::
117 Available modes are::
117 0 - Unix
118 0 - Unix
118 1 - Mac
119 1 - Mac
119 2 - DOS
120 2 - DOS
120
121
121 :param line: given line to convert
122 :param line: given line to convert
122 :param mode: mode to convert to
123 :param mode: mode to convert to
123 :rtype: str
124 :rtype: str
124 :return: converted line according to mode
125 :return: converted line according to mode
125 """
126 """
126 from string import replace
127 from string import replace
127
128
128 if mode == 0:
129 if mode == 0:
129 line = replace(line, '\r\n', '\n')
130 line = replace(line, '\r\n', '\n')
130 line = replace(line, '\r', '\n')
131 line = replace(line, '\r', '\n')
131 elif mode == 1:
132 elif mode == 1:
132 line = replace(line, '\r\n', '\r')
133 line = replace(line, '\r\n', '\r')
133 line = replace(line, '\n', '\r')
134 line = replace(line, '\n', '\r')
134 elif mode == 2:
135 elif mode == 2:
135 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
136 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
136 return line
137 return line
137
138
138
139
139 def detect_mode(line, default):
140 def detect_mode(line, default):
140 """
141 """
141 Detects line break for given line, if line break couldn't be found
142 Detects line break for given line, if line break couldn't be found
142 given default value is returned
143 given default value is returned
143
144
144 :param line: str line
145 :param line: str line
145 :param default: default
146 :param default: default
146 :rtype: int
147 :rtype: int
147 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
148 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
148 """
149 """
149 if line.endswith('\r\n'):
150 if line.endswith('\r\n'):
150 return 2
151 return 2
151 elif line.endswith('\n'):
152 elif line.endswith('\n'):
152 return 0
153 return 0
153 elif line.endswith('\r'):
154 elif line.endswith('\r'):
154 return 1
155 return 1
155 else:
156 else:
156 return default
157 return default
157
158
158
159
159 def generate_api_key(username, salt=None):
160 def generate_api_key(username, salt=None):
160 """
161 """
161 Generates unique API key for given username, if salt is not given
162 Generates unique API key for given username, if salt is not given
162 it'll be generated from some random string
163 it'll be generated from some random string
163
164
164 :param username: username as string
165 :param username: username as string
165 :param salt: salt to hash generate KEY
166 :param salt: salt to hash generate KEY
166 :rtype: str
167 :rtype: str
167 :returns: sha1 hash from username+salt
168 :returns: sha1 hash from username+salt
168 """
169 """
169 from tempfile import _RandomNameSequence
170 from tempfile import _RandomNameSequence
170 import hashlib
171 import hashlib
171
172
172 if salt is None:
173 if salt is None:
173 salt = _RandomNameSequence().next()
174 salt = _RandomNameSequence().next()
174
175
175 return hashlib.sha1(username + salt).hexdigest()
176 return hashlib.sha1(username + salt).hexdigest()
176
177
177
178
178 def safe_int(val, default=None):
179 def safe_int(val, default=None):
179 """
180 """
180 Returns int() of val if val is not convertable to int use default
181 Returns int() of val if val is not convertable to int use default
181 instead
182 instead
182
183
183 :param val:
184 :param val:
184 :param default:
185 :param default:
185 """
186 """
186
187
187 try:
188 try:
188 val = int(val)
189 val = int(val)
189 except (ValueError, TypeError):
190 except (ValueError, TypeError):
190 val = default
191 val = default
191
192
192 return val
193 return val
193
194
194
195
195 def safe_unicode(str_, from_encoding=None):
196 def safe_unicode(str_, from_encoding=None):
196 """
197 """
197 safe unicode function. Does few trick to turn str_ into unicode
198 safe unicode function. Does few trick to turn str_ into unicode
198
199
199 In case of UnicodeDecode error we try to return it with encoding detected
200 In case of UnicodeDecode error we try to return it with encoding detected
200 by chardet library if it fails fallback to unicode with errors replaced
201 by chardet library if it fails fallback to unicode with errors replaced
201
202
202 :param str_: string to decode
203 :param str_: string to decode
203 :rtype: unicode
204 :rtype: unicode
204 :returns: unicode object
205 :returns: unicode object
205 """
206 """
206 if isinstance(str_, unicode):
207 if isinstance(str_, unicode):
207 return str_
208 return str_
208
209
209 if not from_encoding:
210 if not from_encoding:
210 import rhodecode
211 import rhodecode
211 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
212 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
212 'utf8'), sep=',')
213 'utf8'), sep=',')
213 from_encoding = DEFAULT_ENCODINGS
214 from_encoding = DEFAULT_ENCODINGS
214
215
215 if not isinstance(from_encoding, (list, tuple)):
216 if not isinstance(from_encoding, (list, tuple)):
216 from_encoding = [from_encoding]
217 from_encoding = [from_encoding]
217
218
218 try:
219 try:
219 return unicode(str_)
220 return unicode(str_)
220 except UnicodeDecodeError:
221 except UnicodeDecodeError:
221 pass
222 pass
222
223
223 for enc in from_encoding:
224 for enc in from_encoding:
224 try:
225 try:
225 return unicode(str_, enc)
226 return unicode(str_, enc)
226 except UnicodeDecodeError:
227 except UnicodeDecodeError:
227 pass
228 pass
228
229
229 try:
230 try:
230 import chardet
231 import chardet
231 encoding = chardet.detect(str_)['encoding']
232 encoding = chardet.detect(str_)['encoding']
232 if encoding is None:
233 if encoding is None:
233 raise Exception()
234 raise Exception()
234 return str_.decode(encoding)
235 return str_.decode(encoding)
235 except (ImportError, UnicodeDecodeError, Exception):
236 except (ImportError, UnicodeDecodeError, Exception):
236 return unicode(str_, from_encoding[0], 'replace')
237 return unicode(str_, from_encoding[0], 'replace')
237
238
238
239
239 def safe_str(unicode_, to_encoding=None):
240 def safe_str(unicode_, to_encoding=None):
240 """
241 """
241 safe str function. Does few trick to turn unicode_ into string
242 safe str function. Does few trick to turn unicode_ into string
242
243
243 In case of UnicodeEncodeError we try to return it with encoding detected
244 In case of UnicodeEncodeError we try to return it with encoding detected
244 by chardet library if it fails fallback to string with errors replaced
245 by chardet library if it fails fallback to string with errors replaced
245
246
246 :param unicode_: unicode to encode
247 :param unicode_: unicode to encode
247 :rtype: str
248 :rtype: str
248 :returns: str object
249 :returns: str object
249 """
250 """
250
251
251 # if it's not basestr cast to str
252 # if it's not basestr cast to str
252 if not isinstance(unicode_, basestring):
253 if not isinstance(unicode_, basestring):
253 return str(unicode_)
254 return str(unicode_)
254
255
255 if isinstance(unicode_, str):
256 if isinstance(unicode_, str):
256 return unicode_
257 return unicode_
257
258
258 if not to_encoding:
259 if not to_encoding:
259 import rhodecode
260 import rhodecode
260 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
261 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
261 'utf8'), sep=',')
262 'utf8'), sep=',')
262 to_encoding = DEFAULT_ENCODINGS
263 to_encoding = DEFAULT_ENCODINGS
263
264
264 if not isinstance(to_encoding, (list, tuple)):
265 if not isinstance(to_encoding, (list, tuple)):
265 to_encoding = [to_encoding]
266 to_encoding = [to_encoding]
266
267
267 for enc in to_encoding:
268 for enc in to_encoding:
268 try:
269 try:
269 return unicode_.encode(enc)
270 return unicode_.encode(enc)
270 except UnicodeEncodeError:
271 except UnicodeEncodeError:
271 pass
272 pass
272
273
273 try:
274 try:
274 import chardet
275 import chardet
275 encoding = chardet.detect(unicode_)['encoding']
276 encoding = chardet.detect(unicode_)['encoding']
276 if encoding is None:
277 if encoding is None:
277 raise UnicodeEncodeError()
278 raise UnicodeEncodeError()
278
279
279 return unicode_.encode(encoding)
280 return unicode_.encode(encoding)
280 except (ImportError, UnicodeEncodeError):
281 except (ImportError, UnicodeEncodeError):
281 return unicode_.encode(to_encoding[0], 'replace')
282 return unicode_.encode(to_encoding[0], 'replace')
282
283
283 return safe_str
284 return safe_str
284
285
285
286
286 def remove_suffix(s, suffix):
287 def remove_suffix(s, suffix):
287 if s.endswith(suffix):
288 if s.endswith(suffix):
288 s = s[:-1 * len(suffix)]
289 s = s[:-1 * len(suffix)]
289 return s
290 return s
290
291
291
292
292 def remove_prefix(s, prefix):
293 def remove_prefix(s, prefix):
293 if s.startswith(prefix):
294 if s.startswith(prefix):
294 s = s[len(prefix):]
295 s = s[len(prefix):]
295 return s
296 return s
296
297
297
298
298 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
299 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
299 """
300 """
300 Custom engine_from_config functions that makes sure we use NullPool for
301 Custom engine_from_config functions that makes sure we use NullPool for
301 file based sqlite databases. This prevents errors on sqlite. This only
302 file based sqlite databases. This prevents errors on sqlite. This only
302 applies to sqlalchemy versions < 0.7.0
303 applies to sqlalchemy versions < 0.7.0
303
304
304 """
305 """
305 import sqlalchemy
306 import sqlalchemy
306 from sqlalchemy import engine_from_config as efc
307 from sqlalchemy import engine_from_config as efc
307 import logging
308 import logging
308
309
309 if int(sqlalchemy.__version__.split('.')[1]) < 7:
310 if int(sqlalchemy.__version__.split('.')[1]) < 7:
310
311
311 # This solution should work for sqlalchemy < 0.7.0, and should use
312 # This solution should work for sqlalchemy < 0.7.0, and should use
312 # proxy=TimerProxy() for execution time profiling
313 # proxy=TimerProxy() for execution time profiling
313
314
314 from sqlalchemy.pool import NullPool
315 from sqlalchemy.pool import NullPool
315 url = configuration[prefix + 'url']
316 url = configuration[prefix + 'url']
316
317
317 if url.startswith('sqlite'):
318 if url.startswith('sqlite'):
318 kwargs.update({'poolclass': NullPool})
319 kwargs.update({'poolclass': NullPool})
319 return efc(configuration, prefix, **kwargs)
320 return efc(configuration, prefix, **kwargs)
320 else:
321 else:
321 import time
322 import time
322 from sqlalchemy import event
323 from sqlalchemy import event
323 from sqlalchemy.engine import Engine
324 from sqlalchemy.engine import Engine
324
325
325 log = logging.getLogger('sqlalchemy.engine')
326 log = logging.getLogger('sqlalchemy.engine')
326 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
327 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
327 engine = efc(configuration, prefix, **kwargs)
328 engine = efc(configuration, prefix, **kwargs)
328
329
329 def color_sql(sql):
330 def color_sql(sql):
330 COLOR_SEQ = "\033[1;%dm"
331 COLOR_SEQ = "\033[1;%dm"
331 COLOR_SQL = YELLOW
332 COLOR_SQL = YELLOW
332 normal = '\x1b[0m'
333 normal = '\x1b[0m'
333 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
334 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
334
335
335 if configuration['debug']:
336 if configuration['debug']:
336 #attach events only for debug configuration
337 #attach events only for debug configuration
337
338
338 def before_cursor_execute(conn, cursor, statement,
339 def before_cursor_execute(conn, cursor, statement,
339 parameters, context, executemany):
340 parameters, context, executemany):
340 context._query_start_time = time.time()
341 context._query_start_time = time.time()
341 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
342 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
342
343
343 def after_cursor_execute(conn, cursor, statement,
344 def after_cursor_execute(conn, cursor, statement,
344 parameters, context, executemany):
345 parameters, context, executemany):
345 total = time.time() - context._query_start_time
346 total = time.time() - context._query_start_time
346 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
347 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
347
348
348 event.listen(engine, "before_cursor_execute",
349 event.listen(engine, "before_cursor_execute",
349 before_cursor_execute)
350 before_cursor_execute)
350 event.listen(engine, "after_cursor_execute",
351 event.listen(engine, "after_cursor_execute",
351 after_cursor_execute)
352 after_cursor_execute)
352
353
353 return engine
354 return engine
354
355
355
356
356 def age(prevdate, show_short_version=False, now=None):
357 def age(prevdate, show_short_version=False, now=None):
357 """
358 """
358 turns a datetime into an age string.
359 turns a datetime into an age string.
359 If show_short_version is True, then it will generate a not so accurate but shorter string,
360 If show_short_version is True, then it will generate a not so accurate but shorter string,
360 example: 2days ago, instead of 2 days and 23 hours ago.
361 example: 2days ago, instead of 2 days and 23 hours ago.
361
362
362 :param prevdate: datetime object
363 :param prevdate: datetime object
363 :param show_short_version: if it should aproximate the date and return a shorter string
364 :param show_short_version: if it should aproximate the date and return a shorter string
364 :rtype: unicode
365 :rtype: unicode
365 :returns: unicode words describing age
366 :returns: unicode words describing age
366 """
367 """
367 now = now or datetime.datetime.now()
368 now = now or datetime.datetime.now()
368 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
369 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
369 deltas = {}
370 deltas = {}
370 future = False
371 future = False
371
372
372 if prevdate > now:
373 if prevdate > now:
373 now, prevdate = prevdate, now
374 now, prevdate = prevdate, now
374 future = True
375 future = True
375 if future:
376 if future:
376 prevdate = prevdate.replace(microsecond=0)
377 prevdate = prevdate.replace(microsecond=0)
377 # Get date parts deltas
378 # Get date parts deltas
378 from dateutil import relativedelta
379 from dateutil import relativedelta
379 for part in order:
380 for part in order:
380 d = relativedelta.relativedelta(now, prevdate)
381 d = relativedelta.relativedelta(now, prevdate)
381 deltas[part] = getattr(d, part + 's')
382 deltas[part] = getattr(d, part + 's')
382
383
383 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
384 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
384 # not 1 hour, -59 minutes and -59 seconds)
385 # not 1 hour, -59 minutes and -59 seconds)
385 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
386 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
386 part = order[num]
387 part = order[num]
387 carry_part = order[num - 1]
388 carry_part = order[num - 1]
388
389
389 if deltas[part] < 0:
390 if deltas[part] < 0:
390 deltas[part] += length
391 deltas[part] += length
391 deltas[carry_part] -= 1
392 deltas[carry_part] -= 1
392
393
393 # Same thing for days except that the increment depends on the (variable)
394 # Same thing for days except that the increment depends on the (variable)
394 # number of days in the month
395 # number of days in the month
395 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
396 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
396 if deltas['day'] < 0:
397 if deltas['day'] < 0:
397 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
398 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
398 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
399 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
399 deltas['day'] += 29
400 deltas['day'] += 29
400 else:
401 else:
401 deltas['day'] += month_lengths[prevdate.month - 1]
402 deltas['day'] += month_lengths[prevdate.month - 1]
402
403
403 deltas['month'] -= 1
404 deltas['month'] -= 1
404
405
405 if deltas['month'] < 0:
406 if deltas['month'] < 0:
406 deltas['month'] += 12
407 deltas['month'] += 12
407 deltas['year'] -= 1
408 deltas['year'] -= 1
408
409
409 # Format the result
410 # Format the result
410 fmt_funcs = {
411 fmt_funcs = {
411 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
412 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
412 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
413 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
413 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
414 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
414 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
415 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
415 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
416 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
416 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
417 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
417 }
418 }
418
419
419 for i, part in enumerate(order):
420 for i, part in enumerate(order):
420 value = deltas[part]
421 value = deltas[part]
421 if value == 0:
422 if value == 0:
422 continue
423 continue
423
424
424 if i < 5:
425 if i < 5:
425 sub_part = order[i + 1]
426 sub_part = order[i + 1]
426 sub_value = deltas[sub_part]
427 sub_value = deltas[sub_part]
427 else:
428 else:
428 sub_value = 0
429 sub_value = 0
429
430
430 if sub_value == 0 or show_short_version:
431 if sub_value == 0 or show_short_version:
431 if future:
432 if future:
432 return _(u'in %s') % fmt_funcs[part](value)
433 return _(u'in %s') % fmt_funcs[part](value)
433 else:
434 else:
434 return _(u'%s ago') % fmt_funcs[part](value)
435 return _(u'%s ago') % fmt_funcs[part](value)
435 if future:
436 if future:
436 return _(u'in %s and %s') % (fmt_funcs[part](value),
437 return _(u'in %s and %s') % (fmt_funcs[part](value),
437 fmt_funcs[sub_part](sub_value))
438 fmt_funcs[sub_part](sub_value))
438 else:
439 else:
439 return _(u'%s and %s ago') % (fmt_funcs[part](value),
440 return _(u'%s and %s ago') % (fmt_funcs[part](value),
440 fmt_funcs[sub_part](sub_value))
441 fmt_funcs[sub_part](sub_value))
441
442
442 return _(u'just now')
443 return _(u'just now')
443
444
444
445
445 def uri_filter(uri):
446 def uri_filter(uri):
446 """
447 """
447 Removes user:password from given url string
448 Removes user:password from given url string
448
449
449 :param uri:
450 :param uri:
450 :rtype: unicode
451 :rtype: unicode
451 :returns: filtered list of strings
452 :returns: filtered list of strings
452 """
453 """
453 if not uri:
454 if not uri:
454 return ''
455 return ''
455
456
456 proto = ''
457 proto = ''
457
458
458 for pat in ('https://', 'http://'):
459 for pat in ('https://', 'http://'):
459 if uri.startswith(pat):
460 if uri.startswith(pat):
460 uri = uri[len(pat):]
461 uri = uri[len(pat):]
461 proto = pat
462 proto = pat
462 break
463 break
463
464
464 # remove passwords and username
465 # remove passwords and username
465 uri = uri[uri.find('@') + 1:]
466 uri = uri[uri.find('@') + 1:]
466
467
467 # get the port
468 # get the port
468 cred_pos = uri.find(':')
469 cred_pos = uri.find(':')
469 if cred_pos == -1:
470 if cred_pos == -1:
470 host, port = uri, None
471 host, port = uri, None
471 else:
472 else:
472 host, port = uri[:cred_pos], uri[cred_pos + 1:]
473 host, port = uri[:cred_pos], uri[cred_pos + 1:]
473
474
474 return filter(None, [proto, host, port])
475 return filter(None, [proto, host, port])
475
476
476
477
477 def credentials_filter(uri):
478 def credentials_filter(uri):
478 """
479 """
479 Returns a url with removed credentials
480 Returns a url with removed credentials
480
481
481 :param uri:
482 :param uri:
482 """
483 """
483
484
484 uri = uri_filter(uri)
485 uri = uri_filter(uri)
485 #check if we have port
486 #check if we have port
486 if len(uri) > 2 and uri[2]:
487 if len(uri) > 2 and uri[2]:
487 uri[2] = ':' + uri[2]
488 uri[2] = ':' + uri[2]
488
489
489 return ''.join(uri)
490 return ''.join(uri)
490
491
491
492
492 def get_changeset_safe(repo, rev):
493 def get_changeset_safe(repo, rev):
493 """
494 """
494 Safe version of get_changeset if this changeset doesn't exists for a
495 Safe version of get_changeset if this changeset doesn't exists for a
495 repo it returns a Dummy one instead
496 repo it returns a Dummy one instead
496
497
497 :param repo:
498 :param repo:
498 :param rev:
499 :param rev:
499 """
500 """
500 from rhodecode.lib.vcs.backends.base import BaseRepository
501 from rhodecode.lib.vcs.backends.base import BaseRepository
501 from rhodecode.lib.vcs.exceptions import RepositoryError
502 from rhodecode.lib.vcs.exceptions import RepositoryError
502 from rhodecode.lib.vcs.backends.base import EmptyChangeset
503 from rhodecode.lib.vcs.backends.base import EmptyChangeset
503 if not isinstance(repo, BaseRepository):
504 if not isinstance(repo, BaseRepository):
504 raise Exception('You must pass an Repository '
505 raise Exception('You must pass an Repository '
505 'object as first argument got %s', type(repo))
506 'object as first argument got %s', type(repo))
506
507
507 try:
508 try:
508 cs = repo.get_changeset(rev)
509 cs = repo.get_changeset(rev)
509 except RepositoryError:
510 except RepositoryError:
510 cs = EmptyChangeset(requested_revision=rev)
511 cs = EmptyChangeset(requested_revision=rev)
511 return cs
512 return cs
512
513
513
514
514 def datetime_to_time(dt):
515 def datetime_to_time(dt):
515 if dt:
516 if dt:
516 return time.mktime(dt.timetuple())
517 return time.mktime(dt.timetuple())
517
518
518
519
519 def time_to_datetime(tm):
520 def time_to_datetime(tm):
520 if tm:
521 if tm:
521 if isinstance(tm, basestring):
522 if isinstance(tm, basestring):
522 try:
523 try:
523 tm = float(tm)
524 tm = float(tm)
524 except ValueError:
525 except ValueError:
525 return
526 return
526 return datetime.datetime.fromtimestamp(tm)
527 return datetime.datetime.fromtimestamp(tm)
527
528
528 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
529 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
529
530
530
531
531 def extract_mentioned_users(s):
532 def extract_mentioned_users(s):
532 """
533 """
533 Returns unique usernames from given string s that have @mention
534 Returns unique usernames from given string s that have @mention
534
535
535 :param s: string to get mentions
536 :param s: string to get mentions
536 """
537 """
537 usrs = set()
538 usrs = set()
538 for username in re.findall(MENTIONS_REGEX, s):
539 for username in re.findall(MENTIONS_REGEX, s):
539 usrs.add(username)
540 usrs.add(username)
540
541
541 return sorted(list(usrs), key=lambda k: k.lower())
542 return sorted(list(usrs), key=lambda k: k.lower())
542
543
543
544
544 class AttributeDict(dict):
545 class AttributeDict(dict):
545 def __getattr__(self, attr):
546 def __getattr__(self, attr):
546 return self.get(attr, None)
547 return self.get(attr, None)
547 __setattr__ = dict.__setitem__
548 __setattr__ = dict.__setitem__
548 __delattr__ = dict.__delitem__
549 __delattr__ = dict.__delitem__
549
550
550
551
551 def fix_PATH(os_=None):
552 def fix_PATH(os_=None):
552 """
553 """
553 Get current active python path, and append it to PATH variable to fix issues
554 Get current active python path, and append it to PATH variable to fix issues
554 of subprocess calls and different python versions
555 of subprocess calls and different python versions
555 """
556 """
556 if os_ is None:
557 if os_ is None:
557 import os
558 import os
558 else:
559 else:
559 os = os_
560 os = os_
560
561
561 cur_path = os.path.split(sys.executable)[0]
562 cur_path = os.path.split(sys.executable)[0]
562 if not os.environ['PATH'].startswith(cur_path):
563 if not os.environ['PATH'].startswith(cur_path):
563 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
564 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
564
565
565
566
566 def obfuscate_url_pw(engine):
567 def obfuscate_url_pw(engine):
567 _url = engine or ''
568 _url = engine or ''
568 from sqlalchemy.engine import url as sa_url
569 from sqlalchemy.engine import url as sa_url
569 try:
570 try:
570 _url = sa_url.make_url(engine)
571 _url = sa_url.make_url(engine)
571 if _url.password:
572 if _url.password:
572 _url.password = 'XXXXX'
573 _url.password = 'XXXXX'
573 except Exception:
574 except Exception:
574 pass
575 pass
575 return str(_url)
576 return str(_url)
576
577
577
578
578 def get_server_url(environ):
579 def get_server_url(environ):
579 req = webob.Request(environ)
580 req = webob.Request(environ)
580 return req.host_url + req.script_name
581 return req.host_url + req.script_name
581
582
582
583
583 def _extract_extras(env=None):
584 def _extract_extras(env=None):
584 """
585 """
585 Extracts the rc extras data from os.environ, and wraps it into named
586 Extracts the rc extras data from os.environ, and wraps it into named
586 AttributeDict object
587 AttributeDict object
587 """
588 """
588 if not env:
589 if not env:
589 env = os.environ
590 env = os.environ
590
591
591 try:
592 try:
592 rc_extras = json.loads(env['RC_SCM_DATA'])
593 rc_extras = json.loads(env['RC_SCM_DATA'])
593 except Exception:
594 except Exception:
594 print os.environ
595 print os.environ
595 print >> sys.stderr, traceback.format_exc()
596 print >> sys.stderr, traceback.format_exc()
596 rc_extras = {}
597 rc_extras = {}
597
598
598 try:
599 try:
599 for k in ['username', 'repository', 'locked_by', 'scm', 'make_lock',
600 for k in ['username', 'repository', 'locked_by', 'scm', 'make_lock',
600 'action', 'ip']:
601 'action', 'ip']:
601 rc_extras[k]
602 rc_extras[k]
602 except KeyError, e:
603 except KeyError, e:
603 raise Exception('Missing key %s in os.environ %s' % (e, rc_extras))
604 raise Exception('Missing key %s in os.environ %s' % (e, rc_extras))
604
605
605 return AttributeDict(rc_extras)
606 return AttributeDict(rc_extras)
606
607
607
608
608 def _set_extras(extras):
609 def _set_extras(extras):
609 os.environ['RC_SCM_DATA'] = json.dumps(extras)
610 os.environ['RC_SCM_DATA'] = json.dumps(extras)
611
612
613 def unique_id(hexlen=32):
614 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
615 return suuid(truncate_to=hexlen, alphabet=alphabet)
616
617
618 def suuid(url=None, truncate_to=22, alphabet=None):
619 """
620 Generate and return a short URL safe UUID.
621
622 If the url parameter is provided, set the namespace to the provided
623 URL and generate a UUID.
624
625 :param url to get the uuid for
626 :truncate_to: truncate the basic 22 UUID to shorter version
627
628 The IDs won't be universally unique any longer, but the probability of
629 a collision will still be very low.
630 """
631 # Define our alphabet.
632 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
633
634 # If no URL is given, generate a random UUID.
635 if url is None:
636 unique_id = uuid.uuid4().int
637 else:
638 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
639
640 alphabet_length = len(_ALPHABET)
641 output = []
642 while unique_id > 0:
643 digit = unique_id % alphabet_length
644 output.append(_ALPHABET[digit])
645 unique_id = int(unique_id / alphabet_length)
646 return "".join(output)[:truncate_to]
@@ -1,140 +1,137 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.__init__
3 rhodecode.model.__init__
4 ~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 The application's model objects
6 The application's model objects
7
7
8 :created_on: Nov 25, 2010
8 :created_on: Nov 25, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12
12
13
13
14 :example:
14 :example:
15
15
16 .. code-block:: python
16 .. code-block:: python
17
17
18 from paste.deploy import appconfig
18 from paste.deploy import appconfig
19 from pylons import config
19 from pylons import config
20 from sqlalchemy import engine_from_config
20 from sqlalchemy import engine_from_config
21 from rhodecode.config.environment import load_environment
21 from rhodecode.config.environment import load_environment
22
22
23 conf = appconfig('config:development.ini', relative_to = './../../')
23 conf = appconfig('config:development.ini', relative_to = './../../')
24 load_environment(conf.global_conf, conf.local_conf)
24 load_environment(conf.global_conf, conf.local_conf)
25
25
26 engine = engine_from_config(config, 'sqlalchemy.')
26 engine = engine_from_config(config, 'sqlalchemy.')
27 init_model(engine)
27 init_model(engine)
28 # RUN YOUR CODE HERE
28 # RUN YOUR CODE HERE
29
29
30 """
30 """
31 # This program is free software: you can redistribute it and/or modify
31 # This program is free software: you can redistribute it and/or modify
32 # it under the terms of the GNU General Public License as published by
32 # it under the terms of the GNU General Public License as published by
33 # the Free Software Foundation, either version 3 of the License, or
33 # the Free Software Foundation, either version 3 of the License, or
34 # (at your option) any later version.
34 # (at your option) any later version.
35 #
35 #
36 # This program is distributed in the hope that it will be useful,
36 # This program is distributed in the hope that it will be useful,
37 # but WITHOUT ANY WARRANTY; without even the implied warranty of
37 # but WITHOUT ANY WARRANTY; without even the implied warranty of
38 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
39 # GNU General Public License for more details.
39 # GNU General Public License for more details.
40 #
40 #
41 # You should have received a copy of the GNU General Public License
41 # You should have received a copy of the GNU General Public License
42 # along with this program. If not, see <http://www.gnu.org/licenses/>.
42 # along with this program. If not, see <http://www.gnu.org/licenses/>.
43
43
44 import logging
44 import logging
45 from rhodecode.model import meta
45 from rhodecode.model import meta
46 from rhodecode.lib.utils2 import safe_str, obfuscate_url_pw
46 from rhodecode.lib.utils2 import safe_str, obfuscate_url_pw
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 def init_model(engine):
51 def init_model(engine):
52 """
52 """
53 Initializes db session, bind the engine with the metadata,
53 Initializes db session, bind the engine with the metadata,
54 Call this before using any of the tables or classes in the model,
54 Call this before using any of the tables or classes in the model,
55 preferably once in application start
55 preferably once in application start
56
56
57 :param engine: engine to bind to
57 :param engine: engine to bind to
58 """
58 """
59 engine_str = obfuscate_url_pw(str(engine.url))
59 engine_str = obfuscate_url_pw(str(engine.url))
60 log.info("initializing db for %s" % engine_str)
60 log.info("initializing db for %s" % engine_str)
61 meta.Base.metadata.bind = engine
61 meta.Base.metadata.bind = engine
62
62
63
63
64 class BaseModel(object):
64 class BaseModel(object):
65 """
65 """
66 Base Model for all RhodeCode models, it adds sql alchemy session
66 Base Model for all RhodeCode models, it adds sql alchemy session
67 into instance of model
67 into instance of model
68
68
69 :param sa: If passed it reuses this session instead of creating a new one
69 :param sa: If passed it reuses this session instead of creating a new one
70 """
70 """
71
71
72 cls = None # override in child class
72 cls = None # override in child class
73
73
74 def __init__(self, sa=None):
74 def __init__(self, sa=None):
75 if sa is not None:
75 if sa is not None:
76 self.sa = sa
76 self.sa = sa
77 else:
77 else:
78 self.sa = meta.Session()
78 self.sa = meta.Session()
79
79
80 def _get_instance(self, cls, instance, callback=None):
80 def _get_instance(self, cls, instance, callback=None):
81 """
81 """
82 Get's instance of given cls using some simple lookup mechanism.
82 Get's instance of given cls using some simple lookup mechanism.
83
83
84 :param cls: class to fetch
84 :param cls: class to fetch
85 :param instance: int or Instance
85 :param instance: int or Instance
86 :param callback: callback to call if all lookups failed
86 :param callback: callback to call if all lookups failed
87 """
87 """
88
88
89 if isinstance(instance, cls):
89 if isinstance(instance, cls):
90 return instance
90 return instance
91 elif isinstance(instance, (int, long)) or safe_str(instance).isdigit():
91 elif isinstance(instance, (int, long)) or safe_str(instance).isdigit():
92 return cls.get(instance)
92 return cls.get(instance)
93 else:
93 else:
94 if instance:
94 if instance:
95 if callback is None:
95 if callback is None:
96 raise Exception(
96 raise Exception(
97 'given object must be int, long or Instance of %s '
97 'given object must be int, long or Instance of %s '
98 'got %s, no callback provided' % (cls, type(instance))
98 'got %s, no callback provided' % (cls, type(instance))
99 )
99 )
100 else:
100 else:
101 return callback(instance)
101 return callback(instance)
102
102
103 def _get_user(self, user):
103 def _get_user(self, user):
104 """
104 """
105 Helper method to get user by ID, or username fallback
105 Helper method to get user by ID, or username fallback
106
106
107 :param user:
107 :param user: UserID, username, or User instance
108 :type user: UserID, username, or User instance
109 """
108 """
110 from rhodecode.model.db import User
109 from rhodecode.model.db import User
111 return self._get_instance(User, user,
110 return self._get_instance(User, user,
112 callback=User.get_by_username)
111 callback=User.get_by_username)
113
112
114 def _get_repo(self, repository):
113 def _get_repo(self, repository):
115 """
114 """
116 Helper method to get repository by ID, or repository name
115 Helper method to get repository by ID, or repository name
117
116
118 :param repository:
117 :param repository: RepoID, repository name or Repository Instance
119 :type repository: RepoID, repository name or Repository Instance
120 """
118 """
121 from rhodecode.model.db import Repository
119 from rhodecode.model.db import Repository
122 return self._get_instance(Repository, repository,
120 return self._get_instance(Repository, repository,
123 callback=Repository.get_by_repo_name)
121 callback=Repository.get_by_repo_name)
124
122
125 def _get_perm(self, permission):
123 def _get_perm(self, permission):
126 """
124 """
127 Helper method to get permission by ID, or permission name
125 Helper method to get permission by ID, or permission name
128
126
129 :param permission:
127 :param permission: PermissionID, permission_name or Permission instance
130 :type permission: PermissionID, permission_name or Permission instance
131 """
128 """
132 from rhodecode.model.db import Permission
129 from rhodecode.model.db import Permission
133 return self._get_instance(Permission, permission,
130 return self._get_instance(Permission, permission,
134 callback=Permission.get_by_key)
131 callback=Permission.get_by_key)
135
132
136 def get_all(self):
133 def get_all(self):
137 """
134 """
138 Returns all instances of what is defined in `cls` class variable
135 Returns all instances of what is defined in `cls` class variable
139 """
136 """
140 return self.cls.getAll()
137 return self.cls.getAll()
@@ -1,2133 +1,2170 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 import hashlib
30 import hashlib
31 import time
31 import time
32 from collections import defaultdict
32 from collections import defaultdict
33
33
34 from sqlalchemy import *
34 from sqlalchemy import *
35 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.ext.hybrid import hybrid_property
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 from sqlalchemy.exc import DatabaseError
37 from sqlalchemy.exc import DatabaseError
38 from beaker.cache import cache_region, region_invalidate
38 from beaker.cache import cache_region, region_invalidate
39 from webob.exc import HTTPNotFound
39 from webob.exc import HTTPNotFound
40
40
41 from pylons.i18n.translation import lazy_ugettext as _
41 from pylons.i18n.translation import lazy_ugettext as _
42
42
43 from rhodecode.lib.vcs import get_backend
43 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs.utils.helpers import get_scm
44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 from rhodecode.lib.vcs.exceptions import VCSError
45 from rhodecode.lib.vcs.exceptions import VCSError
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48
48
49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
50 safe_unicode, remove_suffix, remove_prefix, time_to_datetime, _set_extras
50 safe_unicode, remove_suffix, remove_prefix, time_to_datetime, _set_extras
51 from rhodecode.lib.compat import json
51 from rhodecode.lib.compat import json
52 from rhodecode.lib.caching_query import FromCache
52 from rhodecode.lib.caching_query import FromCache
53
53
54 from rhodecode.model.meta import Base, Session
54 from rhodecode.model.meta import Base, Session
55
55
56 URL_SEP = '/'
56 URL_SEP = '/'
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59 #==============================================================================
59 #==============================================================================
60 # BASE CLASSES
60 # BASE CLASSES
61 #==============================================================================
61 #==============================================================================
62
62
63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
64
64
65
65
66 class BaseModel(object):
66 class BaseModel(object):
67 """
67 """
68 Base Model for all classess
68 Base Model for all classess
69 """
69 """
70
70
71 @classmethod
71 @classmethod
72 def _get_keys(cls):
72 def _get_keys(cls):
73 """return column names for this model """
73 """return column names for this model """
74 return class_mapper(cls).c.keys()
74 return class_mapper(cls).c.keys()
75
75
76 def get_dict(self):
76 def get_dict(self):
77 """
77 """
78 return dict with keys and values corresponding
78 return dict with keys and values corresponding
79 to this model data """
79 to this model data """
80
80
81 d = {}
81 d = {}
82 for k in self._get_keys():
82 for k in self._get_keys():
83 d[k] = getattr(self, k)
83 d[k] = getattr(self, k)
84
84
85 # also use __json__() if present to get additional fields
85 # also use __json__() if present to get additional fields
86 _json_attr = getattr(self, '__json__', None)
86 _json_attr = getattr(self, '__json__', None)
87 if _json_attr:
87 if _json_attr:
88 # update with attributes from __json__
88 # update with attributes from __json__
89 if callable(_json_attr):
89 if callable(_json_attr):
90 _json_attr = _json_attr()
90 _json_attr = _json_attr()
91 for k, val in _json_attr.iteritems():
91 for k, val in _json_attr.iteritems():
92 d[k] = val
92 d[k] = val
93 return d
93 return d
94
94
95 def get_appstruct(self):
95 def get_appstruct(self):
96 """return list with keys and values tupples corresponding
96 """return list with keys and values tupples corresponding
97 to this model data """
97 to this model data """
98
98
99 l = []
99 l = []
100 for k in self._get_keys():
100 for k in self._get_keys():
101 l.append((k, getattr(self, k),))
101 l.append((k, getattr(self, k),))
102 return l
102 return l
103
103
104 def populate_obj(self, populate_dict):
104 def populate_obj(self, populate_dict):
105 """populate model with data from given populate_dict"""
105 """populate model with data from given populate_dict"""
106
106
107 for k in self._get_keys():
107 for k in self._get_keys():
108 if k in populate_dict:
108 if k in populate_dict:
109 setattr(self, k, populate_dict[k])
109 setattr(self, k, populate_dict[k])
110
110
111 @classmethod
111 @classmethod
112 def query(cls):
112 def query(cls):
113 return Session().query(cls)
113 return Session().query(cls)
114
114
115 @classmethod
115 @classmethod
116 def get(cls, id_):
116 def get(cls, id_):
117 if id_:
117 if id_:
118 return cls.query().get(id_)
118 return cls.query().get(id_)
119
119
120 @classmethod
120 @classmethod
121 def get_or_404(cls, id_):
121 def get_or_404(cls, id_):
122 try:
122 try:
123 id_ = int(id_)
123 id_ = int(id_)
124 except (TypeError, ValueError):
124 except (TypeError, ValueError):
125 raise HTTPNotFound
125 raise HTTPNotFound
126
126
127 res = cls.query().get(id_)
127 res = cls.query().get(id_)
128 if not res:
128 if not res:
129 raise HTTPNotFound
129 raise HTTPNotFound
130 return res
130 return res
131
131
132 @classmethod
132 @classmethod
133 def getAll(cls):
133 def getAll(cls):
134 # deprecated and left for backward compatibility
134 # deprecated and left for backward compatibility
135 return cls.get_all()
135 return cls.get_all()
136
136
137 @classmethod
137 @classmethod
138 def get_all(cls):
138 def get_all(cls):
139 return cls.query().all()
139 return cls.query().all()
140
140
141 @classmethod
141 @classmethod
142 def delete(cls, id_):
142 def delete(cls, id_):
143 obj = cls.query().get(id_)
143 obj = cls.query().get(id_)
144 Session().delete(obj)
144 Session().delete(obj)
145
145
146 def __repr__(self):
146 def __repr__(self):
147 if hasattr(self, '__unicode__'):
147 if hasattr(self, '__unicode__'):
148 # python repr needs to return str
148 # python repr needs to return str
149 return safe_str(self.__unicode__())
149 return safe_str(self.__unicode__())
150 return '<DB:%s>' % (self.__class__.__name__)
150 return '<DB:%s>' % (self.__class__.__name__)
151
151
152
152
153 class RhodeCodeSetting(Base, BaseModel):
153 class RhodeCodeSetting(Base, BaseModel):
154 __tablename__ = 'rhodecode_settings'
154 __tablename__ = 'rhodecode_settings'
155 __table_args__ = (
155 __table_args__ = (
156 UniqueConstraint('app_settings_name'),
156 UniqueConstraint('app_settings_name'),
157 {'extend_existing': True, 'mysql_engine': 'InnoDB',
157 {'extend_existing': True, 'mysql_engine': 'InnoDB',
158 'mysql_charset': 'utf8'}
158 'mysql_charset': 'utf8'}
159 )
159 )
160 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
160 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
161 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
161 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
162 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
162 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
163
163
164 def __init__(self, k='', v=''):
164 def __init__(self, k='', v=''):
165 self.app_settings_name = k
165 self.app_settings_name = k
166 self.app_settings_value = v
166 self.app_settings_value = v
167
167
168 @validates('_app_settings_value')
168 @validates('_app_settings_value')
169 def validate_settings_value(self, key, val):
169 def validate_settings_value(self, key, val):
170 assert type(val) == unicode
170 assert type(val) == unicode
171 return val
171 return val
172
172
173 @hybrid_property
173 @hybrid_property
174 def app_settings_value(self):
174 def app_settings_value(self):
175 v = self._app_settings_value
175 v = self._app_settings_value
176 if self.app_settings_name in ["ldap_active",
176 if self.app_settings_name in ["ldap_active",
177 "default_repo_enable_statistics",
177 "default_repo_enable_statistics",
178 "default_repo_enable_locking",
178 "default_repo_enable_locking",
179 "default_repo_private",
179 "default_repo_private",
180 "default_repo_enable_downloads"]:
180 "default_repo_enable_downloads"]:
181 v = str2bool(v)
181 v = str2bool(v)
182 return v
182 return v
183
183
184 @app_settings_value.setter
184 @app_settings_value.setter
185 def app_settings_value(self, val):
185 def app_settings_value(self, val):
186 """
186 """
187 Setter that will always make sure we use unicode in app_settings_value
187 Setter that will always make sure we use unicode in app_settings_value
188
188
189 :param val:
189 :param val:
190 """
190 """
191 self._app_settings_value = safe_unicode(val)
191 self._app_settings_value = safe_unicode(val)
192
192
193 def __unicode__(self):
193 def __unicode__(self):
194 return u"<%s('%s:%s')>" % (
194 return u"<%s('%s:%s')>" % (
195 self.__class__.__name__,
195 self.__class__.__name__,
196 self.app_settings_name, self.app_settings_value
196 self.app_settings_name, self.app_settings_value
197 )
197 )
198
198
199 @classmethod
199 @classmethod
200 def get_by_name(cls, key):
200 def get_by_name(cls, key):
201 return cls.query()\
201 return cls.query()\
202 .filter(cls.app_settings_name == key).scalar()
202 .filter(cls.app_settings_name == key).scalar()
203
203
204 @classmethod
204 @classmethod
205 def get_by_name_or_create(cls, key):
205 def get_by_name_or_create(cls, key):
206 res = cls.get_by_name(key)
206 res = cls.get_by_name(key)
207 if not res:
207 if not res:
208 res = cls(key)
208 res = cls(key)
209 return res
209 return res
210
210
211 @classmethod
211 @classmethod
212 def get_app_settings(cls, cache=False):
212 def get_app_settings(cls, cache=False):
213
213
214 ret = cls.query()
214 ret = cls.query()
215
215
216 if cache:
216 if cache:
217 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
217 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
218
218
219 if not ret:
219 if not ret:
220 raise Exception('Could not get application settings !')
220 raise Exception('Could not get application settings !')
221 settings = {}
221 settings = {}
222 for each in ret:
222 for each in ret:
223 settings['rhodecode_' + each.app_settings_name] = \
223 settings['rhodecode_' + each.app_settings_name] = \
224 each.app_settings_value
224 each.app_settings_value
225
225
226 return settings
226 return settings
227
227
228 @classmethod
228 @classmethod
229 def get_ldap_settings(cls, cache=False):
229 def get_ldap_settings(cls, cache=False):
230 ret = cls.query()\
230 ret = cls.query()\
231 .filter(cls.app_settings_name.startswith('ldap_')).all()
231 .filter(cls.app_settings_name.startswith('ldap_')).all()
232 fd = {}
232 fd = {}
233 for row in ret:
233 for row in ret:
234 fd.update({row.app_settings_name: row.app_settings_value})
234 fd.update({row.app_settings_name: row.app_settings_value})
235
235
236 return fd
236 return fd
237
237
238 @classmethod
238 @classmethod
239 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
239 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
240 ret = cls.query()\
240 ret = cls.query()\
241 .filter(cls.app_settings_name.startswith('default_')).all()
241 .filter(cls.app_settings_name.startswith('default_')).all()
242 fd = {}
242 fd = {}
243 for row in ret:
243 for row in ret:
244 key = row.app_settings_name
244 key = row.app_settings_name
245 if strip_prefix:
245 if strip_prefix:
246 key = remove_prefix(key, prefix='default_')
246 key = remove_prefix(key, prefix='default_')
247 fd.update({key: row.app_settings_value})
247 fd.update({key: row.app_settings_value})
248
248
249 return fd
249 return fd
250
250
251
251
252 class RhodeCodeUi(Base, BaseModel):
252 class RhodeCodeUi(Base, BaseModel):
253 __tablename__ = 'rhodecode_ui'
253 __tablename__ = 'rhodecode_ui'
254 __table_args__ = (
254 __table_args__ = (
255 UniqueConstraint('ui_key'),
255 UniqueConstraint('ui_key'),
256 {'extend_existing': True, 'mysql_engine': 'InnoDB',
256 {'extend_existing': True, 'mysql_engine': 'InnoDB',
257 'mysql_charset': 'utf8'}
257 'mysql_charset': 'utf8'}
258 )
258 )
259
259
260 HOOK_UPDATE = 'changegroup.update'
260 HOOK_UPDATE = 'changegroup.update'
261 HOOK_REPO_SIZE = 'changegroup.repo_size'
261 HOOK_REPO_SIZE = 'changegroup.repo_size'
262 HOOK_PUSH = 'changegroup.push_logger'
262 HOOK_PUSH = 'changegroup.push_logger'
263 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
263 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
264 HOOK_PULL = 'outgoing.pull_logger'
264 HOOK_PULL = 'outgoing.pull_logger'
265 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
265 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
266
266
267 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
267 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
268 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
270 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
270 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
271 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
271 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
272
272
273 @classmethod
273 @classmethod
274 def get_by_key(cls, key):
274 def get_by_key(cls, key):
275 return cls.query().filter(cls.ui_key == key).scalar()
275 return cls.query().filter(cls.ui_key == key).scalar()
276
276
277 @classmethod
277 @classmethod
278 def get_builtin_hooks(cls):
278 def get_builtin_hooks(cls):
279 q = cls.query()
279 q = cls.query()
280 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
280 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
281 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
281 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
282 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
282 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
283 return q.all()
283 return q.all()
284
284
285 @classmethod
285 @classmethod
286 def get_custom_hooks(cls):
286 def get_custom_hooks(cls):
287 q = cls.query()
287 q = cls.query()
288 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
288 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
289 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
289 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
290 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
290 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
291 q = q.filter(cls.ui_section == 'hooks')
291 q = q.filter(cls.ui_section == 'hooks')
292 return q.all()
292 return q.all()
293
293
294 @classmethod
294 @classmethod
295 def get_repos_location(cls):
295 def get_repos_location(cls):
296 return cls.get_by_key('/').ui_value
296 return cls.get_by_key('/').ui_value
297
297
298 @classmethod
298 @classmethod
299 def create_or_update_hook(cls, key, val):
299 def create_or_update_hook(cls, key, val):
300 new_ui = cls.get_by_key(key) or cls()
300 new_ui = cls.get_by_key(key) or cls()
301 new_ui.ui_section = 'hooks'
301 new_ui.ui_section = 'hooks'
302 new_ui.ui_active = True
302 new_ui.ui_active = True
303 new_ui.ui_key = key
303 new_ui.ui_key = key
304 new_ui.ui_value = val
304 new_ui.ui_value = val
305
305
306 Session().add(new_ui)
306 Session().add(new_ui)
307
307
308 def __repr__(self):
308 def __repr__(self):
309 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
309 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
310 self.ui_value)
310 self.ui_value)
311
311
312
312
313 class User(Base, BaseModel):
313 class User(Base, BaseModel):
314 __tablename__ = 'users'
314 __tablename__ = 'users'
315 __table_args__ = (
315 __table_args__ = (
316 UniqueConstraint('username'), UniqueConstraint('email'),
316 UniqueConstraint('username'), UniqueConstraint('email'),
317 Index('u_username_idx', 'username'),
317 Index('u_username_idx', 'username'),
318 Index('u_email_idx', 'email'),
318 Index('u_email_idx', 'email'),
319 {'extend_existing': True, 'mysql_engine': 'InnoDB',
319 {'extend_existing': True, 'mysql_engine': 'InnoDB',
320 'mysql_charset': 'utf8'}
320 'mysql_charset': 'utf8'}
321 )
321 )
322 DEFAULT_USER = 'default'
322 DEFAULT_USER = 'default'
323
323
324 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
324 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
325 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
325 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
326 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
326 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
327 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
328 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
328 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
329 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
332 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
333 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
333 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
334 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
334 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
335 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
335 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
336
336
337 user_log = relationship('UserLog')
337 user_log = relationship('UserLog')
338 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
338 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
339
339
340 repositories = relationship('Repository')
340 repositories = relationship('Repository')
341 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
341 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
342 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
342 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
343
343
344 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
344 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
345 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
345 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
346
346
347 group_member = relationship('UserGroupMember', cascade='all')
347 group_member = relationship('UserGroupMember', cascade='all')
348
348
349 notifications = relationship('UserNotification', cascade='all')
349 notifications = relationship('UserNotification', cascade='all')
350 # notifications assigned to this user
350 # notifications assigned to this user
351 user_created_notifications = relationship('Notification', cascade='all')
351 user_created_notifications = relationship('Notification', cascade='all')
352 # comments created by this user
352 # comments created by this user
353 user_comments = relationship('ChangesetComment', cascade='all')
353 user_comments = relationship('ChangesetComment', cascade='all')
354 #extra emails for this user
354 #extra emails for this user
355 user_emails = relationship('UserEmailMap', cascade='all')
355 user_emails = relationship('UserEmailMap', cascade='all')
356
356
357 @hybrid_property
357 @hybrid_property
358 def email(self):
358 def email(self):
359 return self._email
359 return self._email
360
360
361 @email.setter
361 @email.setter
362 def email(self, val):
362 def email(self, val):
363 self._email = val.lower() if val else None
363 self._email = val.lower() if val else None
364
364
365 @property
365 @property
366 def firstname(self):
366 def firstname(self):
367 # alias for future
367 # alias for future
368 return self.name
368 return self.name
369
369
370 @property
370 @property
371 def emails(self):
371 def emails(self):
372 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
372 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
373 return [self.email] + [x.email for x in other]
373 return [self.email] + [x.email for x in other]
374
374
375 @property
375 @property
376 def ip_addresses(self):
376 def ip_addresses(self):
377 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
377 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
378 return [x.ip_addr for x in ret]
378 return [x.ip_addr for x in ret]
379
379
380 @property
380 @property
381 def username_and_name(self):
381 def username_and_name(self):
382 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
382 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
383
383
384 @property
384 @property
385 def full_name(self):
385 def full_name(self):
386 return '%s %s' % (self.firstname, self.lastname)
386 return '%s %s' % (self.firstname, self.lastname)
387
387
388 @property
388 @property
389 def full_name_or_username(self):
389 def full_name_or_username(self):
390 return ('%s %s' % (self.firstname, self.lastname)
390 return ('%s %s' % (self.firstname, self.lastname)
391 if (self.firstname and self.lastname) else self.username)
391 if (self.firstname and self.lastname) else self.username)
392
392
393 @property
393 @property
394 def full_contact(self):
394 def full_contact(self):
395 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
395 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
396
396
397 @property
397 @property
398 def short_contact(self):
398 def short_contact(self):
399 return '%s %s' % (self.firstname, self.lastname)
399 return '%s %s' % (self.firstname, self.lastname)
400
400
401 @property
401 @property
402 def is_admin(self):
402 def is_admin(self):
403 return self.admin
403 return self.admin
404
404
405 @property
405 @property
406 def AuthUser(self):
406 def AuthUser(self):
407 """
407 """
408 Returns instance of AuthUser for this user
408 Returns instance of AuthUser for this user
409 """
409 """
410 from rhodecode.lib.auth import AuthUser
410 from rhodecode.lib.auth import AuthUser
411 return AuthUser(user_id=self.user_id, api_key=self.api_key,
411 return AuthUser(user_id=self.user_id, api_key=self.api_key,
412 username=self.username)
412 username=self.username)
413
413
414 def __unicode__(self):
414 def __unicode__(self):
415 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
415 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
416 self.user_id, self.username)
416 self.user_id, self.username)
417
417
418 @classmethod
418 @classmethod
419 def get_by_username(cls, username, case_insensitive=False, cache=False):
419 def get_by_username(cls, username, case_insensitive=False, cache=False):
420 if case_insensitive:
420 if case_insensitive:
421 q = cls.query().filter(cls.username.ilike(username))
421 q = cls.query().filter(cls.username.ilike(username))
422 else:
422 else:
423 q = cls.query().filter(cls.username == username)
423 q = cls.query().filter(cls.username == username)
424
424
425 if cache:
425 if cache:
426 q = q.options(FromCache(
426 q = q.options(FromCache(
427 "sql_cache_short",
427 "sql_cache_short",
428 "get_user_%s" % _hash_key(username)
428 "get_user_%s" % _hash_key(username)
429 )
429 )
430 )
430 )
431 return q.scalar()
431 return q.scalar()
432
432
433 @classmethod
433 @classmethod
434 def get_by_api_key(cls, api_key, cache=False):
434 def get_by_api_key(cls, api_key, cache=False):
435 q = cls.query().filter(cls.api_key == api_key)
435 q = cls.query().filter(cls.api_key == api_key)
436
436
437 if cache:
437 if cache:
438 q = q.options(FromCache("sql_cache_short",
438 q = q.options(FromCache("sql_cache_short",
439 "get_api_key_%s" % api_key))
439 "get_api_key_%s" % api_key))
440 return q.scalar()
440 return q.scalar()
441
441
442 @classmethod
442 @classmethod
443 def get_by_email(cls, email, case_insensitive=False, cache=False):
443 def get_by_email(cls, email, case_insensitive=False, cache=False):
444 if case_insensitive:
444 if case_insensitive:
445 q = cls.query().filter(cls.email.ilike(email))
445 q = cls.query().filter(cls.email.ilike(email))
446 else:
446 else:
447 q = cls.query().filter(cls.email == email)
447 q = cls.query().filter(cls.email == email)
448
448
449 if cache:
449 if cache:
450 q = q.options(FromCache("sql_cache_short",
450 q = q.options(FromCache("sql_cache_short",
451 "get_email_key_%s" % email))
451 "get_email_key_%s" % email))
452
452
453 ret = q.scalar()
453 ret = q.scalar()
454 if ret is None:
454 if ret is None:
455 q = UserEmailMap.query()
455 q = UserEmailMap.query()
456 # try fetching in alternate email map
456 # try fetching in alternate email map
457 if case_insensitive:
457 if case_insensitive:
458 q = q.filter(UserEmailMap.email.ilike(email))
458 q = q.filter(UserEmailMap.email.ilike(email))
459 else:
459 else:
460 q = q.filter(UserEmailMap.email == email)
460 q = q.filter(UserEmailMap.email == email)
461 q = q.options(joinedload(UserEmailMap.user))
461 q = q.options(joinedload(UserEmailMap.user))
462 if cache:
462 if cache:
463 q = q.options(FromCache("sql_cache_short",
463 q = q.options(FromCache("sql_cache_short",
464 "get_email_map_key_%s" % email))
464 "get_email_map_key_%s" % email))
465 ret = getattr(q.scalar(), 'user', None)
465 ret = getattr(q.scalar(), 'user', None)
466
466
467 return ret
467 return ret
468
468
469 @classmethod
469 @classmethod
470 def get_from_cs_author(cls, author):
470 def get_from_cs_author(cls, author):
471 """
471 """
472 Tries to get User objects out of commit author string
472 Tries to get User objects out of commit author string
473
473
474 :param author:
474 :param author:
475 """
475 """
476 from rhodecode.lib.helpers import email, author_name
476 from rhodecode.lib.helpers import email, author_name
477 # Valid email in the attribute passed, see if they're in the system
477 # Valid email in the attribute passed, see if they're in the system
478 _email = email(author)
478 _email = email(author)
479 if _email:
479 if _email:
480 user = cls.get_by_email(_email, case_insensitive=True)
480 user = cls.get_by_email(_email, case_insensitive=True)
481 if user:
481 if user:
482 return user
482 return user
483 # Maybe we can match by username?
483 # Maybe we can match by username?
484 _author = author_name(author)
484 _author = author_name(author)
485 user = cls.get_by_username(_author, case_insensitive=True)
485 user = cls.get_by_username(_author, case_insensitive=True)
486 if user:
486 if user:
487 return user
487 return user
488
488
489 def update_lastlogin(self):
489 def update_lastlogin(self):
490 """Update user lastlogin"""
490 """Update user lastlogin"""
491 self.last_login = datetime.datetime.now()
491 self.last_login = datetime.datetime.now()
492 Session().add(self)
492 Session().add(self)
493 log.debug('updated user %s lastlogin' % self.username)
493 log.debug('updated user %s lastlogin' % self.username)
494
494
495 @classmethod
495 @classmethod
496 def get_first_admin(cls):
496 def get_first_admin(cls):
497 user = User.query().filter(User.admin == True).first()
497 user = User.query().filter(User.admin == True).first()
498 if user is None:
498 if user is None:
499 raise Exception('Missing administrative account!')
499 raise Exception('Missing administrative account!')
500 return user
500 return user
501
501
502 @classmethod
502 @classmethod
503 def get_default_user(cls, cache=False):
503 def get_default_user(cls, cache=False):
504 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
504 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
505 if user is None:
505 if user is None:
506 raise Exception('Missing default account!')
506 raise Exception('Missing default account!')
507 return user
507 return user
508
508
509 def get_api_data(self):
509 def get_api_data(self):
510 """
510 """
511 Common function for generating user related data for API
511 Common function for generating user related data for API
512 """
512 """
513 user = self
513 user = self
514 data = dict(
514 data = dict(
515 user_id=user.user_id,
515 user_id=user.user_id,
516 username=user.username,
516 username=user.username,
517 firstname=user.name,
517 firstname=user.name,
518 lastname=user.lastname,
518 lastname=user.lastname,
519 email=user.email,
519 email=user.email,
520 emails=user.emails,
520 emails=user.emails,
521 api_key=user.api_key,
521 api_key=user.api_key,
522 active=user.active,
522 active=user.active,
523 admin=user.admin,
523 admin=user.admin,
524 ldap_dn=user.ldap_dn,
524 ldap_dn=user.ldap_dn,
525 last_login=user.last_login,
525 last_login=user.last_login,
526 ip_addresses=user.ip_addresses
526 ip_addresses=user.ip_addresses
527 )
527 )
528 return data
528 return data
529
529
530 def __json__(self):
530 def __json__(self):
531 data = dict(
531 data = dict(
532 full_name=self.full_name,
532 full_name=self.full_name,
533 full_name_or_username=self.full_name_or_username,
533 full_name_or_username=self.full_name_or_username,
534 short_contact=self.short_contact,
534 short_contact=self.short_contact,
535 full_contact=self.full_contact
535 full_contact=self.full_contact
536 )
536 )
537 data.update(self.get_api_data())
537 data.update(self.get_api_data())
538 return data
538 return data
539
539
540
540
541 class UserEmailMap(Base, BaseModel):
541 class UserEmailMap(Base, BaseModel):
542 __tablename__ = 'user_email_map'
542 __tablename__ = 'user_email_map'
543 __table_args__ = (
543 __table_args__ = (
544 Index('uem_email_idx', 'email'),
544 Index('uem_email_idx', 'email'),
545 UniqueConstraint('email'),
545 UniqueConstraint('email'),
546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
547 'mysql_charset': 'utf8'}
547 'mysql_charset': 'utf8'}
548 )
548 )
549 __mapper_args__ = {}
549 __mapper_args__ = {}
550
550
551 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
551 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
552 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
552 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
553 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
553 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
554 user = relationship('User', lazy='joined')
554 user = relationship('User', lazy='joined')
555
555
556 @validates('_email')
556 @validates('_email')
557 def validate_email(self, key, email):
557 def validate_email(self, key, email):
558 # check if this email is not main one
558 # check if this email is not main one
559 main_email = Session().query(User).filter(User.email == email).scalar()
559 main_email = Session().query(User).filter(User.email == email).scalar()
560 if main_email is not None:
560 if main_email is not None:
561 raise AttributeError('email %s is present is user table' % email)
561 raise AttributeError('email %s is present is user table' % email)
562 return email
562 return email
563
563
564 @hybrid_property
564 @hybrid_property
565 def email(self):
565 def email(self):
566 return self._email
566 return self._email
567
567
568 @email.setter
568 @email.setter
569 def email(self, val):
569 def email(self, val):
570 self._email = val.lower() if val else None
570 self._email = val.lower() if val else None
571
571
572
572
573 class UserIpMap(Base, BaseModel):
573 class UserIpMap(Base, BaseModel):
574 __tablename__ = 'user_ip_map'
574 __tablename__ = 'user_ip_map'
575 __table_args__ = (
575 __table_args__ = (
576 UniqueConstraint('user_id', 'ip_addr'),
576 UniqueConstraint('user_id', 'ip_addr'),
577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
578 'mysql_charset': 'utf8'}
578 'mysql_charset': 'utf8'}
579 )
579 )
580 __mapper_args__ = {}
580 __mapper_args__ = {}
581
581
582 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
582 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
583 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
583 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
584 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
584 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
585 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
585 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
586 user = relationship('User', lazy='joined')
586 user = relationship('User', lazy='joined')
587
587
588 @classmethod
588 @classmethod
589 def _get_ip_range(cls, ip_addr):
589 def _get_ip_range(cls, ip_addr):
590 from rhodecode.lib import ipaddr
590 from rhodecode.lib import ipaddr
591 net = ipaddr.IPNetwork(address=ip_addr)
591 net = ipaddr.IPNetwork(address=ip_addr)
592 return [str(net.network), str(net.broadcast)]
592 return [str(net.network), str(net.broadcast)]
593
593
594 def __json__(self):
594 def __json__(self):
595 return dict(
595 return dict(
596 ip_addr=self.ip_addr,
596 ip_addr=self.ip_addr,
597 ip_range=self._get_ip_range(self.ip_addr)
597 ip_range=self._get_ip_range(self.ip_addr)
598 )
598 )
599
599
600
600
601 class UserLog(Base, BaseModel):
601 class UserLog(Base, BaseModel):
602 __tablename__ = 'user_logs'
602 __tablename__ = 'user_logs'
603 __table_args__ = (
603 __table_args__ = (
604 {'extend_existing': True, 'mysql_engine': 'InnoDB',
604 {'extend_existing': True, 'mysql_engine': 'InnoDB',
605 'mysql_charset': 'utf8'},
605 'mysql_charset': 'utf8'},
606 )
606 )
607 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
607 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
608 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
608 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
609 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
609 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
610 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
610 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
611 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
611 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
612 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
612 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
613 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
613 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
614 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
614 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
615
615
616 @property
616 @property
617 def action_as_day(self):
617 def action_as_day(self):
618 return datetime.date(*self.action_date.timetuple()[:3])
618 return datetime.date(*self.action_date.timetuple()[:3])
619
619
620 user = relationship('User')
620 user = relationship('User')
621 repository = relationship('Repository', cascade='')
621 repository = relationship('Repository', cascade='')
622
622
623
623
624 class UserGroup(Base, BaseModel):
624 class UserGroup(Base, BaseModel):
625 __tablename__ = 'users_groups'
625 __tablename__ = 'users_groups'
626 __table_args__ = (
626 __table_args__ = (
627 {'extend_existing': True, 'mysql_engine': 'InnoDB',
627 {'extend_existing': True, 'mysql_engine': 'InnoDB',
628 'mysql_charset': 'utf8'},
628 'mysql_charset': 'utf8'},
629 )
629 )
630
630
631 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
631 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
632 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
632 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
633 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
633 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
634 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
634 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
635 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
635 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
636
636
637 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
637 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
638 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
638 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
639 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
639 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
640 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
640 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
641 user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
641 user_user_group_to_perm = relationship('UserUserGroupToPerm ', cascade='all')
642 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
642 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
643
643
644 user = relationship('User')
644 user = relationship('User')
645
645
646 def __unicode__(self):
646 def __unicode__(self):
647 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
647 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
648 self.users_group_id,
648 self.users_group_id,
649 self.users_group_name)
649 self.users_group_name)
650
650
651 @classmethod
651 @classmethod
652 def get_by_group_name(cls, group_name, cache=False,
652 def get_by_group_name(cls, group_name, cache=False,
653 case_insensitive=False):
653 case_insensitive=False):
654 if case_insensitive:
654 if case_insensitive:
655 q = cls.query().filter(cls.users_group_name.ilike(group_name))
655 q = cls.query().filter(cls.users_group_name.ilike(group_name))
656 else:
656 else:
657 q = cls.query().filter(cls.users_group_name == group_name)
657 q = cls.query().filter(cls.users_group_name == group_name)
658 if cache:
658 if cache:
659 q = q.options(FromCache(
659 q = q.options(FromCache(
660 "sql_cache_short",
660 "sql_cache_short",
661 "get_user_%s" % _hash_key(group_name)
661 "get_user_%s" % _hash_key(group_name)
662 )
662 )
663 )
663 )
664 return q.scalar()
664 return q.scalar()
665
665
666 @classmethod
666 @classmethod
667 def get(cls, users_group_id, cache=False):
667 def get(cls, users_group_id, cache=False):
668 users_group = cls.query()
668 users_group = cls.query()
669 if cache:
669 if cache:
670 users_group = users_group.options(FromCache("sql_cache_short",
670 users_group = users_group.options(FromCache("sql_cache_short",
671 "get_users_group_%s" % users_group_id))
671 "get_users_group_%s" % users_group_id))
672 return users_group.get(users_group_id)
672 return users_group.get(users_group_id)
673
673
674 def get_api_data(self):
674 def get_api_data(self):
675 users_group = self
675 users_group = self
676
676
677 data = dict(
677 data = dict(
678 users_group_id=users_group.users_group_id,
678 users_group_id=users_group.users_group_id,
679 group_name=users_group.users_group_name,
679 group_name=users_group.users_group_name,
680 active=users_group.users_group_active,
680 active=users_group.users_group_active,
681 )
681 )
682
682
683 return data
683 return data
684
684
685
685
686 class UserGroupMember(Base, BaseModel):
686 class UserGroupMember(Base, BaseModel):
687 __tablename__ = 'users_groups_members'
687 __tablename__ = 'users_groups_members'
688 __table_args__ = (
688 __table_args__ = (
689 {'extend_existing': True, 'mysql_engine': 'InnoDB',
689 {'extend_existing': True, 'mysql_engine': 'InnoDB',
690 'mysql_charset': 'utf8'},
690 'mysql_charset': 'utf8'},
691 )
691 )
692
692
693 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
693 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
694 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
694 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
695 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
695 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
696
696
697 user = relationship('User', lazy='joined')
697 user = relationship('User', lazy='joined')
698 users_group = relationship('UserGroup')
698 users_group = relationship('UserGroup')
699
699
700 def __init__(self, gr_id='', u_id=''):
700 def __init__(self, gr_id='', u_id=''):
701 self.users_group_id = gr_id
701 self.users_group_id = gr_id
702 self.user_id = u_id
702 self.user_id = u_id
703
703
704
704
705 class RepositoryField(Base, BaseModel):
705 class RepositoryField(Base, BaseModel):
706 __tablename__ = 'repositories_fields'
706 __tablename__ = 'repositories_fields'
707 __table_args__ = (
707 __table_args__ = (
708 UniqueConstraint('repository_id', 'field_key'), # no-multi field
708 UniqueConstraint('repository_id', 'field_key'), # no-multi field
709 {'extend_existing': True, 'mysql_engine': 'InnoDB',
709 {'extend_existing': True, 'mysql_engine': 'InnoDB',
710 'mysql_charset': 'utf8'},
710 'mysql_charset': 'utf8'},
711 )
711 )
712 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
712 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
713
713
714 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
714 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
715 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
715 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
716 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
716 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
717 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
717 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
718 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
718 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
719 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
719 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
720 field_type = Column("field_type", String(256), nullable=False, unique=None)
720 field_type = Column("field_type", String(256), nullable=False, unique=None)
721 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
721 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
722
722
723 repository = relationship('Repository')
723 repository = relationship('Repository')
724
724
725 @property
725 @property
726 def field_key_prefixed(self):
726 def field_key_prefixed(self):
727 return 'ex_%s' % self.field_key
727 return 'ex_%s' % self.field_key
728
728
729 @classmethod
729 @classmethod
730 def un_prefix_key(cls, key):
730 def un_prefix_key(cls, key):
731 if key.startswith(cls.PREFIX):
731 if key.startswith(cls.PREFIX):
732 return key[len(cls.PREFIX):]
732 return key[len(cls.PREFIX):]
733 return key
733 return key
734
734
735 @classmethod
735 @classmethod
736 def get_by_key_name(cls, key, repo):
736 def get_by_key_name(cls, key, repo):
737 row = cls.query()\
737 row = cls.query()\
738 .filter(cls.repository == repo)\
738 .filter(cls.repository == repo)\
739 .filter(cls.field_key == key).scalar()
739 .filter(cls.field_key == key).scalar()
740 return row
740 return row
741
741
742
742
743 class Repository(Base, BaseModel):
743 class Repository(Base, BaseModel):
744 __tablename__ = 'repositories'
744 __tablename__ = 'repositories'
745 __table_args__ = (
745 __table_args__ = (
746 UniqueConstraint('repo_name'),
746 UniqueConstraint('repo_name'),
747 Index('r_repo_name_idx', 'repo_name'),
747 Index('r_repo_name_idx', 'repo_name'),
748 {'extend_existing': True, 'mysql_engine': 'InnoDB',
748 {'extend_existing': True, 'mysql_engine': 'InnoDB',
749 'mysql_charset': 'utf8'},
749 'mysql_charset': 'utf8'},
750 )
750 )
751
751
752 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
752 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
753 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
753 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
754 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
754 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
755 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
755 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
756 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
756 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
757 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
757 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
758 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
758 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
759 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
759 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
760 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
760 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
761 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
761 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
762 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
762 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
763 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
763 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
764 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
764 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
765 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
765 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
766 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
766 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
767
767
768 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
768 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
769 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
769 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
770
770
771 user = relationship('User')
771 user = relationship('User')
772 fork = relationship('Repository', remote_side=repo_id)
772 fork = relationship('Repository', remote_side=repo_id)
773 group = relationship('RepoGroup')
773 group = relationship('RepoGroup')
774 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
774 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
775 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
775 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
776 stats = relationship('Statistics', cascade='all', uselist=False)
776 stats = relationship('Statistics', cascade='all', uselist=False)
777
777
778 followers = relationship('UserFollowing',
778 followers = relationship('UserFollowing',
779 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
779 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
780 cascade='all')
780 cascade='all')
781 extra_fields = relationship('RepositoryField',
781 extra_fields = relationship('RepositoryField',
782 cascade="all, delete, delete-orphan")
782 cascade="all, delete, delete-orphan")
783
783
784 logs = relationship('UserLog')
784 logs = relationship('UserLog')
785 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
785 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
786
786
787 pull_requests_org = relationship('PullRequest',
787 pull_requests_org = relationship('PullRequest',
788 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
788 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
789 cascade="all, delete, delete-orphan")
789 cascade="all, delete, delete-orphan")
790
790
791 pull_requests_other = relationship('PullRequest',
791 pull_requests_other = relationship('PullRequest',
792 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
792 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
793 cascade="all, delete, delete-orphan")
793 cascade="all, delete, delete-orphan")
794
794
795 def __unicode__(self):
795 def __unicode__(self):
796 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
796 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
797 self.repo_name)
797 self.repo_name)
798
798
799 @hybrid_property
799 @hybrid_property
800 def locked(self):
800 def locked(self):
801 # always should return [user_id, timelocked]
801 # always should return [user_id, timelocked]
802 if self._locked:
802 if self._locked:
803 _lock_info = self._locked.split(':')
803 _lock_info = self._locked.split(':')
804 return int(_lock_info[0]), _lock_info[1]
804 return int(_lock_info[0]), _lock_info[1]
805 return [None, None]
805 return [None, None]
806
806
807 @locked.setter
807 @locked.setter
808 def locked(self, val):
808 def locked(self, val):
809 if val and isinstance(val, (list, tuple)):
809 if val and isinstance(val, (list, tuple)):
810 self._locked = ':'.join(map(str, val))
810 self._locked = ':'.join(map(str, val))
811 else:
811 else:
812 self._locked = None
812 self._locked = None
813
813
814 @hybrid_property
814 @hybrid_property
815 def changeset_cache(self):
815 def changeset_cache(self):
816 from rhodecode.lib.vcs.backends.base import EmptyChangeset
816 from rhodecode.lib.vcs.backends.base import EmptyChangeset
817 dummy = EmptyChangeset().__json__()
817 dummy = EmptyChangeset().__json__()
818 if not self._changeset_cache:
818 if not self._changeset_cache:
819 return dummy
819 return dummy
820 try:
820 try:
821 return json.loads(self._changeset_cache)
821 return json.loads(self._changeset_cache)
822 except TypeError:
822 except TypeError:
823 return dummy
823 return dummy
824
824
825 @changeset_cache.setter
825 @changeset_cache.setter
826 def changeset_cache(self, val):
826 def changeset_cache(self, val):
827 try:
827 try:
828 self._changeset_cache = json.dumps(val)
828 self._changeset_cache = json.dumps(val)
829 except Exception:
829 except Exception:
830 log.error(traceback.format_exc())
830 log.error(traceback.format_exc())
831
831
832 @classmethod
832 @classmethod
833 def url_sep(cls):
833 def url_sep(cls):
834 return URL_SEP
834 return URL_SEP
835
835
836 @classmethod
836 @classmethod
837 def normalize_repo_name(cls, repo_name):
837 def normalize_repo_name(cls, repo_name):
838 """
838 """
839 Normalizes os specific repo_name to the format internally stored inside
839 Normalizes os specific repo_name to the format internally stored inside
840 dabatabase using URL_SEP
840 dabatabase using URL_SEP
841
841
842 :param cls:
842 :param cls:
843 :param repo_name:
843 :param repo_name:
844 """
844 """
845 return cls.url_sep().join(repo_name.split(os.sep))
845 return cls.url_sep().join(repo_name.split(os.sep))
846
846
847 @classmethod
847 @classmethod
848 def get_by_repo_name(cls, repo_name):
848 def get_by_repo_name(cls, repo_name):
849 q = Session().query(cls).filter(cls.repo_name == repo_name)
849 q = Session().query(cls).filter(cls.repo_name == repo_name)
850 q = q.options(joinedload(Repository.fork))\
850 q = q.options(joinedload(Repository.fork))\
851 .options(joinedload(Repository.user))\
851 .options(joinedload(Repository.user))\
852 .options(joinedload(Repository.group))
852 .options(joinedload(Repository.group))
853 return q.scalar()
853 return q.scalar()
854
854
855 @classmethod
855 @classmethod
856 def get_by_full_path(cls, repo_full_path):
856 def get_by_full_path(cls, repo_full_path):
857 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
857 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
858 repo_name = cls.normalize_repo_name(repo_name)
858 repo_name = cls.normalize_repo_name(repo_name)
859 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
859 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
860
860
861 @classmethod
861 @classmethod
862 def get_repo_forks(cls, repo_id):
862 def get_repo_forks(cls, repo_id):
863 return cls.query().filter(Repository.fork_id == repo_id)
863 return cls.query().filter(Repository.fork_id == repo_id)
864
864
865 @classmethod
865 @classmethod
866 def base_path(cls):
866 def base_path(cls):
867 """
867 """
868 Returns base path when all repos are stored
868 Returns base path when all repos are stored
869
869
870 :param cls:
870 :param cls:
871 """
871 """
872 q = Session().query(RhodeCodeUi)\
872 q = Session().query(RhodeCodeUi)\
873 .filter(RhodeCodeUi.ui_key == cls.url_sep())
873 .filter(RhodeCodeUi.ui_key == cls.url_sep())
874 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
874 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
875 return q.one().ui_value
875 return q.one().ui_value
876
876
877 @property
877 @property
878 def forks(self):
878 def forks(self):
879 """
879 """
880 Return forks of this repo
880 Return forks of this repo
881 """
881 """
882 return Repository.get_repo_forks(self.repo_id)
882 return Repository.get_repo_forks(self.repo_id)
883
883
884 @property
884 @property
885 def parent(self):
885 def parent(self):
886 """
886 """
887 Returns fork parent
887 Returns fork parent
888 """
888 """
889 return self.fork
889 return self.fork
890
890
891 @property
891 @property
892 def just_name(self):
892 def just_name(self):
893 return self.repo_name.split(Repository.url_sep())[-1]
893 return self.repo_name.split(Repository.url_sep())[-1]
894
894
895 @property
895 @property
896 def groups_with_parents(self):
896 def groups_with_parents(self):
897 groups = []
897 groups = []
898 if self.group is None:
898 if self.group is None:
899 return groups
899 return groups
900
900
901 cur_gr = self.group
901 cur_gr = self.group
902 groups.insert(0, cur_gr)
902 groups.insert(0, cur_gr)
903 while 1:
903 while 1:
904 gr = getattr(cur_gr, 'parent_group', None)
904 gr = getattr(cur_gr, 'parent_group', None)
905 cur_gr = cur_gr.parent_group
905 cur_gr = cur_gr.parent_group
906 if gr is None:
906 if gr is None:
907 break
907 break
908 groups.insert(0, gr)
908 groups.insert(0, gr)
909
909
910 return groups
910 return groups
911
911
912 @property
912 @property
913 def groups_and_repo(self):
913 def groups_and_repo(self):
914 return self.groups_with_parents, self.just_name, self.repo_name
914 return self.groups_with_parents, self.just_name, self.repo_name
915
915
916 @LazyProperty
916 @LazyProperty
917 def repo_path(self):
917 def repo_path(self):
918 """
918 """
919 Returns base full path for that repository means where it actually
919 Returns base full path for that repository means where it actually
920 exists on a filesystem
920 exists on a filesystem
921 """
921 """
922 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
922 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
923 Repository.url_sep())
923 Repository.url_sep())
924 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
924 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
925 return q.one().ui_value
925 return q.one().ui_value
926
926
927 @property
927 @property
928 def repo_full_path(self):
928 def repo_full_path(self):
929 p = [self.repo_path]
929 p = [self.repo_path]
930 # we need to split the name by / since this is how we store the
930 # we need to split the name by / since this is how we store the
931 # names in the database, but that eventually needs to be converted
931 # names in the database, but that eventually needs to be converted
932 # into a valid system path
932 # into a valid system path
933 p += self.repo_name.split(Repository.url_sep())
933 p += self.repo_name.split(Repository.url_sep())
934 return os.path.join(*map(safe_unicode, p))
934 return os.path.join(*map(safe_unicode, p))
935
935
936 @property
936 @property
937 def cache_keys(self):
937 def cache_keys(self):
938 """
938 """
939 Returns associated cache keys for that repo
939 Returns associated cache keys for that repo
940 """
940 """
941 return CacheInvalidation.query()\
941 return CacheInvalidation.query()\
942 .filter(CacheInvalidation.cache_args == self.repo_name)\
942 .filter(CacheInvalidation.cache_args == self.repo_name)\
943 .order_by(CacheInvalidation.cache_key)\
943 .order_by(CacheInvalidation.cache_key)\
944 .all()
944 .all()
945
945
946 def get_new_name(self, repo_name):
946 def get_new_name(self, repo_name):
947 """
947 """
948 returns new full repository name based on assigned group and new new
948 returns new full repository name based on assigned group and new new
949
949
950 :param group_name:
950 :param group_name:
951 """
951 """
952 path_prefix = self.group.full_path_splitted if self.group else []
952 path_prefix = self.group.full_path_splitted if self.group else []
953 return Repository.url_sep().join(path_prefix + [repo_name])
953 return Repository.url_sep().join(path_prefix + [repo_name])
954
954
955 @property
955 @property
956 def _ui(self):
956 def _ui(self):
957 """
957 """
958 Creates an db based ui object for this repository
958 Creates an db based ui object for this repository
959 """
959 """
960 from rhodecode.lib.utils import make_ui
960 from rhodecode.lib.utils import make_ui
961 return make_ui('db', clear_session=False)
961 return make_ui('db', clear_session=False)
962
962
963 @classmethod
963 @classmethod
964 def is_valid(cls, repo_name):
964 def is_valid(cls, repo_name):
965 """
965 """
966 returns True if given repo name is a valid filesystem repository
966 returns True if given repo name is a valid filesystem repository
967
967
968 :param cls:
968 :param cls:
969 :param repo_name:
969 :param repo_name:
970 """
970 """
971 from rhodecode.lib.utils import is_valid_repo
971 from rhodecode.lib.utils import is_valid_repo
972
972
973 return is_valid_repo(repo_name, cls.base_path())
973 return is_valid_repo(repo_name, cls.base_path())
974
974
975 def get_api_data(self):
975 def get_api_data(self):
976 """
976 """
977 Common function for generating repo api data
977 Common function for generating repo api data
978
978
979 """
979 """
980 repo = self
980 repo = self
981 data = dict(
981 data = dict(
982 repo_id=repo.repo_id,
982 repo_id=repo.repo_id,
983 repo_name=repo.repo_name,
983 repo_name=repo.repo_name,
984 repo_type=repo.repo_type,
984 repo_type=repo.repo_type,
985 clone_uri=repo.clone_uri,
985 clone_uri=repo.clone_uri,
986 private=repo.private,
986 private=repo.private,
987 created_on=repo.created_on,
987 created_on=repo.created_on,
988 description=repo.description,
988 description=repo.description,
989 landing_rev=repo.landing_rev,
989 landing_rev=repo.landing_rev,
990 owner=repo.user.username,
990 owner=repo.user.username,
991 fork_of=repo.fork.repo_name if repo.fork else None,
991 fork_of=repo.fork.repo_name if repo.fork else None,
992 enable_statistics=repo.enable_statistics,
992 enable_statistics=repo.enable_statistics,
993 enable_locking=repo.enable_locking,
993 enable_locking=repo.enable_locking,
994 enable_downloads=repo.enable_downloads,
994 enable_downloads=repo.enable_downloads,
995 last_changeset=repo.changeset_cache,
995 last_changeset=repo.changeset_cache,
996 locked_by=User.get(self.locked[0]).get_api_data() \
996 locked_by=User.get(self.locked[0]).get_api_data() \
997 if self.locked[0] else None,
997 if self.locked[0] else None,
998 locked_date=time_to_datetime(self.locked[1]) \
998 locked_date=time_to_datetime(self.locked[1]) \
999 if self.locked[1] else None
999 if self.locked[1] else None
1000 )
1000 )
1001 rc_config = RhodeCodeSetting.get_app_settings()
1001 rc_config = RhodeCodeSetting.get_app_settings()
1002 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
1002 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
1003 if repository_fields:
1003 if repository_fields:
1004 for f in self.extra_fields:
1004 for f in self.extra_fields:
1005 data[f.field_key_prefixed] = f.field_value
1005 data[f.field_key_prefixed] = f.field_value
1006
1006
1007 return data
1007 return data
1008
1008
1009 @classmethod
1009 @classmethod
1010 def lock(cls, repo, user_id, lock_time=None):
1010 def lock(cls, repo, user_id, lock_time=None):
1011 if not lock_time:
1011 if not lock_time:
1012 lock_time = time.time()
1012 lock_time = time.time()
1013 repo.locked = [user_id, lock_time]
1013 repo.locked = [user_id, lock_time]
1014 Session().add(repo)
1014 Session().add(repo)
1015 Session().commit()
1015 Session().commit()
1016
1016
1017 @classmethod
1017 @classmethod
1018 def unlock(cls, repo):
1018 def unlock(cls, repo):
1019 repo.locked = None
1019 repo.locked = None
1020 Session().add(repo)
1020 Session().add(repo)
1021 Session().commit()
1021 Session().commit()
1022
1022
1023 @classmethod
1023 @classmethod
1024 def getlock(cls, repo):
1024 def getlock(cls, repo):
1025 return repo.locked
1025 return repo.locked
1026
1026
1027 @property
1027 @property
1028 def last_db_change(self):
1028 def last_db_change(self):
1029 return self.updated_on
1029 return self.updated_on
1030
1030
1031 def clone_url(self, **override):
1031 def clone_url(self, **override):
1032 from pylons import url
1032 from pylons import url
1033 from urlparse import urlparse
1033 from urlparse import urlparse
1034 import urllib
1034 import urllib
1035 parsed_url = urlparse(url('home', qualified=True))
1035 parsed_url = urlparse(url('home', qualified=True))
1036 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1036 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1037 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1037 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1038 args = {
1038 args = {
1039 'user': '',
1039 'user': '',
1040 'pass': '',
1040 'pass': '',
1041 'scheme': parsed_url.scheme,
1041 'scheme': parsed_url.scheme,
1042 'netloc': parsed_url.netloc,
1042 'netloc': parsed_url.netloc,
1043 'prefix': decoded_path,
1043 'prefix': decoded_path,
1044 'path': self.repo_name
1044 'path': self.repo_name
1045 }
1045 }
1046
1046
1047 args.update(override)
1047 args.update(override)
1048 return default_clone_uri % args
1048 return default_clone_uri % args
1049
1049
1050 #==========================================================================
1050 #==========================================================================
1051 # SCM PROPERTIES
1051 # SCM PROPERTIES
1052 #==========================================================================
1052 #==========================================================================
1053
1053
1054 def get_changeset(self, rev=None):
1054 def get_changeset(self, rev=None):
1055 return get_changeset_safe(self.scm_instance, rev)
1055 return get_changeset_safe(self.scm_instance, rev)
1056
1056
1057 def get_landing_changeset(self):
1057 def get_landing_changeset(self):
1058 """
1058 """
1059 Returns landing changeset, or if that doesn't exist returns the tip
1059 Returns landing changeset, or if that doesn't exist returns the tip
1060 """
1060 """
1061 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1061 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1062 return cs
1062 return cs
1063
1063
1064 def update_changeset_cache(self, cs_cache=None):
1064 def update_changeset_cache(self, cs_cache=None):
1065 """
1065 """
1066 Update cache of last changeset for repository, keys should be::
1066 Update cache of last changeset for repository, keys should be::
1067
1067
1068 short_id
1068 short_id
1069 raw_id
1069 raw_id
1070 revision
1070 revision
1071 message
1071 message
1072 date
1072 date
1073 author
1073 author
1074
1074
1075 :param cs_cache:
1075 :param cs_cache:
1076 """
1076 """
1077 from rhodecode.lib.vcs.backends.base import BaseChangeset
1077 from rhodecode.lib.vcs.backends.base import BaseChangeset
1078 if cs_cache is None:
1078 if cs_cache is None:
1079 cs_cache = EmptyChangeset()
1079 cs_cache = EmptyChangeset()
1080 # use no-cache version here
1080 # use no-cache version here
1081 scm_repo = self.scm_instance_no_cache()
1081 scm_repo = self.scm_instance_no_cache()
1082 if scm_repo:
1082 if scm_repo:
1083 cs_cache = scm_repo.get_changeset()
1083 cs_cache = scm_repo.get_changeset()
1084
1084
1085 if isinstance(cs_cache, BaseChangeset):
1085 if isinstance(cs_cache, BaseChangeset):
1086 cs_cache = cs_cache.__json__()
1086 cs_cache = cs_cache.__json__()
1087
1087
1088 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1088 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1089 _default = datetime.datetime.fromtimestamp(0)
1089 _default = datetime.datetime.fromtimestamp(0)
1090 last_change = cs_cache.get('date') or _default
1090 last_change = cs_cache.get('date') or _default
1091 log.debug('updated repo %s with new cs cache %s'
1091 log.debug('updated repo %s with new cs cache %s'
1092 % (self.repo_name, cs_cache))
1092 % (self.repo_name, cs_cache))
1093 self.updated_on = last_change
1093 self.updated_on = last_change
1094 self.changeset_cache = cs_cache
1094 self.changeset_cache = cs_cache
1095 Session().add(self)
1095 Session().add(self)
1096 Session().commit()
1096 Session().commit()
1097 else:
1097 else:
1098 log.debug('Skipping repo:%s already with latest changes'
1098 log.debug('Skipping repo:%s already with latest changes'
1099 % self.repo_name)
1099 % self.repo_name)
1100
1100
1101 @property
1101 @property
1102 def tip(self):
1102 def tip(self):
1103 return self.get_changeset('tip')
1103 return self.get_changeset('tip')
1104
1104
1105 @property
1105 @property
1106 def author(self):
1106 def author(self):
1107 return self.tip.author
1107 return self.tip.author
1108
1108
1109 @property
1109 @property
1110 def last_change(self):
1110 def last_change(self):
1111 return self.scm_instance.last_change
1111 return self.scm_instance.last_change
1112
1112
1113 def get_comments(self, revisions=None):
1113 def get_comments(self, revisions=None):
1114 """
1114 """
1115 Returns comments for this repository grouped by revisions
1115 Returns comments for this repository grouped by revisions
1116
1116
1117 :param revisions: filter query by revisions only
1117 :param revisions: filter query by revisions only
1118 """
1118 """
1119 cmts = ChangesetComment.query()\
1119 cmts = ChangesetComment.query()\
1120 .filter(ChangesetComment.repo == self)
1120 .filter(ChangesetComment.repo == self)
1121 if revisions:
1121 if revisions:
1122 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1122 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1123 grouped = defaultdict(list)
1123 grouped = defaultdict(list)
1124 for cmt in cmts.all():
1124 for cmt in cmts.all():
1125 grouped[cmt.revision].append(cmt)
1125 grouped[cmt.revision].append(cmt)
1126 return grouped
1126 return grouped
1127
1127
1128 def statuses(self, revisions=None):
1128 def statuses(self, revisions=None):
1129 """
1129 """
1130 Returns statuses for this repository
1130 Returns statuses for this repository
1131
1131
1132 :param revisions: list of revisions to get statuses for
1132 :param revisions: list of revisions to get statuses for
1133 :type revisions: list
1134 """
1133 """
1135
1134
1136 statuses = ChangesetStatus.query()\
1135 statuses = ChangesetStatus.query()\
1137 .filter(ChangesetStatus.repo == self)\
1136 .filter(ChangesetStatus.repo == self)\
1138 .filter(ChangesetStatus.version == 0)
1137 .filter(ChangesetStatus.version == 0)
1139 if revisions:
1138 if revisions:
1140 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1139 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1141 grouped = {}
1140 grouped = {}
1142
1141
1143 #maybe we have open new pullrequest without a status ?
1142 #maybe we have open new pullrequest without a status ?
1144 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1143 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1145 status_lbl = ChangesetStatus.get_status_lbl(stat)
1144 status_lbl = ChangesetStatus.get_status_lbl(stat)
1146 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1145 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1147 for rev in pr.revisions:
1146 for rev in pr.revisions:
1148 pr_id = pr.pull_request_id
1147 pr_id = pr.pull_request_id
1149 pr_repo = pr.other_repo.repo_name
1148 pr_repo = pr.other_repo.repo_name
1150 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1149 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1151
1150
1152 for stat in statuses.all():
1151 for stat in statuses.all():
1153 pr_id = pr_repo = None
1152 pr_id = pr_repo = None
1154 if stat.pull_request:
1153 if stat.pull_request:
1155 pr_id = stat.pull_request.pull_request_id
1154 pr_id = stat.pull_request.pull_request_id
1156 pr_repo = stat.pull_request.other_repo.repo_name
1155 pr_repo = stat.pull_request.other_repo.repo_name
1157 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1156 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1158 pr_id, pr_repo]
1157 pr_id, pr_repo]
1159 return grouped
1158 return grouped
1160
1159
1161 def _repo_size(self):
1160 def _repo_size(self):
1162 from rhodecode.lib import helpers as h
1161 from rhodecode.lib import helpers as h
1163 log.debug('calculating repository size...')
1162 log.debug('calculating repository size...')
1164 return h.format_byte_size(self.scm_instance.size)
1163 return h.format_byte_size(self.scm_instance.size)
1165
1164
1166 #==========================================================================
1165 #==========================================================================
1167 # SCM CACHE INSTANCE
1166 # SCM CACHE INSTANCE
1168 #==========================================================================
1167 #==========================================================================
1169
1168
1170 def set_invalidate(self):
1169 def set_invalidate(self):
1171 """
1170 """
1172 Mark caches of this repo as invalid.
1171 Mark caches of this repo as invalid.
1173 """
1172 """
1174 CacheInvalidation.set_invalidate(self.repo_name)
1173 CacheInvalidation.set_invalidate(self.repo_name)
1175
1174
1176 def scm_instance_no_cache(self):
1175 def scm_instance_no_cache(self):
1177 return self.__get_instance()
1176 return self.__get_instance()
1178
1177
1179 @property
1178 @property
1180 def scm_instance(self):
1179 def scm_instance(self):
1181 import rhodecode
1180 import rhodecode
1182 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1181 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1183 if full_cache:
1182 if full_cache:
1184 return self.scm_instance_cached()
1183 return self.scm_instance_cached()
1185 return self.__get_instance()
1184 return self.__get_instance()
1186
1185
1187 def scm_instance_cached(self, valid_cache_keys=None):
1186 def scm_instance_cached(self, valid_cache_keys=None):
1188 @cache_region('long_term')
1187 @cache_region('long_term')
1189 def _c(repo_name):
1188 def _c(repo_name):
1190 return self.__get_instance()
1189 return self.__get_instance()
1191 rn = self.repo_name
1190 rn = self.repo_name
1192
1191
1193 valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
1192 valid = CacheInvalidation.test_and_set_valid(rn, None, valid_cache_keys=valid_cache_keys)
1194 if not valid:
1193 if not valid:
1195 log.debug('Cache for %s invalidated, getting new object' % (rn))
1194 log.debug('Cache for %s invalidated, getting new object' % (rn))
1196 region_invalidate(_c, None, rn)
1195 region_invalidate(_c, None, rn)
1197 else:
1196 else:
1198 log.debug('Getting obj for %s from cache' % (rn))
1197 log.debug('Getting obj for %s from cache' % (rn))
1199 return _c(rn)
1198 return _c(rn)
1200
1199
1201 def __get_instance(self):
1200 def __get_instance(self):
1202 repo_full_path = self.repo_full_path
1201 repo_full_path = self.repo_full_path
1203 try:
1202 try:
1204 alias = get_scm(repo_full_path)[0]
1203 alias = get_scm(repo_full_path)[0]
1205 log.debug('Creating instance of %s repository from %s'
1204 log.debug('Creating instance of %s repository from %s'
1206 % (alias, repo_full_path))
1205 % (alias, repo_full_path))
1207 backend = get_backend(alias)
1206 backend = get_backend(alias)
1208 except VCSError:
1207 except VCSError:
1209 log.error(traceback.format_exc())
1208 log.error(traceback.format_exc())
1210 log.error('Perhaps this repository is in db and not in '
1209 log.error('Perhaps this repository is in db and not in '
1211 'filesystem run rescan repositories with '
1210 'filesystem run rescan repositories with '
1212 '"destroy old data " option from admin panel')
1211 '"destroy old data " option from admin panel')
1213 return
1212 return
1214
1213
1215 if alias == 'hg':
1214 if alias == 'hg':
1216
1215
1217 repo = backend(safe_str(repo_full_path), create=False,
1216 repo = backend(safe_str(repo_full_path), create=False,
1218 baseui=self._ui)
1217 baseui=self._ui)
1219 # skip hidden web repository
1218 # skip hidden web repository
1220 if repo._get_hidden():
1219 if repo._get_hidden():
1221 return
1220 return
1222 else:
1221 else:
1223 repo = backend(repo_full_path, create=False)
1222 repo = backend(repo_full_path, create=False)
1224
1223
1225 return repo
1224 return repo
1226
1225
1227
1226
1228 class RepoGroup(Base, BaseModel):
1227 class RepoGroup(Base, BaseModel):
1229 __tablename__ = 'groups'
1228 __tablename__ = 'groups'
1230 __table_args__ = (
1229 __table_args__ = (
1231 UniqueConstraint('group_name', 'group_parent_id'),
1230 UniqueConstraint('group_name', 'group_parent_id'),
1232 CheckConstraint('group_id != group_parent_id'),
1231 CheckConstraint('group_id != group_parent_id'),
1233 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1232 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1234 'mysql_charset': 'utf8'},
1233 'mysql_charset': 'utf8'},
1235 )
1234 )
1236 __mapper_args__ = {'order_by': 'group_name'}
1235 __mapper_args__ = {'order_by': 'group_name'}
1237
1236
1238 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1237 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1239 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1238 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1240 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1239 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1241 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1240 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1242 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1241 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1243 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1242 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1244
1243
1245 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1244 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1246 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1245 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1247 parent_group = relationship('RepoGroup', remote_side=group_id)
1246 parent_group = relationship('RepoGroup', remote_side=group_id)
1248 user = relationship('User')
1247 user = relationship('User')
1249
1248
1250 def __init__(self, group_name='', parent_group=None):
1249 def __init__(self, group_name='', parent_group=None):
1251 self.group_name = group_name
1250 self.group_name = group_name
1252 self.parent_group = parent_group
1251 self.parent_group = parent_group
1253
1252
1254 def __unicode__(self):
1253 def __unicode__(self):
1255 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
1254 return u"<%s('id:%s:%s')>" % (self.__class__.__name__, self.group_id,
1256 self.group_name)
1255 self.group_name)
1257
1256
1258 @classmethod
1257 @classmethod
1259 def groups_choices(cls, groups=None, show_empty_group=True):
1258 def groups_choices(cls, groups=None, show_empty_group=True):
1260 from webhelpers.html import literal as _literal
1259 from webhelpers.html import literal as _literal
1261 if not groups:
1260 if not groups:
1262 groups = cls.query().all()
1261 groups = cls.query().all()
1263
1262
1264 repo_groups = []
1263 repo_groups = []
1265 if show_empty_group:
1264 if show_empty_group:
1266 repo_groups = [('-1', '-- %s --' % _('top level'))]
1265 repo_groups = [('-1', '-- %s --' % _('top level'))]
1267 sep = ' &raquo; '
1266 sep = ' &raquo; '
1268 _name = lambda k: _literal(sep.join(k))
1267 _name = lambda k: _literal(sep.join(k))
1269
1268
1270 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1269 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1271 for x in groups])
1270 for x in groups])
1272
1271
1273 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1272 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1274 return repo_groups
1273 return repo_groups
1275
1274
1276 @classmethod
1275 @classmethod
1277 def url_sep(cls):
1276 def url_sep(cls):
1278 return URL_SEP
1277 return URL_SEP
1279
1278
1280 @classmethod
1279 @classmethod
1281 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1280 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1282 if case_insensitive:
1281 if case_insensitive:
1283 gr = cls.query()\
1282 gr = cls.query()\
1284 .filter(cls.group_name.ilike(group_name))
1283 .filter(cls.group_name.ilike(group_name))
1285 else:
1284 else:
1286 gr = cls.query()\
1285 gr = cls.query()\
1287 .filter(cls.group_name == group_name)
1286 .filter(cls.group_name == group_name)
1288 if cache:
1287 if cache:
1289 gr = gr.options(FromCache(
1288 gr = gr.options(FromCache(
1290 "sql_cache_short",
1289 "sql_cache_short",
1291 "get_group_%s" % _hash_key(group_name)
1290 "get_group_%s" % _hash_key(group_name)
1292 )
1291 )
1293 )
1292 )
1294 return gr.scalar()
1293 return gr.scalar()
1295
1294
1296 @property
1295 @property
1297 def parents(self):
1296 def parents(self):
1298 parents_recursion_limit = 5
1297 parents_recursion_limit = 5
1299 groups = []
1298 groups = []
1300 if self.parent_group is None:
1299 if self.parent_group is None:
1301 return groups
1300 return groups
1302 cur_gr = self.parent_group
1301 cur_gr = self.parent_group
1303 groups.insert(0, cur_gr)
1302 groups.insert(0, cur_gr)
1304 cnt = 0
1303 cnt = 0
1305 while 1:
1304 while 1:
1306 cnt += 1
1305 cnt += 1
1307 gr = getattr(cur_gr, 'parent_group', None)
1306 gr = getattr(cur_gr, 'parent_group', None)
1308 cur_gr = cur_gr.parent_group
1307 cur_gr = cur_gr.parent_group
1309 if gr is None:
1308 if gr is None:
1310 break
1309 break
1311 if cnt == parents_recursion_limit:
1310 if cnt == parents_recursion_limit:
1312 # this will prevent accidental infinit loops
1311 # this will prevent accidental infinit loops
1313 log.error('group nested more than %s' %
1312 log.error('group nested more than %s' %
1314 parents_recursion_limit)
1313 parents_recursion_limit)
1315 break
1314 break
1316
1315
1317 groups.insert(0, gr)
1316 groups.insert(0, gr)
1318 return groups
1317 return groups
1319
1318
1320 @property
1319 @property
1321 def children(self):
1320 def children(self):
1322 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1321 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1323
1322
1324 @property
1323 @property
1325 def name(self):
1324 def name(self):
1326 return self.group_name.split(RepoGroup.url_sep())[-1]
1325 return self.group_name.split(RepoGroup.url_sep())[-1]
1327
1326
1328 @property
1327 @property
1329 def full_path(self):
1328 def full_path(self):
1330 return self.group_name
1329 return self.group_name
1331
1330
1332 @property
1331 @property
1333 def full_path_splitted(self):
1332 def full_path_splitted(self):
1334 return self.group_name.split(RepoGroup.url_sep())
1333 return self.group_name.split(RepoGroup.url_sep())
1335
1334
1336 @property
1335 @property
1337 def repositories(self):
1336 def repositories(self):
1338 return Repository.query()\
1337 return Repository.query()\
1339 .filter(Repository.group == self)\
1338 .filter(Repository.group == self)\
1340 .order_by(Repository.repo_name)
1339 .order_by(Repository.repo_name)
1341
1340
1342 @property
1341 @property
1343 def repositories_recursive_count(self):
1342 def repositories_recursive_count(self):
1344 cnt = self.repositories.count()
1343 cnt = self.repositories.count()
1345
1344
1346 def children_count(group):
1345 def children_count(group):
1347 cnt = 0
1346 cnt = 0
1348 for child in group.children:
1347 for child in group.children:
1349 cnt += child.repositories.count()
1348 cnt += child.repositories.count()
1350 cnt += children_count(child)
1349 cnt += children_count(child)
1351 return cnt
1350 return cnt
1352
1351
1353 return cnt + children_count(self)
1352 return cnt + children_count(self)
1354
1353
1355 def _recursive_objects(self, include_repos=True):
1354 def _recursive_objects(self, include_repos=True):
1356 all_ = []
1355 all_ = []
1357
1356
1358 def _get_members(root_gr):
1357 def _get_members(root_gr):
1359 if include_repos:
1358 if include_repos:
1360 for r in root_gr.repositories:
1359 for r in root_gr.repositories:
1361 all_.append(r)
1360 all_.append(r)
1362 childs = root_gr.children.all()
1361 childs = root_gr.children.all()
1363 if childs:
1362 if childs:
1364 for gr in childs:
1363 for gr in childs:
1365 all_.append(gr)
1364 all_.append(gr)
1366 _get_members(gr)
1365 _get_members(gr)
1367
1366
1368 _get_members(self)
1367 _get_members(self)
1369 return [self] + all_
1368 return [self] + all_
1370
1369
1371 def recursive_groups_and_repos(self):
1370 def recursive_groups_and_repos(self):
1372 """
1371 """
1373 Recursive return all groups, with repositories in those groups
1372 Recursive return all groups, with repositories in those groups
1374 """
1373 """
1375 return self._recursive_objects()
1374 return self._recursive_objects()
1376
1375
1377 def recursive_groups(self):
1376 def recursive_groups(self):
1378 """
1377 """
1379 Returns all children groups for this group including children of children
1378 Returns all children groups for this group including children of children
1380 """
1379 """
1381 return self._recursive_objects(include_repos=False)
1380 return self._recursive_objects(include_repos=False)
1382
1381
1383 def get_new_name(self, group_name):
1382 def get_new_name(self, group_name):
1384 """
1383 """
1385 returns new full group name based on parent and new name
1384 returns new full group name based on parent and new name
1386
1385
1387 :param group_name:
1386 :param group_name:
1388 """
1387 """
1389 path_prefix = (self.parent_group.full_path_splitted if
1388 path_prefix = (self.parent_group.full_path_splitted if
1390 self.parent_group else [])
1389 self.parent_group else [])
1391 return RepoGroup.url_sep().join(path_prefix + [group_name])
1390 return RepoGroup.url_sep().join(path_prefix + [group_name])
1392
1391
1393
1392
1394 class Permission(Base, BaseModel):
1393 class Permission(Base, BaseModel):
1395 __tablename__ = 'permissions'
1394 __tablename__ = 'permissions'
1396 __table_args__ = (
1395 __table_args__ = (
1397 Index('p_perm_name_idx', 'permission_name'),
1396 Index('p_perm_name_idx', 'permission_name'),
1398 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1397 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1399 'mysql_charset': 'utf8'},
1398 'mysql_charset': 'utf8'},
1400 )
1399 )
1401 PERMS = [
1400 PERMS = [
1402 ('hg.admin', _('RhodeCode Administrator')),
1401 ('hg.admin', _('RhodeCode Administrator')),
1403
1402
1404 ('repository.none', _('Repository no access')),
1403 ('repository.none', _('Repository no access')),
1405 ('repository.read', _('Repository read access')),
1404 ('repository.read', _('Repository read access')),
1406 ('repository.write', _('Repository write access')),
1405 ('repository.write', _('Repository write access')),
1407 ('repository.admin', _('Repository admin access')),
1406 ('repository.admin', _('Repository admin access')),
1408
1407
1409 ('group.none', _('Repository group no access')),
1408 ('group.none', _('Repository group no access')),
1410 ('group.read', _('Repository group read access')),
1409 ('group.read', _('Repository group read access')),
1411 ('group.write', _('Repository group write access')),
1410 ('group.write', _('Repository group write access')),
1412 ('group.admin', _('Repository group admin access')),
1411 ('group.admin', _('Repository group admin access')),
1413
1412
1414 ('usergroup.none', _('User group no access')),
1413 ('usergroup.none', _('User group no access')),
1415 ('usergroup.read', _('User group read access')),
1414 ('usergroup.read', _('User group read access')),
1416 ('usergroup.write', _('User group write access')),
1415 ('usergroup.write', _('User group write access')),
1417 ('usergroup.admin', _('User group admin access')),
1416 ('usergroup.admin', _('User group admin access')),
1418
1417
1419 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
1418 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
1420 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
1419 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
1421
1420
1422 ('hg.usergroup.create.false', _('User Group creation disabled')),
1421 ('hg.usergroup.create.false', _('User Group creation disabled')),
1423 ('hg.usergroup.create.true', _('User Group creation enabled')),
1422 ('hg.usergroup.create.true', _('User Group creation enabled')),
1424
1423
1425 ('hg.create.none', _('Repository creation disabled')),
1424 ('hg.create.none', _('Repository creation disabled')),
1426 ('hg.create.repository', _('Repository creation enabled')),
1425 ('hg.create.repository', _('Repository creation enabled')),
1427
1426
1428 ('hg.fork.none', _('Repository forking disabled')),
1427 ('hg.fork.none', _('Repository forking disabled')),
1429 ('hg.fork.repository', _('Repository forking enabled')),
1428 ('hg.fork.repository', _('Repository forking enabled')),
1430
1429
1431 ('hg.register.none', _('Registration disabled')),
1430 ('hg.register.none', _('Registration disabled')),
1432 ('hg.register.manual_activate', _('User Registration with manual account activation')),
1431 ('hg.register.manual_activate', _('User Registration with manual account activation')),
1433 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
1432 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
1434
1433
1435 ('hg.extern_activate.manual', _('Manual activation of external account')),
1434 ('hg.extern_activate.manual', _('Manual activation of external account')),
1436 ('hg.extern_activate.auto', _('Automatic activation of external account')),
1435 ('hg.extern_activate.auto', _('Automatic activation of external account')),
1437
1436
1438 ]
1437 ]
1439
1438
1440 #definition of system default permissions for DEFAULT user
1439 #definition of system default permissions for DEFAULT user
1441 DEFAULT_USER_PERMISSIONS = [
1440 DEFAULT_USER_PERMISSIONS = [
1442 'repository.read',
1441 'repository.read',
1443 'group.read',
1442 'group.read',
1444 'usergroup.read',
1443 'usergroup.read',
1445 'hg.create.repository',
1444 'hg.create.repository',
1446 'hg.fork.repository',
1445 'hg.fork.repository',
1447 'hg.register.manual_activate',
1446 'hg.register.manual_activate',
1448 'hg.extern_activate.auto',
1447 'hg.extern_activate.auto',
1449 ]
1448 ]
1450
1449
1451 # defines which permissions are more important higher the more important
1450 # defines which permissions are more important higher the more important
1452 # Weight defines which permissions are more important.
1451 # Weight defines which permissions are more important.
1453 # The higher number the more important.
1452 # The higher number the more important.
1454 PERM_WEIGHTS = {
1453 PERM_WEIGHTS = {
1455 'repository.none': 0,
1454 'repository.none': 0,
1456 'repository.read': 1,
1455 'repository.read': 1,
1457 'repository.write': 3,
1456 'repository.write': 3,
1458 'repository.admin': 4,
1457 'repository.admin': 4,
1459
1458
1460 'group.none': 0,
1459 'group.none': 0,
1461 'group.read': 1,
1460 'group.read': 1,
1462 'group.write': 3,
1461 'group.write': 3,
1463 'group.admin': 4,
1462 'group.admin': 4,
1464
1463
1465 'usergroup.none': 0,
1464 'usergroup.none': 0,
1466 'usergroup.read': 1,
1465 'usergroup.read': 1,
1467 'usergroup.write': 3,
1466 'usergroup.write': 3,
1468 'usergroup.admin': 4,
1467 'usergroup.admin': 4,
1469 'hg.repogroup.create.false': 0,
1468 'hg.repogroup.create.false': 0,
1470 'hg.repogroup.create.true': 1,
1469 'hg.repogroup.create.true': 1,
1471
1470
1472 'hg.usergroup.create.false': 0,
1471 'hg.usergroup.create.false': 0,
1473 'hg.usergroup.create.true': 1,
1472 'hg.usergroup.create.true': 1,
1474
1473
1475 'hg.fork.none': 0,
1474 'hg.fork.none': 0,
1476 'hg.fork.repository': 1,
1475 'hg.fork.repository': 1,
1477 'hg.create.none': 0,
1476 'hg.create.none': 0,
1478 'hg.create.repository': 1
1477 'hg.create.repository': 1
1479 }
1478 }
1480
1479
1481 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1480 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1482 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1481 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1483 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1482 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1484
1483
1485 def __unicode__(self):
1484 def __unicode__(self):
1486 return u"<%s('%s:%s')>" % (
1485 return u"<%s('%s:%s')>" % (
1487 self.__class__.__name__, self.permission_id, self.permission_name
1486 self.__class__.__name__, self.permission_id, self.permission_name
1488 )
1487 )
1489
1488
1490 @classmethod
1489 @classmethod
1491 def get_by_key(cls, key):
1490 def get_by_key(cls, key):
1492 return cls.query().filter(cls.permission_name == key).scalar()
1491 return cls.query().filter(cls.permission_name == key).scalar()
1493
1492
1494 @classmethod
1493 @classmethod
1495 def get_default_perms(cls, default_user_id):
1494 def get_default_perms(cls, default_user_id):
1496 q = Session().query(UserRepoToPerm, Repository, cls)\
1495 q = Session().query(UserRepoToPerm, Repository, cls)\
1497 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1496 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1498 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1497 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1499 .filter(UserRepoToPerm.user_id == default_user_id)
1498 .filter(UserRepoToPerm.user_id == default_user_id)
1500
1499
1501 return q.all()
1500 return q.all()
1502
1501
1503 @classmethod
1502 @classmethod
1504 def get_default_group_perms(cls, default_user_id):
1503 def get_default_group_perms(cls, default_user_id):
1505 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1504 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1506 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1505 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1507 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1506 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1508 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1507 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1509
1508
1510 return q.all()
1509 return q.all()
1511
1510
1512 @classmethod
1511 @classmethod
1513 def get_default_user_group_perms(cls, default_user_id):
1512 def get_default_user_group_perms(cls, default_user_id):
1514 q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
1513 q = Session().query(UserUserGroupToPerm, UserGroup, cls)\
1515 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
1514 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
1516 .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
1515 .join((cls, UserUserGroupToPerm.permission_id == cls.permission_id))\
1517 .filter(UserUserGroupToPerm.user_id == default_user_id)
1516 .filter(UserUserGroupToPerm.user_id == default_user_id)
1518
1517
1519 return q.all()
1518 return q.all()
1520
1519
1521
1520
1522 class UserRepoToPerm(Base, BaseModel):
1521 class UserRepoToPerm(Base, BaseModel):
1523 __tablename__ = 'repo_to_perm'
1522 __tablename__ = 'repo_to_perm'
1524 __table_args__ = (
1523 __table_args__ = (
1525 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1524 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1526 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1525 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1527 'mysql_charset': 'utf8'}
1526 'mysql_charset': 'utf8'}
1528 )
1527 )
1529 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1528 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1530 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1529 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1531 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1530 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1532 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1531 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1533
1532
1534 user = relationship('User')
1533 user = relationship('User')
1535 repository = relationship('Repository')
1534 repository = relationship('Repository')
1536 permission = relationship('Permission')
1535 permission = relationship('Permission')
1537
1536
1538 @classmethod
1537 @classmethod
1539 def create(cls, user, repository, permission):
1538 def create(cls, user, repository, permission):
1540 n = cls()
1539 n = cls()
1541 n.user = user
1540 n.user = user
1542 n.repository = repository
1541 n.repository = repository
1543 n.permission = permission
1542 n.permission = permission
1544 Session().add(n)
1543 Session().add(n)
1545 return n
1544 return n
1546
1545
1547 def __unicode__(self):
1546 def __unicode__(self):
1548 return u'<%s => %s >' % (self.user, self.repository)
1547 return u'<%s => %s >' % (self.user, self.repository)
1549
1548
1550
1549
1551 class UserUserGroupToPerm(Base, BaseModel):
1550 class UserUserGroupToPerm(Base, BaseModel):
1552 __tablename__ = 'user_user_group_to_perm'
1551 __tablename__ = 'user_user_group_to_perm'
1553 __table_args__ = (
1552 __table_args__ = (
1554 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
1553 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
1555 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1554 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1556 'mysql_charset': 'utf8'}
1555 'mysql_charset': 'utf8'}
1557 )
1556 )
1558 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1557 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1559 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1558 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1560 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1559 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1561 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1560 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1562
1561
1563 user = relationship('User')
1562 user = relationship('User')
1564 user_group = relationship('UserGroup')
1563 user_group = relationship('UserGroup')
1565 permission = relationship('Permission')
1564 permission = relationship('Permission')
1566
1565
1567 @classmethod
1566 @classmethod
1568 def create(cls, user, user_group, permission):
1567 def create(cls, user, user_group, permission):
1569 n = cls()
1568 n = cls()
1570 n.user = user
1569 n.user = user
1571 n.user_group = user_group
1570 n.user_group = user_group
1572 n.permission = permission
1571 n.permission = permission
1573 Session().add(n)
1572 Session().add(n)
1574 return n
1573 return n
1575
1574
1576 def __unicode__(self):
1575 def __unicode__(self):
1577 return u'<%s => %s >' % (self.user, self.user_group)
1576 return u'<%s => %s >' % (self.user, self.user_group)
1578
1577
1579
1578
1580 class UserToPerm(Base, BaseModel):
1579 class UserToPerm(Base, BaseModel):
1581 __tablename__ = 'user_to_perm'
1580 __tablename__ = 'user_to_perm'
1582 __table_args__ = (
1581 __table_args__ = (
1583 UniqueConstraint('user_id', 'permission_id'),
1582 UniqueConstraint('user_id', 'permission_id'),
1584 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1583 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1585 'mysql_charset': 'utf8'}
1584 'mysql_charset': 'utf8'}
1586 )
1585 )
1587 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1586 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1588 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1587 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1589 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1588 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1590
1589
1591 user = relationship('User')
1590 user = relationship('User')
1592 permission = relationship('Permission', lazy='joined')
1591 permission = relationship('Permission', lazy='joined')
1593
1592
1594 def __unicode__(self):
1593 def __unicode__(self):
1595 return u'<%s => %s >' % (self.user, self.permission)
1594 return u'<%s => %s >' % (self.user, self.permission)
1596
1595
1597
1596
1598 class UserGroupRepoToPerm(Base, BaseModel):
1597 class UserGroupRepoToPerm(Base, BaseModel):
1599 __tablename__ = 'users_group_repo_to_perm'
1598 __tablename__ = 'users_group_repo_to_perm'
1600 __table_args__ = (
1599 __table_args__ = (
1601 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1600 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1602 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1601 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1603 'mysql_charset': 'utf8'}
1602 'mysql_charset': 'utf8'}
1604 )
1603 )
1605 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1604 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1606 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1605 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1607 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1606 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1608 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1607 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1609
1608
1610 users_group = relationship('UserGroup')
1609 users_group = relationship('UserGroup')
1611 permission = relationship('Permission')
1610 permission = relationship('Permission')
1612 repository = relationship('Repository')
1611 repository = relationship('Repository')
1613
1612
1614 @classmethod
1613 @classmethod
1615 def create(cls, users_group, repository, permission):
1614 def create(cls, users_group, repository, permission):
1616 n = cls()
1615 n = cls()
1617 n.users_group = users_group
1616 n.users_group = users_group
1618 n.repository = repository
1617 n.repository = repository
1619 n.permission = permission
1618 n.permission = permission
1620 Session().add(n)
1619 Session().add(n)
1621 return n
1620 return n
1622
1621
1623 def __unicode__(self):
1622 def __unicode__(self):
1624 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
1623 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
1625
1624
1626
1625
1627 class UserGroupUserGroupToPerm(Base, BaseModel):
1626 class UserGroupUserGroupToPerm(Base, BaseModel):
1628 __tablename__ = 'user_group_user_group_to_perm'
1627 __tablename__ = 'user_group_user_group_to_perm'
1629 __table_args__ = (
1628 __table_args__ = (
1630 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
1629 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
1631 CheckConstraint('target_user_group_id != user_group_id'),
1630 CheckConstraint('target_user_group_id != user_group_id'),
1632 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1631 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1633 'mysql_charset': 'utf8'}
1632 'mysql_charset': 'utf8'}
1634 )
1633 )
1635 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1634 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1636 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1635 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1637 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1636 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1638 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1637 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1639
1638
1640 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
1639 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
1641 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
1640 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
1642 permission = relationship('Permission')
1641 permission = relationship('Permission')
1643
1642
1644 @classmethod
1643 @classmethod
1645 def create(cls, target_user_group, user_group, permission):
1644 def create(cls, target_user_group, user_group, permission):
1646 n = cls()
1645 n = cls()
1647 n.target_user_group = target_user_group
1646 n.target_user_group = target_user_group
1648 n.user_group = user_group
1647 n.user_group = user_group
1649 n.permission = permission
1648 n.permission = permission
1650 Session().add(n)
1649 Session().add(n)
1651 return n
1650 return n
1652
1651
1653 def __unicode__(self):
1652 def __unicode__(self):
1654 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
1653 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
1655
1654
1656
1655
1657 class UserGroupToPerm(Base, BaseModel):
1656 class UserGroupToPerm(Base, BaseModel):
1658 __tablename__ = 'users_group_to_perm'
1657 __tablename__ = 'users_group_to_perm'
1659 __table_args__ = (
1658 __table_args__ = (
1660 UniqueConstraint('users_group_id', 'permission_id',),
1659 UniqueConstraint('users_group_id', 'permission_id',),
1661 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1660 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1662 'mysql_charset': 'utf8'}
1661 'mysql_charset': 'utf8'}
1663 )
1662 )
1664 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1663 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1665 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1664 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1666 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1665 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1667
1666
1668 users_group = relationship('UserGroup')
1667 users_group = relationship('UserGroup')
1669 permission = relationship('Permission')
1668 permission = relationship('Permission')
1670
1669
1671
1670
1672 class UserRepoGroupToPerm(Base, BaseModel):
1671 class UserRepoGroupToPerm(Base, BaseModel):
1673 __tablename__ = 'user_repo_group_to_perm'
1672 __tablename__ = 'user_repo_group_to_perm'
1674 __table_args__ = (
1673 __table_args__ = (
1675 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1674 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1676 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1675 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1677 'mysql_charset': 'utf8'}
1676 'mysql_charset': 'utf8'}
1678 )
1677 )
1679
1678
1680 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1679 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1681 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1680 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1682 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1681 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1683 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1682 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1684
1683
1685 user = relationship('User')
1684 user = relationship('User')
1686 group = relationship('RepoGroup')
1685 group = relationship('RepoGroup')
1687 permission = relationship('Permission')
1686 permission = relationship('Permission')
1688
1687
1689
1688
1690 class UserGroupRepoGroupToPerm(Base, BaseModel):
1689 class UserGroupRepoGroupToPerm(Base, BaseModel):
1691 __tablename__ = 'users_group_repo_group_to_perm'
1690 __tablename__ = 'users_group_repo_group_to_perm'
1692 __table_args__ = (
1691 __table_args__ = (
1693 UniqueConstraint('users_group_id', 'group_id'),
1692 UniqueConstraint('users_group_id', 'group_id'),
1694 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1693 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1695 'mysql_charset': 'utf8'}
1694 'mysql_charset': 'utf8'}
1696 )
1695 )
1697
1696
1698 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1697 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1699 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1698 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1700 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1699 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1701 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1700 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1702
1701
1703 users_group = relationship('UserGroup')
1702 users_group = relationship('UserGroup')
1704 permission = relationship('Permission')
1703 permission = relationship('Permission')
1705 group = relationship('RepoGroup')
1704 group = relationship('RepoGroup')
1706
1705
1707
1706
1708 class Statistics(Base, BaseModel):
1707 class Statistics(Base, BaseModel):
1709 __tablename__ = 'statistics'
1708 __tablename__ = 'statistics'
1710 __table_args__ = (
1709 __table_args__ = (
1711 UniqueConstraint('repository_id'),
1710 UniqueConstraint('repository_id'),
1712 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1711 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1713 'mysql_charset': 'utf8'}
1712 'mysql_charset': 'utf8'}
1714 )
1713 )
1715 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1714 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1716 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1715 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1717 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1716 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1718 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1717 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1719 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1718 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1720 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1719 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1721
1720
1722 repository = relationship('Repository', single_parent=True)
1721 repository = relationship('Repository', single_parent=True)
1723
1722
1724
1723
1725 class UserFollowing(Base, BaseModel):
1724 class UserFollowing(Base, BaseModel):
1726 __tablename__ = 'user_followings'
1725 __tablename__ = 'user_followings'
1727 __table_args__ = (
1726 __table_args__ = (
1728 UniqueConstraint('user_id', 'follows_repository_id'),
1727 UniqueConstraint('user_id', 'follows_repository_id'),
1729 UniqueConstraint('user_id', 'follows_user_id'),
1728 UniqueConstraint('user_id', 'follows_user_id'),
1730 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1729 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1731 'mysql_charset': 'utf8'}
1730 'mysql_charset': 'utf8'}
1732 )
1731 )
1733
1732
1734 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1733 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1735 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1734 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1736 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1735 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1737 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1736 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1738 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1737 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1739
1738
1740 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1739 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1741
1740
1742 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1741 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1743 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1742 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1744
1743
1745 @classmethod
1744 @classmethod
1746 def get_repo_followers(cls, repo_id):
1745 def get_repo_followers(cls, repo_id):
1747 return cls.query().filter(cls.follows_repo_id == repo_id)
1746 return cls.query().filter(cls.follows_repo_id == repo_id)
1748
1747
1749
1748
1750 class CacheInvalidation(Base, BaseModel):
1749 class CacheInvalidation(Base, BaseModel):
1751 __tablename__ = 'cache_invalidation'
1750 __tablename__ = 'cache_invalidation'
1752 __table_args__ = (
1751 __table_args__ = (
1753 UniqueConstraint('cache_key'),
1752 UniqueConstraint('cache_key'),
1754 Index('key_idx', 'cache_key'),
1753 Index('key_idx', 'cache_key'),
1755 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1754 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1756 'mysql_charset': 'utf8'},
1755 'mysql_charset': 'utf8'},
1757 )
1756 )
1758 # cache_id, not used
1757 # cache_id, not used
1759 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1758 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1760 # cache_key as created by _get_cache_key
1759 # cache_key as created by _get_cache_key
1761 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1760 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1762 # cache_args is a repo_name
1761 # cache_args is a repo_name
1763 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1762 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1764 # instance sets cache_active True when it is caching,
1763 # instance sets cache_active True when it is caching,
1765 # other instances set cache_active to False to indicate that this cache is invalid
1764 # other instances set cache_active to False to indicate that this cache is invalid
1766 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1765 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1767
1766
1768 def __init__(self, cache_key, repo_name=''):
1767 def __init__(self, cache_key, repo_name=''):
1769 self.cache_key = cache_key
1768 self.cache_key = cache_key
1770 self.cache_args = repo_name
1769 self.cache_args = repo_name
1771 self.cache_active = False
1770 self.cache_active = False
1772
1771
1773 def __unicode__(self):
1772 def __unicode__(self):
1774 return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
1773 return u"<%s('%s:%s[%s]')>" % (self.__class__.__name__,
1775 self.cache_id, self.cache_key, self.cache_active)
1774 self.cache_id, self.cache_key, self.cache_active)
1776
1775
1777 def _cache_key_partition(self):
1776 def _cache_key_partition(self):
1778 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
1777 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
1779 return prefix, repo_name, suffix
1778 return prefix, repo_name, suffix
1780
1779
1781 def get_prefix(self):
1780 def get_prefix(self):
1782 """
1781 """
1783 get prefix that might have been used in _get_cache_key to
1782 get prefix that might have been used in _get_cache_key to
1784 generate self.cache_key. Only used for informational purposes
1783 generate self.cache_key. Only used for informational purposes
1785 in repo_edit.html.
1784 in repo_edit.html.
1786 """
1785 """
1787 # prefix, repo_name, suffix
1786 # prefix, repo_name, suffix
1788 return self._cache_key_partition()[0]
1787 return self._cache_key_partition()[0]
1789
1788
1790 def get_suffix(self):
1789 def get_suffix(self):
1791 """
1790 """
1792 get suffix that might have been used in _get_cache_key to
1791 get suffix that might have been used in _get_cache_key to
1793 generate self.cache_key. Only used for informational purposes
1792 generate self.cache_key. Only used for informational purposes
1794 in repo_edit.html.
1793 in repo_edit.html.
1795 """
1794 """
1796 # prefix, repo_name, suffix
1795 # prefix, repo_name, suffix
1797 return self._cache_key_partition()[2]
1796 return self._cache_key_partition()[2]
1798
1797
1799 @classmethod
1798 @classmethod
1800 def clear_cache(cls):
1799 def clear_cache(cls):
1801 """
1800 """
1802 Delete all cache keys from database.
1801 Delete all cache keys from database.
1803 Should only be run when all instances are down and all entries thus stale.
1802 Should only be run when all instances are down and all entries thus stale.
1804 """
1803 """
1805 cls.query().delete()
1804 cls.query().delete()
1806 Session().commit()
1805 Session().commit()
1807
1806
1808 @classmethod
1807 @classmethod
1809 def _get_cache_key(cls, key):
1808 def _get_cache_key(cls, key):
1810 """
1809 """
1811 Wrapper for generating a unique cache key for this instance and "key".
1810 Wrapper for generating a unique cache key for this instance and "key".
1812 key must / will start with a repo_name which will be stored in .cache_args .
1811 key must / will start with a repo_name which will be stored in .cache_args .
1813 """
1812 """
1814 import rhodecode
1813 import rhodecode
1815 prefix = rhodecode.CONFIG.get('instance_id', '')
1814 prefix = rhodecode.CONFIG.get('instance_id', '')
1816 return "%s%s" % (prefix, key)
1815 return "%s%s" % (prefix, key)
1817
1816
1818 @classmethod
1817 @classmethod
1819 def set_invalidate(cls, repo_name):
1818 def set_invalidate(cls, repo_name):
1820 """
1819 """
1821 Mark all caches of a repo as invalid in the database.
1820 Mark all caches of a repo as invalid in the database.
1822 """
1821 """
1823 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1822 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1824
1823
1825 try:
1824 try:
1826 for inv_obj in inv_objs:
1825 for inv_obj in inv_objs:
1827 log.debug('marking %s key for invalidation based on repo_name=%s'
1826 log.debug('marking %s key for invalidation based on repo_name=%s'
1828 % (inv_obj, safe_str(repo_name)))
1827 % (inv_obj, safe_str(repo_name)))
1829 inv_obj.cache_active = False
1828 inv_obj.cache_active = False
1830 Session().add(inv_obj)
1829 Session().add(inv_obj)
1831 Session().commit()
1830 Session().commit()
1832 except Exception:
1831 except Exception:
1833 log.error(traceback.format_exc())
1832 log.error(traceback.format_exc())
1834 Session().rollback()
1833 Session().rollback()
1835
1834
1836 @classmethod
1835 @classmethod
1837 def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
1836 def test_and_set_valid(cls, repo_name, kind, valid_cache_keys=None):
1838 """
1837 """
1839 Mark this cache key as active and currently cached.
1838 Mark this cache key as active and currently cached.
1840 Return True if the existing cache registration still was valid.
1839 Return True if the existing cache registration still was valid.
1841 Return False to indicate that it had been invalidated and caches should be refreshed.
1840 Return False to indicate that it had been invalidated and caches should be refreshed.
1842 """
1841 """
1843
1842
1844 key = (repo_name + '_' + kind) if kind else repo_name
1843 key = (repo_name + '_' + kind) if kind else repo_name
1845 cache_key = cls._get_cache_key(key)
1844 cache_key = cls._get_cache_key(key)
1846
1845
1847 if valid_cache_keys and cache_key in valid_cache_keys:
1846 if valid_cache_keys and cache_key in valid_cache_keys:
1848 return True
1847 return True
1849
1848
1850 try:
1849 try:
1851 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
1850 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
1852 if not inv_obj:
1851 if not inv_obj:
1853 inv_obj = CacheInvalidation(cache_key, repo_name)
1852 inv_obj = CacheInvalidation(cache_key, repo_name)
1854 was_valid = inv_obj.cache_active
1853 was_valid = inv_obj.cache_active
1855 inv_obj.cache_active = True
1854 inv_obj.cache_active = True
1856 Session().add(inv_obj)
1855 Session().add(inv_obj)
1857 Session().commit()
1856 Session().commit()
1858 return was_valid
1857 return was_valid
1859 except Exception:
1858 except Exception:
1860 log.error(traceback.format_exc())
1859 log.error(traceback.format_exc())
1861 Session().rollback()
1860 Session().rollback()
1862 return False
1861 return False
1863
1862
1864 @classmethod
1863 @classmethod
1865 def get_valid_cache_keys(cls):
1864 def get_valid_cache_keys(cls):
1866 """
1865 """
1867 Return opaque object with information of which caches still are valid
1866 Return opaque object with information of which caches still are valid
1868 and can be used without checking for invalidation.
1867 and can be used without checking for invalidation.
1869 """
1868 """
1870 return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
1869 return set(inv_obj.cache_key for inv_obj in cls.query().filter(cls.cache_active).all())
1871
1870
1872
1871
1873 class ChangesetComment(Base, BaseModel):
1872 class ChangesetComment(Base, BaseModel):
1874 __tablename__ = 'changeset_comments'
1873 __tablename__ = 'changeset_comments'
1875 __table_args__ = (
1874 __table_args__ = (
1876 Index('cc_revision_idx', 'revision'),
1875 Index('cc_revision_idx', 'revision'),
1877 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1876 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1878 'mysql_charset': 'utf8'},
1877 'mysql_charset': 'utf8'},
1879 )
1878 )
1880 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1879 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1881 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1880 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1882 revision = Column('revision', String(40), nullable=True)
1881 revision = Column('revision', String(40), nullable=True)
1883 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1882 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1884 line_no = Column('line_no', Unicode(10), nullable=True)
1883 line_no = Column('line_no', Unicode(10), nullable=True)
1885 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1884 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1886 f_path = Column('f_path', Unicode(1000), nullable=True)
1885 f_path = Column('f_path', Unicode(1000), nullable=True)
1887 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1886 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1888 text = Column('text', UnicodeText(25000), nullable=False)
1887 text = Column('text', UnicodeText(25000), nullable=False)
1889 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1888 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1890 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1889 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1891
1890
1892 author = relationship('User', lazy='joined')
1891 author = relationship('User', lazy='joined')
1893 repo = relationship('Repository')
1892 repo = relationship('Repository')
1894 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1893 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1895 pull_request = relationship('PullRequest', lazy='joined')
1894 pull_request = relationship('PullRequest', lazy='joined')
1896
1895
1897 @classmethod
1896 @classmethod
1898 def get_users(cls, revision=None, pull_request_id=None):
1897 def get_users(cls, revision=None, pull_request_id=None):
1899 """
1898 """
1900 Returns user associated with this ChangesetComment. ie those
1899 Returns user associated with this ChangesetComment. ie those
1901 who actually commented
1900 who actually commented
1902
1901
1903 :param cls:
1902 :param cls:
1904 :param revision:
1903 :param revision:
1905 """
1904 """
1906 q = Session().query(User)\
1905 q = Session().query(User)\
1907 .join(ChangesetComment.author)
1906 .join(ChangesetComment.author)
1908 if revision:
1907 if revision:
1909 q = q.filter(cls.revision == revision)
1908 q = q.filter(cls.revision == revision)
1910 elif pull_request_id:
1909 elif pull_request_id:
1911 q = q.filter(cls.pull_request_id == pull_request_id)
1910 q = q.filter(cls.pull_request_id == pull_request_id)
1912 return q.all()
1911 return q.all()
1913
1912
1914
1913
1915 class ChangesetStatus(Base, BaseModel):
1914 class ChangesetStatus(Base, BaseModel):
1916 __tablename__ = 'changeset_statuses'
1915 __tablename__ = 'changeset_statuses'
1917 __table_args__ = (
1916 __table_args__ = (
1918 Index('cs_revision_idx', 'revision'),
1917 Index('cs_revision_idx', 'revision'),
1919 Index('cs_version_idx', 'version'),
1918 Index('cs_version_idx', 'version'),
1920 UniqueConstraint('repo_id', 'revision', 'version'),
1919 UniqueConstraint('repo_id', 'revision', 'version'),
1921 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1920 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1922 'mysql_charset': 'utf8'}
1921 'mysql_charset': 'utf8'}
1923 )
1922 )
1924 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1923 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1925 STATUS_APPROVED = 'approved'
1924 STATUS_APPROVED = 'approved'
1926 STATUS_REJECTED = 'rejected'
1925 STATUS_REJECTED = 'rejected'
1927 STATUS_UNDER_REVIEW = 'under_review'
1926 STATUS_UNDER_REVIEW = 'under_review'
1928
1927
1929 STATUSES = [
1928 STATUSES = [
1930 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1929 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1931 (STATUS_APPROVED, _("Approved")),
1930 (STATUS_APPROVED, _("Approved")),
1932 (STATUS_REJECTED, _("Rejected")),
1931 (STATUS_REJECTED, _("Rejected")),
1933 (STATUS_UNDER_REVIEW, _("Under Review")),
1932 (STATUS_UNDER_REVIEW, _("Under Review")),
1934 ]
1933 ]
1935
1934
1936 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1935 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1937 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1936 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1938 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1937 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1939 revision = Column('revision', String(40), nullable=False)
1938 revision = Column('revision', String(40), nullable=False)
1940 status = Column('status', String(128), nullable=False, default=DEFAULT)
1939 status = Column('status', String(128), nullable=False, default=DEFAULT)
1941 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1940 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1942 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1941 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1943 version = Column('version', Integer(), nullable=False, default=0)
1942 version = Column('version', Integer(), nullable=False, default=0)
1944 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1943 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1945
1944
1946 author = relationship('User', lazy='joined')
1945 author = relationship('User', lazy='joined')
1947 repo = relationship('Repository')
1946 repo = relationship('Repository')
1948 comment = relationship('ChangesetComment', lazy='joined')
1947 comment = relationship('ChangesetComment', lazy='joined')
1949 pull_request = relationship('PullRequest', lazy='joined')
1948 pull_request = relationship('PullRequest', lazy='joined')
1950
1949
1951 def __unicode__(self):
1950 def __unicode__(self):
1952 return u"<%s('%s:%s')>" % (
1951 return u"<%s('%s:%s')>" % (
1953 self.__class__.__name__,
1952 self.__class__.__name__,
1954 self.status, self.author
1953 self.status, self.author
1955 )
1954 )
1956
1955
1957 @classmethod
1956 @classmethod
1958 def get_status_lbl(cls, value):
1957 def get_status_lbl(cls, value):
1959 return dict(cls.STATUSES).get(value)
1958 return dict(cls.STATUSES).get(value)
1960
1959
1961 @property
1960 @property
1962 def status_lbl(self):
1961 def status_lbl(self):
1963 return ChangesetStatus.get_status_lbl(self.status)
1962 return ChangesetStatus.get_status_lbl(self.status)
1964
1963
1965
1964
1966 class PullRequest(Base, BaseModel):
1965 class PullRequest(Base, BaseModel):
1967 __tablename__ = 'pull_requests'
1966 __tablename__ = 'pull_requests'
1968 __table_args__ = (
1967 __table_args__ = (
1969 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1968 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1970 'mysql_charset': 'utf8'},
1969 'mysql_charset': 'utf8'},
1971 )
1970 )
1972
1971
1973 STATUS_NEW = u'new'
1972 STATUS_NEW = u'new'
1974 STATUS_OPEN = u'open'
1973 STATUS_OPEN = u'open'
1975 STATUS_CLOSED = u'closed'
1974 STATUS_CLOSED = u'closed'
1976
1975
1977 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1976 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1978 title = Column('title', Unicode(256), nullable=True)
1977 title = Column('title', Unicode(256), nullable=True)
1979 description = Column('description', UnicodeText(10240), nullable=True)
1978 description = Column('description', UnicodeText(10240), nullable=True)
1980 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1979 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1981 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1980 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1982 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1981 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1983 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1982 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1984 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1983 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1985 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1984 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1986 org_ref = Column('org_ref', Unicode(256), nullable=False)
1985 org_ref = Column('org_ref', Unicode(256), nullable=False)
1987 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1986 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1988 other_ref = Column('other_ref', Unicode(256), nullable=False)
1987 other_ref = Column('other_ref', Unicode(256), nullable=False)
1989
1988
1990 @hybrid_property
1989 @hybrid_property
1991 def revisions(self):
1990 def revisions(self):
1992 return self._revisions.split(':')
1991 return self._revisions.split(':')
1993
1992
1994 @revisions.setter
1993 @revisions.setter
1995 def revisions(self, val):
1994 def revisions(self, val):
1996 self._revisions = ':'.join(val)
1995 self._revisions = ':'.join(val)
1997
1996
1998 @property
1997 @property
1999 def org_ref_parts(self):
1998 def org_ref_parts(self):
2000 return self.org_ref.split(':')
1999 return self.org_ref.split(':')
2001
2000
2002 @property
2001 @property
2003 def other_ref_parts(self):
2002 def other_ref_parts(self):
2004 return self.other_ref.split(':')
2003 return self.other_ref.split(':')
2005
2004
2006 author = relationship('User', lazy='joined')
2005 author = relationship('User', lazy='joined')
2007 reviewers = relationship('PullRequestReviewers',
2006 reviewers = relationship('PullRequestReviewers',
2008 cascade="all, delete, delete-orphan")
2007 cascade="all, delete, delete-orphan")
2009 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
2008 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
2010 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
2009 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
2011 statuses = relationship('ChangesetStatus')
2010 statuses = relationship('ChangesetStatus')
2012 comments = relationship('ChangesetComment',
2011 comments = relationship('ChangesetComment',
2013 cascade="all, delete, delete-orphan")
2012 cascade="all, delete, delete-orphan")
2014
2013
2015 def is_closed(self):
2014 def is_closed(self):
2016 return self.status == self.STATUS_CLOSED
2015 return self.status == self.STATUS_CLOSED
2017
2016
2018 @property
2017 @property
2019 def last_review_status(self):
2018 def last_review_status(self):
2020 return self.statuses[-1].status if self.statuses else ''
2019 return self.statuses[-1].status if self.statuses else ''
2021
2020
2022 def __json__(self):
2021 def __json__(self):
2023 return dict(
2022 return dict(
2024 revisions=self.revisions
2023 revisions=self.revisions
2025 )
2024 )
2026
2025
2027
2026
2028 class PullRequestReviewers(Base, BaseModel):
2027 class PullRequestReviewers(Base, BaseModel):
2029 __tablename__ = 'pull_request_reviewers'
2028 __tablename__ = 'pull_request_reviewers'
2030 __table_args__ = (
2029 __table_args__ = (
2031 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2030 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2032 'mysql_charset': 'utf8'},
2031 'mysql_charset': 'utf8'},
2033 )
2032 )
2034
2033
2035 def __init__(self, user=None, pull_request=None):
2034 def __init__(self, user=None, pull_request=None):
2036 self.user = user
2035 self.user = user
2037 self.pull_request = pull_request
2036 self.pull_request = pull_request
2038
2037
2039 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
2038 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
2040 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
2039 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
2041 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
2040 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
2042
2041
2043 user = relationship('User')
2042 user = relationship('User')
2044 pull_request = relationship('PullRequest')
2043 pull_request = relationship('PullRequest')
2045
2044
2046
2045
2047 class Notification(Base, BaseModel):
2046 class Notification(Base, BaseModel):
2048 __tablename__ = 'notifications'
2047 __tablename__ = 'notifications'
2049 __table_args__ = (
2048 __table_args__ = (
2050 Index('notification_type_idx', 'type'),
2049 Index('notification_type_idx', 'type'),
2051 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2050 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2052 'mysql_charset': 'utf8'},
2051 'mysql_charset': 'utf8'},
2053 )
2052 )
2054
2053
2055 TYPE_CHANGESET_COMMENT = u'cs_comment'
2054 TYPE_CHANGESET_COMMENT = u'cs_comment'
2056 TYPE_MESSAGE = u'message'
2055 TYPE_MESSAGE = u'message'
2057 TYPE_MENTION = u'mention'
2056 TYPE_MENTION = u'mention'
2058 TYPE_REGISTRATION = u'registration'
2057 TYPE_REGISTRATION = u'registration'
2059 TYPE_PULL_REQUEST = u'pull_request'
2058 TYPE_PULL_REQUEST = u'pull_request'
2060 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
2059 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
2061
2060
2062 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
2061 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
2063 subject = Column('subject', Unicode(512), nullable=True)
2062 subject = Column('subject', Unicode(512), nullable=True)
2064 body = Column('body', UnicodeText(50000), nullable=True)
2063 body = Column('body', UnicodeText(50000), nullable=True)
2065 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
2064 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
2066 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2065 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2067 type_ = Column('type', Unicode(256))
2066 type_ = Column('type', Unicode(256))
2068
2067
2069 created_by_user = relationship('User')
2068 created_by_user = relationship('User')
2070 notifications_to_users = relationship('UserNotification', lazy='joined',
2069 notifications_to_users = relationship('UserNotification', lazy='joined',
2071 cascade="all, delete, delete-orphan")
2070 cascade="all, delete, delete-orphan")
2072
2071
2073 @property
2072 @property
2074 def recipients(self):
2073 def recipients(self):
2075 return [x.user for x in UserNotification.query()\
2074 return [x.user for x in UserNotification.query()\
2076 .filter(UserNotification.notification == self)\
2075 .filter(UserNotification.notification == self)\
2077 .order_by(UserNotification.user_id.asc()).all()]
2076 .order_by(UserNotification.user_id.asc()).all()]
2078
2077
2079 @classmethod
2078 @classmethod
2080 def create(cls, created_by, subject, body, recipients, type_=None):
2079 def create(cls, created_by, subject, body, recipients, type_=None):
2081 if type_ is None:
2080 if type_ is None:
2082 type_ = Notification.TYPE_MESSAGE
2081 type_ = Notification.TYPE_MESSAGE
2083
2082
2084 notification = cls()
2083 notification = cls()
2085 notification.created_by_user = created_by
2084 notification.created_by_user = created_by
2086 notification.subject = subject
2085 notification.subject = subject
2087 notification.body = body
2086 notification.body = body
2088 notification.type_ = type_
2087 notification.type_ = type_
2089 notification.created_on = datetime.datetime.now()
2088 notification.created_on = datetime.datetime.now()
2090
2089
2091 for u in recipients:
2090 for u in recipients:
2092 assoc = UserNotification()
2091 assoc = UserNotification()
2093 assoc.notification = notification
2092 assoc.notification = notification
2094 u.notifications.append(assoc)
2093 u.notifications.append(assoc)
2095 Session().add(notification)
2094 Session().add(notification)
2096 return notification
2095 return notification
2097
2096
2098 @property
2097 @property
2099 def description(self):
2098 def description(self):
2100 from rhodecode.model.notification import NotificationModel
2099 from rhodecode.model.notification import NotificationModel
2101 return NotificationModel().make_description(self)
2100 return NotificationModel().make_description(self)
2102
2101
2103
2102
2104 class UserNotification(Base, BaseModel):
2103 class UserNotification(Base, BaseModel):
2105 __tablename__ = 'user_to_notification'
2104 __tablename__ = 'user_to_notification'
2106 __table_args__ = (
2105 __table_args__ = (
2107 UniqueConstraint('user_id', 'notification_id'),
2106 UniqueConstraint('user_id', 'notification_id'),
2108 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2107 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2109 'mysql_charset': 'utf8'}
2108 'mysql_charset': 'utf8'}
2110 )
2109 )
2111 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2110 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2112 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2111 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2113 read = Column('read', Boolean, default=False)
2112 read = Column('read', Boolean, default=False)
2114 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2113 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2115
2114
2116 user = relationship('User', lazy="joined")
2115 user = relationship('User', lazy="joined")
2117 notification = relationship('Notification', lazy="joined",
2116 notification = relationship('Notification', lazy="joined",
2118 order_by=lambda: Notification.created_on.desc(),)
2117 order_by=lambda: Notification.created_on.desc(),)
2119
2118
2120 def mark_as_read(self):
2119 def mark_as_read(self):
2121 self.read = True
2120 self.read = True
2122 Session().add(self)
2121 Session().add(self)
2123
2122
2124
2123
2124 class Gist(Base, BaseModel):
2125 __tablename__ = 'gists'
2126 __table_args__ = (
2127 Index('g_gist_access_id_idx', 'gist_access_id'),
2128 Index('g_created_on_idx', 'created_on'),
2129 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2130 'mysql_charset': 'utf8'}
2131 )
2132 GIST_PUBLIC = u'public'
2133 GIST_PRIVATE = u'private'
2134
2135 gist_id = Column('gist_id', Integer(), primary_key=True)
2136 gist_access_id = Column('gist_access_id', UnicodeText(1024))
2137 gist_description = Column('gist_description', UnicodeText(1024))
2138 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
2139 gist_expires = Column('gist_expires', Float(), nullable=False)
2140 gist_type = Column('gist_type', Unicode(128), nullable=False)
2141 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2142 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2143
2144 owner = relationship('User')
2145
2146 @classmethod
2147 def get_or_404(cls, id_):
2148 res = cls.query().filter(cls.gist_access_id == id_).scalar()
2149 if not res:
2150 raise HTTPNotFound
2151 return res
2152
2153 @classmethod
2154 def get_by_access_id(cls, gist_access_id):
2155 return cls.query().filter(cls.gist_access_id==gist_access_id).scalar()
2156
2157 def gist_url(self):
2158 from pylons import url
2159 return url('gist', id=self.gist_access_id, qualified=True)
2160
2161
2125 class DbMigrateVersion(Base, BaseModel):
2162 class DbMigrateVersion(Base, BaseModel):
2126 __tablename__ = 'db_migrate_version'
2163 __tablename__ = 'db_migrate_version'
2127 __table_args__ = (
2164 __table_args__ = (
2128 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2165 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2129 'mysql_charset': 'utf8'},
2166 'mysql_charset': 'utf8'},
2130 )
2167 )
2131 repository_id = Column('repository_id', String(250), primary_key=True)
2168 repository_id = Column('repository_id', String(250), primary_key=True)
2132 repository_path = Column('repository_path', Text)
2169 repository_path = Column('repository_path', Text)
2133 version = Column('version', Integer)
2170 version = Column('version', Integer)
@@ -1,421 +1,434 b''
1 """ this is forms validation classes
1 """ this is forms validation classes
2 http://formencode.org/module-formencode.validators.html
2 http://formencode.org/module-formencode.validators.html
3 for list off all availible validators
3 for list off all availible validators
4
4
5 we can create our own validators
5 we can create our own validators
6
6
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 pre_validators [] These validators will be applied before the schema
8 pre_validators [] These validators will be applied before the schema
9 chained_validators [] These validators will be applied after the schema
9 chained_validators [] These validators will be applied after the schema
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14
14
15
15
16 <name> = formencode.validators.<name of validator>
16 <name> = formencode.validators.<name of validator>
17 <name> must equal form name
17 <name> must equal form name
18 list=[1,2,3,4,5]
18 list=[1,2,3,4,5]
19 for SELECT use formencode.All(OneOf(list), Int())
19 for SELECT use formencode.All(OneOf(list), Int())
20
20
21 """
21 """
22 import logging
22 import logging
23
23
24 import formencode
24 import formencode
25 from formencode import All
25 from formencode import All
26
26
27 from pylons.i18n.translation import _
27 from pylons.i18n.translation import _
28
28
29 from rhodecode.model import validators as v
29 from rhodecode.model import validators as v
30 from rhodecode import BACKENDS
30 from rhodecode import BACKENDS
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34
34
35 class LoginForm(formencode.Schema):
35 class LoginForm(formencode.Schema):
36 allow_extra_fields = True
36 allow_extra_fields = True
37 filter_extra_fields = True
37 filter_extra_fields = True
38 username = v.UnicodeString(
38 username = v.UnicodeString(
39 strip=True,
39 strip=True,
40 min=1,
40 min=1,
41 not_empty=True,
41 not_empty=True,
42 messages={
42 messages={
43 'empty': _(u'Please enter a login'),
43 'empty': _(u'Please enter a login'),
44 'tooShort': _(u'Enter a value %(min)i characters long or more')}
44 'tooShort': _(u'Enter a value %(min)i characters long or more')}
45 )
45 )
46
46
47 password = v.UnicodeString(
47 password = v.UnicodeString(
48 strip=False,
48 strip=False,
49 min=3,
49 min=3,
50 not_empty=True,
50 not_empty=True,
51 messages={
51 messages={
52 'empty': _(u'Please enter a password'),
52 'empty': _(u'Please enter a password'),
53 'tooShort': _(u'Enter %(min)i characters or more')}
53 'tooShort': _(u'Enter %(min)i characters or more')}
54 )
54 )
55
55
56 remember = v.StringBoolean(if_missing=False)
56 remember = v.StringBoolean(if_missing=False)
57
57
58 chained_validators = [v.ValidAuth()]
58 chained_validators = [v.ValidAuth()]
59
59
60
60
61 def UserForm(edit=False, old_data={}):
61 def UserForm(edit=False, old_data={}):
62 class _UserForm(formencode.Schema):
62 class _UserForm(formencode.Schema):
63 allow_extra_fields = True
63 allow_extra_fields = True
64 filter_extra_fields = True
64 filter_extra_fields = True
65 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
65 username = All(v.UnicodeString(strip=True, min=1, not_empty=True),
66 v.ValidUsername(edit, old_data))
66 v.ValidUsername(edit, old_data))
67 if edit:
67 if edit:
68 new_password = All(
68 new_password = All(
69 v.ValidPassword(),
69 v.ValidPassword(),
70 v.UnicodeString(strip=False, min=6, not_empty=False)
70 v.UnicodeString(strip=False, min=6, not_empty=False)
71 )
71 )
72 password_confirmation = All(
72 password_confirmation = All(
73 v.ValidPassword(),
73 v.ValidPassword(),
74 v.UnicodeString(strip=False, min=6, not_empty=False),
74 v.UnicodeString(strip=False, min=6, not_empty=False),
75 )
75 )
76 admin = v.StringBoolean(if_missing=False)
76 admin = v.StringBoolean(if_missing=False)
77 else:
77 else:
78 password = All(
78 password = All(
79 v.ValidPassword(),
79 v.ValidPassword(),
80 v.UnicodeString(strip=False, min=6, not_empty=True)
80 v.UnicodeString(strip=False, min=6, not_empty=True)
81 )
81 )
82 password_confirmation = All(
82 password_confirmation = All(
83 v.ValidPassword(),
83 v.ValidPassword(),
84 v.UnicodeString(strip=False, min=6, not_empty=False)
84 v.UnicodeString(strip=False, min=6, not_empty=False)
85 )
85 )
86
86
87 active = v.StringBoolean(if_missing=False)
87 active = v.StringBoolean(if_missing=False)
88 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
88 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
89 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
89 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
90 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
90 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
91
91
92 chained_validators = [v.ValidPasswordsMatch()]
92 chained_validators = [v.ValidPasswordsMatch()]
93
93
94 return _UserForm
94 return _UserForm
95
95
96
96
97 def UserGroupForm(edit=False, old_data={}, available_members=[]):
97 def UserGroupForm(edit=False, old_data={}, available_members=[]):
98 class _UserGroupForm(formencode.Schema):
98 class _UserGroupForm(formencode.Schema):
99 allow_extra_fields = True
99 allow_extra_fields = True
100 filter_extra_fields = True
100 filter_extra_fields = True
101
101
102 users_group_name = All(
102 users_group_name = All(
103 v.UnicodeString(strip=True, min=1, not_empty=True),
103 v.UnicodeString(strip=True, min=1, not_empty=True),
104 v.ValidUserGroup(edit, old_data)
104 v.ValidUserGroup(edit, old_data)
105 )
105 )
106
106
107 users_group_active = v.StringBoolean(if_missing=False)
107 users_group_active = v.StringBoolean(if_missing=False)
108
108
109 if edit:
109 if edit:
110 users_group_members = v.OneOf(
110 users_group_members = v.OneOf(
111 available_members, hideList=False, testValueList=True,
111 available_members, hideList=False, testValueList=True,
112 if_missing=None, not_empty=False
112 if_missing=None, not_empty=False
113 )
113 )
114
114
115 return _UserGroupForm
115 return _UserGroupForm
116
116
117
117
118 def ReposGroupForm(edit=False, old_data={}, available_groups=[],
118 def ReposGroupForm(edit=False, old_data={}, available_groups=[],
119 can_create_in_root=False):
119 can_create_in_root=False):
120 class _ReposGroupForm(formencode.Schema):
120 class _ReposGroupForm(formencode.Schema):
121 allow_extra_fields = True
121 allow_extra_fields = True
122 filter_extra_fields = False
122 filter_extra_fields = False
123
123
124 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
124 group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
125 v.SlugifyName())
125 v.SlugifyName())
126 group_description = v.UnicodeString(strip=True, min=1,
126 group_description = v.UnicodeString(strip=True, min=1,
127 not_empty=False)
127 not_empty=False)
128 if edit:
128 if edit:
129 #FIXME: do a special check that we cannot move a group to one of
129 #FIXME: do a special check that we cannot move a group to one of
130 #it's children
130 #it's children
131 pass
131 pass
132 group_parent_id = All(v.CanCreateGroup(can_create_in_root),
132 group_parent_id = All(v.CanCreateGroup(can_create_in_root),
133 v.OneOf(available_groups, hideList=False,
133 v.OneOf(available_groups, hideList=False,
134 testValueList=True,
134 testValueList=True,
135 if_missing=None, not_empty=True))
135 if_missing=None, not_empty=True))
136 enable_locking = v.StringBoolean(if_missing=False)
136 enable_locking = v.StringBoolean(if_missing=False)
137 chained_validators = [v.ValidReposGroup(edit, old_data)]
137 chained_validators = [v.ValidReposGroup(edit, old_data)]
138
138
139 return _ReposGroupForm
139 return _ReposGroupForm
140
140
141
141
142 def RegisterForm(edit=False, old_data={}):
142 def RegisterForm(edit=False, old_data={}):
143 class _RegisterForm(formencode.Schema):
143 class _RegisterForm(formencode.Schema):
144 allow_extra_fields = True
144 allow_extra_fields = True
145 filter_extra_fields = True
145 filter_extra_fields = True
146 username = All(
146 username = All(
147 v.ValidUsername(edit, old_data),
147 v.ValidUsername(edit, old_data),
148 v.UnicodeString(strip=True, min=1, not_empty=True)
148 v.UnicodeString(strip=True, min=1, not_empty=True)
149 )
149 )
150 password = All(
150 password = All(
151 v.ValidPassword(),
151 v.ValidPassword(),
152 v.UnicodeString(strip=False, min=6, not_empty=True)
152 v.UnicodeString(strip=False, min=6, not_empty=True)
153 )
153 )
154 password_confirmation = All(
154 password_confirmation = All(
155 v.ValidPassword(),
155 v.ValidPassword(),
156 v.UnicodeString(strip=False, min=6, not_empty=True)
156 v.UnicodeString(strip=False, min=6, not_empty=True)
157 )
157 )
158 active = v.StringBoolean(if_missing=False)
158 active = v.StringBoolean(if_missing=False)
159 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
159 firstname = v.UnicodeString(strip=True, min=1, not_empty=False)
160 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
160 lastname = v.UnicodeString(strip=True, min=1, not_empty=False)
161 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
161 email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data))
162
162
163 chained_validators = [v.ValidPasswordsMatch()]
163 chained_validators = [v.ValidPasswordsMatch()]
164
164
165 return _RegisterForm
165 return _RegisterForm
166
166
167
167
168 def PasswordResetForm():
168 def PasswordResetForm():
169 class _PasswordResetForm(formencode.Schema):
169 class _PasswordResetForm(formencode.Schema):
170 allow_extra_fields = True
170 allow_extra_fields = True
171 filter_extra_fields = True
171 filter_extra_fields = True
172 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
172 email = All(v.ValidSystemEmail(), v.Email(not_empty=True))
173 return _PasswordResetForm
173 return _PasswordResetForm
174
174
175
175
176 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
176 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
177 repo_groups=[], landing_revs=[]):
177 repo_groups=[], landing_revs=[]):
178 class _RepoForm(formencode.Schema):
178 class _RepoForm(formencode.Schema):
179 allow_extra_fields = True
179 allow_extra_fields = True
180 filter_extra_fields = False
180 filter_extra_fields = False
181 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
181 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
182 v.SlugifyName())
182 v.SlugifyName())
183 repo_group = All(v.CanWriteGroup(old_data),
183 repo_group = All(v.CanWriteGroup(old_data),
184 v.OneOf(repo_groups, hideList=True))
184 v.OneOf(repo_groups, hideList=True))
185 repo_type = v.OneOf(supported_backends)
185 repo_type = v.OneOf(supported_backends)
186 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
186 repo_description = v.UnicodeString(strip=True, min=1, not_empty=False)
187 repo_private = v.StringBoolean(if_missing=False)
187 repo_private = v.StringBoolean(if_missing=False)
188 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
188 repo_landing_rev = v.OneOf(landing_revs, hideList=True)
189 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
189 clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False))
190
190
191 repo_enable_statistics = v.StringBoolean(if_missing=False)
191 repo_enable_statistics = v.StringBoolean(if_missing=False)
192 repo_enable_downloads = v.StringBoolean(if_missing=False)
192 repo_enable_downloads = v.StringBoolean(if_missing=False)
193 repo_enable_locking = v.StringBoolean(if_missing=False)
193 repo_enable_locking = v.StringBoolean(if_missing=False)
194
194
195 if edit:
195 if edit:
196 #this is repo owner
196 #this is repo owner
197 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
197 user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser())
198
198
199 chained_validators = [v.ValidCloneUri(),
199 chained_validators = [v.ValidCloneUri(),
200 v.ValidRepoName(edit, old_data)]
200 v.ValidRepoName(edit, old_data)]
201 return _RepoForm
201 return _RepoForm
202
202
203
203
204 def RepoPermsForm():
204 def RepoPermsForm():
205 class _RepoPermsForm(formencode.Schema):
205 class _RepoPermsForm(formencode.Schema):
206 allow_extra_fields = True
206 allow_extra_fields = True
207 filter_extra_fields = False
207 filter_extra_fields = False
208 chained_validators = [v.ValidPerms(type_='repo')]
208 chained_validators = [v.ValidPerms(type_='repo')]
209 return _RepoPermsForm
209 return _RepoPermsForm
210
210
211
211
212 def RepoGroupPermsForm():
212 def RepoGroupPermsForm():
213 class _RepoGroupPermsForm(formencode.Schema):
213 class _RepoGroupPermsForm(formencode.Schema):
214 allow_extra_fields = True
214 allow_extra_fields = True
215 filter_extra_fields = False
215 filter_extra_fields = False
216 recursive = v.StringBoolean(if_missing=False)
216 recursive = v.StringBoolean(if_missing=False)
217 chained_validators = [v.ValidPerms(type_='repo_group')]
217 chained_validators = [v.ValidPerms(type_='repo_group')]
218 return _RepoGroupPermsForm
218 return _RepoGroupPermsForm
219
219
220
220
221 def UserGroupPermsForm():
221 def UserGroupPermsForm():
222 class _UserPermsForm(formencode.Schema):
222 class _UserPermsForm(formencode.Schema):
223 allow_extra_fields = True
223 allow_extra_fields = True
224 filter_extra_fields = False
224 filter_extra_fields = False
225 chained_validators = [v.ValidPerms(type_='user_group')]
225 chained_validators = [v.ValidPerms(type_='user_group')]
226 return _UserPermsForm
226 return _UserPermsForm
227
227
228
228
229 def RepoFieldForm():
229 def RepoFieldForm():
230 class _RepoFieldForm(formencode.Schema):
230 class _RepoFieldForm(formencode.Schema):
231 filter_extra_fields = True
231 filter_extra_fields = True
232 allow_extra_fields = True
232 allow_extra_fields = True
233
233
234 new_field_key = All(v.FieldKey(),
234 new_field_key = All(v.FieldKey(),
235 v.UnicodeString(strip=True, min=3, not_empty=True))
235 v.UnicodeString(strip=True, min=3, not_empty=True))
236 new_field_value = v.UnicodeString(not_empty=False, if_missing='')
236 new_field_value = v.UnicodeString(not_empty=False, if_missing='')
237 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
237 new_field_type = v.OneOf(['str', 'unicode', 'list', 'tuple'],
238 if_missing='str')
238 if_missing='str')
239 new_field_label = v.UnicodeString(not_empty=False)
239 new_field_label = v.UnicodeString(not_empty=False)
240 new_field_desc = v.UnicodeString(not_empty=False)
240 new_field_desc = v.UnicodeString(not_empty=False)
241
241
242 return _RepoFieldForm
242 return _RepoFieldForm
243
243
244
244
245 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
245 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
246 repo_groups=[], landing_revs=[]):
246 repo_groups=[], landing_revs=[]):
247 class _RepoForkForm(formencode.Schema):
247 class _RepoForkForm(formencode.Schema):
248 allow_extra_fields = True
248 allow_extra_fields = True
249 filter_extra_fields = False
249 filter_extra_fields = False
250 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
250 repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True),
251 v.SlugifyName())
251 v.SlugifyName())
252 repo_group = All(v.CanWriteGroup(),
252 repo_group = All(v.CanWriteGroup(),
253 v.OneOf(repo_groups, hideList=True))
253 v.OneOf(repo_groups, hideList=True))
254 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
254 repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends))
255 description = v.UnicodeString(strip=True, min=1, not_empty=True)
255 description = v.UnicodeString(strip=True, min=1, not_empty=True)
256 private = v.StringBoolean(if_missing=False)
256 private = v.StringBoolean(if_missing=False)
257 copy_permissions = v.StringBoolean(if_missing=False)
257 copy_permissions = v.StringBoolean(if_missing=False)
258 update_after_clone = v.StringBoolean(if_missing=False)
258 update_after_clone = v.StringBoolean(if_missing=False)
259 fork_parent_id = v.UnicodeString()
259 fork_parent_id = v.UnicodeString()
260 chained_validators = [v.ValidForkName(edit, old_data)]
260 chained_validators = [v.ValidForkName(edit, old_data)]
261 landing_rev = v.OneOf(landing_revs, hideList=True)
261 landing_rev = v.OneOf(landing_revs, hideList=True)
262
262
263 return _RepoForkForm
263 return _RepoForkForm
264
264
265
265
266 def ApplicationSettingsForm():
266 def ApplicationSettingsForm():
267 class _ApplicationSettingsForm(formencode.Schema):
267 class _ApplicationSettingsForm(formencode.Schema):
268 allow_extra_fields = True
268 allow_extra_fields = True
269 filter_extra_fields = False
269 filter_extra_fields = False
270 rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True)
270 rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True)
271 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
271 rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
272 rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
272 rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
273
273
274 return _ApplicationSettingsForm
274 return _ApplicationSettingsForm
275
275
276
276
277 def ApplicationVisualisationForm():
277 def ApplicationVisualisationForm():
278 class _ApplicationVisualisationForm(formencode.Schema):
278 class _ApplicationVisualisationForm(formencode.Schema):
279 allow_extra_fields = True
279 allow_extra_fields = True
280 filter_extra_fields = False
280 filter_extra_fields = False
281 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
281 rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
282 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
282 rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
283 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
283 rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
284
284
285 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
285 rhodecode_repository_fields = v.StringBoolean(if_missing=False)
286 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
286 rhodecode_lightweight_journal = v.StringBoolean(if_missing=False)
287
287
288 return _ApplicationVisualisationForm
288 return _ApplicationVisualisationForm
289
289
290
290
291 def ApplicationUiSettingsForm():
291 def ApplicationUiSettingsForm():
292 class _ApplicationUiSettingsForm(formencode.Schema):
292 class _ApplicationUiSettingsForm(formencode.Schema):
293 allow_extra_fields = True
293 allow_extra_fields = True
294 filter_extra_fields = False
294 filter_extra_fields = False
295 web_push_ssl = v.StringBoolean(if_missing=False)
295 web_push_ssl = v.StringBoolean(if_missing=False)
296 paths_root_path = All(
296 paths_root_path = All(
297 v.ValidPath(),
297 v.ValidPath(),
298 v.UnicodeString(strip=True, min=1, not_empty=True)
298 v.UnicodeString(strip=True, min=1, not_empty=True)
299 )
299 )
300 hooks_changegroup_update = v.StringBoolean(if_missing=False)
300 hooks_changegroup_update = v.StringBoolean(if_missing=False)
301 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
301 hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
302 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
302 hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
303 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
303 hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False)
304
304
305 extensions_largefiles = v.StringBoolean(if_missing=False)
305 extensions_largefiles = v.StringBoolean(if_missing=False)
306 extensions_hgsubversion = v.StringBoolean(if_missing=False)
306 extensions_hgsubversion = v.StringBoolean(if_missing=False)
307 extensions_hggit = v.StringBoolean(if_missing=False)
307 extensions_hggit = v.StringBoolean(if_missing=False)
308
308
309 return _ApplicationUiSettingsForm
309 return _ApplicationUiSettingsForm
310
310
311
311
312 def DefaultPermissionsForm(repo_perms_choices, group_perms_choices,
312 def DefaultPermissionsForm(repo_perms_choices, group_perms_choices,
313 user_group_perms_choices, create_choices,
313 user_group_perms_choices, create_choices,
314 repo_group_create_choices, user_group_create_choices,
314 repo_group_create_choices, user_group_create_choices,
315 fork_choices, register_choices, extern_activate_choices):
315 fork_choices, register_choices, extern_activate_choices):
316 class _DefaultPermissionsForm(formencode.Schema):
316 class _DefaultPermissionsForm(formencode.Schema):
317 allow_extra_fields = True
317 allow_extra_fields = True
318 filter_extra_fields = True
318 filter_extra_fields = True
319 overwrite_default_repo = v.StringBoolean(if_missing=False)
319 overwrite_default_repo = v.StringBoolean(if_missing=False)
320 overwrite_default_group = v.StringBoolean(if_missing=False)
320 overwrite_default_group = v.StringBoolean(if_missing=False)
321 overwrite_default_user_group = v.StringBoolean(if_missing=False)
321 overwrite_default_user_group = v.StringBoolean(if_missing=False)
322 anonymous = v.StringBoolean(if_missing=False)
322 anonymous = v.StringBoolean(if_missing=False)
323 default_repo_perm = v.OneOf(repo_perms_choices)
323 default_repo_perm = v.OneOf(repo_perms_choices)
324 default_group_perm = v.OneOf(group_perms_choices)
324 default_group_perm = v.OneOf(group_perms_choices)
325 default_user_group_perm = v.OneOf(user_group_perms_choices)
325 default_user_group_perm = v.OneOf(user_group_perms_choices)
326
326
327 default_repo_create = v.OneOf(create_choices)
327 default_repo_create = v.OneOf(create_choices)
328 default_user_group_create = v.OneOf(user_group_create_choices)
328 default_user_group_create = v.OneOf(user_group_create_choices)
329 #default_repo_group_create = v.OneOf(repo_group_create_choices) #not impl. yet
329 #default_repo_group_create = v.OneOf(repo_group_create_choices) #not impl. yet
330 default_fork = v.OneOf(fork_choices)
330 default_fork = v.OneOf(fork_choices)
331
331
332 default_register = v.OneOf(register_choices)
332 default_register = v.OneOf(register_choices)
333 default_extern_activate = v.OneOf(extern_activate_choices)
333 default_extern_activate = v.OneOf(extern_activate_choices)
334 return _DefaultPermissionsForm
334 return _DefaultPermissionsForm
335
335
336
336
337 def CustomDefaultPermissionsForm():
337 def CustomDefaultPermissionsForm():
338 class _CustomDefaultPermissionsForm(formencode.Schema):
338 class _CustomDefaultPermissionsForm(formencode.Schema):
339 filter_extra_fields = True
339 filter_extra_fields = True
340 allow_extra_fields = True
340 allow_extra_fields = True
341 inherit_default_permissions = v.StringBoolean(if_missing=False)
341 inherit_default_permissions = v.StringBoolean(if_missing=False)
342
342
343 create_repo_perm = v.StringBoolean(if_missing=False)
343 create_repo_perm = v.StringBoolean(if_missing=False)
344 create_user_group_perm = v.StringBoolean(if_missing=False)
344 create_user_group_perm = v.StringBoolean(if_missing=False)
345 #create_repo_group_perm Impl. later
345 #create_repo_group_perm Impl. later
346
346
347 fork_repo_perm = v.StringBoolean(if_missing=False)
347 fork_repo_perm = v.StringBoolean(if_missing=False)
348
348
349 return _CustomDefaultPermissionsForm
349 return _CustomDefaultPermissionsForm
350
350
351
351
352 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
352 def DefaultsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
353 class _DefaultsForm(formencode.Schema):
353 class _DefaultsForm(formencode.Schema):
354 allow_extra_fields = True
354 allow_extra_fields = True
355 filter_extra_fields = True
355 filter_extra_fields = True
356 default_repo_type = v.OneOf(supported_backends)
356 default_repo_type = v.OneOf(supported_backends)
357 default_repo_private = v.StringBoolean(if_missing=False)
357 default_repo_private = v.StringBoolean(if_missing=False)
358 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
358 default_repo_enable_statistics = v.StringBoolean(if_missing=False)
359 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
359 default_repo_enable_downloads = v.StringBoolean(if_missing=False)
360 default_repo_enable_locking = v.StringBoolean(if_missing=False)
360 default_repo_enable_locking = v.StringBoolean(if_missing=False)
361
361
362 return _DefaultsForm
362 return _DefaultsForm
363
363
364
364
365 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
365 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
366 tls_kind_choices):
366 tls_kind_choices):
367 class _LdapSettingsForm(formencode.Schema):
367 class _LdapSettingsForm(formencode.Schema):
368 allow_extra_fields = True
368 allow_extra_fields = True
369 filter_extra_fields = True
369 filter_extra_fields = True
370 #pre_validators = [LdapLibValidator]
370 #pre_validators = [LdapLibValidator]
371 ldap_active = v.StringBoolean(if_missing=False)
371 ldap_active = v.StringBoolean(if_missing=False)
372 ldap_host = v.UnicodeString(strip=True,)
372 ldap_host = v.UnicodeString(strip=True,)
373 ldap_port = v.Number(strip=True,)
373 ldap_port = v.Number(strip=True,)
374 ldap_tls_kind = v.OneOf(tls_kind_choices)
374 ldap_tls_kind = v.OneOf(tls_kind_choices)
375 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
375 ldap_tls_reqcert = v.OneOf(tls_reqcert_choices)
376 ldap_dn_user = v.UnicodeString(strip=True,)
376 ldap_dn_user = v.UnicodeString(strip=True,)
377 ldap_dn_pass = v.UnicodeString(strip=True,)
377 ldap_dn_pass = v.UnicodeString(strip=True,)
378 ldap_base_dn = v.UnicodeString(strip=True,)
378 ldap_base_dn = v.UnicodeString(strip=True,)
379 ldap_filter = v.UnicodeString(strip=True,)
379 ldap_filter = v.UnicodeString(strip=True,)
380 ldap_search_scope = v.OneOf(search_scope_choices)
380 ldap_search_scope = v.OneOf(search_scope_choices)
381 ldap_attr_login = v.AttrLoginValidator()(not_empty=True)
381 ldap_attr_login = v.AttrLoginValidator()(not_empty=True)
382 ldap_attr_firstname = v.UnicodeString(strip=True,)
382 ldap_attr_firstname = v.UnicodeString(strip=True,)
383 ldap_attr_lastname = v.UnicodeString(strip=True,)
383 ldap_attr_lastname = v.UnicodeString(strip=True,)
384 ldap_attr_email = v.UnicodeString(strip=True,)
384 ldap_attr_email = v.UnicodeString(strip=True,)
385
385
386 return _LdapSettingsForm
386 return _LdapSettingsForm
387
387
388
388
389 def UserExtraEmailForm():
389 def UserExtraEmailForm():
390 class _UserExtraEmailForm(formencode.Schema):
390 class _UserExtraEmailForm(formencode.Schema):
391 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
391 email = All(v.UniqSystemEmail(), v.Email(not_empty=True))
392 return _UserExtraEmailForm
392 return _UserExtraEmailForm
393
393
394
394
395 def UserExtraIpForm():
395 def UserExtraIpForm():
396 class _UserExtraIpForm(formencode.Schema):
396 class _UserExtraIpForm(formencode.Schema):
397 ip = v.ValidIp()(not_empty=True)
397 ip = v.ValidIp()(not_empty=True)
398 return _UserExtraIpForm
398 return _UserExtraIpForm
399
399
400
400
401 def PullRequestForm(repo_id):
401 def PullRequestForm(repo_id):
402 class _PullRequestForm(formencode.Schema):
402 class _PullRequestForm(formencode.Schema):
403 allow_extra_fields = True
403 allow_extra_fields = True
404 filter_extra_fields = True
404 filter_extra_fields = True
405
405
406 user = v.UnicodeString(strip=True, required=True)
406 user = v.UnicodeString(strip=True, required=True)
407 org_repo = v.UnicodeString(strip=True, required=True)
407 org_repo = v.UnicodeString(strip=True, required=True)
408 org_ref = v.UnicodeString(strip=True, required=True)
408 org_ref = v.UnicodeString(strip=True, required=True)
409 other_repo = v.UnicodeString(strip=True, required=True)
409 other_repo = v.UnicodeString(strip=True, required=True)
410 other_ref = v.UnicodeString(strip=True, required=True)
410 other_ref = v.UnicodeString(strip=True, required=True)
411 revisions = All(#v.NotReviewedRevisions(repo_id)(),
411 revisions = All(#v.NotReviewedRevisions(repo_id)(),
412 v.UniqueList(not_empty=True))
412 v.UniqueList(not_empty=True))
413 review_members = v.UniqueList(not_empty=True)
413 review_members = v.UniqueList(not_empty=True)
414
414
415 pullrequest_title = v.UnicodeString(strip=True, required=True, min=3)
415 pullrequest_title = v.UnicodeString(strip=True, required=True, min=3)
416 pullrequest_desc = v.UnicodeString(strip=True, required=False)
416 pullrequest_desc = v.UnicodeString(strip=True, required=False)
417
417
418 ancestor_rev = v.UnicodeString(strip=True, required=True)
418 ancestor_rev = v.UnicodeString(strip=True, required=True)
419 merge_rev = v.UnicodeString(strip=True, required=True)
419 merge_rev = v.UnicodeString(strip=True, required=True)
420
420
421 return _PullRequestForm
421 return _PullRequestForm
422
423
424 def GistForm(lifetime_options):
425 class _GistForm(formencode.Schema):
426
427 filename = v.UnicodeString(strip=True, required=False)
428 description = v.UnicodeString(required=False, if_missing='')
429 lifetime = v.OneOf(lifetime_options)
430 content = v.UnicodeString(required=True, not_empty=True)
431 public = v.UnicodeString(required=False, if_missing='')
432 private = v.UnicodeString(required=False, if_missing='')
433
434 return _GistForm
@@ -1,746 +1,755 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.repo
3 rhodecode.model.repo
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 Repository model for rhodecode
6 Repository model for rhodecode
7
7
8 :created_on: Jun 5, 2010
8 :created_on: Jun 5, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 from __future__ import with_statement
25 from __future__ import with_statement
26 import os
26 import os
27 import shutil
27 import shutil
28 import logging
28 import logging
29 import traceback
29 import traceback
30 from datetime import datetime
30 from datetime import datetime
31
31
32 from rhodecode.lib.vcs.backends import get_backend
32 from rhodecode.lib.vcs.backends import get_backend
33 from rhodecode.lib.compat import json
33 from rhodecode.lib.compat import json
34 from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode,\
34 from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode,\
35 remove_prefix, obfuscate_url_pw
35 remove_prefix, obfuscate_url_pw
36 from rhodecode.lib.caching_query import FromCache
36 from rhodecode.lib.caching_query import FromCache
37 from rhodecode.lib.hooks import log_create_repository, log_delete_repository
37 from rhodecode.lib.hooks import log_create_repository, log_delete_repository
38
38
39 from rhodecode.model import BaseModel
39 from rhodecode.model import BaseModel
40 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
40 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
41 Statistics, UserGroup, UserGroupRepoToPerm, RhodeCodeUi, RepoGroup,\
41 Statistics, UserGroup, UserGroupRepoToPerm, RhodeCodeUi, RepoGroup,\
42 RhodeCodeSetting, RepositoryField
42 RhodeCodeSetting, RepositoryField
43 from rhodecode.lib import helpers as h
43 from rhodecode.lib import helpers as h
44 from rhodecode.lib.auth import HasRepoPermissionAny, HasUserGroupPermissionAny
44 from rhodecode.lib.auth import HasRepoPermissionAny, HasUserGroupPermissionAny
45 from rhodecode.lib.exceptions import AttachedForksError
45 from rhodecode.lib.exceptions import AttachedForksError
46 from rhodecode.model.scm import UserGroupList
46 from rhodecode.model.scm import UserGroupList
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 class RepoModel(BaseModel):
51 class RepoModel(BaseModel):
52
52
53 cls = Repository
53 cls = Repository
54 URL_SEPARATOR = Repository.url_sep()
54 URL_SEPARATOR = Repository.url_sep()
55
55
56 def _get_user_group(self, users_group):
56 def _get_user_group(self, users_group):
57 return self._get_instance(UserGroup, users_group,
57 return self._get_instance(UserGroup, users_group,
58 callback=UserGroup.get_by_group_name)
58 callback=UserGroup.get_by_group_name)
59
59
60 def _get_repo_group(self, repos_group):
60 def _get_repo_group(self, repos_group):
61 return self._get_instance(RepoGroup, repos_group,
61 return self._get_instance(RepoGroup, repos_group,
62 callback=RepoGroup.get_by_group_name)
62 callback=RepoGroup.get_by_group_name)
63
63
64 def _create_default_perms(self, repository, private):
64 def _create_default_perms(self, repository, private):
65 # create default permission
65 # create default permission
66 default = 'repository.read'
66 default = 'repository.read'
67 def_user = User.get_default_user()
67 def_user = User.get_default_user()
68 for p in def_user.user_perms:
68 for p in def_user.user_perms:
69 if p.permission.permission_name.startswith('repository.'):
69 if p.permission.permission_name.startswith('repository.'):
70 default = p.permission.permission_name
70 default = p.permission.permission_name
71 break
71 break
72
72
73 default_perm = 'repository.none' if private else default
73 default_perm = 'repository.none' if private else default
74
74
75 repo_to_perm = UserRepoToPerm()
75 repo_to_perm = UserRepoToPerm()
76 repo_to_perm.permission = Permission.get_by_key(default_perm)
76 repo_to_perm.permission = Permission.get_by_key(default_perm)
77
77
78 repo_to_perm.repository = repository
78 repo_to_perm.repository = repository
79 repo_to_perm.user_id = def_user.user_id
79 repo_to_perm.user_id = def_user.user_id
80
80
81 return repo_to_perm
81 return repo_to_perm
82
82
83 @LazyProperty
83 @LazyProperty
84 def repos_path(self):
84 def repos_path(self):
85 """
85 """
86 Get's the repositories root path from database
86 Get's the repositories root path from database
87 """
87 """
88
88
89 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
89 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
90 return q.ui_value
90 return q.ui_value
91
91
92 def get(self, repo_id, cache=False):
92 def get(self, repo_id, cache=False):
93 repo = self.sa.query(Repository)\
93 repo = self.sa.query(Repository)\
94 .filter(Repository.repo_id == repo_id)
94 .filter(Repository.repo_id == repo_id)
95
95
96 if cache:
96 if cache:
97 repo = repo.options(FromCache("sql_cache_short",
97 repo = repo.options(FromCache("sql_cache_short",
98 "get_repo_%s" % repo_id))
98 "get_repo_%s" % repo_id))
99 return repo.scalar()
99 return repo.scalar()
100
100
101 def get_repo(self, repository):
101 def get_repo(self, repository):
102 return self._get_repo(repository)
102 return self._get_repo(repository)
103
103
104 def get_by_repo_name(self, repo_name, cache=False):
104 def get_by_repo_name(self, repo_name, cache=False):
105 repo = self.sa.query(Repository)\
105 repo = self.sa.query(Repository)\
106 .filter(Repository.repo_name == repo_name)
106 .filter(Repository.repo_name == repo_name)
107
107
108 if cache:
108 if cache:
109 repo = repo.options(FromCache("sql_cache_short",
109 repo = repo.options(FromCache("sql_cache_short",
110 "get_repo_%s" % repo_name))
110 "get_repo_%s" % repo_name))
111 return repo.scalar()
111 return repo.scalar()
112
112
113 def get_all_user_repos(self, user):
113 def get_all_user_repos(self, user):
114 """
114 """
115 Get's all repositories that user have at least read access
115 Get's all repositories that user have at least read access
116
116
117 :param user:
117 :param user:
118 :type user:
119 """
118 """
120 from rhodecode.lib.auth import AuthUser
119 from rhodecode.lib.auth import AuthUser
121 user = self._get_user(user)
120 user = self._get_user(user)
122 repos = AuthUser(user_id=user.user_id).permissions['repositories']
121 repos = AuthUser(user_id=user.user_id).permissions['repositories']
123 access_check = lambda r: r[1] in ['repository.read',
122 access_check = lambda r: r[1] in ['repository.read',
124 'repository.write',
123 'repository.write',
125 'repository.admin']
124 'repository.admin']
126 repos = [x[0] for x in filter(access_check, repos.items())]
125 repos = [x[0] for x in filter(access_check, repos.items())]
127 return Repository.query().filter(Repository.repo_name.in_(repos))
126 return Repository.query().filter(Repository.repo_name.in_(repos))
128
127
129 def get_users_js(self):
128 def get_users_js(self):
130 users = self.sa.query(User).filter(User.active == True).all()
129 users = self.sa.query(User).filter(User.active == True).all()
131 return json.dumps([
130 return json.dumps([
132 {
131 {
133 'id': u.user_id,
132 'id': u.user_id,
134 'fname': u.name,
133 'fname': u.name,
135 'lname': u.lastname,
134 'lname': u.lastname,
136 'nname': u.username,
135 'nname': u.username,
137 'gravatar_lnk': h.gravatar_url(u.email, 14)
136 'gravatar_lnk': h.gravatar_url(u.email, 14)
138 } for u in users]
137 } for u in users]
139 )
138 )
140
139
141 def get_users_groups_js(self):
140 def get_users_groups_js(self):
142 users_groups = self.sa.query(UserGroup)\
141 users_groups = self.sa.query(UserGroup)\
143 .filter(UserGroup.users_group_active == True).all()
142 .filter(UserGroup.users_group_active == True).all()
144 users_groups = UserGroupList(users_groups, perm_set=['usergroup.read',
143 users_groups = UserGroupList(users_groups, perm_set=['usergroup.read',
145 'usergroup.write',
144 'usergroup.write',
146 'usergroup.admin'])
145 'usergroup.admin'])
147 return json.dumps([
146 return json.dumps([
148 {
147 {
149 'id': gr.users_group_id,
148 'id': gr.users_group_id,
150 'grname': gr.users_group_name,
149 'grname': gr.users_group_name,
151 'grmembers': len(gr.members),
150 'grmembers': len(gr.members),
152 } for gr in users_groups]
151 } for gr in users_groups]
153 )
152 )
154
153
155 @classmethod
154 @classmethod
156 def _render_datatable(cls, tmpl, *args, **kwargs):
155 def _render_datatable(cls, tmpl, *args, **kwargs):
157 import rhodecode
156 import rhodecode
158 from pylons import tmpl_context as c
157 from pylons import tmpl_context as c
159 from pylons.i18n.translation import _
158 from pylons.i18n.translation import _
160
159
161 _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
160 _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
162 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
161 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
163
162
164 tmpl = template.get_def(tmpl)
163 tmpl = template.get_def(tmpl)
165 kwargs.update(dict(_=_, h=h, c=c))
164 kwargs.update(dict(_=_, h=h, c=c))
166 return tmpl.render(*args, **kwargs)
165 return tmpl.render(*args, **kwargs)
167
166
168 @classmethod
167 @classmethod
169 def update_repoinfo(cls, repositories=None):
168 def update_repoinfo(cls, repositories=None):
170 if not repositories:
169 if not repositories:
171 repositories = Repository.getAll()
170 repositories = Repository.getAll()
172 for repo in repositories:
171 for repo in repositories:
173 repo.update_changeset_cache()
172 repo.update_changeset_cache()
174
173
175 def get_repos_as_dict(self, repos_list=None, admin=False, perm_check=True,
174 def get_repos_as_dict(self, repos_list=None, admin=False, perm_check=True,
176 super_user_actions=False):
175 super_user_actions=False):
177 _render = self._render_datatable
176 _render = self._render_datatable
178 from pylons import tmpl_context as c
177 from pylons import tmpl_context as c
179
178
180 def quick_menu(repo_name):
179 def quick_menu(repo_name):
181 return _render('quick_menu', repo_name)
180 return _render('quick_menu', repo_name)
182
181
183 def repo_lnk(name, rtype, private, fork_of):
182 def repo_lnk(name, rtype, private, fork_of):
184 return _render('repo_name', name, rtype, private, fork_of,
183 return _render('repo_name', name, rtype, private, fork_of,
185 short_name=not admin, admin=False)
184 short_name=not admin, admin=False)
186
185
187 def last_change(last_change):
186 def last_change(last_change):
188 return _render("last_change", last_change)
187 return _render("last_change", last_change)
189
188
190 def rss_lnk(repo_name):
189 def rss_lnk(repo_name):
191 return _render("rss", repo_name)
190 return _render("rss", repo_name)
192
191
193 def atom_lnk(repo_name):
192 def atom_lnk(repo_name):
194 return _render("atom", repo_name)
193 return _render("atom", repo_name)
195
194
196 def last_rev(repo_name, cs_cache):
195 def last_rev(repo_name, cs_cache):
197 return _render('revision', repo_name, cs_cache.get('revision'),
196 return _render('revision', repo_name, cs_cache.get('revision'),
198 cs_cache.get('raw_id'), cs_cache.get('author'),
197 cs_cache.get('raw_id'), cs_cache.get('author'),
199 cs_cache.get('message'))
198 cs_cache.get('message'))
200
199
201 def desc(desc):
200 def desc(desc):
202 if c.visual.stylify_metatags:
201 if c.visual.stylify_metatags:
203 return h.urlify_text(h.desc_stylize(h.truncate(desc, 60)))
202 return h.urlify_text(h.desc_stylize(h.truncate(desc, 60)))
204 else:
203 else:
205 return h.urlify_text(h.truncate(desc, 60))
204 return h.urlify_text(h.truncate(desc, 60))
206
205
207 def repo_actions(repo_name):
206 def repo_actions(repo_name):
208 return _render('repo_actions', repo_name, super_user_actions)
207 return _render('repo_actions', repo_name, super_user_actions)
209
208
210 def owner_actions(user_id, username):
209 def owner_actions(user_id, username):
211 return _render('user_name', user_id, username)
210 return _render('user_name', user_id, username)
212
211
213 repos_data = []
212 repos_data = []
214 for repo in repos_list:
213 for repo in repos_list:
215 if perm_check:
214 if perm_check:
216 # check permission at this level
215 # check permission at this level
217 if not HasRepoPermissionAny(
216 if not HasRepoPermissionAny(
218 'repository.read', 'repository.write', 'repository.admin'
217 'repository.read', 'repository.write', 'repository.admin'
219 )(repo.repo_name, 'get_repos_as_dict check'):
218 )(repo.repo_name, 'get_repos_as_dict check'):
220 continue
219 continue
221 cs_cache = repo.changeset_cache
220 cs_cache = repo.changeset_cache
222 row = {
221 row = {
223 "menu": quick_menu(repo.repo_name),
222 "menu": quick_menu(repo.repo_name),
224 "raw_name": repo.repo_name.lower(),
223 "raw_name": repo.repo_name.lower(),
225 "name": repo_lnk(repo.repo_name, repo.repo_type,
224 "name": repo_lnk(repo.repo_name, repo.repo_type,
226 repo.private, repo.fork),
225 repo.private, repo.fork),
227 "last_change": last_change(repo.last_db_change),
226 "last_change": last_change(repo.last_db_change),
228 "last_changeset": last_rev(repo.repo_name, cs_cache),
227 "last_changeset": last_rev(repo.repo_name, cs_cache),
229 "raw_tip": cs_cache.get('revision'),
228 "raw_tip": cs_cache.get('revision'),
230 "desc": desc(repo.description),
229 "desc": desc(repo.description),
231 "owner": h.person(repo.user.username),
230 "owner": h.person(repo.user.username),
232 "rss": rss_lnk(repo.repo_name),
231 "rss": rss_lnk(repo.repo_name),
233 "atom": atom_lnk(repo.repo_name),
232 "atom": atom_lnk(repo.repo_name),
234
233
235 }
234 }
236 if admin:
235 if admin:
237 row.update({
236 row.update({
238 "action": repo_actions(repo.repo_name),
237 "action": repo_actions(repo.repo_name),
239 "owner": owner_actions(repo.user.user_id,
238 "owner": owner_actions(repo.user.user_id,
240 h.person(repo.user.username))
239 h.person(repo.user.username))
241 })
240 })
242 repos_data.append(row)
241 repos_data.append(row)
243
242
244 return {
243 return {
245 "totalRecords": len(repos_list),
244 "totalRecords": len(repos_list),
246 "startIndex": 0,
245 "startIndex": 0,
247 "sort": "name",
246 "sort": "name",
248 "dir": "asc",
247 "dir": "asc",
249 "records": repos_data
248 "records": repos_data
250 }
249 }
251
250
252 def _get_defaults(self, repo_name):
251 def _get_defaults(self, repo_name):
253 """
252 """
254 Get's information about repository, and returns a dict for
253 Get's information about repository, and returns a dict for
255 usage in forms
254 usage in forms
256
255
257 :param repo_name:
256 :param repo_name:
258 """
257 """
259
258
260 repo_info = Repository.get_by_repo_name(repo_name)
259 repo_info = Repository.get_by_repo_name(repo_name)
261
260
262 if repo_info is None:
261 if repo_info is None:
263 return None
262 return None
264
263
265 defaults = repo_info.get_dict()
264 defaults = repo_info.get_dict()
266 group, repo_name, repo_name_full = repo_info.groups_and_repo
265 group, repo_name, repo_name_full = repo_info.groups_and_repo
267 defaults['repo_name'] = repo_name
266 defaults['repo_name'] = repo_name
268 defaults['repo_group'] = getattr(group[-1] if group else None,
267 defaults['repo_group'] = getattr(group[-1] if group else None,
269 'group_id', None)
268 'group_id', None)
270
269
271 for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
270 for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
272 (1, 'repo_description'), (1, 'repo_enable_locking'),
271 (1, 'repo_description'), (1, 'repo_enable_locking'),
273 (1, 'repo_landing_rev'), (0, 'clone_uri'),
272 (1, 'repo_landing_rev'), (0, 'clone_uri'),
274 (1, 'repo_private'), (1, 'repo_enable_statistics')]:
273 (1, 'repo_private'), (1, 'repo_enable_statistics')]:
275 attr = k
274 attr = k
276 if strip:
275 if strip:
277 attr = remove_prefix(k, 'repo_')
276 attr = remove_prefix(k, 'repo_')
278
277
279 defaults[k] = defaults[attr]
278 defaults[k] = defaults[attr]
280
279
281 # fill owner
280 # fill owner
282 if repo_info.user:
281 if repo_info.user:
283 defaults.update({'user': repo_info.user.username})
282 defaults.update({'user': repo_info.user.username})
284 else:
283 else:
285 replacement_user = User.query().filter(User.admin ==
284 replacement_user = User.query().filter(User.admin ==
286 True).first().username
285 True).first().username
287 defaults.update({'user': replacement_user})
286 defaults.update({'user': replacement_user})
288
287
289 # fill repository users
288 # fill repository users
290 for p in repo_info.repo_to_perm:
289 for p in repo_info.repo_to_perm:
291 defaults.update({'u_perm_%s' % p.user.username:
290 defaults.update({'u_perm_%s' % p.user.username:
292 p.permission.permission_name})
291 p.permission.permission_name})
293
292
294 # fill repository groups
293 # fill repository groups
295 for p in repo_info.users_group_to_perm:
294 for p in repo_info.users_group_to_perm:
296 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
295 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
297 p.permission.permission_name})
296 p.permission.permission_name})
298
297
299 return defaults
298 return defaults
300
299
301 def update(self, org_repo_name, **kwargs):
300 def update(self, org_repo_name, **kwargs):
302 try:
301 try:
303 cur_repo = self.get_by_repo_name(org_repo_name, cache=False)
302 cur_repo = self.get_by_repo_name(org_repo_name, cache=False)
304
303
305 if 'user' in kwargs:
304 if 'user' in kwargs:
306 cur_repo.user = User.get_by_username(kwargs['user'])
305 cur_repo.user = User.get_by_username(kwargs['user'])
307
306
308 if 'repo_group' in kwargs:
307 if 'repo_group' in kwargs:
309 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
308 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
310
309
311 for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
310 for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
312 (1, 'repo_description'), (1, 'repo_enable_locking'),
311 (1, 'repo_description'), (1, 'repo_enable_locking'),
313 (1, 'repo_landing_rev'), (0, 'clone_uri'),
312 (1, 'repo_landing_rev'), (0, 'clone_uri'),
314 (1, 'repo_private'), (1, 'repo_enable_statistics')]:
313 (1, 'repo_private'), (1, 'repo_enable_statistics')]:
315 if k in kwargs:
314 if k in kwargs:
316 val = kwargs[k]
315 val = kwargs[k]
317 if strip:
316 if strip:
318 k = remove_prefix(k, 'repo_')
317 k = remove_prefix(k, 'repo_')
319 setattr(cur_repo, k, val)
318 setattr(cur_repo, k, val)
320
319
321 new_name = cur_repo.get_new_name(kwargs['repo_name'])
320 new_name = cur_repo.get_new_name(kwargs['repo_name'])
322 cur_repo.repo_name = new_name
321 cur_repo.repo_name = new_name
323 #if private flag is set, reset default permission to NONE
322 #if private flag is set, reset default permission to NONE
324
323
325 if kwargs.get('repo_private'):
324 if kwargs.get('repo_private'):
326 EMPTY_PERM = 'repository.none'
325 EMPTY_PERM = 'repository.none'
327 RepoModel().grant_user_permission(
326 RepoModel().grant_user_permission(
328 repo=cur_repo, user='default', perm=EMPTY_PERM
327 repo=cur_repo, user='default', perm=EMPTY_PERM
329 )
328 )
330 #handle extra fields
329 #handle extra fields
331 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs):
330 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs):
332 k = RepositoryField.un_prefix_key(field)
331 k = RepositoryField.un_prefix_key(field)
333 ex_field = RepositoryField.get_by_key_name(key=k, repo=cur_repo)
332 ex_field = RepositoryField.get_by_key_name(key=k, repo=cur_repo)
334 if ex_field:
333 if ex_field:
335 ex_field.field_value = kwargs[field]
334 ex_field.field_value = kwargs[field]
336 self.sa.add(ex_field)
335 self.sa.add(ex_field)
337 self.sa.add(cur_repo)
336 self.sa.add(cur_repo)
338
337
339 if org_repo_name != new_name:
338 if org_repo_name != new_name:
340 # rename repository
339 # rename repository
341 self.__rename_repo(old=org_repo_name, new=new_name)
340 self.__rename_repo(old=org_repo_name, new=new_name)
342
341
343 return cur_repo
342 return cur_repo
344 except Exception:
343 except Exception:
345 log.error(traceback.format_exc())
344 log.error(traceback.format_exc())
346 raise
345 raise
347
346
348 def create_repo(self, repo_name, repo_type, description, owner,
347 def create_repo(self, repo_name, repo_type, description, owner,
349 private=False, clone_uri=None, repos_group=None,
348 private=False, clone_uri=None, repos_group=None,
350 landing_rev='tip', just_db=False, fork_of=None,
349 landing_rev='tip', just_db=False, fork_of=None,
351 copy_fork_permissions=False, enable_statistics=False,
350 copy_fork_permissions=False, enable_statistics=False,
352 enable_locking=False, enable_downloads=False):
351 enable_locking=False, enable_downloads=False):
353 """
352 """
354 Create repository
353 Create repository
355
354
356 """
355 """
357 from rhodecode.model.scm import ScmModel
356 from rhodecode.model.scm import ScmModel
358
357
359 owner = self._get_user(owner)
358 owner = self._get_user(owner)
360 fork_of = self._get_repo(fork_of)
359 fork_of = self._get_repo(fork_of)
361 repos_group = self._get_repo_group(repos_group)
360 repos_group = self._get_repo_group(repos_group)
362 try:
361 try:
363
362
364 # repo name is just a name of repository
363 # repo name is just a name of repository
365 # while repo_name_full is a full qualified name that is combined
364 # while repo_name_full is a full qualified name that is combined
366 # with name and path of group
365 # with name and path of group
367 repo_name_full = repo_name
366 repo_name_full = repo_name
368 repo_name = repo_name.split(self.URL_SEPARATOR)[-1]
367 repo_name = repo_name.split(self.URL_SEPARATOR)[-1]
369
368
370 new_repo = Repository()
369 new_repo = Repository()
371 new_repo.enable_statistics = False
370 new_repo.enable_statistics = False
372 new_repo.repo_name = repo_name_full
371 new_repo.repo_name = repo_name_full
373 new_repo.repo_type = repo_type
372 new_repo.repo_type = repo_type
374 new_repo.user = owner
373 new_repo.user = owner
375 new_repo.group = repos_group
374 new_repo.group = repos_group
376 new_repo.description = description or repo_name
375 new_repo.description = description or repo_name
377 new_repo.private = private
376 new_repo.private = private
378 new_repo.clone_uri = clone_uri
377 new_repo.clone_uri = clone_uri
379 new_repo.landing_rev = landing_rev
378 new_repo.landing_rev = landing_rev
380
379
381 new_repo.enable_statistics = enable_statistics
380 new_repo.enable_statistics = enable_statistics
382 new_repo.enable_locking = enable_locking
381 new_repo.enable_locking = enable_locking
383 new_repo.enable_downloads = enable_downloads
382 new_repo.enable_downloads = enable_downloads
384
383
385 if repos_group:
384 if repos_group:
386 new_repo.enable_locking = repos_group.enable_locking
385 new_repo.enable_locking = repos_group.enable_locking
387
386
388 if fork_of:
387 if fork_of:
389 parent_repo = fork_of
388 parent_repo = fork_of
390 new_repo.fork = parent_repo
389 new_repo.fork = parent_repo
391
390
392 self.sa.add(new_repo)
391 self.sa.add(new_repo)
393
392
394 if fork_of:
393 if fork_of:
395 if copy_fork_permissions:
394 if copy_fork_permissions:
396 repo = fork_of
395 repo = fork_of
397 user_perms = UserRepoToPerm.query()\
396 user_perms = UserRepoToPerm.query()\
398 .filter(UserRepoToPerm.repository == repo).all()
397 .filter(UserRepoToPerm.repository == repo).all()
399 group_perms = UserGroupRepoToPerm.query()\
398 group_perms = UserGroupRepoToPerm.query()\
400 .filter(UserGroupRepoToPerm.repository == repo).all()
399 .filter(UserGroupRepoToPerm.repository == repo).all()
401
400
402 for perm in user_perms:
401 for perm in user_perms:
403 UserRepoToPerm.create(perm.user, new_repo,
402 UserRepoToPerm.create(perm.user, new_repo,
404 perm.permission)
403 perm.permission)
405
404
406 for perm in group_perms:
405 for perm in group_perms:
407 UserGroupRepoToPerm.create(perm.users_group, new_repo,
406 UserGroupRepoToPerm.create(perm.users_group, new_repo,
408 perm.permission)
407 perm.permission)
409 else:
408 else:
410 perm_obj = self._create_default_perms(new_repo, private)
409 perm_obj = self._create_default_perms(new_repo, private)
411 self.sa.add(perm_obj)
410 self.sa.add(perm_obj)
412 else:
411 else:
413 perm_obj = self._create_default_perms(new_repo, private)
412 perm_obj = self._create_default_perms(new_repo, private)
414 self.sa.add(perm_obj)
413 self.sa.add(perm_obj)
415
414
416 if not just_db:
415 if not just_db:
417 self.__create_repo(repo_name, repo_type,
416 self.__create_repo(repo_name, repo_type,
418 repos_group,
417 repos_group,
419 clone_uri)
418 clone_uri)
420 log_create_repository(new_repo.get_dict(),
419 log_create_repository(new_repo.get_dict(),
421 created_by=owner.username)
420 created_by=owner.username)
422
421
423 # now automatically start following this repository as owner
422 # now automatically start following this repository as owner
424 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
423 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
425 owner.user_id)
424 owner.user_id)
426 return new_repo
425 return new_repo
427 except Exception:
426 except Exception:
428 log.error(traceback.format_exc())
427 log.error(traceback.format_exc())
429 raise
428 raise
430
429
431 def create(self, form_data, cur_user, just_db=False, fork=None):
430 def create(self, form_data, cur_user, just_db=False, fork=None):
432 """
431 """
433 Backward compatibility function, just a wrapper on top of create_repo
432 Backward compatibility function, just a wrapper on top of create_repo
434
433
435 :param form_data:
434 :param form_data:
436 :param cur_user:
435 :param cur_user:
437 :param just_db:
436 :param just_db:
438 :param fork:
437 :param fork:
439 """
438 """
440 owner = cur_user
439 owner = cur_user
441 repo_name = form_data['repo_name_full']
440 repo_name = form_data['repo_name_full']
442 repo_type = form_data['repo_type']
441 repo_type = form_data['repo_type']
443 description = form_data['repo_description']
442 description = form_data['repo_description']
444 private = form_data['repo_private']
443 private = form_data['repo_private']
445 clone_uri = form_data.get('clone_uri')
444 clone_uri = form_data.get('clone_uri')
446 repos_group = form_data['repo_group']
445 repos_group = form_data['repo_group']
447 landing_rev = form_data['repo_landing_rev']
446 landing_rev = form_data['repo_landing_rev']
448 copy_fork_permissions = form_data.get('copy_permissions')
447 copy_fork_permissions = form_data.get('copy_permissions')
449 fork_of = form_data.get('fork_parent_id')
448 fork_of = form_data.get('fork_parent_id')
450
449
451 ## repo creation defaults, private and repo_type are filled in form
450 ## repo creation defaults, private and repo_type are filled in form
452 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
451 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
453 enable_statistics = defs.get('repo_enable_statistics')
452 enable_statistics = defs.get('repo_enable_statistics')
454 enable_locking = defs.get('repo_enable_locking')
453 enable_locking = defs.get('repo_enable_locking')
455 enable_downloads = defs.get('repo_enable_downloads')
454 enable_downloads = defs.get('repo_enable_downloads')
456
455
457 return self.create_repo(
456 return self.create_repo(
458 repo_name, repo_type, description, owner, private, clone_uri,
457 repo_name, repo_type, description, owner, private, clone_uri,
459 repos_group, landing_rev, just_db, fork_of, copy_fork_permissions,
458 repos_group, landing_rev, just_db, fork_of, copy_fork_permissions,
460 enable_statistics, enable_locking, enable_downloads
459 enable_statistics, enable_locking, enable_downloads
461 )
460 )
462
461
463 def _update_permissions(self, repo, perms_new=None, perms_updates=None,
462 def _update_permissions(self, repo, perms_new=None, perms_updates=None,
464 check_perms=True):
463 check_perms=True):
465 if not perms_new:
464 if not perms_new:
466 perms_new = []
465 perms_new = []
467 if not perms_updates:
466 if not perms_updates:
468 perms_updates = []
467 perms_updates = []
469
468
470 # update permissions
469 # update permissions
471 for member, perm, member_type in perms_updates:
470 for member, perm, member_type in perms_updates:
472 if member_type == 'user':
471 if member_type == 'user':
473 # this updates existing one
472 # this updates existing one
474 self.grant_user_permission(
473 self.grant_user_permission(
475 repo=repo, user=member, perm=perm
474 repo=repo, user=member, perm=perm
476 )
475 )
477 else:
476 else:
478 #check if we have permissions to alter this usergroup
477 #check if we have permissions to alter this usergroup
479 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
478 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
480 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member):
479 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member):
481 self.grant_users_group_permission(
480 self.grant_users_group_permission(
482 repo=repo, group_name=member, perm=perm
481 repo=repo, group_name=member, perm=perm
483 )
482 )
484 # set new permissions
483 # set new permissions
485 for member, perm, member_type in perms_new:
484 for member, perm, member_type in perms_new:
486 if member_type == 'user':
485 if member_type == 'user':
487 self.grant_user_permission(
486 self.grant_user_permission(
488 repo=repo, user=member, perm=perm
487 repo=repo, user=member, perm=perm
489 )
488 )
490 else:
489 else:
491 #check if we have permissions to alter this usergroup
490 #check if we have permissions to alter this usergroup
492 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
491 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
493 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member):
492 if not check_perms or HasUserGroupPermissionAny(*req_perms)(member):
494 self.grant_users_group_permission(
493 self.grant_users_group_permission(
495 repo=repo, group_name=member, perm=perm
494 repo=repo, group_name=member, perm=perm
496 )
495 )
497
496
498 def create_fork(self, form_data, cur_user):
497 def create_fork(self, form_data, cur_user):
499 """
498 """
500 Simple wrapper into executing celery task for fork creation
499 Simple wrapper into executing celery task for fork creation
501
500
502 :param form_data:
501 :param form_data:
503 :param cur_user:
502 :param cur_user:
504 """
503 """
505 from rhodecode.lib.celerylib import tasks, run_task
504 from rhodecode.lib.celerylib import tasks, run_task
506 run_task(tasks.create_repo_fork, form_data, cur_user)
505 run_task(tasks.create_repo_fork, form_data, cur_user)
507
506
508 def delete(self, repo, forks=None, fs_remove=True):
507 def delete(self, repo, forks=None, fs_remove=True):
509 """
508 """
510 Delete given repository, forks parameter defines what do do with
509 Delete given repository, forks parameter defines what do do with
511 attached forks. Throws AttachedForksError if deleted repo has attached
510 attached forks. Throws AttachedForksError if deleted repo has attached
512 forks
511 forks
513
512
514 :param repo:
513 :param repo:
515 :param forks: str 'delete' or 'detach'
514 :param forks: str 'delete' or 'detach'
516 :param fs_remove: remove(archive) repo from filesystem
515 :param fs_remove: remove(archive) repo from filesystem
517 """
516 """
518 repo = self._get_repo(repo)
517 repo = self._get_repo(repo)
519 if repo:
518 if repo:
520 if forks == 'detach':
519 if forks == 'detach':
521 for r in repo.forks:
520 for r in repo.forks:
522 r.fork = None
521 r.fork = None
523 self.sa.add(r)
522 self.sa.add(r)
524 elif forks == 'delete':
523 elif forks == 'delete':
525 for r in repo.forks:
524 for r in repo.forks:
526 self.delete(r, forks='delete')
525 self.delete(r, forks='delete')
527 elif [f for f in repo.forks]:
526 elif [f for f in repo.forks]:
528 raise AttachedForksError()
527 raise AttachedForksError()
529
528
530 old_repo_dict = repo.get_dict()
529 old_repo_dict = repo.get_dict()
531 owner = repo.user
530 owner = repo.user
532 try:
531 try:
533 self.sa.delete(repo)
532 self.sa.delete(repo)
534 if fs_remove:
533 if fs_remove:
535 self.__delete_repo(repo)
534 self.__delete_repo(repo)
536 else:
535 else:
537 log.debug('skipping removal from filesystem')
536 log.debug('skipping removal from filesystem')
538 log_delete_repository(old_repo_dict,
537 log_delete_repository(old_repo_dict,
539 deleted_by=owner.username)
538 deleted_by=owner.username)
540 except Exception:
539 except Exception:
541 log.error(traceback.format_exc())
540 log.error(traceback.format_exc())
542 raise
541 raise
543
542
544 def grant_user_permission(self, repo, user, perm):
543 def grant_user_permission(self, repo, user, perm):
545 """
544 """
546 Grant permission for user on given repository, or update existing one
545 Grant permission for user on given repository, or update existing one
547 if found
546 if found
548
547
549 :param repo: Instance of Repository, repository_id, or repository name
548 :param repo: Instance of Repository, repository_id, or repository name
550 :param user: Instance of User, user_id or username
549 :param user: Instance of User, user_id or username
551 :param perm: Instance of Permission, or permission_name
550 :param perm: Instance of Permission, or permission_name
552 """
551 """
553 user = self._get_user(user)
552 user = self._get_user(user)
554 repo = self._get_repo(repo)
553 repo = self._get_repo(repo)
555 permission = self._get_perm(perm)
554 permission = self._get_perm(perm)
556
555
557 # check if we have that permission already
556 # check if we have that permission already
558 obj = self.sa.query(UserRepoToPerm)\
557 obj = self.sa.query(UserRepoToPerm)\
559 .filter(UserRepoToPerm.user == user)\
558 .filter(UserRepoToPerm.user == user)\
560 .filter(UserRepoToPerm.repository == repo)\
559 .filter(UserRepoToPerm.repository == repo)\
561 .scalar()
560 .scalar()
562 if obj is None:
561 if obj is None:
563 # create new !
562 # create new !
564 obj = UserRepoToPerm()
563 obj = UserRepoToPerm()
565 obj.repository = repo
564 obj.repository = repo
566 obj.user = user
565 obj.user = user
567 obj.permission = permission
566 obj.permission = permission
568 self.sa.add(obj)
567 self.sa.add(obj)
569 log.debug('Granted perm %s to %s on %s' % (perm, user, repo))
568 log.debug('Granted perm %s to %s on %s' % (perm, user, repo))
570
569
571 def revoke_user_permission(self, repo, user):
570 def revoke_user_permission(self, repo, user):
572 """
571 """
573 Revoke permission for user on given repository
572 Revoke permission for user on given repository
574
573
575 :param repo: Instance of Repository, repository_id, or repository name
574 :param repo: Instance of Repository, repository_id, or repository name
576 :param user: Instance of User, user_id or username
575 :param user: Instance of User, user_id or username
577 """
576 """
578
577
579 user = self._get_user(user)
578 user = self._get_user(user)
580 repo = self._get_repo(repo)
579 repo = self._get_repo(repo)
581
580
582 obj = self.sa.query(UserRepoToPerm)\
581 obj = self.sa.query(UserRepoToPerm)\
583 .filter(UserRepoToPerm.repository == repo)\
582 .filter(UserRepoToPerm.repository == repo)\
584 .filter(UserRepoToPerm.user == user)\
583 .filter(UserRepoToPerm.user == user)\
585 .scalar()
584 .scalar()
586 if obj:
585 if obj:
587 self.sa.delete(obj)
586 self.sa.delete(obj)
588 log.debug('Revoked perm on %s on %s' % (repo, user))
587 log.debug('Revoked perm on %s on %s' % (repo, user))
589
588
590 def grant_users_group_permission(self, repo, group_name, perm):
589 def grant_users_group_permission(self, repo, group_name, perm):
591 """
590 """
592 Grant permission for user group on given repository, or update
591 Grant permission for user group on given repository, or update
593 existing one if found
592 existing one if found
594
593
595 :param repo: Instance of Repository, repository_id, or repository name
594 :param repo: Instance of Repository, repository_id, or repository name
596 :param group_name: Instance of UserGroup, users_group_id,
595 :param group_name: Instance of UserGroup, users_group_id,
597 or user group name
596 or user group name
598 :param perm: Instance of Permission, or permission_name
597 :param perm: Instance of Permission, or permission_name
599 """
598 """
600 repo = self._get_repo(repo)
599 repo = self._get_repo(repo)
601 group_name = self._get_user_group(group_name)
600 group_name = self._get_user_group(group_name)
602 permission = self._get_perm(perm)
601 permission = self._get_perm(perm)
603
602
604 # check if we have that permission already
603 # check if we have that permission already
605 obj = self.sa.query(UserGroupRepoToPerm)\
604 obj = self.sa.query(UserGroupRepoToPerm)\
606 .filter(UserGroupRepoToPerm.users_group == group_name)\
605 .filter(UserGroupRepoToPerm.users_group == group_name)\
607 .filter(UserGroupRepoToPerm.repository == repo)\
606 .filter(UserGroupRepoToPerm.repository == repo)\
608 .scalar()
607 .scalar()
609
608
610 if obj is None:
609 if obj is None:
611 # create new
610 # create new
612 obj = UserGroupRepoToPerm()
611 obj = UserGroupRepoToPerm()
613
612
614 obj.repository = repo
613 obj.repository = repo
615 obj.users_group = group_name
614 obj.users_group = group_name
616 obj.permission = permission
615 obj.permission = permission
617 self.sa.add(obj)
616 self.sa.add(obj)
618 log.debug('Granted perm %s to %s on %s' % (perm, group_name, repo))
617 log.debug('Granted perm %s to %s on %s' % (perm, group_name, repo))
619
618
620 def revoke_users_group_permission(self, repo, group_name):
619 def revoke_users_group_permission(self, repo, group_name):
621 """
620 """
622 Revoke permission for user group on given repository
621 Revoke permission for user group on given repository
623
622
624 :param repo: Instance of Repository, repository_id, or repository name
623 :param repo: Instance of Repository, repository_id, or repository name
625 :param group_name: Instance of UserGroup, users_group_id,
624 :param group_name: Instance of UserGroup, users_group_id,
626 or user group name
625 or user group name
627 """
626 """
628 repo = self._get_repo(repo)
627 repo = self._get_repo(repo)
629 group_name = self._get_user_group(group_name)
628 group_name = self._get_user_group(group_name)
630
629
631 obj = self.sa.query(UserGroupRepoToPerm)\
630 obj = self.sa.query(UserGroupRepoToPerm)\
632 .filter(UserGroupRepoToPerm.repository == repo)\
631 .filter(UserGroupRepoToPerm.repository == repo)\
633 .filter(UserGroupRepoToPerm.users_group == group_name)\
632 .filter(UserGroupRepoToPerm.users_group == group_name)\
634 .scalar()
633 .scalar()
635 if obj:
634 if obj:
636 self.sa.delete(obj)
635 self.sa.delete(obj)
637 log.debug('Revoked perm to %s on %s' % (repo, group_name))
636 log.debug('Revoked perm to %s on %s' % (repo, group_name))
638
637
639 def delete_stats(self, repo_name):
638 def delete_stats(self, repo_name):
640 """
639 """
641 removes stats for given repo
640 removes stats for given repo
642
641
643 :param repo_name:
642 :param repo_name:
644 """
643 """
645 repo = self._get_repo(repo_name)
644 repo = self._get_repo(repo_name)
646 try:
645 try:
647 obj = self.sa.query(Statistics)\
646 obj = self.sa.query(Statistics)\
648 .filter(Statistics.repository == repo).scalar()
647 .filter(Statistics.repository == repo).scalar()
649 if obj:
648 if obj:
650 self.sa.delete(obj)
649 self.sa.delete(obj)
651 except Exception:
650 except Exception:
652 log.error(traceback.format_exc())
651 log.error(traceback.format_exc())
653 raise
652 raise
654
653
655 def __create_repo(self, repo_name, alias, parent, clone_uri=False):
654 def _create_repo(self, repo_name, alias, parent, clone_uri=False,
655 repo_store_location=None):
656 return self.__create_repo(repo_name, alias, parent, clone_uri,
657 repo_store_location)
658
659 def __create_repo(self, repo_name, alias, parent, clone_uri=False,
660 repo_store_location=None):
656 """
661 """
657 makes repository on filesystem. It's group aware means it'll create
662 makes repository on filesystem. It's group aware means it'll create
658 a repository within a group, and alter the paths accordingly of
663 a repository within a group, and alter the paths accordingly of
659 group location
664 group location
660
665
661 :param repo_name:
666 :param repo_name:
662 :param alias:
667 :param alias:
663 :param parent_id:
668 :param parent_id:
664 :param clone_uri:
669 :param clone_uri:
670 :param repo_path:
665 """
671 """
666 from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
672 from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
667 from rhodecode.model.scm import ScmModel
673 from rhodecode.model.scm import ScmModel
668
674
669 if parent:
675 if parent:
670 new_parent_path = os.sep.join(parent.full_path_splitted)
676 new_parent_path = os.sep.join(parent.full_path_splitted)
671 else:
677 else:
672 new_parent_path = ''
678 new_parent_path = ''
673
679 if repo_store_location:
680 _paths = [repo_store_location]
681 else:
682 _paths = [self.repos_path, new_parent_path, repo_name]
674 # we need to make it str for mercurial
683 # we need to make it str for mercurial
675 repo_path = os.path.join(*map(lambda x: safe_str(x),
684 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
676 [self.repos_path, new_parent_path, repo_name]))
677
685
678 # check if this path is not a repository
686 # check if this path is not a repository
679 if is_valid_repo(repo_path, self.repos_path):
687 if is_valid_repo(repo_path, self.repos_path):
680 raise Exception('This path %s is a valid repository' % repo_path)
688 raise Exception('This path %s is a valid repository' % repo_path)
681
689
682 # check if this path is a group
690 # check if this path is a group
683 if is_valid_repos_group(repo_path, self.repos_path):
691 if is_valid_repos_group(repo_path, self.repos_path):
684 raise Exception('This path %s is a valid group' % repo_path)
692 raise Exception('This path %s is a valid group' % repo_path)
685
693
686 log.info('creating repo %s in %s @ %s' % (
694 log.info('creating repo %s in %s @ %s' % (
687 repo_name, safe_unicode(repo_path),
695 repo_name, safe_unicode(repo_path),
688 obfuscate_url_pw(clone_uri)
696 obfuscate_url_pw(clone_uri)
689 )
697 )
690 )
698 )
691 backend = get_backend(alias)
699 backend = get_backend(alias)
692 if alias == 'hg':
700 if alias == 'hg':
693 backend(repo_path, create=True, src_url=clone_uri)
701 repo = backend(repo_path, create=True, src_url=clone_uri)
694 elif alias == 'git':
702 elif alias == 'git':
695 r = backend(repo_path, create=True, src_url=clone_uri, bare=True)
703 repo = backend(repo_path, create=True, src_url=clone_uri, bare=True)
696 # add rhodecode hook into this repo
704 # add rhodecode hook into this repo
697 ScmModel().install_git_hook(repo=r)
705 ScmModel().install_git_hook(repo=repo)
698 else:
706 else:
699 raise Exception('Undefined alias %s' % alias)
707 raise Exception('Undefined alias %s' % alias)
708 return repo
700
709
701 def __rename_repo(self, old, new):
710 def __rename_repo(self, old, new):
702 """
711 """
703 renames repository on filesystem
712 renames repository on filesystem
704
713
705 :param old: old name
714 :param old: old name
706 :param new: new name
715 :param new: new name
707 """
716 """
708 log.info('renaming repo from %s to %s' % (old, new))
717 log.info('renaming repo from %s to %s' % (old, new))
709
718
710 old_path = os.path.join(self.repos_path, old)
719 old_path = os.path.join(self.repos_path, old)
711 new_path = os.path.join(self.repos_path, new)
720 new_path = os.path.join(self.repos_path, new)
712 if os.path.isdir(new_path):
721 if os.path.isdir(new_path):
713 raise Exception(
722 raise Exception(
714 'Was trying to rename to already existing dir %s' % new_path
723 'Was trying to rename to already existing dir %s' % new_path
715 )
724 )
716 shutil.move(old_path, new_path)
725 shutil.move(old_path, new_path)
717
726
718 def __delete_repo(self, repo):
727 def __delete_repo(self, repo):
719 """
728 """
720 removes repo from filesystem, the removal is acctually made by
729 removes repo from filesystem, the removal is acctually made by
721 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
730 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
722 repository is no longer valid for rhodecode, can be undeleted later on
731 repository is no longer valid for rhodecode, can be undeleted later on
723 by reverting the renames on this repository
732 by reverting the renames on this repository
724
733
725 :param repo: repo object
734 :param repo: repo object
726 """
735 """
727 rm_path = os.path.join(self.repos_path, repo.repo_name)
736 rm_path = os.path.join(self.repos_path, repo.repo_name)
728 log.info("Removing %s" % (rm_path))
737 log.info("Removing %s" % (rm_path))
729 # disable hg/git internal that it doesn't get detected as repo
738 # disable hg/git internal that it doesn't get detected as repo
730 alias = repo.repo_type
739 alias = repo.repo_type
731
740
732 bare = getattr(repo.scm_instance, 'bare', False)
741 bare = getattr(repo.scm_instance, 'bare', False)
733
742
734 if not bare:
743 if not bare:
735 # skip this for bare git repos
744 # skip this for bare git repos
736 shutil.move(os.path.join(rm_path, '.%s' % alias),
745 shutil.move(os.path.join(rm_path, '.%s' % alias),
737 os.path.join(rm_path, 'rm__.%s' % alias))
746 os.path.join(rm_path, 'rm__.%s' % alias))
738 # disable repo
747 # disable repo
739 _now = datetime.now()
748 _now = datetime.now()
740 _ms = str(_now.microsecond).rjust(6, '0')
749 _ms = str(_now.microsecond).rjust(6, '0')
741 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
750 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
742 repo.just_name)
751 repo.just_name)
743 if repo.group:
752 if repo.group:
744 args = repo.group.full_path_splitted + [_d]
753 args = repo.group.full_path_splitted + [_d]
745 _d = os.path.join(*args)
754 _d = os.path.join(*args)
746 shutil.move(rm_path, os.path.join(self.repos_path, _d))
755 shutil.move(rm_path, os.path.join(self.repos_path, _d))
@@ -1,695 +1,727 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.scm
3 rhodecode.model.scm
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Scm model for RhodeCode
6 Scm model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 from __future__ import with_statement
25 from __future__ import with_statement
26 import os
26 import os
27 import re
27 import re
28 import time
28 import time
29 import traceback
29 import traceback
30 import logging
30 import logging
31 import cStringIO
31 import cStringIO
32 import pkg_resources
32 import pkg_resources
33 from os.path import dirname as dn, join as jn
33 from os.path import dirname as dn, join as jn
34
34
35 from sqlalchemy import func
35 from sqlalchemy import func
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37
37
38 import rhodecode
38 import rhodecode
39 from rhodecode.lib.vcs import get_backend
39 from rhodecode.lib.vcs import get_backend
40 from rhodecode.lib.vcs.exceptions import RepositoryError
40 from rhodecode.lib.vcs.exceptions import RepositoryError
41 from rhodecode.lib.vcs.utils.lazy import LazyProperty
41 from rhodecode.lib.vcs.utils.lazy import LazyProperty
42 from rhodecode.lib.vcs.nodes import FileNode
42 from rhodecode.lib.vcs.nodes import FileNode
43 from rhodecode.lib.vcs.backends.base import EmptyChangeset
43 from rhodecode.lib.vcs.backends.base import EmptyChangeset
44
44
45 from rhodecode import BACKENDS
45 from rhodecode import BACKENDS
46 from rhodecode.lib import helpers as h
46 from rhodecode.lib import helpers as h
47 from rhodecode.lib.utils2 import safe_str, safe_unicode, get_server_url,\
47 from rhodecode.lib.utils2 import safe_str, safe_unicode, get_server_url,\
48 _set_extras
48 _set_extras
49 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny,\
49 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny,\
50 HasUserGroupPermissionAnyDecorator, HasUserGroupPermissionAny
50 HasUserGroupPermissionAnyDecorator, HasUserGroupPermissionAny
51 from rhodecode.lib.utils import get_filesystem_repos, make_ui, \
51 from rhodecode.lib.utils import get_filesystem_repos, make_ui, \
52 action_logger, REMOVED_REPO_PAT
52 action_logger, REMOVED_REPO_PAT
53 from rhodecode.model import BaseModel
53 from rhodecode.model import BaseModel
54 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
54 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
55 UserFollowing, UserLog, User, RepoGroup, PullRequest
55 UserFollowing, UserLog, User, RepoGroup, PullRequest
56 from rhodecode.lib.hooks import log_push_action
56 from rhodecode.lib.hooks import log_push_action
57 from rhodecode.lib.exceptions import NonRelativePathError
57
58
58 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
59
60
60
61
61 class UserTemp(object):
62 class UserTemp(object):
62 def __init__(self, user_id):
63 def __init__(self, user_id):
63 self.user_id = user_id
64 self.user_id = user_id
64
65
65 def __repr__(self):
66 def __repr__(self):
66 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
67 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
67
68
68
69
69 class RepoTemp(object):
70 class RepoTemp(object):
70 def __init__(self, repo_id):
71 def __init__(self, repo_id):
71 self.repo_id = repo_id
72 self.repo_id = repo_id
72
73
73 def __repr__(self):
74 def __repr__(self):
74 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
75 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
75
76
76
77
77 class CachedRepoList(object):
78 class CachedRepoList(object):
78 """
79 """
79 Cached repo list, uses in-memory cache after initialization, that is
80 Cached repo list, uses in-memory cache after initialization, that is
80 super fast
81 super fast
81 """
82 """
82
83
83 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
84 def __init__(self, db_repo_list, repos_path, order_by=None, perm_set=None):
84 self.db_repo_list = db_repo_list
85 self.db_repo_list = db_repo_list
85 self.repos_path = repos_path
86 self.repos_path = repos_path
86 self.order_by = order_by
87 self.order_by = order_by
87 self.reversed = (order_by or '').startswith('-')
88 self.reversed = (order_by or '').startswith('-')
88 if not perm_set:
89 if not perm_set:
89 perm_set = ['repository.read', 'repository.write',
90 perm_set = ['repository.read', 'repository.write',
90 'repository.admin']
91 'repository.admin']
91 self.perm_set = perm_set
92 self.perm_set = perm_set
92
93
93 def __len__(self):
94 def __len__(self):
94 return len(self.db_repo_list)
95 return len(self.db_repo_list)
95
96
96 def __repr__(self):
97 def __repr__(self):
97 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
98 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
98
99
99 def __iter__(self):
100 def __iter__(self):
100 # pre-propagated valid_cache_keys to save executing select statements
101 # pre-propagated valid_cache_keys to save executing select statements
101 # for each repo
102 # for each repo
102 valid_cache_keys = CacheInvalidation.get_valid_cache_keys()
103 valid_cache_keys = CacheInvalidation.get_valid_cache_keys()
103
104
104 for dbr in self.db_repo_list:
105 for dbr in self.db_repo_list:
105 scmr = dbr.scm_instance_cached(valid_cache_keys)
106 scmr = dbr.scm_instance_cached(valid_cache_keys)
106 # check permission at this level
107 # check permission at this level
107 if not HasRepoPermissionAny(
108 if not HasRepoPermissionAny(
108 *self.perm_set)(dbr.repo_name, 'get repo check'):
109 *self.perm_set)(dbr.repo_name, 'get repo check'):
109 continue
110 continue
110
111
111 try:
112 try:
112 last_change = scmr.last_change
113 last_change = scmr.last_change
113 tip = h.get_changeset_safe(scmr, 'tip')
114 tip = h.get_changeset_safe(scmr, 'tip')
114 except Exception:
115 except Exception:
115 log.error(
116 log.error(
116 '%s this repository is present in database but it '
117 '%s this repository is present in database but it '
117 'cannot be created as an scm instance, org_exc:%s'
118 'cannot be created as an scm instance, org_exc:%s'
118 % (dbr.repo_name, traceback.format_exc())
119 % (dbr.repo_name, traceback.format_exc())
119 )
120 )
120 continue
121 continue
121
122
122 tmp_d = {}
123 tmp_d = {}
123 tmp_d['name'] = dbr.repo_name
124 tmp_d['name'] = dbr.repo_name
124 tmp_d['name_sort'] = tmp_d['name'].lower()
125 tmp_d['name_sort'] = tmp_d['name'].lower()
125 tmp_d['raw_name'] = tmp_d['name'].lower()
126 tmp_d['raw_name'] = tmp_d['name'].lower()
126 tmp_d['description'] = dbr.description
127 tmp_d['description'] = dbr.description
127 tmp_d['description_sort'] = tmp_d['description'].lower()
128 tmp_d['description_sort'] = tmp_d['description'].lower()
128 tmp_d['last_change'] = last_change
129 tmp_d['last_change'] = last_change
129 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
130 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
130 tmp_d['tip'] = tip.raw_id
131 tmp_d['tip'] = tip.raw_id
131 tmp_d['tip_sort'] = tip.revision
132 tmp_d['tip_sort'] = tip.revision
132 tmp_d['rev'] = tip.revision
133 tmp_d['rev'] = tip.revision
133 tmp_d['contact'] = dbr.user.full_contact
134 tmp_d['contact'] = dbr.user.full_contact
134 tmp_d['contact_sort'] = tmp_d['contact']
135 tmp_d['contact_sort'] = tmp_d['contact']
135 tmp_d['owner_sort'] = tmp_d['contact']
136 tmp_d['owner_sort'] = tmp_d['contact']
136 tmp_d['repo_archives'] = list(scmr._get_archives())
137 tmp_d['repo_archives'] = list(scmr._get_archives())
137 tmp_d['last_msg'] = tip.message
138 tmp_d['last_msg'] = tip.message
138 tmp_d['author'] = tip.author
139 tmp_d['author'] = tip.author
139 tmp_d['dbrepo'] = dbr.get_dict()
140 tmp_d['dbrepo'] = dbr.get_dict()
140 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
141 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
141 yield tmp_d
142 yield tmp_d
142
143
143
144
144 class SimpleCachedRepoList(CachedRepoList):
145 class SimpleCachedRepoList(CachedRepoList):
145 """
146 """
146 Lighter version of CachedRepoList without the scm initialisation
147 Lighter version of CachedRepoList without the scm initialisation
147 """
148 """
148
149
149 def __iter__(self):
150 def __iter__(self):
150 for dbr in self.db_repo_list:
151 for dbr in self.db_repo_list:
151 # check permission at this level
152 # check permission at this level
152 if not HasRepoPermissionAny(
153 if not HasRepoPermissionAny(
153 *self.perm_set)(dbr.repo_name, 'get repo check'):
154 *self.perm_set)(dbr.repo_name, 'get repo check'):
154 continue
155 continue
155
156
156 tmp_d = {}
157 tmp_d = {}
157 tmp_d['name'] = dbr.repo_name
158 tmp_d['name'] = dbr.repo_name
158 tmp_d['name_sort'] = tmp_d['name'].lower()
159 tmp_d['name_sort'] = tmp_d['name'].lower()
159 tmp_d['raw_name'] = tmp_d['name'].lower()
160 tmp_d['raw_name'] = tmp_d['name'].lower()
160 tmp_d['description'] = dbr.description
161 tmp_d['description'] = dbr.description
161 tmp_d['description_sort'] = tmp_d['description'].lower()
162 tmp_d['description_sort'] = tmp_d['description'].lower()
162 tmp_d['dbrepo'] = dbr.get_dict()
163 tmp_d['dbrepo'] = dbr.get_dict()
163 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
164 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
164 yield tmp_d
165 yield tmp_d
165
166
166
167
167 class _PermCheckIterator(object):
168 class _PermCheckIterator(object):
168 def __init__(self, obj_list, obj_attr, perm_set, perm_checker):
169 def __init__(self, obj_list, obj_attr, perm_set, perm_checker):
169 """
170 """
170 Creates iterator from given list of objects, additionally
171 Creates iterator from given list of objects, additionally
171 checking permission for them from perm_set var
172 checking permission for them from perm_set var
172
173
173 :param obj_list: list of db objects
174 :param obj_list: list of db objects
174 :param obj_attr: attribute of object to pass into perm_checker
175 :param obj_attr: attribute of object to pass into perm_checker
175 :param perm_set: list of permissions to check
176 :param perm_set: list of permissions to check
176 :param perm_checker: callable to check permissions against
177 :param perm_checker: callable to check permissions against
177 """
178 """
178 self.obj_list = obj_list
179 self.obj_list = obj_list
179 self.obj_attr = obj_attr
180 self.obj_attr = obj_attr
180 self.perm_set = perm_set
181 self.perm_set = perm_set
181 self.perm_checker = perm_checker
182 self.perm_checker = perm_checker
182
183
183 def __len__(self):
184 def __len__(self):
184 return len(self.obj_list)
185 return len(self.obj_list)
185
186
186 def __repr__(self):
187 def __repr__(self):
187 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
188 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
188
189
189 def __iter__(self):
190 def __iter__(self):
190 for db_obj in self.obj_list:
191 for db_obj in self.obj_list:
191 # check permission at this level
192 # check permission at this level
192 name = getattr(db_obj, self.obj_attr, None)
193 name = getattr(db_obj, self.obj_attr, None)
193 if not self.perm_checker(*self.perm_set)(name, self.__class__.__name__):
194 if not self.perm_checker(*self.perm_set)(name, self.__class__.__name__):
194 continue
195 continue
195
196
196 yield db_obj
197 yield db_obj
197
198
198
199
199 class RepoGroupList(_PermCheckIterator):
200 class RepoGroupList(_PermCheckIterator):
200
201
201 def __init__(self, db_repo_group_list, perm_set=None):
202 def __init__(self, db_repo_group_list, perm_set=None):
202 if not perm_set:
203 if not perm_set:
203 perm_set = ['group.read', 'group.write', 'group.admin']
204 perm_set = ['group.read', 'group.write', 'group.admin']
204
205
205 super(RepoGroupList, self).__init__(obj_list=db_repo_group_list,
206 super(RepoGroupList, self).__init__(obj_list=db_repo_group_list,
206 obj_attr='group_name', perm_set=perm_set,
207 obj_attr='group_name', perm_set=perm_set,
207 perm_checker=HasReposGroupPermissionAny)
208 perm_checker=HasReposGroupPermissionAny)
208
209
209
210
210 class UserGroupList(_PermCheckIterator):
211 class UserGroupList(_PermCheckIterator):
211
212
212 def __init__(self, db_user_group_list, perm_set=None):
213 def __init__(self, db_user_group_list, perm_set=None):
213 if not perm_set:
214 if not perm_set:
214 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
215 perm_set = ['usergroup.read', 'usergroup.write', 'usergroup.admin']
215
216
216 super(UserGroupList, self).__init__(obj_list=db_user_group_list,
217 super(UserGroupList, self).__init__(obj_list=db_user_group_list,
217 obj_attr='users_group_name', perm_set=perm_set,
218 obj_attr='users_group_name', perm_set=perm_set,
218 perm_checker=HasUserGroupPermissionAny)
219 perm_checker=HasUserGroupPermissionAny)
219
220
220
221
221 class ScmModel(BaseModel):
222 class ScmModel(BaseModel):
222 """
223 """
223 Generic Scm Model
224 Generic Scm Model
224 """
225 """
225
226
226 def __get_repo(self, instance):
227 def __get_repo(self, instance):
227 cls = Repository
228 cls = Repository
228 if isinstance(instance, cls):
229 if isinstance(instance, cls):
229 return instance
230 return instance
230 elif isinstance(instance, int) or safe_str(instance).isdigit():
231 elif isinstance(instance, int) or safe_str(instance).isdigit():
231 return cls.get(instance)
232 return cls.get(instance)
232 elif isinstance(instance, basestring):
233 elif isinstance(instance, basestring):
233 return cls.get_by_repo_name(instance)
234 return cls.get_by_repo_name(instance)
234 elif instance:
235 elif instance:
235 raise Exception('given object must be int, basestr or Instance'
236 raise Exception('given object must be int, basestr or Instance'
236 ' of %s got %s' % (type(cls), type(instance)))
237 ' of %s got %s' % (type(cls), type(instance)))
237
238
238 @LazyProperty
239 @LazyProperty
239 def repos_path(self):
240 def repos_path(self):
240 """
241 """
241 Get's the repositories root path from database
242 Get's the repositories root path from database
242 """
243 """
243
244
244 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
245 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
245
246
246 return q.ui_value
247 return q.ui_value
247
248
248 def repo_scan(self, repos_path=None):
249 def repo_scan(self, repos_path=None):
249 """
250 """
250 Listing of repositories in given path. This path should not be a
251 Listing of repositories in given path. This path should not be a
251 repository itself. Return a dictionary of repository objects
252 repository itself. Return a dictionary of repository objects
252
253
253 :param repos_path: path to directory containing repositories
254 :param repos_path: path to directory containing repositories
254 """
255 """
255
256
256 if repos_path is None:
257 if repos_path is None:
257 repos_path = self.repos_path
258 repos_path = self.repos_path
258
259
259 log.info('scanning for repositories in %s' % repos_path)
260 log.info('scanning for repositories in %s' % repos_path)
260
261
261 baseui = make_ui('db')
262 baseui = make_ui('db')
262 repos = {}
263 repos = {}
263
264
264 for name, path in get_filesystem_repos(repos_path, recursive=True):
265 for name, path in get_filesystem_repos(repos_path, recursive=True):
265 # name need to be decomposed and put back together using the /
266 # name need to be decomposed and put back together using the /
266 # since this is internal storage separator for rhodecode
267 # since this is internal storage separator for rhodecode
267 name = Repository.normalize_repo_name(name)
268 name = Repository.normalize_repo_name(name)
268
269
269 try:
270 try:
270 if name in repos:
271 if name in repos:
271 raise RepositoryError('Duplicate repository name %s '
272 raise RepositoryError('Duplicate repository name %s '
272 'found in %s' % (name, path))
273 'found in %s' % (name, path))
273 else:
274 else:
274
275
275 klass = get_backend(path[0])
276 klass = get_backend(path[0])
276
277
277 if path[0] == 'hg' and path[0] in BACKENDS.keys():
278 if path[0] == 'hg' and path[0] in BACKENDS.keys():
278 repos[name] = klass(safe_str(path[1]), baseui=baseui)
279 repos[name] = klass(safe_str(path[1]), baseui=baseui)
279
280
280 if path[0] == 'git' and path[0] in BACKENDS.keys():
281 if path[0] == 'git' and path[0] in BACKENDS.keys():
281 repos[name] = klass(path[1])
282 repos[name] = klass(path[1])
282 except OSError:
283 except OSError:
283 continue
284 continue
284 log.debug('found %s paths with repositories' % (len(repos)))
285 log.debug('found %s paths with repositories' % (len(repos)))
285 return repos
286 return repos
286
287
287 def get_repos(self, all_repos=None, sort_key=None, simple=False):
288 def get_repos(self, all_repos=None, sort_key=None, simple=False):
288 """
289 """
289 Get all repos from db and for each repo create it's
290 Get all repos from db and for each repo create it's
290 backend instance and fill that backed with information from database
291 backend instance and fill that backed with information from database
291
292
292 :param all_repos: list of repository names as strings
293 :param all_repos: list of repository names as strings
293 give specific repositories list, good for filtering
294 give specific repositories list, good for filtering
294
295
295 :param sort_key: initial sorting of repos
296 :param sort_key: initial sorting of repos
296 :param simple: use SimpleCachedList - one without the SCM info
297 :param simple: use SimpleCachedList - one without the SCM info
297 """
298 """
298 if all_repos is None:
299 if all_repos is None:
299 all_repos = self.sa.query(Repository)\
300 all_repos = self.sa.query(Repository)\
300 .filter(Repository.group_id == None)\
301 .filter(Repository.group_id == None)\
301 .order_by(func.lower(Repository.repo_name)).all()
302 .order_by(func.lower(Repository.repo_name)).all()
302 if simple:
303 if simple:
303 repo_iter = SimpleCachedRepoList(all_repos,
304 repo_iter = SimpleCachedRepoList(all_repos,
304 repos_path=self.repos_path,
305 repos_path=self.repos_path,
305 order_by=sort_key)
306 order_by=sort_key)
306 else:
307 else:
307 repo_iter = CachedRepoList(all_repos,
308 repo_iter = CachedRepoList(all_repos,
308 repos_path=self.repos_path,
309 repos_path=self.repos_path,
309 order_by=sort_key)
310 order_by=sort_key)
310
311
311 return repo_iter
312 return repo_iter
312
313
313 def get_repos_groups(self, all_groups=None):
314 def get_repos_groups(self, all_groups=None):
314 if all_groups is None:
315 if all_groups is None:
315 all_groups = RepoGroup.query()\
316 all_groups = RepoGroup.query()\
316 .filter(RepoGroup.group_parent_id == None).all()
317 .filter(RepoGroup.group_parent_id == None).all()
317 return [x for x in RepoGroupList(all_groups)]
318 return [x for x in RepoGroupList(all_groups)]
318
319
319 def mark_for_invalidation(self, repo_name):
320 def mark_for_invalidation(self, repo_name):
320 """
321 """
321 Mark caches of this repo invalid in the database.
322 Mark caches of this repo invalid in the database.
322
323
323 :param repo_name: the repo for which caches should be marked invalid
324 :param repo_name: the repo for which caches should be marked invalid
324 """
325 """
325 CacheInvalidation.set_invalidate(repo_name)
326 CacheInvalidation.set_invalidate(repo_name)
326 repo = Repository.get_by_repo_name(repo_name)
327 repo = Repository.get_by_repo_name(repo_name)
327 if repo:
328 if repo:
328 repo.update_changeset_cache()
329 repo.update_changeset_cache()
329
330
330 def toggle_following_repo(self, follow_repo_id, user_id):
331 def toggle_following_repo(self, follow_repo_id, user_id):
331
332
332 f = self.sa.query(UserFollowing)\
333 f = self.sa.query(UserFollowing)\
333 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
334 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
334 .filter(UserFollowing.user_id == user_id).scalar()
335 .filter(UserFollowing.user_id == user_id).scalar()
335
336
336 if f is not None:
337 if f is not None:
337 try:
338 try:
338 self.sa.delete(f)
339 self.sa.delete(f)
339 action_logger(UserTemp(user_id),
340 action_logger(UserTemp(user_id),
340 'stopped_following_repo',
341 'stopped_following_repo',
341 RepoTemp(follow_repo_id))
342 RepoTemp(follow_repo_id))
342 return
343 return
343 except Exception:
344 except Exception:
344 log.error(traceback.format_exc())
345 log.error(traceback.format_exc())
345 raise
346 raise
346
347
347 try:
348 try:
348 f = UserFollowing()
349 f = UserFollowing()
349 f.user_id = user_id
350 f.user_id = user_id
350 f.follows_repo_id = follow_repo_id
351 f.follows_repo_id = follow_repo_id
351 self.sa.add(f)
352 self.sa.add(f)
352
353
353 action_logger(UserTemp(user_id),
354 action_logger(UserTemp(user_id),
354 'started_following_repo',
355 'started_following_repo',
355 RepoTemp(follow_repo_id))
356 RepoTemp(follow_repo_id))
356 except Exception:
357 except Exception:
357 log.error(traceback.format_exc())
358 log.error(traceback.format_exc())
358 raise
359 raise
359
360
360 def toggle_following_user(self, follow_user_id, user_id):
361 def toggle_following_user(self, follow_user_id, user_id):
361 f = self.sa.query(UserFollowing)\
362 f = self.sa.query(UserFollowing)\
362 .filter(UserFollowing.follows_user_id == follow_user_id)\
363 .filter(UserFollowing.follows_user_id == follow_user_id)\
363 .filter(UserFollowing.user_id == user_id).scalar()
364 .filter(UserFollowing.user_id == user_id).scalar()
364
365
365 if f is not None:
366 if f is not None:
366 try:
367 try:
367 self.sa.delete(f)
368 self.sa.delete(f)
368 return
369 return
369 except Exception:
370 except Exception:
370 log.error(traceback.format_exc())
371 log.error(traceback.format_exc())
371 raise
372 raise
372
373
373 try:
374 try:
374 f = UserFollowing()
375 f = UserFollowing()
375 f.user_id = user_id
376 f.user_id = user_id
376 f.follows_user_id = follow_user_id
377 f.follows_user_id = follow_user_id
377 self.sa.add(f)
378 self.sa.add(f)
378 except Exception:
379 except Exception:
379 log.error(traceback.format_exc())
380 log.error(traceback.format_exc())
380 raise
381 raise
381
382
382 def is_following_repo(self, repo_name, user_id, cache=False):
383 def is_following_repo(self, repo_name, user_id, cache=False):
383 r = self.sa.query(Repository)\
384 r = self.sa.query(Repository)\
384 .filter(Repository.repo_name == repo_name).scalar()
385 .filter(Repository.repo_name == repo_name).scalar()
385
386
386 f = self.sa.query(UserFollowing)\
387 f = self.sa.query(UserFollowing)\
387 .filter(UserFollowing.follows_repository == r)\
388 .filter(UserFollowing.follows_repository == r)\
388 .filter(UserFollowing.user_id == user_id).scalar()
389 .filter(UserFollowing.user_id == user_id).scalar()
389
390
390 return f is not None
391 return f is not None
391
392
392 def is_following_user(self, username, user_id, cache=False):
393 def is_following_user(self, username, user_id, cache=False):
393 u = User.get_by_username(username)
394 u = User.get_by_username(username)
394
395
395 f = self.sa.query(UserFollowing)\
396 f = self.sa.query(UserFollowing)\
396 .filter(UserFollowing.follows_user == u)\
397 .filter(UserFollowing.follows_user == u)\
397 .filter(UserFollowing.user_id == user_id).scalar()
398 .filter(UserFollowing.user_id == user_id).scalar()
398
399
399 return f is not None
400 return f is not None
400
401
401 def get_followers(self, repo):
402 def get_followers(self, repo):
402 repo = self._get_repo(repo)
403 repo = self._get_repo(repo)
403
404
404 return self.sa.query(UserFollowing)\
405 return self.sa.query(UserFollowing)\
405 .filter(UserFollowing.follows_repository == repo).count()
406 .filter(UserFollowing.follows_repository == repo).count()
406
407
407 def get_forks(self, repo):
408 def get_forks(self, repo):
408 repo = self._get_repo(repo)
409 repo = self._get_repo(repo)
409 return self.sa.query(Repository)\
410 return self.sa.query(Repository)\
410 .filter(Repository.fork == repo).count()
411 .filter(Repository.fork == repo).count()
411
412
412 def get_pull_requests(self, repo):
413 def get_pull_requests(self, repo):
413 repo = self._get_repo(repo)
414 repo = self._get_repo(repo)
414 return self.sa.query(PullRequest)\
415 return self.sa.query(PullRequest)\
415 .filter(PullRequest.other_repo == repo)\
416 .filter(PullRequest.other_repo == repo)\
416 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
417 .filter(PullRequest.status != PullRequest.STATUS_CLOSED).count()
417
418
418 def mark_as_fork(self, repo, fork, user):
419 def mark_as_fork(self, repo, fork, user):
419 repo = self.__get_repo(repo)
420 repo = self.__get_repo(repo)
420 fork = self.__get_repo(fork)
421 fork = self.__get_repo(fork)
421 if fork and repo.repo_id == fork.repo_id:
422 if fork and repo.repo_id == fork.repo_id:
422 raise Exception("Cannot set repository as fork of itself")
423 raise Exception("Cannot set repository as fork of itself")
423 repo.fork = fork
424 repo.fork = fork
424 self.sa.add(repo)
425 self.sa.add(repo)
425 return repo
426 return repo
426
427
427 def _handle_rc_scm_extras(self, username, repo_name, repo_alias):
428 def _handle_rc_scm_extras(self, username, repo_name, repo_alias):
428 from rhodecode import CONFIG
429 from rhodecode import CONFIG
429 from rhodecode.lib.base import _get_ip_addr
430 from rhodecode.lib.base import _get_ip_addr
430 try:
431 try:
431 from pylons import request
432 from pylons import request
432 environ = request.environ
433 environ = request.environ
433 except TypeError:
434 except TypeError:
434 # we might use this outside of request context, let's fake the
435 # we might use this outside of request context, let's fake the
435 # environ data
436 # environ data
436 from webob import Request
437 from webob import Request
437 environ = Request.blank('').environ
438 environ = Request.blank('').environ
438 extras = {
439 extras = {
439 'ip': _get_ip_addr(environ),
440 'ip': _get_ip_addr(environ),
440 'username': username,
441 'username': username,
441 'action': 'push_local',
442 'action': 'push_local',
442 'repository': repo_name,
443 'repository': repo_name,
443 'scm': repo_alias,
444 'scm': repo_alias,
444 'config': CONFIG['__file__'],
445 'config': CONFIG['__file__'],
445 'server_url': get_server_url(environ),
446 'server_url': get_server_url(environ),
446 'make_lock': None,
447 'make_lock': None,
447 'locked_by': [None, None]
448 'locked_by': [None, None]
448 }
449 }
449 _set_extras(extras)
450 _set_extras(extras)
450
451
451 def _handle_push(self, repo, username, action, repo_name, revisions):
452 def _handle_push(self, repo, username, action, repo_name, revisions):
452 """
453 """
453 Triggers push action hooks
454 Triggers push action hooks
454
455
455 :param repo: SCM repo
456 :param repo: SCM repo
456 :param username: username who pushes
457 :param username: username who pushes
457 :param action: push/push_loca/push_remote
458 :param action: push/push_loca/push_remote
458 :param repo_name: name of repo
459 :param repo_name: name of repo
459 :param revisions: list of revisions that we pushed
460 :param revisions: list of revisions that we pushed
460 """
461 """
461 self._handle_rc_scm_extras(username, repo_name, repo_alias=repo.alias)
462 self._handle_rc_scm_extras(username, repo_name, repo_alias=repo.alias)
462 _scm_repo = repo._repo
463 _scm_repo = repo._repo
463 # trigger push hook
464 # trigger push hook
464 if repo.alias == 'hg':
465 if repo.alias == 'hg':
465 log_push_action(_scm_repo.ui, _scm_repo, node=revisions[0])
466 log_push_action(_scm_repo.ui, _scm_repo, node=revisions[0])
466 elif repo.alias == 'git':
467 elif repo.alias == 'git':
467 log_push_action(None, _scm_repo, _git_revs=revisions)
468 log_push_action(None, _scm_repo, _git_revs=revisions)
468
469
469 def _get_IMC_module(self, scm_type):
470 def _get_IMC_module(self, scm_type):
470 """
471 """
471 Returns InMemoryCommit class based on scm_type
472 Returns InMemoryCommit class based on scm_type
472
473
473 :param scm_type:
474 :param scm_type:
474 """
475 """
475 if scm_type == 'hg':
476 if scm_type == 'hg':
476 from rhodecode.lib.vcs.backends.hg import \
477 from rhodecode.lib.vcs.backends.hg import \
477 MercurialInMemoryChangeset as IMC
478 MercurialInMemoryChangeset as IMC
478 elif scm_type == 'git':
479 elif scm_type == 'git':
479 from rhodecode.lib.vcs.backends.git import \
480 from rhodecode.lib.vcs.backends.git import \
480 GitInMemoryChangeset as IMC
481 GitInMemoryChangeset as IMC
481 return IMC
482 return IMC
482
483
483 def pull_changes(self, repo, username):
484 def pull_changes(self, repo, username):
484 dbrepo = self.__get_repo(repo)
485 dbrepo = self.__get_repo(repo)
485 clone_uri = dbrepo.clone_uri
486 clone_uri = dbrepo.clone_uri
486 if not clone_uri:
487 if not clone_uri:
487 raise Exception("This repository doesn't have a clone uri")
488 raise Exception("This repository doesn't have a clone uri")
488
489
489 repo = dbrepo.scm_instance
490 repo = dbrepo.scm_instance
490 repo_name = dbrepo.repo_name
491 repo_name = dbrepo.repo_name
491 try:
492 try:
492 if repo.alias == 'git':
493 if repo.alias == 'git':
493 repo.fetch(clone_uri)
494 repo.fetch(clone_uri)
494 else:
495 else:
495 repo.pull(clone_uri)
496 repo.pull(clone_uri)
496 self.mark_for_invalidation(repo_name)
497 self.mark_for_invalidation(repo_name)
497 except Exception:
498 except Exception:
498 log.error(traceback.format_exc())
499 log.error(traceback.format_exc())
499 raise
500 raise
500
501
501 def commit_change(self, repo, repo_name, cs, user, author, message,
502 def commit_change(self, repo, repo_name, cs, user, author, message,
502 content, f_path):
503 content, f_path):
503 """
504 """
504 Commits changes
505 Commits changes
505
506
506 :param repo: SCM instance
507 :param repo: SCM instance
507
508
508 """
509 """
509 user = self._get_user(user)
510 user = self._get_user(user)
510 IMC = self._get_IMC_module(repo.alias)
511 IMC = self._get_IMC_module(repo.alias)
511
512
512 # decoding here will force that we have proper encoded values
513 # decoding here will force that we have proper encoded values
513 # in any other case this will throw exceptions and deny commit
514 # in any other case this will throw exceptions and deny commit
514 content = safe_str(content)
515 content = safe_str(content)
515 path = safe_str(f_path)
516 path = safe_str(f_path)
516 # message and author needs to be unicode
517 # message and author needs to be unicode
517 # proper backend should then translate that into required type
518 # proper backend should then translate that into required type
518 message = safe_unicode(message)
519 message = safe_unicode(message)
519 author = safe_unicode(author)
520 author = safe_unicode(author)
520 imc = IMC(repo)
521 imc = IMC(repo)
521 imc.change(FileNode(path, content, mode=cs.get_file_mode(f_path)))
522 imc.change(FileNode(path, content, mode=cs.get_file_mode(f_path)))
522 tip = imc.commit(message=message,
523 tip = imc.commit(message=message,
523 author=author,
524 author=author,
524 parents=[cs], branch=cs.branch)
525 parents=[cs], branch=cs.branch)
525
526
526 self.mark_for_invalidation(repo_name)
527 self.mark_for_invalidation(repo_name)
527 self._handle_push(repo,
528 self._handle_push(repo,
528 username=user.username,
529 username=user.username,
529 action='push_local',
530 action='push_local',
530 repo_name=repo_name,
531 repo_name=repo_name,
531 revisions=[tip.raw_id])
532 revisions=[tip.raw_id])
532 return tip
533 return tip
533
534
534 def create_node(self, repo, repo_name, cs, user, author, message, content,
535 def create_nodes(self, user, repo, message, nodes, parent_cs=None,
535 f_path):
536 author=None, trigger_push_hook=True):
537 """
538 Commits given multiple nodes into repo
539
540 :param user: RhodeCode User object or user_id, the commiter
541 :param repo: RhodeCode Repository object
542 :param message: commit message
543 :param nodes: mapping {filename:{'content':content},...}
544 :param parent_cs: parent changeset, can be empty than it's initial commit
545 :param author: author of commit, cna be different that commiter only for git
546 :param trigger_push_hook: trigger push hooks
547
548 :returns: new commited changeset
549 """
550
536 user = self._get_user(user)
551 user = self._get_user(user)
537 IMC = self._get_IMC_module(repo.alias)
552 scm_instance = repo.scm_instance_no_cache()
538
553
539 # decoding here will force that we have proper encoded values
554 processed_nodes = []
540 # in any other case this will throw exceptions and deny commit
555 for f_path in nodes:
541 if isinstance(content, (basestring,)):
556 if f_path.startswith('/') or f_path.startswith('.') or '../' in f_path:
542 content = safe_str(content)
557 raise NonRelativePathError('%s is not an relative path' % f_path)
543 elif isinstance(content, (file, cStringIO.OutputType,)):
558 if f_path:
544 content = content.read()
559 f_path = os.path.normpath(f_path)
545 else:
560 f_path = safe_str(f_path)
546 raise Exception('Content is of unrecognized type %s' % (
561 content = nodes[f_path]['content']
547 type(content)
562 # decoding here will force that we have proper encoded values
548 ))
563 # in any other case this will throw exceptions and deny commit
564 if isinstance(content, (basestring,)):
565 content = safe_str(content)
566 elif isinstance(content, (file, cStringIO.OutputType,)):
567 content = content.read()
568 else:
569 raise Exception('Content is of unrecognized type %s' % (
570 type(content)
571 ))
572 processed_nodes.append((f_path, content))
549
573
550 message = safe_unicode(message)
574 message = safe_unicode(message)
551 author = safe_unicode(author)
575 commiter = user.full_contact
552 path = safe_str(f_path)
576 author = safe_unicode(author) if author else commiter
553 m = IMC(repo)
554
577
555 if isinstance(cs, EmptyChangeset):
578 IMC = self._get_IMC_module(scm_instance.alias)
579 imc = IMC(scm_instance)
580
581 if not parent_cs:
582 parent_cs = EmptyChangeset(alias=scm_instance.alias)
583
584 if isinstance(parent_cs, EmptyChangeset):
556 # EmptyChangeset means we we're editing empty repository
585 # EmptyChangeset means we we're editing empty repository
557 parents = None
586 parents = None
558 else:
587 else:
559 parents = [cs]
588 parents = [parent_cs]
560
589 # add multiple nodes
561 m.add(FileNode(path, content=content))
590 for path, content in processed_nodes:
562 tip = m.commit(message=message,
591 imc.add(FileNode(path, content=content))
563 author=author,
564 parents=parents, branch=cs.branch)
565
592
566 self.mark_for_invalidation(repo_name)
593 tip = imc.commit(message=message,
567 self._handle_push(repo,
594 author=author,
568 username=user.username,
595 parents=parents,
569 action='push_local',
596 branch=parent_cs.branch)
570 repo_name=repo_name,
597
571 revisions=[tip.raw_id])
598 self.mark_for_invalidation(repo.repo_name)
599 if trigger_push_hook:
600 self._handle_push(scm_instance,
601 username=user.username,
602 action='push_local',
603 repo_name=repo.repo_name,
604 revisions=[tip.raw_id])
572 return tip
605 return tip
573
606
574 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
607 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
575 """
608 """
576 recursive walk in root dir and return a set of all path in that dir
609 recursive walk in root dir and return a set of all path in that dir
577 based on repository walk function
610 based on repository walk function
578
611
579 :param repo_name: name of repository
612 :param repo_name: name of repository
580 :param revision: revision for which to list nodes
613 :param revision: revision for which to list nodes
581 :param root_path: root path to list
614 :param root_path: root path to list
582 :param flat: return as a list, if False returns a dict with decription
615 :param flat: return as a list, if False returns a dict with decription
583
616
584 """
617 """
585 _files = list()
618 _files = list()
586 _dirs = list()
619 _dirs = list()
587 try:
620 try:
588 _repo = self.__get_repo(repo_name)
621 _repo = self.__get_repo(repo_name)
589 changeset = _repo.scm_instance.get_changeset(revision)
622 changeset = _repo.scm_instance.get_changeset(revision)
590 root_path = root_path.lstrip('/')
623 root_path = root_path.lstrip('/')
591 for topnode, dirs, files in changeset.walk(root_path):
624 for topnode, dirs, files in changeset.walk(root_path):
592 for f in files:
625 for f in files:
593 _files.append(f.path if flat else {"name": f.path,
626 _files.append(f.path if flat else {"name": f.path,
594 "type": "file"})
627 "type": "file"})
595 for d in dirs:
628 for d in dirs:
596 _dirs.append(d.path if flat else {"name": d.path,
629 _dirs.append(d.path if flat else {"name": d.path,
597 "type": "dir"})
630 "type": "dir"})
598 except RepositoryError:
631 except RepositoryError:
599 log.debug(traceback.format_exc())
632 log.debug(traceback.format_exc())
600 raise
633 raise
601
634
602 return _dirs, _files
635 return _dirs, _files
603
636
604 def get_unread_journal(self):
637 def get_unread_journal(self):
605 return self.sa.query(UserLog).count()
638 return self.sa.query(UserLog).count()
606
639
607 def get_repo_landing_revs(self, repo=None):
640 def get_repo_landing_revs(self, repo=None):
608 """
641 """
609 Generates select option with tags branches and bookmarks (for hg only)
642 Generates select option with tags branches and bookmarks (for hg only)
610 grouped by type
643 grouped by type
611
644
612 :param repo:
645 :param repo:
613 :type repo:
614 """
646 """
615
647
616 hist_l = []
648 hist_l = []
617 choices = []
649 choices = []
618 repo = self.__get_repo(repo)
650 repo = self.__get_repo(repo)
619 hist_l.append(['tip', _('latest tip')])
651 hist_l.append(['tip', _('latest tip')])
620 choices.append('tip')
652 choices.append('tip')
621 if not repo:
653 if not repo:
622 return choices, hist_l
654 return choices, hist_l
623
655
624 repo = repo.scm_instance
656 repo = repo.scm_instance
625
657
626 branches_group = ([(k, k) for k, v in
658 branches_group = ([(k, k) for k, v in
627 repo.branches.iteritems()], _("Branches"))
659 repo.branches.iteritems()], _("Branches"))
628 hist_l.append(branches_group)
660 hist_l.append(branches_group)
629 choices.extend([x[0] for x in branches_group[0]])
661 choices.extend([x[0] for x in branches_group[0]])
630
662
631 if repo.alias == 'hg':
663 if repo.alias == 'hg':
632 bookmarks_group = ([(k, k) for k, v in
664 bookmarks_group = ([(k, k) for k, v in
633 repo.bookmarks.iteritems()], _("Bookmarks"))
665 repo.bookmarks.iteritems()], _("Bookmarks"))
634 hist_l.append(bookmarks_group)
666 hist_l.append(bookmarks_group)
635 choices.extend([x[0] for x in bookmarks_group[0]])
667 choices.extend([x[0] for x in bookmarks_group[0]])
636
668
637 tags_group = ([(k, k) for k, v in
669 tags_group = ([(k, k) for k, v in
638 repo.tags.iteritems()], _("Tags"))
670 repo.tags.iteritems()], _("Tags"))
639 hist_l.append(tags_group)
671 hist_l.append(tags_group)
640 choices.extend([x[0] for x in tags_group[0]])
672 choices.extend([x[0] for x in tags_group[0]])
641
673
642 return choices, hist_l
674 return choices, hist_l
643
675
644 def install_git_hook(self, repo, force_create=False):
676 def install_git_hook(self, repo, force_create=False):
645 """
677 """
646 Creates a rhodecode hook inside a git repository
678 Creates a rhodecode hook inside a git repository
647
679
648 :param repo: Instance of VCS repo
680 :param repo: Instance of VCS repo
649 :param force_create: Create even if same name hook exists
681 :param force_create: Create even if same name hook exists
650 """
682 """
651
683
652 loc = jn(repo.path, 'hooks')
684 loc = jn(repo.path, 'hooks')
653 if not repo.bare:
685 if not repo.bare:
654 loc = jn(repo.path, '.git', 'hooks')
686 loc = jn(repo.path, '.git', 'hooks')
655 if not os.path.isdir(loc):
687 if not os.path.isdir(loc):
656 os.makedirs(loc)
688 os.makedirs(loc)
657
689
658 tmpl_post = pkg_resources.resource_string(
690 tmpl_post = pkg_resources.resource_string(
659 'rhodecode', jn('config', 'post_receive_tmpl.py')
691 'rhodecode', jn('config', 'post_receive_tmpl.py')
660 )
692 )
661 tmpl_pre = pkg_resources.resource_string(
693 tmpl_pre = pkg_resources.resource_string(
662 'rhodecode', jn('config', 'pre_receive_tmpl.py')
694 'rhodecode', jn('config', 'pre_receive_tmpl.py')
663 )
695 )
664
696
665 for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
697 for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]:
666 _hook_file = jn(loc, '%s-receive' % h_type)
698 _hook_file = jn(loc, '%s-receive' % h_type)
667 _rhodecode_hook = False
699 _rhodecode_hook = False
668 log.debug('Installing git hook in repo %s' % repo)
700 log.debug('Installing git hook in repo %s' % repo)
669 if os.path.exists(_hook_file):
701 if os.path.exists(_hook_file):
670 # let's take a look at this hook, maybe it's rhodecode ?
702 # let's take a look at this hook, maybe it's rhodecode ?
671 log.debug('hook exists, checking if it is from rhodecode')
703 log.debug('hook exists, checking if it is from rhodecode')
672 _HOOK_VER_PAT = re.compile(r'^RC_HOOK_VER')
704 _HOOK_VER_PAT = re.compile(r'^RC_HOOK_VER')
673 with open(_hook_file, 'rb') as f:
705 with open(_hook_file, 'rb') as f:
674 data = f.read()
706 data = f.read()
675 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
707 matches = re.compile(r'(?:%s)\s*=\s*(.*)'
676 % 'RC_HOOK_VER').search(data)
708 % 'RC_HOOK_VER').search(data)
677 if matches:
709 if matches:
678 try:
710 try:
679 ver = matches.groups()[0]
711 ver = matches.groups()[0]
680 log.debug('got %s it is rhodecode' % (ver))
712 log.debug('got %s it is rhodecode' % (ver))
681 _rhodecode_hook = True
713 _rhodecode_hook = True
682 except Exception:
714 except Exception:
683 log.error(traceback.format_exc())
715 log.error(traceback.format_exc())
684 else:
716 else:
685 # there is no hook in this dir, so we want to create one
717 # there is no hook in this dir, so we want to create one
686 _rhodecode_hook = True
718 _rhodecode_hook = True
687
719
688 if _rhodecode_hook or force_create:
720 if _rhodecode_hook or force_create:
689 log.debug('writing %s hook file !' % h_type)
721 log.debug('writing %s hook file !' % h_type)
690 with open(_hook_file, 'wb') as f:
722 with open(_hook_file, 'wb') as f:
691 tmpl = tmpl.replace('_TMPL_', rhodecode.__version__)
723 tmpl = tmpl.replace('_TMPL_', rhodecode.__version__)
692 f.write(tmpl)
724 f.write(tmpl)
693 os.chmod(_hook_file, 0755)
725 os.chmod(_hook_file, 0755)
694 else:
726 else:
695 log.debug('skipping writing hook file')
727 log.debug('skipping writing hook file')
@@ -1,809 +1,809 b''
1 """
1 """
2 Set of generic validators
2 Set of generic validators
3 """
3 """
4 import os
4 import os
5 import re
5 import re
6 import formencode
6 import formencode
7 import logging
7 import logging
8 from collections import defaultdict
8 from collections import defaultdict
9 from pylons.i18n.translation import _
9 from pylons.i18n.translation import _
10 from webhelpers.pylonslib.secure_form import authentication_token
10 from webhelpers.pylonslib.secure_form import authentication_token
11
11
12 from formencode.validators import (
12 from formencode.validators import (
13 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
13 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set,
14 NotEmpty, IPAddress, CIDR
14 NotEmpty, IPAddress, CIDR, String, FancyValidator
15 )
15 )
16 from rhodecode.lib.compat import OrderedSet
16 from rhodecode.lib.compat import OrderedSet
17 from rhodecode.lib import ipaddr
17 from rhodecode.lib import ipaddr
18 from rhodecode.lib.utils import repo_name_slug
18 from rhodecode.lib.utils import repo_name_slug
19 from rhodecode.lib.utils2 import safe_int, str2bool
19 from rhodecode.lib.utils2 import safe_int, str2bool
20 from rhodecode.model.db import RepoGroup, Repository, UserGroup, User,\
20 from rhodecode.model.db import RepoGroup, Repository, UserGroup, User,\
21 ChangesetStatus
21 ChangesetStatus
22 from rhodecode.lib.exceptions import LdapImportError
22 from rhodecode.lib.exceptions import LdapImportError
23 from rhodecode.config.routing import ADMIN_PREFIX
23 from rhodecode.config.routing import ADMIN_PREFIX
24 from rhodecode.lib.auth import HasReposGroupPermissionAny, HasPermissionAny
24 from rhodecode.lib.auth import HasReposGroupPermissionAny, HasPermissionAny
25
25
26 # silence warnings and pylint
26 # silence warnings and pylint
27 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
27 UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \
28 NotEmpty, IPAddress, CIDR
28 NotEmpty, IPAddress, CIDR, String, FancyValidator
29
29
30 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
31
31
32
32
33 class UniqueList(formencode.FancyValidator):
33 class UniqueList(formencode.FancyValidator):
34 """
34 """
35 Unique List !
35 Unique List !
36 """
36 """
37 messages = dict(
37 messages = dict(
38 empty=_('Value cannot be an empty list'),
38 empty=_('Value cannot be an empty list'),
39 missing_value=_('Value cannot be an empty list'),
39 missing_value=_('Value cannot be an empty list'),
40 )
40 )
41
41
42 def _to_python(self, value, state):
42 def _to_python(self, value, state):
43 if isinstance(value, list):
43 if isinstance(value, list):
44 return value
44 return value
45 elif isinstance(value, set):
45 elif isinstance(value, set):
46 return list(value)
46 return list(value)
47 elif isinstance(value, tuple):
47 elif isinstance(value, tuple):
48 return list(value)
48 return list(value)
49 elif value is None:
49 elif value is None:
50 return []
50 return []
51 else:
51 else:
52 return [value]
52 return [value]
53
53
54 def empty_value(self, value):
54 def empty_value(self, value):
55 return []
55 return []
56
56
57
57
58 class StateObj(object):
58 class StateObj(object):
59 """
59 """
60 this is needed to translate the messages using _() in validators
60 this is needed to translate the messages using _() in validators
61 """
61 """
62 _ = staticmethod(_)
62 _ = staticmethod(_)
63
63
64
64
65 def M(self, key, state=None, **kwargs):
65 def M(self, key, state=None, **kwargs):
66 """
66 """
67 returns string from self.message based on given key,
67 returns string from self.message based on given key,
68 passed kw params are used to substitute %(named)s params inside
68 passed kw params are used to substitute %(named)s params inside
69 translated strings
69 translated strings
70
70
71 :param msg:
71 :param msg:
72 :param state:
72 :param state:
73 """
73 """
74 if state is None:
74 if state is None:
75 state = StateObj()
75 state = StateObj()
76 else:
76 else:
77 state._ = staticmethod(_)
77 state._ = staticmethod(_)
78 #inject validator into state object
78 #inject validator into state object
79 return self.message(key, state, **kwargs)
79 return self.message(key, state, **kwargs)
80
80
81
81
82 def ValidUsername(edit=False, old_data={}):
82 def ValidUsername(edit=False, old_data={}):
83 class _validator(formencode.validators.FancyValidator):
83 class _validator(formencode.validators.FancyValidator):
84 messages = {
84 messages = {
85 'username_exists': _(u'Username "%(username)s" already exists'),
85 'username_exists': _(u'Username "%(username)s" already exists'),
86 'system_invalid_username':
86 'system_invalid_username':
87 _(u'Username "%(username)s" is forbidden'),
87 _(u'Username "%(username)s" is forbidden'),
88 'invalid_username':
88 'invalid_username':
89 _(u'Username may only contain alphanumeric characters '
89 _(u'Username may only contain alphanumeric characters '
90 'underscores, periods or dashes and must begin with '
90 'underscores, periods or dashes and must begin with '
91 'alphanumeric character')
91 'alphanumeric character')
92 }
92 }
93
93
94 def validate_python(self, value, state):
94 def validate_python(self, value, state):
95 if value in ['default', 'new_user']:
95 if value in ['default', 'new_user']:
96 msg = M(self, 'system_invalid_username', state, username=value)
96 msg = M(self, 'system_invalid_username', state, username=value)
97 raise formencode.Invalid(msg, value, state)
97 raise formencode.Invalid(msg, value, state)
98 #check if user is unique
98 #check if user is unique
99 old_un = None
99 old_un = None
100 if edit:
100 if edit:
101 old_un = User.get(old_data.get('user_id')).username
101 old_un = User.get(old_data.get('user_id')).username
102
102
103 if old_un != value or not edit:
103 if old_un != value or not edit:
104 if User.get_by_username(value, case_insensitive=True):
104 if User.get_by_username(value, case_insensitive=True):
105 msg = M(self, 'username_exists', state, username=value)
105 msg = M(self, 'username_exists', state, username=value)
106 raise formencode.Invalid(msg, value, state)
106 raise formencode.Invalid(msg, value, state)
107
107
108 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]*$', value) is None:
108 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]*$', value) is None:
109 msg = M(self, 'invalid_username', state)
109 msg = M(self, 'invalid_username', state)
110 raise formencode.Invalid(msg, value, state)
110 raise formencode.Invalid(msg, value, state)
111 return _validator
111 return _validator
112
112
113
113
114 def ValidRepoUser():
114 def ValidRepoUser():
115 class _validator(formencode.validators.FancyValidator):
115 class _validator(formencode.validators.FancyValidator):
116 messages = {
116 messages = {
117 'invalid_username': _(u'Username %(username)s is not valid')
117 'invalid_username': _(u'Username %(username)s is not valid')
118 }
118 }
119
119
120 def validate_python(self, value, state):
120 def validate_python(self, value, state):
121 try:
121 try:
122 User.query().filter(User.active == True)\
122 User.query().filter(User.active == True)\
123 .filter(User.username == value).one()
123 .filter(User.username == value).one()
124 except Exception:
124 except Exception:
125 msg = M(self, 'invalid_username', state, username=value)
125 msg = M(self, 'invalid_username', state, username=value)
126 raise formencode.Invalid(msg, value, state,
126 raise formencode.Invalid(msg, value, state,
127 error_dict=dict(username=msg)
127 error_dict=dict(username=msg)
128 )
128 )
129
129
130 return _validator
130 return _validator
131
131
132
132
133 def ValidUserGroup(edit=False, old_data={}):
133 def ValidUserGroup(edit=False, old_data={}):
134 class _validator(formencode.validators.FancyValidator):
134 class _validator(formencode.validators.FancyValidator):
135 messages = {
135 messages = {
136 'invalid_group': _(u'Invalid user group name'),
136 'invalid_group': _(u'Invalid user group name'),
137 'group_exist': _(u'User group "%(usergroup)s" already exists'),
137 'group_exist': _(u'User group "%(usergroup)s" already exists'),
138 'invalid_usergroup_name':
138 'invalid_usergroup_name':
139 _(u'user group name may only contain alphanumeric '
139 _(u'user group name may only contain alphanumeric '
140 'characters underscores, periods or dashes and must begin '
140 'characters underscores, periods or dashes and must begin '
141 'with alphanumeric character')
141 'with alphanumeric character')
142 }
142 }
143
143
144 def validate_python(self, value, state):
144 def validate_python(self, value, state):
145 if value in ['default']:
145 if value in ['default']:
146 msg = M(self, 'invalid_group', state)
146 msg = M(self, 'invalid_group', state)
147 raise formencode.Invalid(msg, value, state,
147 raise formencode.Invalid(msg, value, state,
148 error_dict=dict(users_group_name=msg)
148 error_dict=dict(users_group_name=msg)
149 )
149 )
150 #check if group is unique
150 #check if group is unique
151 old_ugname = None
151 old_ugname = None
152 if edit:
152 if edit:
153 old_id = old_data.get('users_group_id')
153 old_id = old_data.get('users_group_id')
154 old_ugname = UserGroup.get(old_id).users_group_name
154 old_ugname = UserGroup.get(old_id).users_group_name
155
155
156 if old_ugname != value or not edit:
156 if old_ugname != value or not edit:
157 is_existing_group = UserGroup.get_by_group_name(value,
157 is_existing_group = UserGroup.get_by_group_name(value,
158 case_insensitive=True)
158 case_insensitive=True)
159 if is_existing_group:
159 if is_existing_group:
160 msg = M(self, 'group_exist', state, usergroup=value)
160 msg = M(self, 'group_exist', state, usergroup=value)
161 raise formencode.Invalid(msg, value, state,
161 raise formencode.Invalid(msg, value, state,
162 error_dict=dict(users_group_name=msg)
162 error_dict=dict(users_group_name=msg)
163 )
163 )
164
164
165 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
165 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
166 msg = M(self, 'invalid_usergroup_name', state)
166 msg = M(self, 'invalid_usergroup_name', state)
167 raise formencode.Invalid(msg, value, state,
167 raise formencode.Invalid(msg, value, state,
168 error_dict=dict(users_group_name=msg)
168 error_dict=dict(users_group_name=msg)
169 )
169 )
170
170
171 return _validator
171 return _validator
172
172
173
173
174 def ValidReposGroup(edit=False, old_data={}):
174 def ValidReposGroup(edit=False, old_data={}):
175 class _validator(formencode.validators.FancyValidator):
175 class _validator(formencode.validators.FancyValidator):
176 messages = {
176 messages = {
177 'group_parent_id': _(u'Cannot assign this group as parent'),
177 'group_parent_id': _(u'Cannot assign this group as parent'),
178 'group_exists': _(u'Group "%(group_name)s" already exists'),
178 'group_exists': _(u'Group "%(group_name)s" already exists'),
179 'repo_exists':
179 'repo_exists':
180 _(u'Repository with name "%(group_name)s" already exists')
180 _(u'Repository with name "%(group_name)s" already exists')
181 }
181 }
182
182
183 def validate_python(self, value, state):
183 def validate_python(self, value, state):
184 # TODO WRITE VALIDATIONS
184 # TODO WRITE VALIDATIONS
185 group_name = value.get('group_name')
185 group_name = value.get('group_name')
186 group_parent_id = value.get('group_parent_id')
186 group_parent_id = value.get('group_parent_id')
187
187
188 # slugify repo group just in case :)
188 # slugify repo group just in case :)
189 slug = repo_name_slug(group_name)
189 slug = repo_name_slug(group_name)
190
190
191 # check for parent of self
191 # check for parent of self
192 parent_of_self = lambda: (
192 parent_of_self = lambda: (
193 old_data['group_id'] == int(group_parent_id)
193 old_data['group_id'] == int(group_parent_id)
194 if group_parent_id else False
194 if group_parent_id else False
195 )
195 )
196 if edit and parent_of_self():
196 if edit and parent_of_self():
197 msg = M(self, 'group_parent_id', state)
197 msg = M(self, 'group_parent_id', state)
198 raise formencode.Invalid(msg, value, state,
198 raise formencode.Invalid(msg, value, state,
199 error_dict=dict(group_parent_id=msg)
199 error_dict=dict(group_parent_id=msg)
200 )
200 )
201
201
202 old_gname = None
202 old_gname = None
203 if edit:
203 if edit:
204 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
204 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
205
205
206 if old_gname != group_name or not edit:
206 if old_gname != group_name or not edit:
207
207
208 # check group
208 # check group
209 gr = RepoGroup.query()\
209 gr = RepoGroup.query()\
210 .filter(RepoGroup.group_name == slug)\
210 .filter(RepoGroup.group_name == slug)\
211 .filter(RepoGroup.group_parent_id == group_parent_id)\
211 .filter(RepoGroup.group_parent_id == group_parent_id)\
212 .scalar()
212 .scalar()
213
213
214 if gr:
214 if gr:
215 msg = M(self, 'group_exists', state, group_name=slug)
215 msg = M(self, 'group_exists', state, group_name=slug)
216 raise formencode.Invalid(msg, value, state,
216 raise formencode.Invalid(msg, value, state,
217 error_dict=dict(group_name=msg)
217 error_dict=dict(group_name=msg)
218 )
218 )
219
219
220 # check for same repo
220 # check for same repo
221 repo = Repository.query()\
221 repo = Repository.query()\
222 .filter(Repository.repo_name == slug)\
222 .filter(Repository.repo_name == slug)\
223 .scalar()
223 .scalar()
224
224
225 if repo:
225 if repo:
226 msg = M(self, 'repo_exists', state, group_name=slug)
226 msg = M(self, 'repo_exists', state, group_name=slug)
227 raise formencode.Invalid(msg, value, state,
227 raise formencode.Invalid(msg, value, state,
228 error_dict=dict(group_name=msg)
228 error_dict=dict(group_name=msg)
229 )
229 )
230
230
231 return _validator
231 return _validator
232
232
233
233
234 def ValidPassword():
234 def ValidPassword():
235 class _validator(formencode.validators.FancyValidator):
235 class _validator(formencode.validators.FancyValidator):
236 messages = {
236 messages = {
237 'invalid_password':
237 'invalid_password':
238 _(u'Invalid characters (non-ascii) in password')
238 _(u'Invalid characters (non-ascii) in password')
239 }
239 }
240
240
241 def validate_python(self, value, state):
241 def validate_python(self, value, state):
242 try:
242 try:
243 (value or '').decode('ascii')
243 (value or '').decode('ascii')
244 except UnicodeError:
244 except UnicodeError:
245 msg = M(self, 'invalid_password', state)
245 msg = M(self, 'invalid_password', state)
246 raise formencode.Invalid(msg, value, state,)
246 raise formencode.Invalid(msg, value, state,)
247 return _validator
247 return _validator
248
248
249
249
250 def ValidPasswordsMatch():
250 def ValidPasswordsMatch():
251 class _validator(formencode.validators.FancyValidator):
251 class _validator(formencode.validators.FancyValidator):
252 messages = {
252 messages = {
253 'password_mismatch': _(u'Passwords do not match'),
253 'password_mismatch': _(u'Passwords do not match'),
254 }
254 }
255
255
256 def validate_python(self, value, state):
256 def validate_python(self, value, state):
257
257
258 pass_val = value.get('password') or value.get('new_password')
258 pass_val = value.get('password') or value.get('new_password')
259 if pass_val != value['password_confirmation']:
259 if pass_val != value['password_confirmation']:
260 msg = M(self, 'password_mismatch', state)
260 msg = M(self, 'password_mismatch', state)
261 raise formencode.Invalid(msg, value, state,
261 raise formencode.Invalid(msg, value, state,
262 error_dict=dict(password_confirmation=msg)
262 error_dict=dict(password_confirmation=msg)
263 )
263 )
264 return _validator
264 return _validator
265
265
266
266
267 def ValidAuth():
267 def ValidAuth():
268 class _validator(formencode.validators.FancyValidator):
268 class _validator(formencode.validators.FancyValidator):
269 messages = {
269 messages = {
270 'invalid_password': _(u'invalid password'),
270 'invalid_password': _(u'invalid password'),
271 'invalid_username': _(u'invalid user name'),
271 'invalid_username': _(u'invalid user name'),
272 'disabled_account': _(u'Your account is disabled')
272 'disabled_account': _(u'Your account is disabled')
273 }
273 }
274
274
275 def validate_python(self, value, state):
275 def validate_python(self, value, state):
276 from rhodecode.lib.auth import authenticate
276 from rhodecode.lib.auth import authenticate
277
277
278 password = value['password']
278 password = value['password']
279 username = value['username']
279 username = value['username']
280
280
281 if not authenticate(username, password):
281 if not authenticate(username, password):
282 user = User.get_by_username(username)
282 user = User.get_by_username(username)
283 if user and not user.active:
283 if user and not user.active:
284 log.warning('user %s is disabled' % username)
284 log.warning('user %s is disabled' % username)
285 msg = M(self, 'disabled_account', state)
285 msg = M(self, 'disabled_account', state)
286 raise formencode.Invalid(msg, value, state,
286 raise formencode.Invalid(msg, value, state,
287 error_dict=dict(username=msg)
287 error_dict=dict(username=msg)
288 )
288 )
289 else:
289 else:
290 log.warning('user %s failed to authenticate' % username)
290 log.warning('user %s failed to authenticate' % username)
291 msg = M(self, 'invalid_username', state)
291 msg = M(self, 'invalid_username', state)
292 msg2 = M(self, 'invalid_password', state)
292 msg2 = M(self, 'invalid_password', state)
293 raise formencode.Invalid(msg, value, state,
293 raise formencode.Invalid(msg, value, state,
294 error_dict=dict(username=msg, password=msg2)
294 error_dict=dict(username=msg, password=msg2)
295 )
295 )
296 return _validator
296 return _validator
297
297
298
298
299 def ValidAuthToken():
299 def ValidAuthToken():
300 class _validator(formencode.validators.FancyValidator):
300 class _validator(formencode.validators.FancyValidator):
301 messages = {
301 messages = {
302 'invalid_token': _(u'Token mismatch')
302 'invalid_token': _(u'Token mismatch')
303 }
303 }
304
304
305 def validate_python(self, value, state):
305 def validate_python(self, value, state):
306 if value != authentication_token():
306 if value != authentication_token():
307 msg = M(self, 'invalid_token', state)
307 msg = M(self, 'invalid_token', state)
308 raise formencode.Invalid(msg, value, state)
308 raise formencode.Invalid(msg, value, state)
309 return _validator
309 return _validator
310
310
311
311
312 def ValidRepoName(edit=False, old_data={}):
312 def ValidRepoName(edit=False, old_data={}):
313 class _validator(formencode.validators.FancyValidator):
313 class _validator(formencode.validators.FancyValidator):
314 messages = {
314 messages = {
315 'invalid_repo_name':
315 'invalid_repo_name':
316 _(u'Repository name %(repo)s is disallowed'),
316 _(u'Repository name %(repo)s is disallowed'),
317 'repository_exists':
317 'repository_exists':
318 _(u'Repository named %(repo)s already exists'),
318 _(u'Repository named %(repo)s already exists'),
319 'repository_in_group_exists': _(u'Repository "%(repo)s" already '
319 'repository_in_group_exists': _(u'Repository "%(repo)s" already '
320 'exists in group "%(group)s"'),
320 'exists in group "%(group)s"'),
321 'same_group_exists': _(u'Repository group with name "%(repo)s" '
321 'same_group_exists': _(u'Repository group with name "%(repo)s" '
322 'already exists')
322 'already exists')
323 }
323 }
324
324
325 def _to_python(self, value, state):
325 def _to_python(self, value, state):
326 repo_name = repo_name_slug(value.get('repo_name', ''))
326 repo_name = repo_name_slug(value.get('repo_name', ''))
327 repo_group = value.get('repo_group')
327 repo_group = value.get('repo_group')
328 if repo_group:
328 if repo_group:
329 gr = RepoGroup.get(repo_group)
329 gr = RepoGroup.get(repo_group)
330 group_path = gr.full_path
330 group_path = gr.full_path
331 group_name = gr.group_name
331 group_name = gr.group_name
332 # value needs to be aware of group name in order to check
332 # value needs to be aware of group name in order to check
333 # db key This is an actual just the name to store in the
333 # db key This is an actual just the name to store in the
334 # database
334 # database
335 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
335 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
336 else:
336 else:
337 group_name = group_path = ''
337 group_name = group_path = ''
338 repo_name_full = repo_name
338 repo_name_full = repo_name
339
339
340 value['repo_name'] = repo_name
340 value['repo_name'] = repo_name
341 value['repo_name_full'] = repo_name_full
341 value['repo_name_full'] = repo_name_full
342 value['group_path'] = group_path
342 value['group_path'] = group_path
343 value['group_name'] = group_name
343 value['group_name'] = group_name
344 return value
344 return value
345
345
346 def validate_python(self, value, state):
346 def validate_python(self, value, state):
347
347
348 repo_name = value.get('repo_name')
348 repo_name = value.get('repo_name')
349 repo_name_full = value.get('repo_name_full')
349 repo_name_full = value.get('repo_name_full')
350 group_path = value.get('group_path')
350 group_path = value.get('group_path')
351 group_name = value.get('group_name')
351 group_name = value.get('group_name')
352
352
353 if repo_name in [ADMIN_PREFIX, '']:
353 if repo_name in [ADMIN_PREFIX, '']:
354 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
354 msg = M(self, 'invalid_repo_name', state, repo=repo_name)
355 raise formencode.Invalid(msg, value, state,
355 raise formencode.Invalid(msg, value, state,
356 error_dict=dict(repo_name=msg)
356 error_dict=dict(repo_name=msg)
357 )
357 )
358
358
359 rename = old_data.get('repo_name') != repo_name_full
359 rename = old_data.get('repo_name') != repo_name_full
360 create = not edit
360 create = not edit
361 if rename or create:
361 if rename or create:
362
362
363 if group_path != '':
363 if group_path != '':
364 if Repository.get_by_repo_name(repo_name_full):
364 if Repository.get_by_repo_name(repo_name_full):
365 msg = M(self, 'repository_in_group_exists', state,
365 msg = M(self, 'repository_in_group_exists', state,
366 repo=repo_name, group=group_name)
366 repo=repo_name, group=group_name)
367 raise formencode.Invalid(msg, value, state,
367 raise formencode.Invalid(msg, value, state,
368 error_dict=dict(repo_name=msg)
368 error_dict=dict(repo_name=msg)
369 )
369 )
370 elif RepoGroup.get_by_group_name(repo_name_full):
370 elif RepoGroup.get_by_group_name(repo_name_full):
371 msg = M(self, 'same_group_exists', state,
371 msg = M(self, 'same_group_exists', state,
372 repo=repo_name)
372 repo=repo_name)
373 raise formencode.Invalid(msg, value, state,
373 raise formencode.Invalid(msg, value, state,
374 error_dict=dict(repo_name=msg)
374 error_dict=dict(repo_name=msg)
375 )
375 )
376
376
377 elif Repository.get_by_repo_name(repo_name_full):
377 elif Repository.get_by_repo_name(repo_name_full):
378 msg = M(self, 'repository_exists', state,
378 msg = M(self, 'repository_exists', state,
379 repo=repo_name)
379 repo=repo_name)
380 raise formencode.Invalid(msg, value, state,
380 raise formencode.Invalid(msg, value, state,
381 error_dict=dict(repo_name=msg)
381 error_dict=dict(repo_name=msg)
382 )
382 )
383 return value
383 return value
384 return _validator
384 return _validator
385
385
386
386
387 def ValidForkName(*args, **kwargs):
387 def ValidForkName(*args, **kwargs):
388 return ValidRepoName(*args, **kwargs)
388 return ValidRepoName(*args, **kwargs)
389
389
390
390
391 def SlugifyName():
391 def SlugifyName():
392 class _validator(formencode.validators.FancyValidator):
392 class _validator(formencode.validators.FancyValidator):
393
393
394 def _to_python(self, value, state):
394 def _to_python(self, value, state):
395 return repo_name_slug(value)
395 return repo_name_slug(value)
396
396
397 def validate_python(self, value, state):
397 def validate_python(self, value, state):
398 pass
398 pass
399
399
400 return _validator
400 return _validator
401
401
402
402
403 def ValidCloneUri():
403 def ValidCloneUri():
404 from rhodecode.lib.utils import make_ui
404 from rhodecode.lib.utils import make_ui
405
405
406 def url_handler(repo_type, url, ui=None):
406 def url_handler(repo_type, url, ui=None):
407 if repo_type == 'hg':
407 if repo_type == 'hg':
408 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
408 from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository
409 from mercurial.httppeer import httppeer
409 from mercurial.httppeer import httppeer
410 if url.startswith('http'):
410 if url.startswith('http'):
411 ## initially check if it's at least the proper URL
411 ## initially check if it's at least the proper URL
412 ## or does it pass basic auth
412 ## or does it pass basic auth
413 MercurialRepository._check_url(url)
413 MercurialRepository._check_url(url)
414 httppeer(ui, url)._capabilities()
414 httppeer(ui, url)._capabilities()
415 elif url.startswith('svn+http'):
415 elif url.startswith('svn+http'):
416 from hgsubversion.svnrepo import svnremoterepo
416 from hgsubversion.svnrepo import svnremoterepo
417 svnremoterepo(ui, url).capabilities
417 svnremoterepo(ui, url).capabilities
418 elif url.startswith('git+http'):
418 elif url.startswith('git+http'):
419 raise NotImplementedError()
419 raise NotImplementedError()
420 else:
420 else:
421 raise Exception('clone from URI %s not allowed' % (url))
421 raise Exception('clone from URI %s not allowed' % (url))
422
422
423 elif repo_type == 'git':
423 elif repo_type == 'git':
424 from rhodecode.lib.vcs.backends.git.repository import GitRepository
424 from rhodecode.lib.vcs.backends.git.repository import GitRepository
425 if url.startswith('http'):
425 if url.startswith('http'):
426 ## initially check if it's at least the proper URL
426 ## initially check if it's at least the proper URL
427 ## or does it pass basic auth
427 ## or does it pass basic auth
428 GitRepository._check_url(url)
428 GitRepository._check_url(url)
429 elif url.startswith('svn+http'):
429 elif url.startswith('svn+http'):
430 raise NotImplementedError()
430 raise NotImplementedError()
431 elif url.startswith('hg+http'):
431 elif url.startswith('hg+http'):
432 raise NotImplementedError()
432 raise NotImplementedError()
433 else:
433 else:
434 raise Exception('clone from URI %s not allowed' % (url))
434 raise Exception('clone from URI %s not allowed' % (url))
435
435
436 class _validator(formencode.validators.FancyValidator):
436 class _validator(formencode.validators.FancyValidator):
437 messages = {
437 messages = {
438 'clone_uri': _(u'invalid clone url'),
438 'clone_uri': _(u'invalid clone url'),
439 'invalid_clone_uri': _(u'Invalid clone url, provide a '
439 'invalid_clone_uri': _(u'Invalid clone url, provide a '
440 'valid clone http(s)/svn+http(s) url')
440 'valid clone http(s)/svn+http(s) url')
441 }
441 }
442
442
443 def validate_python(self, value, state):
443 def validate_python(self, value, state):
444 repo_type = value.get('repo_type')
444 repo_type = value.get('repo_type')
445 url = value.get('clone_uri')
445 url = value.get('clone_uri')
446
446
447 if not url:
447 if not url:
448 pass
448 pass
449 else:
449 else:
450 try:
450 try:
451 url_handler(repo_type, url, make_ui('db', clear_session=False))
451 url_handler(repo_type, url, make_ui('db', clear_session=False))
452 except Exception:
452 except Exception:
453 log.exception('Url validation failed')
453 log.exception('Url validation failed')
454 msg = M(self, 'clone_uri')
454 msg = M(self, 'clone_uri')
455 raise formencode.Invalid(msg, value, state,
455 raise formencode.Invalid(msg, value, state,
456 error_dict=dict(clone_uri=msg)
456 error_dict=dict(clone_uri=msg)
457 )
457 )
458 return _validator
458 return _validator
459
459
460
460
461 def ValidForkType(old_data={}):
461 def ValidForkType(old_data={}):
462 class _validator(formencode.validators.FancyValidator):
462 class _validator(formencode.validators.FancyValidator):
463 messages = {
463 messages = {
464 'invalid_fork_type': _(u'Fork have to be the same type as parent')
464 'invalid_fork_type': _(u'Fork have to be the same type as parent')
465 }
465 }
466
466
467 def validate_python(self, value, state):
467 def validate_python(self, value, state):
468 if old_data['repo_type'] != value:
468 if old_data['repo_type'] != value:
469 msg = M(self, 'invalid_fork_type', state)
469 msg = M(self, 'invalid_fork_type', state)
470 raise formencode.Invalid(msg, value, state,
470 raise formencode.Invalid(msg, value, state,
471 error_dict=dict(repo_type=msg)
471 error_dict=dict(repo_type=msg)
472 )
472 )
473 return _validator
473 return _validator
474
474
475
475
476 def CanWriteGroup(old_data=None):
476 def CanWriteGroup(old_data=None):
477 class _validator(formencode.validators.FancyValidator):
477 class _validator(formencode.validators.FancyValidator):
478 messages = {
478 messages = {
479 'permission_denied': _(u"You don't have permissions "
479 'permission_denied': _(u"You don't have permissions "
480 "to create repository in this group"),
480 "to create repository in this group"),
481 'permission_denied_root': _(u"no permission to create repository "
481 'permission_denied_root': _(u"no permission to create repository "
482 "in root location")
482 "in root location")
483 }
483 }
484
484
485 def _to_python(self, value, state):
485 def _to_python(self, value, state):
486 #root location
486 #root location
487 if value in [-1, "-1"]:
487 if value in [-1, "-1"]:
488 return None
488 return None
489 return value
489 return value
490
490
491 def validate_python(self, value, state):
491 def validate_python(self, value, state):
492 gr = RepoGroup.get(value)
492 gr = RepoGroup.get(value)
493 gr_name = gr.group_name if gr else None # None means ROOT location
493 gr_name = gr.group_name if gr else None # None means ROOT location
494 val = HasReposGroupPermissionAny('group.write', 'group.admin')
494 val = HasReposGroupPermissionAny('group.write', 'group.admin')
495 can_create_repos = HasPermissionAny('hg.admin', 'hg.create.repository')
495 can_create_repos = HasPermissionAny('hg.admin', 'hg.create.repository')
496 forbidden = not val(gr_name, 'can write into group validator')
496 forbidden = not val(gr_name, 'can write into group validator')
497 value_changed = True # old_data['repo_group'].get('group_id') != safe_int(value)
497 value_changed = True # old_data['repo_group'].get('group_id') != safe_int(value)
498 if value_changed: # do check if we changed the value
498 if value_changed: # do check if we changed the value
499 #parent group need to be existing
499 #parent group need to be existing
500 if gr and forbidden:
500 if gr and forbidden:
501 msg = M(self, 'permission_denied', state)
501 msg = M(self, 'permission_denied', state)
502 raise formencode.Invalid(msg, value, state,
502 raise formencode.Invalid(msg, value, state,
503 error_dict=dict(repo_type=msg)
503 error_dict=dict(repo_type=msg)
504 )
504 )
505 ## check if we can write to root location !
505 ## check if we can write to root location !
506 elif gr is None and not can_create_repos():
506 elif gr is None and not can_create_repos():
507 msg = M(self, 'permission_denied_root', state)
507 msg = M(self, 'permission_denied_root', state)
508 raise formencode.Invalid(msg, value, state,
508 raise formencode.Invalid(msg, value, state,
509 error_dict=dict(repo_type=msg)
509 error_dict=dict(repo_type=msg)
510 )
510 )
511
511
512 return _validator
512 return _validator
513
513
514
514
515 def CanCreateGroup(can_create_in_root=False):
515 def CanCreateGroup(can_create_in_root=False):
516 class _validator(formencode.validators.FancyValidator):
516 class _validator(formencode.validators.FancyValidator):
517 messages = {
517 messages = {
518 'permission_denied': _(u"You don't have permissions "
518 'permission_denied': _(u"You don't have permissions "
519 "to create a group in this location")
519 "to create a group in this location")
520 }
520 }
521
521
522 def to_python(self, value, state):
522 def to_python(self, value, state):
523 #root location
523 #root location
524 if value in [-1, "-1"]:
524 if value in [-1, "-1"]:
525 return None
525 return None
526 return value
526 return value
527
527
528 def validate_python(self, value, state):
528 def validate_python(self, value, state):
529 gr = RepoGroup.get(value)
529 gr = RepoGroup.get(value)
530 gr_name = gr.group_name if gr else None # None means ROOT location
530 gr_name = gr.group_name if gr else None # None means ROOT location
531
531
532 if can_create_in_root and gr is None:
532 if can_create_in_root and gr is None:
533 #we can create in root, we're fine no validations required
533 #we can create in root, we're fine no validations required
534 return
534 return
535
535
536 forbidden_in_root = gr is None and not can_create_in_root
536 forbidden_in_root = gr is None and not can_create_in_root
537 val = HasReposGroupPermissionAny('group.admin')
537 val = HasReposGroupPermissionAny('group.admin')
538 forbidden = not val(gr_name, 'can create group validator')
538 forbidden = not val(gr_name, 'can create group validator')
539 if forbidden_in_root or forbidden:
539 if forbidden_in_root or forbidden:
540 msg = M(self, 'permission_denied', state)
540 msg = M(self, 'permission_denied', state)
541 raise formencode.Invalid(msg, value, state,
541 raise formencode.Invalid(msg, value, state,
542 error_dict=dict(group_parent_id=msg)
542 error_dict=dict(group_parent_id=msg)
543 )
543 )
544
544
545 return _validator
545 return _validator
546
546
547
547
548 def ValidPerms(type_='repo'):
548 def ValidPerms(type_='repo'):
549 if type_ == 'repo_group':
549 if type_ == 'repo_group':
550 EMPTY_PERM = 'group.none'
550 EMPTY_PERM = 'group.none'
551 elif type_ == 'repo':
551 elif type_ == 'repo':
552 EMPTY_PERM = 'repository.none'
552 EMPTY_PERM = 'repository.none'
553 elif type_ == 'user_group':
553 elif type_ == 'user_group':
554 EMPTY_PERM = 'usergroup.none'
554 EMPTY_PERM = 'usergroup.none'
555
555
556 class _validator(formencode.validators.FancyValidator):
556 class _validator(formencode.validators.FancyValidator):
557 messages = {
557 messages = {
558 'perm_new_member_name':
558 'perm_new_member_name':
559 _(u'This username or user group name is not valid')
559 _(u'This username or user group name is not valid')
560 }
560 }
561
561
562 def to_python(self, value, state):
562 def to_python(self, value, state):
563 perms_update = OrderedSet()
563 perms_update = OrderedSet()
564 perms_new = OrderedSet()
564 perms_new = OrderedSet()
565 # build a list of permission to update and new permission to create
565 # build a list of permission to update and new permission to create
566
566
567 #CLEAN OUT ORG VALUE FROM NEW MEMBERS, and group them using
567 #CLEAN OUT ORG VALUE FROM NEW MEMBERS, and group them using
568 new_perms_group = defaultdict(dict)
568 new_perms_group = defaultdict(dict)
569 for k, v in value.copy().iteritems():
569 for k, v in value.copy().iteritems():
570 if k.startswith('perm_new_member'):
570 if k.startswith('perm_new_member'):
571 del value[k]
571 del value[k]
572 _type, part = k.split('perm_new_member_')
572 _type, part = k.split('perm_new_member_')
573 args = part.split('_')
573 args = part.split('_')
574 if len(args) == 1:
574 if len(args) == 1:
575 new_perms_group[args[0]]['perm'] = v
575 new_perms_group[args[0]]['perm'] = v
576 elif len(args) == 2:
576 elif len(args) == 2:
577 _key, pos = args
577 _key, pos = args
578 new_perms_group[pos][_key] = v
578 new_perms_group[pos][_key] = v
579
579
580 # fill new permissions in order of how they were added
580 # fill new permissions in order of how they were added
581 for k in sorted(map(int, new_perms_group.keys())):
581 for k in sorted(map(int, new_perms_group.keys())):
582 perm_dict = new_perms_group[str(k)]
582 perm_dict = new_perms_group[str(k)]
583 new_member = perm_dict.get('name')
583 new_member = perm_dict.get('name')
584 new_perm = perm_dict.get('perm')
584 new_perm = perm_dict.get('perm')
585 new_type = perm_dict.get('type')
585 new_type = perm_dict.get('type')
586 if new_member and new_perm and new_type:
586 if new_member and new_perm and new_type:
587 perms_new.add((new_member, new_perm, new_type))
587 perms_new.add((new_member, new_perm, new_type))
588
588
589 for k, v in value.iteritems():
589 for k, v in value.iteritems():
590 if k.startswith('u_perm_') or k.startswith('g_perm_'):
590 if k.startswith('u_perm_') or k.startswith('g_perm_'):
591 member = k[7:]
591 member = k[7:]
592 t = {'u': 'user',
592 t = {'u': 'user',
593 'g': 'users_group'
593 'g': 'users_group'
594 }[k[0]]
594 }[k[0]]
595 if member == 'default':
595 if member == 'default':
596 if str2bool(value.get('repo_private')):
596 if str2bool(value.get('repo_private')):
597 # set none for default when updating to
597 # set none for default when updating to
598 # private repo protects agains form manipulation
598 # private repo protects agains form manipulation
599 v = EMPTY_PERM
599 v = EMPTY_PERM
600 perms_update.add((member, v, t))
600 perms_update.add((member, v, t))
601
601
602 value['perms_updates'] = list(perms_update)
602 value['perms_updates'] = list(perms_update)
603 value['perms_new'] = list(perms_new)
603 value['perms_new'] = list(perms_new)
604
604
605 # update permissions
605 # update permissions
606 for k, v, t in perms_new:
606 for k, v, t in perms_new:
607 try:
607 try:
608 if t is 'user':
608 if t is 'user':
609 self.user_db = User.query()\
609 self.user_db = User.query()\
610 .filter(User.active == True)\
610 .filter(User.active == True)\
611 .filter(User.username == k).one()
611 .filter(User.username == k).one()
612 if t is 'users_group':
612 if t is 'users_group':
613 self.user_db = UserGroup.query()\
613 self.user_db = UserGroup.query()\
614 .filter(UserGroup.users_group_active == True)\
614 .filter(UserGroup.users_group_active == True)\
615 .filter(UserGroup.users_group_name == k).one()
615 .filter(UserGroup.users_group_name == k).one()
616
616
617 except Exception:
617 except Exception:
618 log.exception('Updated permission failed')
618 log.exception('Updated permission failed')
619 msg = M(self, 'perm_new_member_type', state)
619 msg = M(self, 'perm_new_member_type', state)
620 raise formencode.Invalid(msg, value, state,
620 raise formencode.Invalid(msg, value, state,
621 error_dict=dict(perm_new_member_name=msg)
621 error_dict=dict(perm_new_member_name=msg)
622 )
622 )
623 return value
623 return value
624 return _validator
624 return _validator
625
625
626
626
627 def ValidSettings():
627 def ValidSettings():
628 class _validator(formencode.validators.FancyValidator):
628 class _validator(formencode.validators.FancyValidator):
629 def _to_python(self, value, state):
629 def _to_python(self, value, state):
630 # settings form for users that are not admin
630 # settings form for users that are not admin
631 # can't edit certain parameters, it's extra backup if they mangle
631 # can't edit certain parameters, it's extra backup if they mangle
632 # with forms
632 # with forms
633
633
634 forbidden_params = [
634 forbidden_params = [
635 'user', 'repo_type', 'repo_enable_locking',
635 'user', 'repo_type', 'repo_enable_locking',
636 'repo_enable_downloads', 'repo_enable_statistics'
636 'repo_enable_downloads', 'repo_enable_statistics'
637 ]
637 ]
638
638
639 for param in forbidden_params:
639 for param in forbidden_params:
640 if param in value:
640 if param in value:
641 del value[param]
641 del value[param]
642 return value
642 return value
643
643
644 def validate_python(self, value, state):
644 def validate_python(self, value, state):
645 pass
645 pass
646 return _validator
646 return _validator
647
647
648
648
649 def ValidPath():
649 def ValidPath():
650 class _validator(formencode.validators.FancyValidator):
650 class _validator(formencode.validators.FancyValidator):
651 messages = {
651 messages = {
652 'invalid_path': _(u'This is not a valid path')
652 'invalid_path': _(u'This is not a valid path')
653 }
653 }
654
654
655 def validate_python(self, value, state):
655 def validate_python(self, value, state):
656 if not os.path.isdir(value):
656 if not os.path.isdir(value):
657 msg = M(self, 'invalid_path', state)
657 msg = M(self, 'invalid_path', state)
658 raise formencode.Invalid(msg, value, state,
658 raise formencode.Invalid(msg, value, state,
659 error_dict=dict(paths_root_path=msg)
659 error_dict=dict(paths_root_path=msg)
660 )
660 )
661 return _validator
661 return _validator
662
662
663
663
664 def UniqSystemEmail(old_data={}):
664 def UniqSystemEmail(old_data={}):
665 class _validator(formencode.validators.FancyValidator):
665 class _validator(formencode.validators.FancyValidator):
666 messages = {
666 messages = {
667 'email_taken': _(u'This e-mail address is already taken')
667 'email_taken': _(u'This e-mail address is already taken')
668 }
668 }
669
669
670 def _to_python(self, value, state):
670 def _to_python(self, value, state):
671 return value.lower()
671 return value.lower()
672
672
673 def validate_python(self, value, state):
673 def validate_python(self, value, state):
674 if (old_data.get('email') or '').lower() != value:
674 if (old_data.get('email') or '').lower() != value:
675 user = User.get_by_email(value, case_insensitive=True)
675 user = User.get_by_email(value, case_insensitive=True)
676 if user:
676 if user:
677 msg = M(self, 'email_taken', state)
677 msg = M(self, 'email_taken', state)
678 raise formencode.Invalid(msg, value, state,
678 raise formencode.Invalid(msg, value, state,
679 error_dict=dict(email=msg)
679 error_dict=dict(email=msg)
680 )
680 )
681 return _validator
681 return _validator
682
682
683
683
684 def ValidSystemEmail():
684 def ValidSystemEmail():
685 class _validator(formencode.validators.FancyValidator):
685 class _validator(formencode.validators.FancyValidator):
686 messages = {
686 messages = {
687 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
687 'non_existing_email': _(u'e-mail "%(email)s" does not exist.')
688 }
688 }
689
689
690 def _to_python(self, value, state):
690 def _to_python(self, value, state):
691 return value.lower()
691 return value.lower()
692
692
693 def validate_python(self, value, state):
693 def validate_python(self, value, state):
694 user = User.get_by_email(value, case_insensitive=True)
694 user = User.get_by_email(value, case_insensitive=True)
695 if user is None:
695 if user is None:
696 msg = M(self, 'non_existing_email', state, email=value)
696 msg = M(self, 'non_existing_email', state, email=value)
697 raise formencode.Invalid(msg, value, state,
697 raise formencode.Invalid(msg, value, state,
698 error_dict=dict(email=msg)
698 error_dict=dict(email=msg)
699 )
699 )
700
700
701 return _validator
701 return _validator
702
702
703
703
704 def LdapLibValidator():
704 def LdapLibValidator():
705 class _validator(formencode.validators.FancyValidator):
705 class _validator(formencode.validators.FancyValidator):
706 messages = {
706 messages = {
707
707
708 }
708 }
709
709
710 def validate_python(self, value, state):
710 def validate_python(self, value, state):
711 try:
711 try:
712 import ldap
712 import ldap
713 ldap # pyflakes silence !
713 ldap # pyflakes silence !
714 except ImportError:
714 except ImportError:
715 raise LdapImportError()
715 raise LdapImportError()
716
716
717 return _validator
717 return _validator
718
718
719
719
720 def AttrLoginValidator():
720 def AttrLoginValidator():
721 class _validator(formencode.validators.FancyValidator):
721 class _validator(formencode.validators.FancyValidator):
722 messages = {
722 messages = {
723 'invalid_cn':
723 'invalid_cn':
724 _(u'The LDAP Login attribute of the CN must be specified - '
724 _(u'The LDAP Login attribute of the CN must be specified - '
725 'this is the name of the attribute that is equivalent '
725 'this is the name of the attribute that is equivalent '
726 'to "username"')
726 'to "username"')
727 }
727 }
728 messages['empty'] = messages['invalid_cn']
728 messages['empty'] = messages['invalid_cn']
729
729
730 return _validator
730 return _validator
731
731
732
732
733 def NotReviewedRevisions(repo_id):
733 def NotReviewedRevisions(repo_id):
734 class _validator(formencode.validators.FancyValidator):
734 class _validator(formencode.validators.FancyValidator):
735 messages = {
735 messages = {
736 'rev_already_reviewed':
736 'rev_already_reviewed':
737 _(u'Revisions %(revs)s are already part of pull request '
737 _(u'Revisions %(revs)s are already part of pull request '
738 'or have set status')
738 'or have set status')
739 }
739 }
740
740
741 def validate_python(self, value, state):
741 def validate_python(self, value, state):
742 # check revisions if they are not reviewed, or a part of another
742 # check revisions if they are not reviewed, or a part of another
743 # pull request
743 # pull request
744 statuses = ChangesetStatus.query()\
744 statuses = ChangesetStatus.query()\
745 .filter(ChangesetStatus.revision.in_(value))\
745 .filter(ChangesetStatus.revision.in_(value))\
746 .filter(ChangesetStatus.repo_id == repo_id)\
746 .filter(ChangesetStatus.repo_id == repo_id)\
747 .all()
747 .all()
748
748
749 errors = []
749 errors = []
750 for cs in statuses:
750 for cs in statuses:
751 if cs.pull_request_id:
751 if cs.pull_request_id:
752 errors.append(['pull_req', cs.revision[:12]])
752 errors.append(['pull_req', cs.revision[:12]])
753 elif cs.status:
753 elif cs.status:
754 errors.append(['status', cs.revision[:12]])
754 errors.append(['status', cs.revision[:12]])
755
755
756 if errors:
756 if errors:
757 revs = ','.join([x[1] for x in errors])
757 revs = ','.join([x[1] for x in errors])
758 msg = M(self, 'rev_already_reviewed', state, revs=revs)
758 msg = M(self, 'rev_already_reviewed', state, revs=revs)
759 raise formencode.Invalid(msg, value, state,
759 raise formencode.Invalid(msg, value, state,
760 error_dict=dict(revisions=revs)
760 error_dict=dict(revisions=revs)
761 )
761 )
762
762
763 return _validator
763 return _validator
764
764
765
765
766 def ValidIp():
766 def ValidIp():
767 class _validator(CIDR):
767 class _validator(CIDR):
768 messages = dict(
768 messages = dict(
769 badFormat=_('Please enter a valid IPv4 or IpV6 address'),
769 badFormat=_('Please enter a valid IPv4 or IpV6 address'),
770 illegalBits=_('The network size (bits) must be within the range'
770 illegalBits=_('The network size (bits) must be within the range'
771 ' of 0-32 (not %(bits)r)'))
771 ' of 0-32 (not %(bits)r)'))
772
772
773 def to_python(self, value, state):
773 def to_python(self, value, state):
774 v = super(_validator, self).to_python(value, state)
774 v = super(_validator, self).to_python(value, state)
775 v = v.strip()
775 v = v.strip()
776 net = ipaddr.IPNetwork(address=v)
776 net = ipaddr.IPNetwork(address=v)
777 if isinstance(net, ipaddr.IPv4Network):
777 if isinstance(net, ipaddr.IPv4Network):
778 #if IPv4 doesn't end with a mask, add /32
778 #if IPv4 doesn't end with a mask, add /32
779 if '/' not in value:
779 if '/' not in value:
780 v += '/32'
780 v += '/32'
781 if isinstance(net, ipaddr.IPv6Network):
781 if isinstance(net, ipaddr.IPv6Network):
782 #if IPv6 doesn't end with a mask, add /128
782 #if IPv6 doesn't end with a mask, add /128
783 if '/' not in value:
783 if '/' not in value:
784 v += '/128'
784 v += '/128'
785 return v
785 return v
786
786
787 def validate_python(self, value, state):
787 def validate_python(self, value, state):
788 try:
788 try:
789 addr = value.strip()
789 addr = value.strip()
790 #this raises an ValueError if address is not IpV4 or IpV6
790 #this raises an ValueError if address is not IpV4 or IpV6
791 ipaddr.IPNetwork(address=addr)
791 ipaddr.IPNetwork(address=addr)
792 except ValueError:
792 except ValueError:
793 raise formencode.Invalid(self.message('badFormat', state),
793 raise formencode.Invalid(self.message('badFormat', state),
794 value, state)
794 value, state)
795
795
796 return _validator
796 return _validator
797
797
798
798
799 def FieldKey():
799 def FieldKey():
800 class _validator(formencode.validators.FancyValidator):
800 class _validator(formencode.validators.FancyValidator):
801 messages = dict(
801 messages = dict(
802 badFormat=_('Key name can only consist of letters, '
802 badFormat=_('Key name can only consist of letters, '
803 'underscore, dash or numbers'),)
803 'underscore, dash or numbers'),)
804
804
805 def validate_python(self, value, state):
805 def validate_python(self, value, state):
806 if not re.match('[a-zA-Z0-9_-]+$', value):
806 if not re.match('[a-zA-Z0-9_-]+$', value):
807 raise formencode.Invalid(self.message('badFormat', state),
807 raise formencode.Invalid(self.message('badFormat', state),
808 value, state)
808 value, state)
809 return _validator
809 return _validator
@@ -1,342 +1,348 b''
1 /**
1 /**
2 * Stylesheets for the context bar
2 * Stylesheets for the context bar
3 */
3 */
4
4
5 #quick .repo_switcher { background-image: url("../images/icons/database.png"); }
5 #quick .repo_switcher { background-image: url("../images/icons/database.png"); }
6 #quick .journal { background-image: url("../images/icons/book.png"); }
6 #quick .journal { background-image: url("../images/icons/book.png"); }
7 #quick .gists { background-image: url("../images/icons/note.png"); }
8 #quick .gists-private { background-image: url("../images/icons/note_error.png"); }
9 #quick .gists-new { background-image: url("../images/icons/note_add.png"); }
7 #quick .search { background-image: url("../images/icons/search_16.png"); }
10 #quick .search { background-image: url("../images/icons/search_16.png"); }
8 #quick .admin { background-image: url("../images/icons/cog_edit.png"); }
11 #quick .admin { background-image: url("../images/icons/cog_edit.png"); }
9
12
10 #context-bar a.follow { background-image: url("../images/icons/heart.png"); }
13 #context-bar a.follow { background-image: url("../images/icons/heart.png"); }
11 #context-bar a.following { background-image: url("../images/icons/heart_delete.png"); }
14 #context-bar a.following { background-image: url("../images/icons/heart_delete.png"); }
12 #context-bar a.fork { background-image: url("../images/icons/arrow_divide.png"); }
15 #context-bar a.fork { background-image: url("../images/icons/arrow_divide.png"); }
13 #context-bar a.summary { background-image: url("../images/icons/clipboard_16.png"); }
16 #context-bar a.summary { background-image: url("../images/icons/clipboard_16.png"); }
14 #context-bar a.changelogs { background-image: url("../images/icons/time.png"); }
17 #context-bar a.changelogs { background-image: url("../images/icons/time.png"); }
15 #context-bar a.files { background-image: url("../images/icons/file.png"); }
18 #context-bar a.files { background-image: url("../images/icons/file.png"); }
16 #context-bar a.switch-to { background-image: url("../images/icons/arrow_switch.png"); }
19 #context-bar a.switch-to { background-image: url("../images/icons/arrow_switch.png"); }
17 #context-bar a.options { background-image: url("../images/icons/table_gear.png"); }
20 #context-bar a.options { background-image: url("../images/icons/table_gear.png"); }
18 #context-bar a.forks { background-image: url("../images/icons/arrow_divide.png"); }
21 #context-bar a.forks { background-image: url("../images/icons/arrow_divide.png"); }
19 #context-bar a.pull-request { background-image: url("../images/icons/arrow_join.png"); }
22 #context-bar a.pull-request { background-image: url("../images/icons/arrow_join.png"); }
20 #context-bar a.branches { background-image: url("../images/icons/arrow_branch.png"); }
23 #context-bar a.branches { background-image: url("../images/icons/arrow_branch.png"); }
21 #context-bar a.tags { background-image: url("../images/icons/tag_blue.png"); }
24 #context-bar a.tags { background-image: url("../images/icons/tag_blue.png"); }
22 #context-bar a.bookmarks { background-image: url("../images/icons/tag_green.png"); }
25 #context-bar a.bookmarks { background-image: url("../images/icons/tag_green.png"); }
23 #context-bar a.settings { background-image: url("../images/icons/cog.png"); }
26 #context-bar a.settings { background-image: url("../images/icons/cog.png"); }
24 #context-bar a.search { background-image: url("../images/icons/search_16.png"); }
27 #context-bar a.search { background-image: url("../images/icons/search_16.png"); }
25 #context-bar a.admin { background-image: url("../images/icons/cog_edit.png"); }
28 #context-bar a.admin { background-image: url("../images/icons/cog_edit.png"); }
26
29
27 #context-bar a.journal { background-image: url("../images/icons/book.png"); }
30 #context-bar a.journal { background-image: url("../images/icons/book.png"); }
31 #context-bar a.gists { background-image: url("../images/icons/note.png"); }
32 #context-bar a.gists-private { background-image: url("../images/icons/note_error.png"); }
33 #context-bar a.gists-new { background-image: url("../images/icons/note_add.png"); }
28 #context-bar a.repos { background-image: url("../images/icons/database_edit.png"); }
34 #context-bar a.repos { background-image: url("../images/icons/database_edit.png"); }
29 #context-bar a.repos_groups { background-image: url("../images/icons/database_link.png"); }
35 #context-bar a.repos_groups { background-image: url("../images/icons/database_link.png"); }
30 #context-bar a.users { background-image: url("../images/icons/user_edit.png"); }
36 #context-bar a.users { background-image: url("../images/icons/user_edit.png"); }
31 #context-bar a.groups { background-image: url("../images/icons/group_edit.png"); }
37 #context-bar a.groups { background-image: url("../images/icons/group_edit.png"); }
32 #context-bar a.permissions { background-image: url("../images/icons/key.png"); }
38 #context-bar a.permissions { background-image: url("../images/icons/key.png"); }
33 #context-bar a.ldap { background-image: url("../images/icons/server_key.png"); }
39 #context-bar a.ldap { background-image: url("../images/icons/server_key.png"); }
34 #context-bar a.defaults { background-image: url("../images/icons/wrench.png"); }
40 #context-bar a.defaults { background-image: url("../images/icons/wrench.png"); }
35 #context-bar a.settings { background-image: url("../images/icons/cog_edit.png"); }
41 #context-bar a.settings { background-image: url("../images/icons/cog_edit.png"); }
36 #context-bar a.compare_request { background-image: url('../images/icons/arrow_inout.png')}
42 #context-bar a.compare_request { background-image: url('../images/icons/arrow_inout.png')}
37 #context-bar a.locking_del { background-image: url('../images/icons/lock_delete.png')}
43 #context-bar a.locking_del { background-image: url('../images/icons/lock_delete.png')}
38 #context-bar a.locking_add { background-image: url('../images/icons/lock_add.png')}
44 #context-bar a.locking_add { background-image: url('../images/icons/lock_add.png')}
39
45
40 #content #context-bar {
46 #content #context-bar {
41 position: relative;
47 position: relative;
42 overflow: visible;
48 overflow: visible;
43 background-color: #336699;
49 background-color: #336699;
44 border-top: 1px solid #517da8;
50 border-top: 1px solid #517da8;
45 border-bottom: 1px solid #003162;
51 border-bottom: 1px solid #003162;
46 padding: 0 5px;
52 padding: 0 5px;
47 min-height: 36px;
53 min-height: 36px;
48 }
54 }
49
55
50 #header #header-inner #quick a,
56 #header #header-inner #quick a,
51 #content #context-bar,
57 #content #context-bar,
52 #content #context-bar a {
58 #content #context-bar a {
53 color: #FFFFFF;
59 color: #FFFFFF;
54 }
60 }
55
61
56 #header #header-inner #quick a:hover,
62 #header #header-inner #quick a:hover,
57 #content #context-bar a:hover {
63 #content #context-bar a:hover {
58 text-decoration: none;
64 text-decoration: none;
59 }
65 }
60
66
61 #content #context-bar .icon {
67 #content #context-bar .icon {
62 display: inline-block;
68 display: inline-block;
63 width: 16px;
69 width: 16px;
64 height: 16px;
70 height: 16px;
65 vertical-align: text-bottom;
71 vertical-align: text-bottom;
66 }
72 }
67
73
68 ul.horizontal-list {
74 ul.horizontal-list {
69 display: block;
75 display: block;
70 }
76 }
71
77
72 ul.horizontal-list > li {
78 ul.horizontal-list > li {
73 float: left;
79 float: left;
74 position: relative;
80 position: relative;
75 }
81 }
76
82
77 #header #header-inner #quick ul,
83 #header #header-inner #quick ul,
78 ul.horizontal-list > li ul {
84 ul.horizontal-list > li ul {
79 position: absolute;
85 position: absolute;
80 display: none;
86 display: none;
81 right: 0;
87 right: 0;
82 z-index: 999;
88 z-index: 999;
83 }
89 }
84
90
85 #header #header-inner #quick li:hover > ul,
91 #header #header-inner #quick li:hover > ul,
86 ul.horizontal-list li:hover > ul {
92 ul.horizontal-list li:hover > ul {
87 display: block;
93 display: block;
88 }
94 }
89
95
90 #header #header-inner #quick li ul li,
96 #header #header-inner #quick li ul li,
91 ul.horizontal-list ul li {
97 ul.horizontal-list ul li {
92 position: relative;
98 position: relative;
93 border-bottom: 1px solid rgba(0,0,0,0.1);
99 border-bottom: 1px solid rgba(0,0,0,0.1);
94 border-top: 1px solid rgba(255,255,255,0.1);
100 border-top: 1px solid rgba(255,255,255,0.1);
95 }
101 }
96
102
97 ul.horizontal-list > li ul ul {
103 ul.horizontal-list > li ul ul {
98 position: absolute;
104 position: absolute;
99 right: 100%;
105 right: 100%;
100 top: -1px;
106 top: -1px;
101 min-width: 200px;
107 min-width: 200px;
102 max-height: 400px;
108 max-height: 400px;
103 overflow-x: hidden;
109 overflow-x: hidden;
104 overflow-y: auto;
110 overflow-y: auto;
105 }
111 }
106
112
107 #header #header-inner #quick ul a,
113 #header #header-inner #quick ul a,
108 ul.horizontal-list li a {
114 ul.horizontal-list li a {
109 white-space: nowrap;
115 white-space: nowrap;
110 }
116 }
111
117
112 #breadcrumbs {
118 #breadcrumbs {
113 float: left;
119 float: left;
114 padding: 6px 0 5px 0;
120 padding: 6px 0 5px 0;
115 padding-left: 5px;
121 padding-left: 5px;
116 font-weight: bold;
122 font-weight: bold;
117 font-size: 14px;
123 font-size: 14px;
118 }
124 }
119
125
120 #breadcrumbs span {
126 #breadcrumbs span {
121 font-weight: bold;
127 font-weight: bold;
122 font-size: 1.4em;
128 font-size: 1.4em;
123 }
129 }
124
130
125 #header #header-inner #quick ul,
131 #header #header-inner #quick ul,
126 #revision-changer,
132 #revision-changer,
127 #context-pages,
133 #context-pages,
128 #context-pages ul {
134 #context-pages ul {
129 background: #3b6998; /* Old browsers */
135 background: #3b6998; /* Old browsers */
130 background: -moz-linear-gradient(top, #4574a2 0%, #2f5d8b 100%); /* FF3.6+ */
136 background: -moz-linear-gradient(top, #4574a2 0%, #2f5d8b 100%); /* FF3.6+ */
131 background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4574a2), color-stop(100%,#2f5d8b)); /* Chrome,Safari4+ */
137 background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4574a2), color-stop(100%,#2f5d8b)); /* Chrome,Safari4+ */
132 background: -webkit-linear-gradient(top, #4574a2 0%, #2f5d8b 100%); /* Chrome10+,Safari5.1+ */
138 background: -webkit-linear-gradient(top, #4574a2 0%, #2f5d8b 100%); /* Chrome10+,Safari5.1+ */
133 background: -o-linear-gradient(top, #4574a2 0%, #2f5d8b 100%); /* Opera 11.10+ */
139 background: -o-linear-gradient(top, #4574a2 0%, #2f5d8b 100%); /* Opera 11.10+ */
134 background: -ms-linear-gradient(top, #4574a2 0%, #2f5d8b 100%); /* IE10+ */
140 background: -ms-linear-gradient(top, #4574a2 0%, #2f5d8b 100%); /* IE10+ */
135 background: linear-gradient(to bottom, #4574a2 0%, #2f5d8b 100%); /* W3C */
141 background: linear-gradient(to bottom, #4574a2 0%, #2f5d8b 100%); /* W3C */
136 /*Filter on IE will also use overflow:hidden implicitly, and that would clip our inner menus.*/
142 /*Filter on IE will also use overflow:hidden implicitly, and that would clip our inner menus.*/
137 /*filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4574a2', endColorstr='#2f5d8b',GradientType=0 ); /* IE6-9 */*/
143 /*filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4574a2', endColorstr='#2f5d8b',GradientType=0 ); /* IE6-9 */*/
138 }
144 }
139
145
140 #header #header-inner #quick a,
146 #header #header-inner #quick a,
141 #context-actions a,
147 #context-actions a,
142 #context-pages a {
148 #context-pages a {
143 background-repeat: no-repeat;
149 background-repeat: no-repeat;
144 background-position: 10px 50%;
150 background-position: 10px 50%;
145 padding-left: 30px;
151 padding-left: 30px;
146 }
152 }
147
153
148 #quick a,
154 #quick a,
149 #context-pages ul ul a {
155 #context-pages ul ul a {
150 padding-left: 10px;
156 padding-left: 10px;
151 }
157 }
152
158
153 ul#context-actions {
159 ul#context-actions {
154 display: inline-block;
160 display: inline-block;
155 float: right;
161 float: right;
156 border-radius: 4px;
162 border-radius: 4px;
157 background-image: linear-gradient(top, #4574a2 0%, #2f5d8b 100%);
163 background-image: linear-gradient(top, #4574a2 0%, #2f5d8b 100%);
158 }
164 }
159
165
160 #content ul#context-actions li {
166 #content ul#context-actions li {
161 padding: 0px;
167 padding: 0px;
162 border-right: 1px solid rgba(0,0,0,0.1);
168 border-right: 1px solid rgba(0,0,0,0.1);
163 border-left: 1px solid rgba(255,255,255,0.1);
169 border-left: 1px solid rgba(255,255,255,0.1);
164 }
170 }
165
171
166 #context-actions a {
172 #context-actions a {
167 display: block;
173 display: block;
168 cursor: pointer;
174 cursor: pointer;
169 background: none;
175 background: none;
170 border: none;
176 border: none;
171 margin: 0px;
177 margin: 0px;
172 height: auto;
178 height: auto;
173 padding: 10px 10px 10px 30px;
179 padding: 10px 10px 10px 30px;
174 background-repeat: no-repeat;
180 background-repeat: no-repeat;
175 background-position: 10px 50%;
181 background-position: 10px 50%;
176 font-size: 1em;
182 font-size: 1em;
177 }
183 }
178
184
179 #context-actions a {
185 #context-actions a {
180 padding: 11px 10px 12px 30px;
186 padding: 11px 10px 12px 30px;
181 }
187 }
182
188
183 #header #header-inner #quick li:hover,
189 #header #header-inner #quick li:hover,
184 #revision-changer:hover,
190 #revision-changer:hover,
185 #context-pages li:hover,
191 #context-pages li:hover,
186 #context-actions li:hover,
192 #context-actions li:hover,
187 #content #context-actions li:hover,
193 #content #context-actions li:hover,
188 #header #header-inner #quick li.current,
194 #header #header-inner #quick li.current,
189 #context-pages li.current {
195 #context-pages li.current {
190 background: #6388ad; /* Old browsers */
196 background: #6388ad; /* Old browsers */
191 background: -moz-linear-gradient(top, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* FF3.6+ */
197 background: -moz-linear-gradient(top, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* FF3.6+ */
192 background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,0.1)), color-stop(100%,rgba(255,255,255,0))); /* Chrome,Safari4+ */
198 background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,0.1)), color-stop(100%,rgba(255,255,255,0))); /* Chrome,Safari4+ */
193 background: -webkit-linear-gradient(top, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* Chrome10+,Safari5.1+ */
199 background: -webkit-linear-gradient(top, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* Chrome10+,Safari5.1+ */
194 background: -o-linear-gradient(top, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* Opera 11.10+ */
200 background: -o-linear-gradient(top, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* Opera 11.10+ */
195 background: -ms-linear-gradient(top, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* IE10+ */
201 background: -ms-linear-gradient(top, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* IE10+ */
196 background: linear-gradient(to bottom, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* W3C */
202 background: linear-gradient(to bottom, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0.1) 100%); /* W3C */
197 /*filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#88bfe8', endColorstr='#70b0e0',GradientType=0 ); /* IE6-9 */*/
203 /*filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#88bfe8', endColorstr='#70b0e0',GradientType=0 ); /* IE6-9 */*/
198 }
204 }
199
205
200
206
201 #content #context-actions li:first-child {
207 #content #context-actions li:first-child {
202 border-left: none;
208 border-left: none;
203 border-radius: 4px 0 0px 4px;
209 border-radius: 4px 0 0px 4px;
204 }
210 }
205
211
206 #content #context-actions li:last-child {
212 #content #context-actions li:last-child {
207 border-right: none;
213 border-right: none;
208 border-radius: 0 4px 4px 0;
214 border-radius: 0 4px 4px 0;
209 }
215 }
210
216
211 #content #context-actions .icon {
217 #content #context-actions .icon {
212 margin: auto;
218 margin: auto;
213 margin-bottom: 5px;
219 margin-bottom: 5px;
214 display: block;
220 display: block;
215 clear: both;
221 clear: both;
216 float: none;
222 float: none;
217 }
223 }
218
224
219 #content #context-pages .follow .show-following,
225 #content #context-pages .follow .show-following,
220 #content #context-pages .following .show-follow {
226 #content #context-pages .following .show-follow {
221 display: none;
227 display: none;
222 }
228 }
223
229
224 #context-pages {
230 #context-pages {
225 float: right;
231 float: right;
226 border-left: 1px solid rgba(0,0,0,0.1);
232 border-left: 1px solid rgba(0,0,0,0.1);
227 }
233 }
228
234
229 #context-pages li.current {
235 #context-pages li.current {
230 background: #535353; /* Old browsers */
236 background: #535353; /* Old browsers */
231 background: -moz-linear-gradient(top, #5d5d5d 0%, #484848 100%); /* FF3.6+ */
237 background: -moz-linear-gradient(top, #5d5d5d 0%, #484848 100%); /* FF3.6+ */
232 background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#5d5d5d), color-stop(100%,#484848)); /* Chrome,Safari4+ */
238 background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#5d5d5d), color-stop(100%,#484848)); /* Chrome,Safari4+ */
233 background: -webkit-linear-gradient(top, #5d5d5d 0%, #484848 100%); /* Chrome10+,Safari5.1+ */
239 background: -webkit-linear-gradient(top, #5d5d5d 0%, #484848 100%); /* Chrome10+,Safari5.1+ */
234 background: -o-linear-gradient(top, #5d5d5d 0%, #484848 100%); /* Opera 11.10+ */
240 background: -o-linear-gradient(top, #5d5d5d 0%, #484848 100%); /* Opera 11.10+ */
235 background: -ms-linear-gradient(top, #5d5d5d 0%, #484848 100%); /* IE10+ */
241 background: -ms-linear-gradient(top, #5d5d5d 0%, #484848 100%); /* IE10+ */
236 background: linear-gradient(to bottom, #5d5d5d 0%, #484848 100%); /* W3C */
242 background: linear-gradient(to bottom, #5d5d5d 0%, #484848 100%); /* W3C */
237 }
243 }
238
244
239 #content #context-pages .icon {
245 #content #context-pages .icon {
240 margin-right: 5px;
246 margin-right: 5px;
241 }
247 }
242
248
243 #header #header-inner #quick li,
249 #header #header-inner #quick li,
244 #content #context-pages li {
250 #content #context-pages li {
245 border-right: 1px solid rgba(0,0,0,0.1);
251 border-right: 1px solid rgba(0,0,0,0.1);
246 border-left: 1px solid rgba(255,255,255,0.1);
252 border-left: 1px solid rgba(255,255,255,0.1);
247 padding: 0;
253 padding: 0;
248 }
254 }
249
255
250 #header #header-inner #quick li:last-child,
256 #header #header-inner #quick li:last-child,
251 #content #context-pages li:last-child {
257 #content #context-pages li:last-child {
252 border-right: none;
258 border-right: none;
253 }
259 }
254
260
255 #header #header-inner #quick > li:first-child {
261 #header #header-inner #quick > li:first-child {
256 border-left: none;
262 border-left: none;
257 }
263 }
258
264
259 #header #header-inner #quick > li:first-child > a {
265 #header #header-inner #quick > li:first-child > a {
260 border-radius: 4px 0 0 4px;
266 border-radius: 4px 0 0 4px;
261 }
267 }
262
268
263 #header #header-inner #quick a,
269 #header #header-inner #quick a,
264 #context-pages a,
270 #context-pages a,
265 #context-pages .admin_menu a {
271 #context-pages .admin_menu a {
266 display: block;
272 display: block;
267 padding: 0px 10px 1px 30px;
273 padding: 0px 10px 1px 30px;
268 padding-left: 30px;
274 padding-left: 30px;
269 line-height: 35px;
275 line-height: 35px;
270 }
276 }
271
277
272 #header #header-inner #quick a.thin,
278 #header #header-inner #quick a.thin,
273 #context-pages a.thin,
279 #context-pages a.thin,
274 #context-pages .admin_menu a.thin {
280 #context-pages .admin_menu a.thin {
275 line-height: 28px !important;
281 line-height: 28px !important;
276 }
282 }
277
283
278 #header #header-inner #quick a#quick_login_link {
284 #header #header-inner #quick a#quick_login_link {
279 padding-left: 0px;
285 padding-left: 0px;
280 }
286 }
281
287
282 #header #header-inner #quick a {
288 #header #header-inner #quick a {
283 overflow: hidden;
289 overflow: hidden;
284 }
290 }
285 #quick a.childs:after,
291 #quick a.childs:after,
286 #revision-changer:before,
292 #revision-changer:before,
287 #context-pages a.childs:after,
293 #context-pages a.childs:after,
288 #context-pages a.dropdown:after {
294 #context-pages a.dropdown:after {
289 content: ' \25BE';
295 content: ' \25BE';
290 }
296 }
291 #context-pages a.childs {
297 #context-pages a.childs {
292 padding-right: 30px;
298 padding-right: 30px;
293 }
299 }
294 #context-pages a.childs:after {
300 #context-pages a.childs:after {
295 position: absolute;
301 position: absolute;
296 float: right;
302 float: right;
297 padding-left: 5px;
303 padding-left: 5px;
298 padding-right: 5px;
304 padding-right: 5px;
299 }
305 }
300
306
301 #revision-changer:before {
307 #revision-changer:before {
302 position: absolute;
308 position: absolute;
303 top: 0px;
309 top: 0px;
304 right: 0px;
310 right: 0px;
305 border-right: 1px solid rgba(0,0,0,0.1);
311 border-right: 1px solid rgba(0,0,0,0.1);
306 height: 25px;
312 height: 25px;
307 padding-top: 10px;
313 padding-top: 10px;
308 padding-right: 10px;
314 padding-right: 10px;
309 }
315 }
310
316
311 #context-pages li:last-child a {
317 #context-pages li:last-child a {
312 padding-right: 10px;
318 padding-right: 10px;
313 }
319 }
314
320
315 #context-bar #revision-changer {
321 #context-bar #revision-changer {
316 position: relative;
322 position: relative;
317 cursor: pointer;
323 cursor: pointer;
318 border: none;
324 border: none;
319 padding: 0;
325 padding: 0;
320 margin: 0;
326 margin: 0;
321 color: #FFFFFF;
327 color: #FFFFFF;
322 font-size: 0.85em;
328 font-size: 0.85em;
323 padding: 2px 15px;
329 padding: 2px 15px;
324 padding-bottom: 3px;
330 padding-bottom: 3px;
325 padding-right: 30px;
331 padding-right: 30px;
326 border-right: 1px solid rgba(255,255,255,0.1);
332 border-right: 1px solid rgba(255,255,255,0.1);
327 }
333 }
328
334
329 #revision-changer .branch-name,
335 #revision-changer .branch-name,
330 #revision-changer .revision {
336 #revision-changer .revision {
331 display: block;
337 display: block;
332 text-align: center;
338 text-align: center;
333 line-height: 1.5em;
339 line-height: 1.5em;
334 }
340 }
335
341
336 #revision-changer .branch-name {
342 #revision-changer .branch-name {
337 font-weight: bold;
343 font-weight: bold;
338 }
344 }
339
345
340 #revision-changer .revision {
346 #revision-changer .revision {
341 text-transform: uppercase;
347 text-transform: uppercase;
342 }
348 }
@@ -1,173 +1,177 b''
1 div.codeblock {
1 div.codeblock {
2 overflow: auto;
2 overflow: auto;
3 padding: 0px;
3 padding: 0px;
4 border: 1px solid #ccc;
4 border: 1px solid #ccc;
5 background: #f8f8f8;
5 background: #f8f8f8;
6 font-size: 100%;
6 font-size: 100%;
7 line-height: 100%;
7 line-height: 100%;
8 /* new */
8 /* new */
9 line-height: 125%;
9 line-height: 125%;
10 -webkit-border-radius: 4px;
10 -webkit-border-radius: 4px;
11 -moz-border-radius: 4px;
11 -moz-border-radius: 4px;
12 border-radius: 4px;
12 border-radius: 4px;
13 }
13 }
14 div.codeblock .code-header {
14 div.codeblock .code-header {
15 border-bottom: 1px solid #CCCCCC;
15 border-bottom: 1px solid #CCCCCC;
16 background: #EEEEEE;
16 background: #EEEEEE;
17 padding: 10px 0 10px 0;
17 padding: 10px 0 5px 0;
18 }
18 }
19
19
20 div.codeblock .code-header .stats {
20 div.codeblock .code-header .stats {
21 clear: both;
21 clear: both;
22 padding: 6px 8px 6px 10px;
22 padding: 2px 8px 2px 14px;
23 border-bottom: 1px solid rgb(204, 204, 204);
23 border-bottom: 1px solid rgb(204, 204, 204);
24 height: 23px;
24 height: 23px;
25 margin-bottom: 6px;
25 margin-bottom: 6px;
26 }
26 }
27
27
28 div.codeblock .code-header .stats .left {
28 div.codeblock .code-header .stats .left {
29 float: left;
29 float: left;
30 }
30 }
31 div.codeblock .code-header .stats .left.img {
31 div.codeblock .code-header .stats .left.img {
32 margin-top: -2px;
32 margin-top: -2px;
33 }
33 }
34 div.codeblock .code-header .stats .left.item {
34 div.codeblock .code-header .stats .left.item {
35 float: left;
35 float: left;
36 padding: 0 9px 0 9px;
36 padding: 0 9px 0 9px;
37 border-right: 1px solid #ccc;
37 border-right: 1px solid #ccc;
38 }
38 }
39 div.codeblock .code-header .stats .left.item pre {
39 div.codeblock .code-header .stats .left.item pre {
40 }
40 }
41 div.codeblock .code-header .stats .left.item.last {
41 div.codeblock .code-header .stats .left.item.last {
42 border-right: none;
42 border-right: none;
43 }
43 }
44 div.codeblock .code-header .stats .buttons {
44 div.codeblock .code-header .stats .buttons {
45 float: right;
45 float: right;
46 padding-right: 4px;
46 padding-right: 4px;
47 }
47 }
48
48
49 div.codeblock .code-header .author {
49 div.codeblock .code-header .author {
50 margin-left: 25px;
50 margin-left: 15px;
51 font-weight: bold;
51 font-weight: bold;
52 height: 25px;
52 height: 25px;
53 }
53 }
54 div.codeblock .code-header .author .user {
54 div.codeblock .code-header .author .user {
55 padding-top: 3px;
55 padding-top: 3px;
56 }
56 }
57 div.codeblock .code-header .commit {
57 div.codeblock .code-header .commit {
58 margin-left: 25px;
58 margin-left: 15px;
59 font-weight: normal;
59 font-weight: normal;
60 white-space: pre;
60 white-space: pre;
61 }
61 }
62
62
63 .code-highlighttable,
63 div.codeblock .code-body table {
64 div.codeblock .code-body table {
64 width: 0 !important;
65 width: 0 !important;
65 border: 0px !important;
66 border: 0px !important;
66 }
67 }
68
69 .code-highlighttable,
67 div.codeblock .code-body table td {
70 div.codeblock .code-body table td {
68 border: 0px !important;
71 border: 0px !important;
69 }
72 }
73
70 div.code-body {
74 div.code-body {
71 background-color: #FFFFFF;
75 background-color: #FFFFFF;
72 }
76 }
73
77
74 div.codeblock .code-header .search-path {
78 div.codeblock .code-header .search-path {
75 padding: 0px 0px 0px 10px;
79 padding: 0px 0px 0px 10px;
76 }
80 }
77
81
78 div.search-code-body {
82 div.search-code-body {
79 background-color: #FFFFFF;
83 background-color: #FFFFFF;
80 padding: 5px 0px 5px 10px;
84 padding: 5px 0px 5px 10px;
81 }
85 }
82
86
83 div.search-code-body pre .match {
87 div.search-code-body pre .match {
84 background-color: #FAFFA6;
88 background-color: #FAFFA6;
85 }
89 }
86 div.search-code-body pre .break {
90 div.search-code-body pre .break {
87 background-color: #DDE7EF;
91 background-color: #DDE7EF;
88 width: 100%;
92 width: 100%;
89 color: #747474;
93 color: #747474;
90 display: block;
94 display: block;
91 }
95 }
92 div.annotatediv {
96 div.annotatediv {
93 margin-left: 2px;
97 margin-left: 2px;
94 margin-right: 4px;
98 margin-right: 4px;
95 }
99 }
96 .code-highlight {
100 .code-highlight {
97 padding: 0px;
101 padding: 0px;
98 margin-top: 5px;
102 margin-top: 5px;
99 margin-bottom: 5px;
103 margin-bottom: 5px;
100 border-left: 2px solid #ccc;
104 border-left: 1px solid #ccc;
101 }
105 }
102 .code-highlight pre, .linenodiv pre {
106 .code-highlight pre, .linenodiv pre {
103 padding: 5px;
107 padding: 5px 2px 0px 5px;
104 margin: 0;
108 margin: 0;
105 }
109 }
106 .code-highlight pre div:target {
110 .code-highlight pre div:target {
107 background-color: #FFFFBE !important;
111 background-color: #FFFFBE !important;
108 }
112 }
109
113 .linenos { padding: 0px !important; border:0px !important;}
110 .linenos a { text-decoration: none; }
114 .linenos a { text-decoration: none; }
111
115
112 .code { display: block; }
116 .code { display: block; border:0px !important; }
113 .code-highlight .hll, .codehilite .hll { background-color: #ffffcc }
117 .code-highlight .hll, .codehilite .hll { background-color: #ffffcc }
114 .code-highlight .c, .codehilite .c { color: #408080; font-style: italic } /* Comment */
118 .code-highlight .c, .codehilite .c { color: #408080; font-style: italic } /* Comment */
115 .code-highlight .err, .codehilite .err { border: 1px solid #FF0000 } /* Error */
119 .code-highlight .err, .codehilite .err { border: 1px solid #FF0000 } /* Error */
116 .code-highlight .k, .codehilite .k { color: #008000; font-weight: bold } /* Keyword */
120 .code-highlight .k, .codehilite .k { color: #008000; font-weight: bold } /* Keyword */
117 .code-highlight .o, .codehilite .o { color: #666666 } /* Operator */
121 .code-highlight .o, .codehilite .o { color: #666666 } /* Operator */
118 .code-highlight .cm, .codehilite .cm { color: #408080; font-style: italic } /* Comment.Multiline */
122 .code-highlight .cm, .codehilite .cm { color: #408080; font-style: italic } /* Comment.Multiline */
119 .code-highlight .cp, .codehilite .cp { color: #BC7A00 } /* Comment.Preproc */
123 .code-highlight .cp, .codehilite .cp { color: #BC7A00 } /* Comment.Preproc */
120 .code-highlight .c1, .codehilite .c1 { color: #408080; font-style: italic } /* Comment.Single */
124 .code-highlight .c1, .codehilite .c1 { color: #408080; font-style: italic } /* Comment.Single */
121 .code-highlight .cs, .codehilite .cs { color: #408080; font-style: italic } /* Comment.Special */
125 .code-highlight .cs, .codehilite .cs { color: #408080; font-style: italic } /* Comment.Special */
122 .code-highlight .gd, .codehilite .gd { color: #A00000 } /* Generic.Deleted */
126 .code-highlight .gd, .codehilite .gd { color: #A00000 } /* Generic.Deleted */
123 .code-highlight .ge, .codehilite .ge { font-style: italic } /* Generic.Emph */
127 .code-highlight .ge, .codehilite .ge { font-style: italic } /* Generic.Emph */
124 .code-highlight .gr, .codehilite .gr { color: #FF0000 } /* Generic.Error */
128 .code-highlight .gr, .codehilite .gr { color: #FF0000 } /* Generic.Error */
125 .code-highlight .gh, .codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
129 .code-highlight .gh, .codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
126 .code-highlight .gi, .codehilite .gi { color: #00A000 } /* Generic.Inserted */
130 .code-highlight .gi, .codehilite .gi { color: #00A000 } /* Generic.Inserted */
127 .code-highlight .go, .codehilite .go { color: #808080 } /* Generic.Output */
131 .code-highlight .go, .codehilite .go { color: #808080 } /* Generic.Output */
128 .code-highlight .gp, .codehilite .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
132 .code-highlight .gp, .codehilite .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
129 .code-highlight .gs, .codehilite .gs { font-weight: bold } /* Generic.Strong */
133 .code-highlight .gs, .codehilite .gs { font-weight: bold } /* Generic.Strong */
130 .code-highlight .gu, .codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
134 .code-highlight .gu, .codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
131 .code-highlight .gt, .codehilite .gt { color: #0040D0 } /* Generic.Traceback */
135 .code-highlight .gt, .codehilite .gt { color: #0040D0 } /* Generic.Traceback */
132 .code-highlight .kc, .codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
136 .code-highlight .kc, .codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
133 .code-highlight .kd, .codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
137 .code-highlight .kd, .codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
134 .code-highlight .kn, .codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
138 .code-highlight .kn, .codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
135 .code-highlight .kp, .codehilite .kp { color: #008000 } /* Keyword.Pseudo */
139 .code-highlight .kp, .codehilite .kp { color: #008000 } /* Keyword.Pseudo */
136 .code-highlight .kr, .codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
140 .code-highlight .kr, .codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
137 .code-highlight .kt, .codehilite .kt { color: #B00040 } /* Keyword.Type */
141 .code-highlight .kt, .codehilite .kt { color: #B00040 } /* Keyword.Type */
138 .code-highlight .m, .codehilite .m { color: #666666 } /* Literal.Number */
142 .code-highlight .m, .codehilite .m { color: #666666 } /* Literal.Number */
139 .code-highlight .s, .codehilite .s { color: #BA2121 } /* Literal.String */
143 .code-highlight .s, .codehilite .s { color: #BA2121 } /* Literal.String */
140 .code-highlight .na, .codehilite .na { color: #7D9029 } /* Name.Attribute */
144 .code-highlight .na, .codehilite .na { color: #7D9029 } /* Name.Attribute */
141 .code-highlight .nb, .codehilite .nb { color: #008000 } /* Name.Builtin */
145 .code-highlight .nb, .codehilite .nb { color: #008000 } /* Name.Builtin */
142 .code-highlight .nc, .codehilite .nc { color: #0000FF; font-weight: bold } /* Name.Class */
146 .code-highlight .nc, .codehilite .nc { color: #0000FF; font-weight: bold } /* Name.Class */
143 .code-highlight .no, .codehilite .no { color: #880000 } /* Name.Constant */
147 .code-highlight .no, .codehilite .no { color: #880000 } /* Name.Constant */
144 .code-highlight .nd, .codehilite .nd { color: #AA22FF } /* Name.Decorator */
148 .code-highlight .nd, .codehilite .nd { color: #AA22FF } /* Name.Decorator */
145 .code-highlight .ni, .codehilite .ni { color: #999999; font-weight: bold } /* Name.Entity */
149 .code-highlight .ni, .codehilite .ni { color: #999999; font-weight: bold } /* Name.Entity */
146 .code-highlight .ne, .codehilite .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
150 .code-highlight .ne, .codehilite .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
147 .code-highlight .nf, .codehilite .nf { color: #0000FF } /* Name.Function */
151 .code-highlight .nf, .codehilite .nf { color: #0000FF } /* Name.Function */
148 .code-highlight .nl, .codehilite .nl { color: #A0A000 } /* Name.Label */
152 .code-highlight .nl, .codehilite .nl { color: #A0A000 } /* Name.Label */
149 .code-highlight .nn, .codehilite .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
153 .code-highlight .nn, .codehilite .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
150 .code-highlight .nt, .codehilite .nt { color: #008000; font-weight: bold } /* Name.Tag */
154 .code-highlight .nt, .codehilite .nt { color: #008000; font-weight: bold } /* Name.Tag */
151 .code-highlight .nv, .codehilite .nv { color: #19177C } /* Name.Variable */
155 .code-highlight .nv, .codehilite .nv { color: #19177C } /* Name.Variable */
152 .code-highlight .ow, .codehilite .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
156 .code-highlight .ow, .codehilite .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
153 .code-highlight .w, .codehilite .w { color: #bbbbbb } /* Text.Whitespace */
157 .code-highlight .w, .codehilite .w { color: #bbbbbb } /* Text.Whitespace */
154 .code-highlight .mf, .codehilite .mf { color: #666666 } /* Literal.Number.Float */
158 .code-highlight .mf, .codehilite .mf { color: #666666 } /* Literal.Number.Float */
155 .code-highlight .mh, .codehilite .mh { color: #666666 } /* Literal.Number.Hex */
159 .code-highlight .mh, .codehilite .mh { color: #666666 } /* Literal.Number.Hex */
156 .code-highlight .mi, .codehilite .mi { color: #666666 } /* Literal.Number.Integer */
160 .code-highlight .mi, .codehilite .mi { color: #666666 } /* Literal.Number.Integer */
157 .code-highlight .mo, .codehilite .mo { color: #666666 } /* Literal.Number.Oct */
161 .code-highlight .mo, .codehilite .mo { color: #666666 } /* Literal.Number.Oct */
158 .code-highlight .sb, .codehilite .sb { color: #BA2121 } /* Literal.String.Backtick */
162 .code-highlight .sb, .codehilite .sb { color: #BA2121 } /* Literal.String.Backtick */
159 .code-highlight .sc, .codehilite .sc { color: #BA2121 } /* Literal.String.Char */
163 .code-highlight .sc, .codehilite .sc { color: #BA2121 } /* Literal.String.Char */
160 .code-highlight .sd, .codehilite .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
164 .code-highlight .sd, .codehilite .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
161 .code-highlight .s2, .codehilite .s2 { color: #BA2121 } /* Literal.String.Double */
165 .code-highlight .s2, .codehilite .s2 { color: #BA2121 } /* Literal.String.Double */
162 .code-highlight .se, .codehilite .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
166 .code-highlight .se, .codehilite .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
163 .code-highlight .sh, .codehilite .sh { color: #BA2121 } /* Literal.String.Heredoc */
167 .code-highlight .sh, .codehilite .sh { color: #BA2121 } /* Literal.String.Heredoc */
164 .code-highlight .si, .codehilite .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
168 .code-highlight .si, .codehilite .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
165 .code-highlight .sx, .codehilite .sx { color: #008000 } /* Literal.String.Other */
169 .code-highlight .sx, .codehilite .sx { color: #008000 } /* Literal.String.Other */
166 .code-highlight .sr, .codehilite .sr { color: #BB6688 } /* Literal.String.Regex */
170 .code-highlight .sr, .codehilite .sr { color: #BB6688 } /* Literal.String.Regex */
167 .code-highlight .s1, .codehilite .s1 { color: #BA2121 } /* Literal.String.Single */
171 .code-highlight .s1, .codehilite .s1 { color: #BA2121 } /* Literal.String.Single */
168 .code-highlight .ss, .codehilite .ss { color: #19177C } /* Literal.String.Symbol */
172 .code-highlight .ss, .codehilite .ss { color: #19177C } /* Literal.String.Symbol */
169 .code-highlight .bp, .codehilite .bp { color: #008000 } /* Name.Builtin.Pseudo */
173 .code-highlight .bp, .codehilite .bp { color: #008000 } /* Name.Builtin.Pseudo */
170 .code-highlight .vc, .codehilite .vc { color: #19177C } /* Name.Variable.Class */
174 .code-highlight .vc, .codehilite .vc { color: #19177C } /* Name.Variable.Class */
171 .code-highlight .vg, .codehilite .vg { color: #19177C } /* Name.Variable.Global */
175 .code-highlight .vg, .codehilite .vg { color: #19177C } /* Name.Variable.Global */
172 .code-highlight .vi, .codehilite .vi { color: #19177C } /* Name.Variable.Instance */
176 .code-highlight .vi, .codehilite .vi { color: #19177C } /* Name.Variable.Instance */
173 .code-highlight .il, .codehilite .il { color: #666666 } /* Literal.Number.Integer.Long */
177 .code-highlight .il, .codehilite .il { color: #666666 } /* Literal.Number.Integer.Long */
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now