Show More
@@ -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 |
@@ -0,0 +1,68 b'' | |||||
|
1 | ## -*- coding: utf-8 -*- | |||
|
2 | <%inherit file="/base/base.html"/> | |||
|
3 | ||||
|
4 | <%def name="title()"> | |||
|
5 | ${_('Gists')} · ${c.rhodecode_name} | |||
|
6 | </%def> | |||
|
7 | ||||
|
8 | <%def name="breadcrumbs_links()"> | |||
|
9 | %if c.show_private: | |||
|
10 | ${_('Private Gists for user %s') % c.rhodecode_user.username} | |||
|
11 | %else: | |||
|
12 | ${_('Public Gists')} | |||
|
13 | %endif | |||
|
14 | - ${c.gists_pager.item_count} | |||
|
15 | </%def> | |||
|
16 | ||||
|
17 | <%def name="page_nav()"> | |||
|
18 | ${self.menu('gists')} | |||
|
19 | </%def> | |||
|
20 | ||||
|
21 | <%def name="main()"> | |||
|
22 | <div class="box"> | |||
|
23 | <!-- box / title --> | |||
|
24 | <div class="title"> | |||
|
25 | ${self.breadcrumbs()} | |||
|
26 | %if c.rhodecode_user.username != 'default': | |||
|
27 | <ul class="links"> | |||
|
28 | <li> | |||
|
29 | <span>${h.link_to(_(u'Create new gist'), h.url('new_gist'))}</span> | |||
|
30 | </li> | |||
|
31 | </ul> | |||
|
32 | %endif | |||
|
33 | </div> | |||
|
34 | %if c.gists_pager.item_count>0: | |||
|
35 | % for gist in c.gists_pager: | |||
|
36 | <div class="gist-item" style="padding:10px 20px 10px 15px"> | |||
|
37 | ||||
|
38 | <div class="gravatar"> | |||
|
39 | <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(gist.owner.full_contact),24)}"/> | |||
|
40 | </div> | |||
|
41 | <div title="${gist.owner.full_contact}" class="user"> | |||
|
42 | <b>${h.person(gist.owner.full_contact)}</b> / | |||
|
43 | <b><a href="${h.url('gist',id=gist.gist_access_id)}">gist:${gist.gist_access_id}</a></b> | |||
|
44 | <span style="color: #AAA"> | |||
|
45 | %if gist.gist_expires == -1: | |||
|
46 | ${_('Expires')}: ${_('never')} | |||
|
47 | %else: | |||
|
48 | ${_('Expires')}: ${h.age(h.time_to_datetime(gist.gist_expires))} | |||
|
49 | %endif | |||
|
50 | </span> | |||
|
51 | </div> | |||
|
52 | <div>${_('Created')} ${h.age(gist.created_on)} | |||
|
53 | </div> | |||
|
54 | ||||
|
55 | <div style="border:0px;padding:10px 0px 0px 35px;color:#AAA">${gist.gist_description}</div> | |||
|
56 | </div> | |||
|
57 | % endfor | |||
|
58 | ||||
|
59 | <div class="notification-paginator"> | |||
|
60 | <div class="pagination-wh pagination-left"> | |||
|
61 | ${c.gists_pager.pager('$link_previous ~2~ $link_next')} | |||
|
62 | </div> | |||
|
63 | </div> | |||
|
64 | %else: | |||
|
65 | <div class="table">${_('There are no gists yet')}</div> | |||
|
66 | %endif | |||
|
67 | </div> | |||
|
68 | </%def> |
@@ -0,0 +1,64 b'' | |||||
|
1 | ## -*- coding: utf-8 -*- | |||
|
2 | <%inherit file="/base/base.html"/> | |||
|
3 | ||||
|
4 | <%def name="title()"> | |||
|
5 | ${_('New gist')} · ${c.rhodecode_name} | |||
|
6 | </%def> | |||
|
7 | ||||
|
8 | <%def name="js_extra()"> | |||
|
9 | <script type="text/javascript" src="${h.url('/js/codemirror.js')}"></script> | |||
|
10 | </%def> | |||
|
11 | <%def name="css_extra()"> | |||
|
12 | <link rel="stylesheet" type="text/css" href="${h.url('/css/codemirror.css')}"/> | |||
|
13 | </%def> | |||
|
14 | ||||
|
15 | <%def name="breadcrumbs_links()"> | |||
|
16 | ${_('New gist')} | |||
|
17 | </%def> | |||
|
18 | ||||
|
19 | <%def name="page_nav()"> | |||
|
20 | ${self.menu('gists')} | |||
|
21 | </%def> | |||
|
22 | ||||
|
23 | <%def name="main()"> | |||
|
24 | <div class="box"> | |||
|
25 | <!-- box / title --> | |||
|
26 | <div class="title"> | |||
|
27 | ${self.breadcrumbs()} | |||
|
28 | </div> | |||
|
29 | ||||
|
30 | <div class="table"> | |||
|
31 | <div id="files_data"> | |||
|
32 | ${h.form(h.url('gists'), method='post',id='eform')} | |||
|
33 | <div> | |||
|
34 | <div class="gravatar"> | |||
|
35 | <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(c.rhodecode_user.full_contact),32)}"/> | |||
|
36 | </div> | |||
|
37 | <textarea style="resize:vertical; width:400px;border: 1px solid #ccc;border-radius: 3px;" id="description" name="description" placeholder="${_('Gist description ...')}"></textarea> | |||
|
38 | </div> | |||
|
39 | <div id="body" class="codeblock"> | |||
|
40 | <div style="padding: 10px 10px 10px 22px;color:#666666"> | |||
|
41 | ##<input type="text" value="" size="30" name="filename" id="filename" placeholder="gistfile1.txt"> | |||
|
42 | ${h.text('filename', size=30, placeholder='gistfile1.txt')} | |||
|
43 | ##<input type="text" value="" size="30" name="filename" id="filename" placeholder="gistfile1.txt"> | |||
|
44 | ${h.select('lifetime', '', c.lifetime_options)} | |||
|
45 | </div> | |||
|
46 | <div id="editor_container"> | |||
|
47 | <pre id="editor_pre"></pre> | |||
|
48 | <textarea id="editor" name="content" style="display:none"></textarea> | |||
|
49 | </div> | |||
|
50 | </div> | |||
|
51 | <div style="padding-top: 5px"> | |||
|
52 | ${h.submit('private',_('Create private gist'),class_="ui-btn yellow")} | |||
|
53 | ${h.submit('public',_('Create public gist'),class_="ui-btn")} | |||
|
54 | ${h.reset('reset',_('Reset'),class_="ui-btn")} | |||
|
55 | </div> | |||
|
56 | ${h.end_form()} | |||
|
57 | <script type="text/javascript"> | |||
|
58 | initCodeMirror('editor',''); | |||
|
59 | </script> | |||
|
60 | </div> | |||
|
61 | </div> | |||
|
62 | ||||
|
63 | </div> | |||
|
64 | </%def> |
@@ -0,0 +1,87 b'' | |||||
|
1 | ## -*- coding: utf-8 -*- | |||
|
2 | <%inherit file="/base/base.html"/> | |||
|
3 | ||||
|
4 | <%def name="title()"> | |||
|
5 | ${_('gist')}:${c.gist.gist_access_id} · ${c.rhodecode_name} | |||
|
6 | </%def> | |||
|
7 | ||||
|
8 | <%def name="breadcrumbs_links()"> | |||
|
9 | ${_('Gist')} · gist:${c.gist.gist_access_id} | |||
|
10 | </%def> | |||
|
11 | ||||
|
12 | <%def name="page_nav()"> | |||
|
13 | ${self.menu('gists')} | |||
|
14 | </%def> | |||
|
15 | ||||
|
16 | <%def name="main()"> | |||
|
17 | <div class="box"> | |||
|
18 | <!-- box / title --> | |||
|
19 | <div class="title"> | |||
|
20 | ${self.breadcrumbs()} | |||
|
21 | %if c.rhodecode_user.username != 'default': | |||
|
22 | <ul class="links"> | |||
|
23 | <li> | |||
|
24 | <span>${h.link_to(_(u'Create new gist'), h.url('new_gist'))}</span> | |||
|
25 | </li> | |||
|
26 | </ul> | |||
|
27 | %endif | |||
|
28 | </div> | |||
|
29 | <div class="table"> | |||
|
30 | <div id="files_data"> | |||
|
31 | <div id="body" class="codeblock"> | |||
|
32 | <div class="code-header"> | |||
|
33 | <div class="stats"> | |||
|
34 | <div class="left" style="margin: -4px 0px 0px 0px"> | |||
|
35 | %if c.gist.gist_type == 'public': | |||
|
36 | <div class="ui-btn green badge">${_('Public gist')}</div> | |||
|
37 | %else: | |||
|
38 | <div class="ui-btn yellow badge">${_('Private gist')}</div> | |||
|
39 | %endif | |||
|
40 | </div> | |||
|
41 | <span style="color: #AAA"> | |||
|
42 | %if c.gist.gist_expires == -1: | |||
|
43 | ${_('Expires')}: ${_('never')} | |||
|
44 | %else: | |||
|
45 | ${_('Expires')}: ${h.age(h.time_to_datetime(c.gist.gist_expires))} | |||
|
46 | %endif | |||
|
47 | </span> | |||
|
48 | <div class="left item last">${c.gist.gist_description}</div> | |||
|
49 | <div class="buttons"> | |||
|
50 | ## only owner should see that | |||
|
51 | %if c.gist.owner.username == c.rhodecode_user.username: | |||
|
52 | ##${h.link_to(_('Edit'),h.url(''),class_="ui-btn")} | |||
|
53 | ##${h.link_to(_('Delete'),h.url(''),class_="ui-btn red")} | |||
|
54 | %endif | |||
|
55 | </div> | |||
|
56 | </div> | |||
|
57 | ||||
|
58 | <div class="author"> | |||
|
59 | <div class="gravatar"> | |||
|
60 | <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(c.file_changeset.author),16)}"/> | |||
|
61 | </div> | |||
|
62 | <div title="${c.file_changeset.author}" class="user">${h.person(c.file_changeset.author)} - ${_('created')} ${h.age(c.file_changeset.date)}</div> | |||
|
63 | </div> | |||
|
64 | <div class="commit">${h.urlify_commit(c.file_changeset.message,c.repo_name)}</div> | |||
|
65 | </div> | |||
|
66 | </div> | |||
|
67 | ||||
|
68 | ## iterate over the files | |||
|
69 | % for file in c.files: | |||
|
70 | <div style="border: 1px solid #EEE;margin-top:20px"> | |||
|
71 | <div id="${h.FID('G', file.path)}" class="stats" style="border-bottom: 1px solid #DDD;padding: 8px 14px;"> | |||
|
72 | <b>${file.path}</b> | |||
|
73 | ##<div class="buttons"> | |||
|
74 | ## ${h.link_to(_('Show as raw'),h.url(''),class_="ui-btn")} | |||
|
75 | ##</div> | |||
|
76 | </div> | |||
|
77 | <div class="code-body"> | |||
|
78 | ${h.pygmentize(file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")} | |||
|
79 | </div> | |||
|
80 | </div> | |||
|
81 | %endfor | |||
|
82 | </div> | |||
|
83 | </div> | |||
|
84 | ||||
|
85 | ||||
|
86 | </div> | |||
|
87 | </%def> |
@@ -0,0 +1,125 b'' | |||||
|
1 | from rhodecode.tests import * | |||
|
2 | from rhodecode.model.gist import GistModel | |||
|
3 | from rhodecode.model.meta import Session | |||
|
4 | from rhodecode.model.db import User, Gist | |||
|
5 | ||||
|
6 | ||||
|
7 | def _create_gist(f_name, content='some gist', lifetime=-1, | |||
|
8 | description='gist-desc', gist_type='public'): | |||
|
9 | gist_mapping = { | |||
|
10 | f_name: {'content': content} | |||
|
11 | } | |||
|
12 | user = User.get_by_username(TEST_USER_ADMIN_LOGIN) | |||
|
13 | gist = GistModel().create(description, owner=user, | |||
|
14 | gist_mapping=gist_mapping, gist_type=gist_type, | |||
|
15 | lifetime=lifetime) | |||
|
16 | Session().commit() | |||
|
17 | return gist | |||
|
18 | ||||
|
19 | ||||
|
20 | class TestGistsController(TestController): | |||
|
21 | ||||
|
22 | def tearDown(self): | |||
|
23 | for g in Gist.get_all(): | |||
|
24 | GistModel().delete(g) | |||
|
25 | Session().commit() | |||
|
26 | ||||
|
27 | def test_index(self): | |||
|
28 | self.log_user() | |||
|
29 | response = self.app.get(url('gists')) | |||
|
30 | # Test response... | |||
|
31 | response.mustcontain('There are no gists yet') | |||
|
32 | ||||
|
33 | _create_gist('gist1') | |||
|
34 | _create_gist('gist2', lifetime=1400) | |||
|
35 | _create_gist('gist3', description='gist3-desc') | |||
|
36 | _create_gist('gist4', gist_type='private') | |||
|
37 | response = self.app.get(url('gists')) | |||
|
38 | # Test response... | |||
|
39 | response.mustcontain('gist:1') | |||
|
40 | response.mustcontain('gist:2') | |||
|
41 | response.mustcontain('Expires: in 23 hours') # we don't care about the end | |||
|
42 | response.mustcontain('gist:3') | |||
|
43 | response.mustcontain('gist3-desc') | |||
|
44 | response.mustcontain(no=['gist:4']) | |||
|
45 | ||||
|
46 | def test_index_private_gists(self): | |||
|
47 | self.log_user() | |||
|
48 | gist = _create_gist('gist5', gist_type='private') | |||
|
49 | response = self.app.get(url('gists', private=1)) | |||
|
50 | # Test response... | |||
|
51 | ||||
|
52 | #and privates | |||
|
53 | response.mustcontain('gist:%s' % gist.gist_access_id) | |||
|
54 | ||||
|
55 | def test_create_missing_description(self): | |||
|
56 | self.log_user() | |||
|
57 | response = self.app.post(url('gists'), | |||
|
58 | params={'lifetime': -1}, status=200) | |||
|
59 | ||||
|
60 | response.mustcontain('Missing value') | |||
|
61 | ||||
|
62 | def test_create(self): | |||
|
63 | self.log_user() | |||
|
64 | response = self.app.post(url('gists'), | |||
|
65 | params={'lifetime': -1, | |||
|
66 | 'content': 'gist test', | |||
|
67 | 'filename': 'foo', | |||
|
68 | 'public': 'public'}, | |||
|
69 | status=302) | |||
|
70 | response = response.follow() | |||
|
71 | response.mustcontain('added file: foo') | |||
|
72 | response.mustcontain('gist test') | |||
|
73 | response.mustcontain('<div class="ui-btn green badge">Public gist</div>') | |||
|
74 | ||||
|
75 | def test_create_private(self): | |||
|
76 | self.log_user() | |||
|
77 | response = self.app.post(url('gists'), | |||
|
78 | params={'lifetime': -1, | |||
|
79 | 'content': 'private gist test', | |||
|
80 | 'filename': 'private-foo', | |||
|
81 | 'private': 'private'}, | |||
|
82 | status=302) | |||
|
83 | response = response.follow() | |||
|
84 | response.mustcontain('added file: private-foo<') | |||
|
85 | response.mustcontain('private gist test') | |||
|
86 | response.mustcontain('<div class="ui-btn yellow badge">Private gist</div>') | |||
|
87 | ||||
|
88 | def test_create_with_description(self): | |||
|
89 | self.log_user() | |||
|
90 | response = self.app.post(url('gists'), | |||
|
91 | params={'lifetime': -1, | |||
|
92 | 'content': 'gist test', | |||
|
93 | 'filename': 'foo-desc', | |||
|
94 | 'description': 'gist-desc', | |||
|
95 | 'public': 'public'}, | |||
|
96 | status=302) | |||
|
97 | response = response.follow() | |||
|
98 | response.mustcontain('added file: foo-desc') | |||
|
99 | response.mustcontain('gist test') | |||
|
100 | response.mustcontain('gist-desc') | |||
|
101 | response.mustcontain('<div class="ui-btn green badge">Public gist</div>') | |||
|
102 | ||||
|
103 | def test_new(self): | |||
|
104 | self.log_user() | |||
|
105 | response = self.app.get(url('new_gist')) | |||
|
106 | ||||
|
107 | def test_update(self): | |||
|
108 | self.skipTest('not implemented') | |||
|
109 | response = self.app.put(url('gist', id=1)) | |||
|
110 | ||||
|
111 | def test_delete(self): | |||
|
112 | self.skipTest('not implemented') | |||
|
113 | response = self.app.delete(url('gist', id=1)) | |||
|
114 | ||||
|
115 | def test_show(self): | |||
|
116 | gist = _create_gist('gist-show-me') | |||
|
117 | response = self.app.get(url('gist', id=gist.gist_access_id)) | |||
|
118 | response.mustcontain('added file: gist-show-me<') | |||
|
119 | response.mustcontain('test_admin (RhodeCode Admin) - created just now') | |||
|
120 | response.mustcontain('gist-desc') | |||
|
121 | response.mustcontain('<div class="ui-btn green badge">Public gist</div>') | |||
|
122 | ||||
|
123 | def test_edit(self): | |||
|
124 | self.skipTest('not implemented') | |||
|
125 | response = self.app.get(url('edit_gist', id=1)) |
@@ -35,7 +35,6 b' def api_call(apikey, apihost, format, me' | |||||
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, | |
@@ -80,7 +79,9 b' class RcConf(object):' | |||||
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) | |
@@ -106,7 +107,6 b' class RcConf(object):' | |||||
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): |
@@ -66,7 +66,6 b' def main(argv=None):' | |||||
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 |
@@ -391,6 +391,9 b' def make_map(config):' | |||||
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 | #========================================================================== |
@@ -42,9 +42,10 b' from rhodecode.model.repo import RepoMod' | |||||
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 | |||
@@ -888,6 +889,7 b' class ApiController(JSONRPCController):' | |||||
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 | |
@@ -1064,3 +1066,44 b' class ApiController(JSONRPCController):' | |||||
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 |
@@ -57,6 +57,7 b' from rhodecode.model.db import Repositor' | |||||
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__) | |
@@ -371,25 +372,32 b' class FilesController(BaseRepoController' | |||||
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: |
@@ -165,7 +165,6 b' class PullrequestsController(BaseRepoCon' | |||||
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, |
@@ -558,7 +558,6 b' class DbManage(object):' | |||||
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: |
@@ -39,6 +39,13 b' def upgrade(migrate_engine):' | |||||
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 | |
@@ -48,7 +55,7 b' def upgrade(migrate_engine):' | |||||
48 | user_id.create(table=tbl) |
|
55 | user_id.create(table=tbl) | |
49 |
|
56 | |||
50 | #========================================================================== |
|
57 | #========================================================================== | |
51 |
# |
|
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__ |
@@ -236,7 +236,6 b' class DiffProcessor(object):' | |||||
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) | |
@@ -331,7 +330,6 b' class DiffProcessor(object):' | |||||
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': |
@@ -66,6 +66,10 b' 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 |
@@ -306,11 +306,8 b' def handle_git_receive(repo_path, revs, ' | |||||
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 |
@@ -59,7 +59,6 b' class GitRepository(object):' | |||||
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 |
@@ -27,6 +27,7 b' 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 | |
@@ -607,3 +608,39 b' def _extract_extras(env=None):' | |||||
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] |
@@ -104,8 +104,7 b' class BaseModel(object):' | |||||
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, | |
@@ -115,8 +114,7 b' class BaseModel(object):' | |||||
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, | |
@@ -126,8 +124,7 b' class BaseModel(object):' | |||||
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, |
@@ -1130,7 +1130,6 b' class Repository(Base, BaseModel):' | |||||
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()\ | |
@@ -2122,6 +2121,44 b' class UserNotification(Base, BaseModel):' | |||||
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__ = ( |
@@ -419,3 +419,16 b' def PullRequestForm(repo_id):' | |||||
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 |
@@ -115,7 +115,6 b' class RepoModel(BaseModel):' | |||||
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) | |
@@ -652,7 +651,13 b' class RepoModel(BaseModel):' | |||||
652 | log.error(traceback.format_exc()) |
|
651 | log.error(traceback.format_exc()) | |
653 | raise |
|
652 | raise | |
654 |
|
653 | |||
655 |
def |
|
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 | |
@@ -662,6 +667,7 b' class RepoModel(BaseModel):' | |||||
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 | |
@@ -670,10 +676,12 b' class RepoModel(BaseModel):' | |||||
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): | |
@@ -690,13 +698,14 b' class RepoModel(BaseModel):' | |||||
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 | """ |
@@ -54,6 +54,7 b' 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 | |||
@@ -531,44 +532,76 b' class ScmModel(BaseModel):' | |||||
531 | revisions=[tip.raw_id]) |
|
532 | revisions=[tip.raw_id]) | |
532 | return tip |
|
533 | return tip | |
533 |
|
534 | |||
534 |
def create_node(self, repo, |
|
535 | def create_nodes(self, user, repo, message, nodes, parent_cs=None, | |
535 |
|
|
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 |
|
|
595 | parents=parents, | |
569 |
|
|
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): | |
@@ -610,7 +643,6 b' class ScmModel(BaseModel):' | |||||
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 = [] |
@@ -11,7 +11,7 b' from webhelpers.pylonslib.secure_form im' | |||||
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 | |
@@ -25,7 +25,7 b' from rhodecode.lib.auth import HasReposG' | |||||
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 |
@@ -4,6 +4,9 b'' | |||||
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 | |||
@@ -25,6 +28,9 b'' | |||||
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"); } |
@@ -14,12 +14,12 b' div.codeblock {' | |||||
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 |
|
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: |
|
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; | |
@@ -47,7 +47,7 b' div.codeblock .code-header .stats .butto' | |||||
47 | } |
|
47 | } | |
48 |
|
48 | |||
49 | div.codeblock .code-header .author { |
|
49 | div.codeblock .code-header .author { | |
50 |
margin-left: |
|
50 | margin-left: 15px; | |
51 | font-weight: bold; |
|
51 | font-weight: bold; | |
52 | height: 25px; |
|
52 | height: 25px; | |
53 | } |
|
53 | } | |
@@ -55,18 +55,22 b' 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: |
|
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 | } | |
@@ -97,19 +101,19 b' div.annotatediv {' | |||||
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: |
|
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 */ |
@@ -2306,6 +2306,11 b' h3.files_location {' | |||||
2306 | padding: 5px !important; |
|
2306 | padding: 5px !important; | |
2307 | } |
|
2307 | } | |
2308 |
|
2308 | |||
|
2309 | #files_data .codeblock #editor_container .error-message { | |||
|
2310 | color: red; | |||
|
2311 | padding: 10px 10px 10px 26px | |||
|
2312 | } | |||
|
2313 | ||||
2309 | .file_history { |
|
2314 | .file_history { | |
2310 | padding-top: 10px; |
|
2315 | padding-top: 10px; | |
2311 | font-size: 16px; |
|
2316 | font-size: 16px; | |
@@ -3566,8 +3571,12 b' div.gravatar img {' | |||||
3566 | border-radius: 4px 4px 4px 4px !important; |
|
3571 | border-radius: 4px 4px 4px 4px !important; | |
3567 | cursor: pointer !important; |
|
3572 | cursor: pointer !important; | |
3568 | padding: 3px 3px 3px 3px; |
|
3573 | padding: 3px 3px 3px 3px; | |
3569 |
background-position: 0 -1 |
|
3574 | background-position: 0 -100px; | |
3570 |
|
3575 | |||
|
3576 | } | |||
|
3577 | ||||
|
3578 | .ui-btn.badge { | |||
|
3579 | cursor: default !important; | |||
3571 | } |
|
3580 | } | |
3572 |
|
3581 | |||
3573 | .ui-btn.disabled { |
|
3582 | .ui-btn.disabled { | |
@@ -3598,12 +3607,14 b' div.gravatar img {' | |||||
3598 | outline: none; |
|
3607 | outline: none; | |
3599 | } |
|
3608 | } | |
3600 | .ui-btn:hover { |
|
3609 | .ui-btn:hover { | |
3601 |
background-position: 0 -1 |
|
3610 | background-position: 0 -100px; | |
3602 | text-decoration: none; |
|
3611 | text-decoration: none; | |
3603 | color: #515151; |
|
3612 | color: #515151; | |
3604 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 0 3px #FFFFFF !important; |
|
3613 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 0 3px #FFFFFF !important; | |
3605 | } |
|
3614 | } | |
3606 |
|
3615 | .ui-btn.badge:hover { | ||
|
3616 | box-shadow: none !important; | |||
|
3617 | } | |||
3607 | .ui-btn.disabled:hover { |
|
3618 | .ui-btn.disabled:hover { | |
3608 | background-position: 0; |
|
3619 | background-position: 0; | |
3609 | color: #999; |
|
3620 | color: #999; | |
@@ -3645,6 +3656,7 b' div.gravatar img {' | |||||
3645 | } |
|
3656 | } | |
3646 |
|
3657 | |||
3647 | .ui-btn.green { |
|
3658 | .ui-btn.green { | |
|
3659 | color: #fff; | |||
3648 | background-color: #57a957; |
|
3660 | background-color: #57a957; | |
3649 | background-repeat: repeat-x; |
|
3661 | background-repeat: repeat-x; | |
3650 | background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957)); |
|
3662 | background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957)); | |
@@ -3659,6 +3671,22 b' div.gravatar img {' | |||||
3659 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); |
|
3671 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); | |
3660 | } |
|
3672 | } | |
3661 |
|
3673 | |||
|
3674 | .ui-btn.yellow { | |||
|
3675 | color: #fff; | |||
|
3676 | background-color: #faa732; | |||
|
3677 | background-repeat: repeat-x; | |||
|
3678 | background-image: -khtml-gradient(linear, left top, left bottom, from(#fbb450), to(#f89406)); | |||
|
3679 | background-image: -moz-linear-gradient(top, #fbb450, #f89406); | |||
|
3680 | background-image: -ms-linear-gradient(top, #fbb450, #f89406); | |||
|
3681 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fbb450), color-stop(100%, #f89406)); | |||
|
3682 | background-image: -webkit-linear-gradient(top, #fbb450, #f89406); | |||
|
3683 | background-image: -o-linear-gradient(top, #fbb450, #f89406); | |||
|
3684 | background-image: linear-gradient(to bottom, #fbb450, #f89406); | |||
|
3685 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbb450', endColorstr='#f89406', GradientType=0); | |||
|
3686 | border-color: #f89406 #f89406 #ad6704; | |||
|
3687 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); | |||
|
3688 | } | |||
|
3689 | ||||
3662 | .ui-btn.blue.hidden { |
|
3690 | .ui-btn.blue.hidden { | |
3663 | display: none; |
|
3691 | display: none; | |
3664 | } |
|
3692 | } |
@@ -286,6 +286,18 b'' | |||||
286 | </a> |
|
286 | </a> | |
287 | </li> |
|
287 | </li> | |
288 | %endif |
|
288 | %endif | |
|
289 | <li ${is_current('gists')}> | |||
|
290 | <a class="menu_link gists childs" title="${_('Show public gists')}" href="${h.url('gists')}"> | |||
|
291 | ${_('Gists')} | |||
|
292 | </a> | |||
|
293 | <ul class="admin_menu"> | |||
|
294 | <li>${h.link_to(_('Create new gist'),h.url('new_gist'),class_='gists-new ')}</li> | |||
|
295 | <li>${h.link_to(_('Public gists'),h.url('gists'),class_='gists ')}</li> | |||
|
296 | %if c.rhodecode_user.username != 'default': | |||
|
297 | <li>${h.link_to(_('My private gists'),h.url('gists', private=1),class_='gists-private ')}</li> | |||
|
298 | %endif | |||
|
299 | </ul> | |||
|
300 | </li> | |||
289 | <li ${is_current('search')}> |
|
301 | <li ${is_current('search')}> | |
290 | <a class="menu_link search" title="${_('Search in repositories')}" href="${h.url('search')}"> |
|
302 | <a class="menu_link search" title="${_('Search in repositories')}" href="${h.url('search')}"> | |
291 | ${_('Search')} |
|
303 | ${_('Search')} |
@@ -26,7 +26,6 b' def _build_data(apikey, method, **kw):' | |||||
26 | Builds API data with given random ID |
|
26 | Builds API data with given random ID | |
27 |
|
27 | |||
28 | :param random_id: |
|
28 | :param random_id: | |
29 | :type random_id: |
|
|||
30 | """ |
|
29 | """ | |
31 | random_id = random.randrange(1, 9999) |
|
30 | random_id = random.randrange(1, 9999) | |
32 | return random_id, json.dumps({ |
|
31 | return random_id, json.dumps({ |
@@ -16,13 +16,17 b' def _commit_change(repo, filename, conte' | |||||
16 | _cs = EmptyChangeset(alias=vcs_type) |
|
16 | _cs = EmptyChangeset(alias=vcs_type) | |
17 |
|
17 | |||
18 | if newfile: |
|
18 | if newfile: | |
19 | cs = ScmModel().create_node( |
|
19 | nodes = { | |
20 | repo=repo.scm_instance, repo_name=repo.repo_name, |
|
20 | filename: { | |
21 | cs=_cs, user=TEST_USER_ADMIN_LOGIN, |
|
21 | 'content': content | |
|
22 | } | |||
|
23 | } | |||
|
24 | cs = ScmModel().create_nodes( | |||
|
25 | user=TEST_USER_ADMIN_LOGIN, repo=repo, | |||
|
26 | message=message, | |||
|
27 | nodes=nodes, | |||
|
28 | parent_cs=_cs, | |||
22 | author=TEST_USER_ADMIN_LOGIN, |
|
29 | author=TEST_USER_ADMIN_LOGIN, | |
23 | message=message, |
|
|||
24 | content=content, |
|
|||
25 | f_path=filename |
|
|||
26 | ) |
|
30 | ) | |
27 | else: |
|
31 | else: | |
28 | cs = ScmModel().commit_change( |
|
32 | cs = ScmModel().commit_change( | |
@@ -317,15 +321,9 b' class TestCompareController(TestControll' | |||||
317 | self.r1_id = repo1.repo_id |
|
321 | self.r1_id = repo1.repo_id | |
318 | r1_name = repo1.repo_name |
|
322 | r1_name = repo1.repo_name | |
319 |
|
323 | |||
320 | #commit something initially ! |
|
324 | cs0 = _commit_change(repo=r1_name, filename='file1', | |
321 | cs0 = ScmModel().create_node( |
|
325 | content='line1', message='commit1', vcs_type='hg', | |
322 | repo=repo1.scm_instance, repo_name=r1_name, |
|
326 | newfile=True) | |
323 | cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN, |
|
|||
324 | author=TEST_USER_ADMIN_LOGIN, |
|
|||
325 | message='commit1', |
|
|||
326 | content='line1', |
|
|||
327 | f_path='file1' |
|
|||
328 | ) |
|
|||
329 | Session().commit() |
|
327 | Session().commit() | |
330 | self.assertEqual(repo1.scm_instance.revisions, [cs0.raw_id]) |
|
328 | self.assertEqual(repo1.scm_instance.revisions, [cs0.raw_id]) | |
331 | #fork the repo1 |
|
329 | #fork the repo1 | |
@@ -339,32 +337,20 b' class TestCompareController(TestControll' | |||||
339 | self.r2_id = repo2.repo_id |
|
337 | self.r2_id = repo2.repo_id | |
340 | r2_name = repo2.repo_name |
|
338 | r2_name = repo2.repo_name | |
341 |
|
339 | |||
342 | #make 3 new commits in fork |
|
340 | ||
343 | cs1 = ScmModel().create_node( |
|
341 | cs1 = _commit_change(repo=r2_name, filename='file1-fork', | |
344 | repo=repo2.scm_instance, repo_name=r2_name, |
|
342 | content='file1-line1-from-fork', message='commit1-fork', | |
345 | cs=repo2.scm_instance[-1], user=TEST_USER_ADMIN_LOGIN, |
|
343 | vcs_type='hg', parent=repo2.scm_instance[-1], | |
346 | author=TEST_USER_ADMIN_LOGIN, |
|
344 | newfile=True) | |
347 | message='commit1-fork', |
|
|||
348 | content='file1-line1-from-fork', |
|
|||
349 | f_path='file1-fork' |
|
|||
350 | ) |
|
|||
351 | cs2 = ScmModel().create_node( |
|
|||
352 | repo=repo2.scm_instance, repo_name=r2_name, |
|
|||
353 | cs=cs1, user=TEST_USER_ADMIN_LOGIN, |
|
|||
354 | author=TEST_USER_ADMIN_LOGIN, |
|
|||
355 | message='commit2-fork', |
|
|||
356 | content='file2-line1-from-fork', |
|
|||
357 | f_path='file2-fork' |
|
|||
358 | ) |
|
|||
359 | cs3 = ScmModel().create_node( |
|
|||
360 | repo=repo2.scm_instance, repo_name=r2_name, |
|
|||
361 | cs=cs2, user=TEST_USER_ADMIN_LOGIN, |
|
|||
362 | author=TEST_USER_ADMIN_LOGIN, |
|
|||
363 | message='commit3-fork', |
|
|||
364 | content='file3-line1-from-fork', |
|
|||
365 | f_path='file3-fork' |
|
|||
366 | ) |
|
|||
367 |
|
345 | |||
|
346 | cs2 = _commit_change(repo=r2_name, filename='file2-fork', | |||
|
347 | content='file2-line1-from-fork', message='commit2-fork', | |||
|
348 | vcs_type='hg', parent=cs1, | |||
|
349 | newfile=True) | |||
|
350 | ||||
|
351 | cs3 = _commit_change(repo=r2_name, filename='file3-fork', | |||
|
352 | content='file3-line1-from-fork', message='commit3-fork', | |||
|
353 | vcs_type='hg', parent=cs2, newfile=True) | |||
368 | #compare ! |
|
354 | #compare ! | |
369 | rev1 = 'default' |
|
355 | rev1 = 'default' | |
370 | rev2 = 'default' |
|
356 | rev2 = 'default' | |
@@ -383,14 +369,18 b' class TestCompareController(TestControll' | |||||
383 | response.mustcontain('No changesets') |
|
369 | response.mustcontain('No changesets') | |
384 |
|
370 | |||
385 | #add new commit into parent ! |
|
371 | #add new commit into parent ! | |
386 | cs0 = ScmModel().create_node( |
|
372 | # cs0 = ScmModel().create_node( | |
387 | repo=repo1.scm_instance, repo_name=r1_name, |
|
373 | # repo=repo1.scm_instance, repo_name=r1_name, | |
388 | cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN, |
|
374 | # cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN, | |
389 | author=TEST_USER_ADMIN_LOGIN, |
|
375 | # author=TEST_USER_ADMIN_LOGIN, | |
390 | message='commit2-parent', |
|
376 | # message='commit2-parent', | |
391 | content='line1-added-after-fork', |
|
377 | # content='line1-added-after-fork', | |
392 | f_path='file2' |
|
378 | # f_path='file2' | |
393 | ) |
|
379 | # ) | |
|
380 | cs0 = _commit_change(repo=r1_name, filename='file2', | |||
|
381 | content='line1-added-after-fork', message='commit2-parent', | |||
|
382 | vcs_type='hg', parent=None, newfile=True) | |||
|
383 | ||||
394 | #compare ! |
|
384 | #compare ! | |
395 | rev1 = 'default' |
|
385 | rev1 = 'default' | |
396 | rev2 = 'default' |
|
386 | rev2 = 'default' |
@@ -151,6 +151,7 b' setup(' | |||||
151 | entry_points=""" |
|
151 | entry_points=""" | |
152 | [console_scripts] |
|
152 | [console_scripts] | |
153 | rhodecode-api = rhodecode.bin.rhodecode_api:main |
|
153 | rhodecode-api = rhodecode.bin.rhodecode_api:main | |
|
154 | rhodecode-gist = rhodecode.bin.rhodecode_gist:main | |||
154 |
|
155 | |||
155 | [paste.app_factory] |
|
156 | [paste.app_factory] | |
156 | main = rhodecode.config.middleware:make_app |
|
157 | main = rhodecode.config.middleware:make_app |
General Comments 0
You need to be logged in to leave comments.
Login now