##// END OF EJS Templates
whitespace cleanup
marcink -
r2453:d2a528b6 beta
parent child Browse files
Show More
@@ -1,590 +1,590 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 vcs.backends.git
3 vcs.backends.git
4 ~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~
5
5
6 Git backend implementation.
6 Git backend implementation.
7
7
8 :created_on: Apr 8, 2010
8 :created_on: Apr 8, 2010
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 """
10 """
11
11
12 import os
12 import os
13 import re
13 import re
14 import time
14 import time
15 import posixpath
15 import posixpath
16 from dulwich.repo import Repo, NotGitRepository
16 from dulwich.repo import Repo, NotGitRepository
17 #from dulwich.config import ConfigFile
17 #from dulwich.config import ConfigFile
18 from string import Template
18 from string import Template
19 from subprocess import Popen, PIPE
19 from subprocess import Popen, PIPE
20 from rhodecode.lib.vcs.backends.base import BaseRepository
20 from rhodecode.lib.vcs.backends.base import BaseRepository
21 from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError
21 from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError
22 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
22 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
23 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
23 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
24 from rhodecode.lib.vcs.exceptions import RepositoryError
24 from rhodecode.lib.vcs.exceptions import RepositoryError
25 from rhodecode.lib.vcs.exceptions import TagAlreadyExistError
25 from rhodecode.lib.vcs.exceptions import TagAlreadyExistError
26 from rhodecode.lib.vcs.exceptions import TagDoesNotExistError
26 from rhodecode.lib.vcs.exceptions import TagDoesNotExistError
27 from rhodecode.lib.vcs.utils import safe_unicode, makedate, date_fromtimestamp
27 from rhodecode.lib.vcs.utils import safe_unicode, makedate, date_fromtimestamp
28 from rhodecode.lib.vcs.utils.lazy import LazyProperty
28 from rhodecode.lib.vcs.utils.lazy import LazyProperty
29 from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
29 from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
30 from rhodecode.lib.vcs.utils.paths import abspath
30 from rhodecode.lib.vcs.utils.paths import abspath
31 from rhodecode.lib.vcs.utils.paths import get_user_home
31 from rhodecode.lib.vcs.utils.paths import get_user_home
32 from .workdir import GitWorkdir
32 from .workdir import GitWorkdir
33 from .changeset import GitChangeset
33 from .changeset import GitChangeset
34 from .inmemory import GitInMemoryChangeset
34 from .inmemory import GitInMemoryChangeset
35 from .config import ConfigFile
35 from .config import ConfigFile
36
36
37
37
38 class GitRepository(BaseRepository):
38 class GitRepository(BaseRepository):
39 """
39 """
40 Git repository backend.
40 Git repository backend.
41 """
41 """
42 DEFAULT_BRANCH_NAME = 'master'
42 DEFAULT_BRANCH_NAME = 'master'
43 scm = 'git'
43 scm = 'git'
44
44
45 def __init__(self, repo_path, create=False, src_url=None,
45 def __init__(self, repo_path, create=False, src_url=None,
46 update_after_clone=False, bare=False):
46 update_after_clone=False, bare=False):
47
47
48 self.path = abspath(repo_path)
48 self.path = abspath(repo_path)
49 self._repo = self._get_repo(create, src_url, update_after_clone, bare)
49 self._repo = self._get_repo(create, src_url, update_after_clone, bare)
50 #temporary set that to now at later we will move it to constructor
50 #temporary set that to now at later we will move it to constructor
51 baseui = None
51 baseui = None
52 if baseui is None:
52 if baseui is None:
53 from mercurial.ui import ui
53 from mercurial.ui import ui
54 baseui = ui()
54 baseui = ui()
55 # patch the instance of GitRepo with an "FAKE" ui object to add
55 # patch the instance of GitRepo with an "FAKE" ui object to add
56 # compatibility layer with Mercurial
56 # compatibility layer with Mercurial
57 setattr(self._repo, 'ui', baseui)
57 setattr(self._repo, 'ui', baseui)
58
58
59 try:
59 try:
60 self.head = self._repo.head()
60 self.head = self._repo.head()
61 except KeyError:
61 except KeyError:
62 self.head = None
62 self.head = None
63
63
64 self._config_files = [
64 self._config_files = [
65 bare and abspath(self.path, 'config') or abspath(self.path, '.git',
65 bare and abspath(self.path, 'config') or abspath(self.path, '.git',
66 'config'),
66 'config'),
67 abspath(get_user_home(), '.gitconfig'),
67 abspath(get_user_home(), '.gitconfig'),
68 ]
68 ]
69 self.bare = self._repo.bare
69 self.bare = self._repo.bare
70
70
71 @LazyProperty
71 @LazyProperty
72 def revisions(self):
72 def revisions(self):
73 """
73 """
74 Returns list of revisions' ids, in ascending order. Being lazy
74 Returns list of revisions' ids, in ascending order. Being lazy
75 attribute allows external tools to inject shas from cache.
75 attribute allows external tools to inject shas from cache.
76 """
76 """
77 return self._get_all_revisions()
77 return self._get_all_revisions()
78
78
79 def run_git_command(self, cmd):
79 def run_git_command(self, cmd):
80 """
80 """
81 Runs given ``cmd`` as git command and returns tuple
81 Runs given ``cmd`` as git command and returns tuple
82 (returncode, stdout, stderr).
82 (returncode, stdout, stderr).
83
83
84 .. note::
84 .. note::
85 This method exists only until log/blame functionality is implemented
85 This method exists only until log/blame functionality is implemented
86 at Dulwich (see https://bugs.launchpad.net/bugs/645142). Parsing
86 at Dulwich (see https://bugs.launchpad.net/bugs/645142). Parsing
87 os command's output is road to hell...
87 os command's output is road to hell...
88
88
89 :param cmd: git command to be executed
89 :param cmd: git command to be executed
90 """
90 """
91
91
92 _copts = ['-c', 'core.quotepath=false', ]
92 _copts = ['-c', 'core.quotepath=false', ]
93 _str_cmd = False
93 _str_cmd = False
94 if isinstance(cmd, basestring):
94 if isinstance(cmd, basestring):
95 cmd = [cmd]
95 cmd = [cmd]
96 _str_cmd = True
96 _str_cmd = True
97
97
98 gitenv = os.environ
98 gitenv = os.environ
99 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
99 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
100
100
101 cmd = ['git'] + _copts + cmd
101 cmd = ['git'] + _copts + cmd
102 if _str_cmd:
102 if _str_cmd:
103 cmd = ' '.join(cmd)
103 cmd = ' '.join(cmd)
104 try:
104 try:
105 opts = dict(
105 opts = dict(
106 shell=isinstance(cmd, basestring),
106 shell=isinstance(cmd, basestring),
107 stdout=PIPE,
107 stdout=PIPE,
108 stderr=PIPE,
108 stderr=PIPE,
109 env=gitenv,
109 env=gitenv,
110 )
110 )
111 if os.path.isdir(self.path):
111 if os.path.isdir(self.path):
112 opts['cwd'] = self.path
112 opts['cwd'] = self.path
113 p = Popen(cmd, **opts)
113 p = Popen(cmd, **opts)
114 except OSError, err:
114 except OSError, err:
115 raise RepositoryError("Couldn't run git command (%s).\n"
115 raise RepositoryError("Couldn't run git command (%s).\n"
116 "Original error was:%s" % (cmd, err))
116 "Original error was:%s" % (cmd, err))
117 so, se = p.communicate()
117 so, se = p.communicate()
118 if not se.startswith("fatal: bad default revision 'HEAD'") and \
118 if not se.startswith("fatal: bad default revision 'HEAD'") and \
119 p.returncode != 0:
119 p.returncode != 0:
120 raise RepositoryError("Couldn't run git command (%s).\n"
120 raise RepositoryError("Couldn't run git command (%s).\n"
121 "stderr:\n%s" % (cmd, se))
121 "stderr:\n%s" % (cmd, se))
122 return so, se
122 return so, se
123
123
124 def _check_url(self, url):
124 def _check_url(self, url):
125 """
125 """
126 Functon will check given url and try to verify if it's a valid
126 Functon will check given url and try to verify if it's a valid
127 link. Sometimes it may happened that mercurial will issue basic
127 link. Sometimes it may happened that mercurial will issue basic
128 auth request that can cause whole API to hang when used from python
128 auth request that can cause whole API to hang when used from python
129 or other external calls.
129 or other external calls.
130
130
131 On failures it'll raise urllib2.HTTPError
131 On failures it'll raise urllib2.HTTPError
132 """
132 """
133
133
134 #TODO: implement this
134 #TODO: implement this
135 pass
135 pass
136
136
137 def _get_repo(self, create, src_url=None, update_after_clone=False,
137 def _get_repo(self, create, src_url=None, update_after_clone=False,
138 bare=False):
138 bare=False):
139 if create and os.path.exists(self.path):
139 if create and os.path.exists(self.path):
140 raise RepositoryError("Location already exist")
140 raise RepositoryError("Location already exist")
141 if src_url and not create:
141 if src_url and not create:
142 raise RepositoryError("Create should be set to True if src_url is "
142 raise RepositoryError("Create should be set to True if src_url is "
143 "given (clone operation creates repository)")
143 "given (clone operation creates repository)")
144 try:
144 try:
145 if create and src_url:
145 if create and src_url:
146 self._check_url(src_url)
146 self._check_url(src_url)
147 self.clone(src_url, update_after_clone, bare)
147 self.clone(src_url, update_after_clone, bare)
148 return Repo(self.path)
148 return Repo(self.path)
149 elif create:
149 elif create:
150 os.mkdir(self.path)
150 os.mkdir(self.path)
151 if bare:
151 if bare:
152 return Repo.init_bare(self.path)
152 return Repo.init_bare(self.path)
153 else:
153 else:
154 return Repo.init(self.path)
154 return Repo.init(self.path)
155 else:
155 else:
156 return Repo(self.path)
156 return Repo(self.path)
157 except (NotGitRepository, OSError), err:
157 except (NotGitRepository, OSError), err:
158 raise RepositoryError(err)
158 raise RepositoryError(err)
159
159
160 def _get_all_revisions(self):
160 def _get_all_revisions(self):
161 cmd = 'rev-list --all --date-order'
161 cmd = 'rev-list --all --date-order'
162 try:
162 try:
163 so, se = self.run_git_command(cmd)
163 so, se = self.run_git_command(cmd)
164 except RepositoryError:
164 except RepositoryError:
165 # Can be raised for empty repositories
165 # Can be raised for empty repositories
166 return []
166 return []
167 revisions = so.splitlines()
167 revisions = so.splitlines()
168 revisions.reverse()
168 revisions.reverse()
169 return revisions
169 return revisions
170
170
171 def _get_revision(self, revision):
171 def _get_revision(self, revision):
172 """
172 """
173 For git backend we always return integer here. This way we ensure
173 For git backend we always return integer here. This way we ensure
174 that changset's revision attribute would become integer.
174 that changset's revision attribute would become integer.
175 """
175 """
176 pattern = re.compile(r'^[[0-9a-fA-F]{12}|[0-9a-fA-F]{40}]$')
176 pattern = re.compile(r'^[[0-9a-fA-F]{12}|[0-9a-fA-F]{40}]$')
177 is_bstr = lambda o: isinstance(o, (str, unicode))
177 is_bstr = lambda o: isinstance(o, (str, unicode))
178 is_null = lambda o: len(o) == revision.count('0')
178 is_null = lambda o: len(o) == revision.count('0')
179
179
180 if len(self.revisions) == 0:
180 if len(self.revisions) == 0:
181 raise EmptyRepositoryError("There are no changesets yet")
181 raise EmptyRepositoryError("There are no changesets yet")
182
182
183 if revision in (None, '', 'tip', 'HEAD', 'head', -1):
183 if revision in (None, '', 'tip', 'HEAD', 'head', -1):
184 revision = self.revisions[-1]
184 revision = self.revisions[-1]
185
185
186 if ((is_bstr(revision) and revision.isdigit() and len(revision) < 12)
186 if ((is_bstr(revision) and revision.isdigit() and len(revision) < 12)
187 or isinstance(revision, int) or is_null(revision)):
187 or isinstance(revision, int) or is_null(revision)):
188 try:
188 try:
189 revision = self.revisions[int(revision)]
189 revision = self.revisions[int(revision)]
190 except:
190 except:
191 raise ChangesetDoesNotExistError("Revision %r does not exist "
191 raise ChangesetDoesNotExistError("Revision %r does not exist "
192 "for this repository %s" % (revision, self))
192 "for this repository %s" % (revision, self))
193
193
194 elif is_bstr(revision):
194 elif is_bstr(revision):
195 _ref_revision = self._parsed_refs.get(revision)
195 _ref_revision = self._parsed_refs.get(revision)
196 if _ref_revision: # and _ref_revision[1] in ['H', 'RH', 'T']:
196 if _ref_revision: # and _ref_revision[1] in ['H', 'RH', 'T']:
197 return _ref_revision[0]
197 return _ref_revision[0]
198
198
199 elif not pattern.match(revision) or revision not in self.revisions:
199 elif not pattern.match(revision) or revision not in self.revisions:
200 raise ChangesetDoesNotExistError("Revision %r does not exist "
200 raise ChangesetDoesNotExistError("Revision %r does not exist "
201 "for this repository %s" % (revision, self))
201 "for this repository %s" % (revision, self))
202
202
203 # Ensure we return full id
203 # Ensure we return full id
204 if not pattern.match(str(revision)):
204 if not pattern.match(str(revision)):
205 raise ChangesetDoesNotExistError("Given revision %r not recognized"
205 raise ChangesetDoesNotExistError("Given revision %r not recognized"
206 % revision)
206 % revision)
207 return revision
207 return revision
208
208
209 def _get_archives(self, archive_name='tip'):
209 def _get_archives(self, archive_name='tip'):
210
210
211 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
211 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
212 yield {"type": i[0], "extension": i[1], "node": archive_name}
212 yield {"type": i[0], "extension": i[1], "node": archive_name}
213
213
214 def _get_url(self, url):
214 def _get_url(self, url):
215 """
215 """
216 Returns normalized url. If schema is not given, would fall to
216 Returns normalized url. If schema is not given, would fall to
217 filesystem (``file:///``) schema.
217 filesystem (``file:///``) schema.
218 """
218 """
219 url = str(url)
219 url = str(url)
220 if url != 'default' and not '://' in url:
220 if url != 'default' and not '://' in url:
221 url = ':///'.join(('file', url))
221 url = ':///'.join(('file', url))
222 return url
222 return url
223
223
224 @LazyProperty
224 @LazyProperty
225 def name(self):
225 def name(self):
226 return os.path.basename(self.path)
226 return os.path.basename(self.path)
227
227
228 @LazyProperty
228 @LazyProperty
229 def last_change(self):
229 def last_change(self):
230 """
230 """
231 Returns last change made on this repository as datetime object
231 Returns last change made on this repository as datetime object
232 """
232 """
233 return date_fromtimestamp(self._get_mtime(), makedate()[1])
233 return date_fromtimestamp(self._get_mtime(), makedate()[1])
234
234
235 def _get_mtime(self):
235 def _get_mtime(self):
236 try:
236 try:
237 return time.mktime(self.get_changeset().date.timetuple())
237 return time.mktime(self.get_changeset().date.timetuple())
238 except RepositoryError:
238 except RepositoryError:
239 idx_loc = '' if self.bare else '.git'
239 idx_loc = '' if self.bare else '.git'
240 # fallback to filesystem
240 # fallback to filesystem
241 in_path = os.path.join(self.path, idx_loc, "index")
241 in_path = os.path.join(self.path, idx_loc, "index")
242 he_path = os.path.join(self.path, idx_loc, "HEAD")
242 he_path = os.path.join(self.path, idx_loc, "HEAD")
243 if os.path.exists(in_path):
243 if os.path.exists(in_path):
244 return os.stat(in_path).st_mtime
244 return os.stat(in_path).st_mtime
245 else:
245 else:
246 return os.stat(he_path).st_mtime
246 return os.stat(he_path).st_mtime
247
247
248 @LazyProperty
248 @LazyProperty
249 def description(self):
249 def description(self):
250 idx_loc = '' if self.bare else '.git'
250 idx_loc = '' if self.bare else '.git'
251 undefined_description = u'unknown'
251 undefined_description = u'unknown'
252 description_path = os.path.join(self.path, idx_loc, 'description')
252 description_path = os.path.join(self.path, idx_loc, 'description')
253 if os.path.isfile(description_path):
253 if os.path.isfile(description_path):
254 return safe_unicode(open(description_path).read())
254 return safe_unicode(open(description_path).read())
255 else:
255 else:
256 return undefined_description
256 return undefined_description
257
257
258 @LazyProperty
258 @LazyProperty
259 def contact(self):
259 def contact(self):
260 undefined_contact = u'Unknown'
260 undefined_contact = u'Unknown'
261 return undefined_contact
261 return undefined_contact
262
262
263 @property
263 @property
264 def branches(self):
264 def branches(self):
265 if not self.revisions:
265 if not self.revisions:
266 return {}
266 return {}
267 refs = self._repo.refs.as_dict()
267 refs = self._repo.refs.as_dict()
268 sortkey = lambda ctx: ctx[0]
268 sortkey = lambda ctx: ctx[0]
269 _branches = [('/'.join(ref.split('/')[2:]), head)
269 _branches = [('/'.join(ref.split('/')[2:]), head)
270 for ref, head in refs.items()
270 for ref, head in refs.items()
271 if ref.startswith('refs/heads/') and not ref.endswith('/HEAD')]
271 if ref.startswith('refs/heads/') and not ref.endswith('/HEAD')]
272 return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
272 return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
273
273
274 @LazyProperty
274 @LazyProperty
275 def tags(self):
275 def tags(self):
276 return self._get_tags()
276 return self._get_tags()
277
277
278 def _get_tags(self):
278 def _get_tags(self):
279 if not self.revisions:
279 if not self.revisions:
280 return {}
280 return {}
281 sortkey = lambda ctx: ctx[0]
281 sortkey = lambda ctx: ctx[0]
282 _tags = [('/'.join(ref.split('/')[2:]), head) for ref, head in
282 _tags = [('/'.join(ref.split('/')[2:]), head) for ref, head in
283 self._repo.get_refs().items() if ref.startswith('refs/tags/')]
283 self._repo.get_refs().items() if ref.startswith('refs/tags/')]
284 return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
284 return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
285
285
286 def tag(self, name, user, revision=None, message=None, date=None,
286 def tag(self, name, user, revision=None, message=None, date=None,
287 **kwargs):
287 **kwargs):
288 """
288 """
289 Creates and returns a tag for the given ``revision``.
289 Creates and returns a tag for the given ``revision``.
290
290
291 :param name: name for new tag
291 :param name: name for new tag
292 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
292 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
293 :param revision: changeset id for which new tag would be created
293 :param revision: changeset id for which new tag would be created
294 :param message: message of the tag's commit
294 :param message: message of the tag's commit
295 :param date: date of tag's commit
295 :param date: date of tag's commit
296
296
297 :raises TagAlreadyExistError: if tag with same name already exists
297 :raises TagAlreadyExistError: if tag with same name already exists
298 """
298 """
299 if name in self.tags:
299 if name in self.tags:
300 raise TagAlreadyExistError("Tag %s already exists" % name)
300 raise TagAlreadyExistError("Tag %s already exists" % name)
301 changeset = self.get_changeset(revision)
301 changeset = self.get_changeset(revision)
302 message = message or "Added tag %s for commit %s" % (name,
302 message = message or "Added tag %s for commit %s" % (name,
303 changeset.raw_id)
303 changeset.raw_id)
304 self._repo.refs["refs/tags/%s" % name] = changeset._commit.id
304 self._repo.refs["refs/tags/%s" % name] = changeset._commit.id
305
305
306 self.tags = self._get_tags()
306 self.tags = self._get_tags()
307 return changeset
307 return changeset
308
308
309 def remove_tag(self, name, user, message=None, date=None):
309 def remove_tag(self, name, user, message=None, date=None):
310 """
310 """
311 Removes tag with the given ``name``.
311 Removes tag with the given ``name``.
312
312
313 :param name: name of the tag to be removed
313 :param name: name of the tag to be removed
314 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
314 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
315 :param message: message of the tag's removal commit
315 :param message: message of the tag's removal commit
316 :param date: date of tag's removal commit
316 :param date: date of tag's removal commit
317
317
318 :raises TagDoesNotExistError: if tag with given name does not exists
318 :raises TagDoesNotExistError: if tag with given name does not exists
319 """
319 """
320 if name not in self.tags:
320 if name not in self.tags:
321 raise TagDoesNotExistError("Tag %s does not exist" % name)
321 raise TagDoesNotExistError("Tag %s does not exist" % name)
322 tagpath = posixpath.join(self._repo.refs.path, 'refs', 'tags', name)
322 tagpath = posixpath.join(self._repo.refs.path, 'refs', 'tags', name)
323 try:
323 try:
324 os.remove(tagpath)
324 os.remove(tagpath)
325 self.tags = self._get_tags()
325 self.tags = self._get_tags()
326 except OSError, e:
326 except OSError, e:
327 raise RepositoryError(e.strerror)
327 raise RepositoryError(e.strerror)
328
328
329 @LazyProperty
329 @LazyProperty
330 def _parsed_refs(self):
330 def _parsed_refs(self):
331 refs = self._repo.get_refs()
331 refs = self._repo.get_refs()
332 keys = [('refs/heads/', 'H'),
332 keys = [('refs/heads/', 'H'),
333 ('refs/remotes/origin/', 'RH'),
333 ('refs/remotes/origin/', 'RH'),
334 ('refs/tags/', 'T')]
334 ('refs/tags/', 'T')]
335 _refs = {}
335 _refs = {}
336 for ref, sha in refs.iteritems():
336 for ref, sha in refs.iteritems():
337 for k, type_ in keys:
337 for k, type_ in keys:
338 if ref.startswith(k):
338 if ref.startswith(k):
339 _key = ref[len(k):]
339 _key = ref[len(k):]
340 _refs[_key] = [sha, type_]
340 _refs[_key] = [sha, type_]
341 break
341 break
342 return _refs
342 return _refs
343
343
344 def _heads(self, reverse=False):
344 def _heads(self, reverse=False):
345 refs = self._repo.get_refs()
345 refs = self._repo.get_refs()
346 heads = {}
346 heads = {}
347
347
348 for key, val in refs.items():
348 for key, val in refs.items():
349 for ref_key in ['refs/heads/', 'refs/remotes/origin/']:
349 for ref_key in ['refs/heads/', 'refs/remotes/origin/']:
350 if key.startswith(ref_key):
350 if key.startswith(ref_key):
351 n = key[len(ref_key):]
351 n = key[len(ref_key):]
352 if n not in ['HEAD']:
352 if n not in ['HEAD']:
353 heads[n] = val
353 heads[n] = val
354
354
355 return heads if reverse else dict((y, x) for x, y in heads.iteritems())
355 return heads if reverse else dict((y, x) for x, y in heads.iteritems())
356
356
357 def get_changeset(self, revision=None):
357 def get_changeset(self, revision=None):
358 """
358 """
359 Returns ``GitChangeset`` object representing commit from git repository
359 Returns ``GitChangeset`` object representing commit from git repository
360 at the given revision or head (most recent commit) if None given.
360 at the given revision or head (most recent commit) if None given.
361 """
361 """
362 if isinstance(revision, GitChangeset):
362 if isinstance(revision, GitChangeset):
363 return revision
363 return revision
364 revision = self._get_revision(revision)
364 revision = self._get_revision(revision)
365 changeset = GitChangeset(repository=self, revision=revision)
365 changeset = GitChangeset(repository=self, revision=revision)
366 return changeset
366 return changeset
367
367
368 def get_changesets(self, start=None, end=None, start_date=None,
368 def get_changesets(self, start=None, end=None, start_date=None,
369 end_date=None, branch_name=None, reverse=False):
369 end_date=None, branch_name=None, reverse=False):
370 """
370 """
371 Returns iterator of ``GitChangeset`` objects from start to end (both
371 Returns iterator of ``GitChangeset`` objects from start to end (both
372 are inclusive), in ascending date order (unless ``reverse`` is set).
372 are inclusive), in ascending date order (unless ``reverse`` is set).
373
373
374 :param start: changeset ID, as str; first returned changeset
374 :param start: changeset ID, as str; first returned changeset
375 :param end: changeset ID, as str; last returned changeset
375 :param end: changeset ID, as str; last returned changeset
376 :param start_date: if specified, changesets with commit date less than
376 :param start_date: if specified, changesets with commit date less than
377 ``start_date`` would be filtered out from returned set
377 ``start_date`` would be filtered out from returned set
378 :param end_date: if specified, changesets with commit date greater than
378 :param end_date: if specified, changesets with commit date greater than
379 ``end_date`` would be filtered out from returned set
379 ``end_date`` would be filtered out from returned set
380 :param branch_name: if specified, changesets not reachable from given
380 :param branch_name: if specified, changesets not reachable from given
381 branch would be filtered out from returned set
381 branch would be filtered out from returned set
382 :param reverse: if ``True``, returned generator would be reversed
382 :param reverse: if ``True``, returned generator would be reversed
383 (meaning that returned changesets would have descending date order)
383 (meaning that returned changesets would have descending date order)
384
384
385 :raise BranchDoesNotExistError: If given ``branch_name`` does not
385 :raise BranchDoesNotExistError: If given ``branch_name`` does not
386 exist.
386 exist.
387 :raise ChangesetDoesNotExistError: If changeset for given ``start`` or
387 :raise ChangesetDoesNotExistError: If changeset for given ``start`` or
388 ``end`` could not be found.
388 ``end`` could not be found.
389
389
390 """
390 """
391 if branch_name and branch_name not in self.branches:
391 if branch_name and branch_name not in self.branches:
392 raise BranchDoesNotExistError("Branch '%s' not found" \
392 raise BranchDoesNotExistError("Branch '%s' not found" \
393 % branch_name)
393 % branch_name)
394 # %H at format means (full) commit hash, initial hashes are retrieved
394 # %H at format means (full) commit hash, initial hashes are retrieved
395 # in ascending date order
395 # in ascending date order
396 cmd_template = 'log --date-order --reverse --pretty=format:"%H"'
396 cmd_template = 'log --date-order --reverse --pretty=format:"%H"'
397 cmd_params = {}
397 cmd_params = {}
398 if start_date:
398 if start_date:
399 cmd_template += ' --since "$since"'
399 cmd_template += ' --since "$since"'
400 cmd_params['since'] = start_date.strftime('%m/%d/%y %H:%M:%S')
400 cmd_params['since'] = start_date.strftime('%m/%d/%y %H:%M:%S')
401 if end_date:
401 if end_date:
402 cmd_template += ' --until "$until"'
402 cmd_template += ' --until "$until"'
403 cmd_params['until'] = end_date.strftime('%m/%d/%y %H:%M:%S')
403 cmd_params['until'] = end_date.strftime('%m/%d/%y %H:%M:%S')
404 if branch_name:
404 if branch_name:
405 cmd_template += ' $branch_name'
405 cmd_template += ' $branch_name'
406 cmd_params['branch_name'] = branch_name
406 cmd_params['branch_name'] = branch_name
407 else:
407 else:
408 cmd_template += ' --all'
408 cmd_template += ' --all'
409
409
410 cmd = Template(cmd_template).safe_substitute(**cmd_params)
410 cmd = Template(cmd_template).safe_substitute(**cmd_params)
411 revs = self.run_git_command(cmd)[0].splitlines()
411 revs = self.run_git_command(cmd)[0].splitlines()
412 start_pos = 0
412 start_pos = 0
413 end_pos = len(revs)
413 end_pos = len(revs)
414 if start:
414 if start:
415 _start = self._get_revision(start)
415 _start = self._get_revision(start)
416 try:
416 try:
417 start_pos = revs.index(_start)
417 start_pos = revs.index(_start)
418 except ValueError:
418 except ValueError:
419 pass
419 pass
420
420
421 if end is not None:
421 if end is not None:
422 _end = self._get_revision(end)
422 _end = self._get_revision(end)
423 try:
423 try:
424 end_pos = revs.index(_end)
424 end_pos = revs.index(_end)
425 except ValueError:
425 except ValueError:
426 pass
426 pass
427
427
428 if None not in [start, end] and start_pos > end_pos:
428 if None not in [start, end] and start_pos > end_pos:
429 raise RepositoryError('start cannot be after end')
429 raise RepositoryError('start cannot be after end')
430
430
431 if end_pos is not None:
431 if end_pos is not None:
432 end_pos += 1
432 end_pos += 1
433
433
434 revs = revs[start_pos:end_pos]
434 revs = revs[start_pos:end_pos]
435 if reverse:
435 if reverse:
436 revs = reversed(revs)
436 revs = reversed(revs)
437 for rev in revs:
437 for rev in revs:
438 yield self.get_changeset(rev)
438 yield self.get_changeset(rev)
439
439
440 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
440 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
441 context=3):
441 context=3):
442 """
442 """
443 Returns (git like) *diff*, as plain text. Shows changes introduced by
443 Returns (git like) *diff*, as plain text. Shows changes introduced by
444 ``rev2`` since ``rev1``.
444 ``rev2`` since ``rev1``.
445
445
446 :param rev1: Entry point from which diff is shown. Can be
446 :param rev1: Entry point from which diff is shown. Can be
447 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
447 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
448 the changes since empty state of the repository until ``rev2``
448 the changes since empty state of the repository until ``rev2``
449 :param rev2: Until which revision changes should be shown.
449 :param rev2: Until which revision changes should be shown.
450 :param ignore_whitespace: If set to ``True``, would not show whitespace
450 :param ignore_whitespace: If set to ``True``, would not show whitespace
451 changes. Defaults to ``False``.
451 changes. Defaults to ``False``.
452 :param context: How many lines before/after changed lines should be
452 :param context: How many lines before/after changed lines should be
453 shown. Defaults to ``3``.
453 shown. Defaults to ``3``.
454 """
454 """
455 flags = ['-U%s' % context]
455 flags = ['-U%s' % context]
456 if ignore_whitespace:
456 if ignore_whitespace:
457 flags.append('-w')
457 flags.append('-w')
458
458
459 if hasattr(rev1, 'raw_id'):
459 if hasattr(rev1, 'raw_id'):
460 rev1 = getattr(rev1, 'raw_id')
460 rev1 = getattr(rev1, 'raw_id')
461
461
462 if hasattr(rev2, 'raw_id'):
462 if hasattr(rev2, 'raw_id'):
463 rev2 = getattr(rev2, 'raw_id')
463 rev2 = getattr(rev2, 'raw_id')
464
464
465 if rev1 == self.EMPTY_CHANGESET:
465 if rev1 == self.EMPTY_CHANGESET:
466 rev2 = self.get_changeset(rev2).raw_id
466 rev2 = self.get_changeset(rev2).raw_id
467 cmd = ' '.join(['show'] + flags + [rev2])
467 cmd = ' '.join(['show'] + flags + [rev2])
468 else:
468 else:
469 rev1 = self.get_changeset(rev1).raw_id
469 rev1 = self.get_changeset(rev1).raw_id
470 rev2 = self.get_changeset(rev2).raw_id
470 rev2 = self.get_changeset(rev2).raw_id
471 cmd = ' '.join(['diff'] + flags + [rev1, rev2])
471 cmd = ' '.join(['diff'] + flags + [rev1, rev2])
472
472
473 if path:
473 if path:
474 cmd += ' -- "%s"' % path
474 cmd += ' -- "%s"' % path
475 stdout, stderr = self.run_git_command(cmd)
475 stdout, stderr = self.run_git_command(cmd)
476 # If we used 'show' command, strip first few lines (until actual diff
476 # If we used 'show' command, strip first few lines (until actual diff
477 # starts)
477 # starts)
478 if rev1 == self.EMPTY_CHANGESET:
478 if rev1 == self.EMPTY_CHANGESET:
479 lines = stdout.splitlines()
479 lines = stdout.splitlines()
480 x = 0
480 x = 0
481 for line in lines:
481 for line in lines:
482 if line.startswith('diff'):
482 if line.startswith('diff'):
483 break
483 break
484 x += 1
484 x += 1
485 # Append new line just like 'diff' command do
485 # Append new line just like 'diff' command do
486 stdout = '\n'.join(lines[x:]) + '\n'
486 stdout = '\n'.join(lines[x:]) + '\n'
487 return stdout
487 return stdout
488
488
489 @LazyProperty
489 @LazyProperty
490 def in_memory_changeset(self):
490 def in_memory_changeset(self):
491 """
491 """
492 Returns ``GitInMemoryChangeset`` object for this repository.
492 Returns ``GitInMemoryChangeset`` object for this repository.
493 """
493 """
494 return GitInMemoryChangeset(self)
494 return GitInMemoryChangeset(self)
495
495
496 def clone(self, url, update_after_clone=True, bare=False):
496 def clone(self, url, update_after_clone=True, bare=False):
497 """
497 """
498 Tries to clone changes from external location.
498 Tries to clone changes from external location.
499
499
500 :param update_after_clone: If set to ``False``, git won't checkout
500 :param update_after_clone: If set to ``False``, git won't checkout
501 working directory
501 working directory
502 :param bare: If set to ``True``, repository would be cloned into
502 :param bare: If set to ``True``, repository would be cloned into
503 *bare* git repository (no working directory at all).
503 *bare* git repository (no working directory at all).
504 """
504 """
505 url = self._get_url(url)
505 url = self._get_url(url)
506 cmd = ['clone']
506 cmd = ['clone']
507 if bare:
507 if bare:
508 cmd.append('--bare')
508 cmd.append('--bare')
509 elif not update_after_clone:
509 elif not update_after_clone:
510 cmd.append('--no-checkout')
510 cmd.append('--no-checkout')
511 cmd += ['--', '"%s"' % url, '"%s"' % self.path]
511 cmd += ['--', '"%s"' % url, '"%s"' % self.path]
512 cmd = ' '.join(cmd)
512 cmd = ' '.join(cmd)
513 # If error occurs run_git_command raises RepositoryError already
513 # If error occurs run_git_command raises RepositoryError already
514 self.run_git_command(cmd)
514 self.run_git_command(cmd)
515
515
516 def pull(self, url):
516 def pull(self, url):
517 """
517 """
518 Tries to pull changes from external location.
518 Tries to pull changes from external location.
519 """
519 """
520 url = self._get_url(url)
520 url = self._get_url(url)
521 cmd = ['pull']
521 cmd = ['pull']
522 cmd.append("--ff-only")
522 cmd.append("--ff-only")
523 cmd.append(url)
523 cmd.append(url)
524 cmd = ' '.join(cmd)
524 cmd = ' '.join(cmd)
525 # If error occurs run_git_command raises RepositoryError already
525 # If error occurs run_git_command raises RepositoryError already
526 self.run_git_command(cmd)
526 self.run_git_command(cmd)
527
527
528 def fetch(self, url):
528 def fetch(self, url):
529 """
529 """
530 Tries to pull changes from external location.
530 Tries to pull changes from external location.
531 """
531 """
532 url = self._get_url(url)
532 url = self._get_url(url)
533 cmd = ['fetch']
533 cmd = ['fetch']
534 cmd.append(url)
534 cmd.append(url)
535 cmd = ' '.join(cmd)
535 cmd = ' '.join(cmd)
536 # If error occurs run_git_command raises RepositoryError already
536 # If error occurs run_git_command raises RepositoryError already
537 self.run_git_command(cmd)
537 self.run_git_command(cmd)
538
538
539 @LazyProperty
539 @LazyProperty
540 def workdir(self):
540 def workdir(self):
541 """
541 """
542 Returns ``Workdir`` instance for this repository.
542 Returns ``Workdir`` instance for this repository.
543 """
543 """
544 return GitWorkdir(self)
544 return GitWorkdir(self)
545
545
546 def get_config_value(self, section, name, config_file=None):
546 def get_config_value(self, section, name, config_file=None):
547 """
547 """
548 Returns configuration value for a given [``section``] and ``name``.
548 Returns configuration value for a given [``section``] and ``name``.
549
549
550 :param section: Section we want to retrieve value from
550 :param section: Section we want to retrieve value from
551 :param name: Name of configuration we want to retrieve
551 :param name: Name of configuration we want to retrieve
552 :param config_file: A path to file which should be used to retrieve
552 :param config_file: A path to file which should be used to retrieve
553 configuration from (might also be a list of file paths)
553 configuration from (might also be a list of file paths)
554 """
554 """
555 if config_file is None:
555 if config_file is None:
556 config_file = []
556 config_file = []
557 elif isinstance(config_file, basestring):
557 elif isinstance(config_file, basestring):
558 config_file = [config_file]
558 config_file = [config_file]
559
559
560 def gen_configs():
560 def gen_configs():
561 for path in config_file + self._config_files:
561 for path in config_file + self._config_files:
562 try:
562 try:
563 yield ConfigFile.from_path(path)
563 yield ConfigFile.from_path(path)
564 except (IOError, OSError, ValueError):
564 except (IOError, OSError, ValueError):
565 continue
565 continue
566
566
567 for config in gen_configs():
567 for config in gen_configs():
568 try:
568 try:
569 return config.get(section, name)
569 return config.get(section, name)
570 except KeyError:
570 except KeyError:
571 continue
571 continue
572 return None
572 return None
573
573
574 def get_user_name(self, config_file=None):
574 def get_user_name(self, config_file=None):
575 """
575 """
576 Returns user's name from global configuration file.
576 Returns user's name from global configuration file.
577
577
578 :param config_file: A path to file which should be used to retrieve
578 :param config_file: A path to file which should be used to retrieve
579 configuration from (might also be a list of file paths)
579 configuration from (might also be a list of file paths)
580 """
580 """
581 return self.get_config_value('user', 'name', config_file)
581 return self.get_config_value('user', 'name', config_file)
582
582
583 def get_user_email(self, config_file=None):
583 def get_user_email(self, config_file=None):
584 """
584 """
585 Returns user's email from global configuration file.
585 Returns user's email from global configuration file.
586
586
587 :param config_file: A path to file which should be used to retrieve
587 :param config_file: A path to file which should be used to retrieve
588 configuration from (might also be a list of file paths)
588 configuration from (might also be a list of file paths)
589 """
589 """
590 return self.get_config_value('user', 'email', config_file)
590 return self.get_config_value('user', 'email', config_file)
@@ -1,207 +1,207 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Edit user')} ${c.user.username} - ${c.rhodecode_name}
5 ${_('Edit user')} ${c.user.username} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
10 &raquo;
11 ${h.link_to(_('Users'),h.url('users'))}
11 ${h.link_to(_('Users'),h.url('users'))}
12 &raquo;
12 &raquo;
13 ${_('edit')} "${c.user.username}"
13 ${_('edit')} "${c.user.username}"
14 </%def>
14 </%def>
15
15
16 <%def name="page_nav()">
16 <%def name="page_nav()">
17 ${self.menu('admin')}
17 ${self.menu('admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box box-left">
21 <div class="box box-left">
22 <!-- box / title -->
22 <!-- box / title -->
23 <div class="title">
23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 </div>
25 </div>
26 <!-- end box / title -->
26 <!-- end box / title -->
27 ${h.form(url('update_user', id=c.user.user_id),method='put')}
27 ${h.form(url('update_user', id=c.user.user_id),method='put')}
28 <div class="form">
28 <div class="form">
29 <div class="field">
29 <div class="field">
30 <div class="gravatar_box">
30 <div class="gravatar_box">
31 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
31 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
32 <p>
32 <p>
33 %if c.use_gravatar:
33 %if c.use_gravatar:
34 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
34 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
35 <br/>${_('Using')} ${c.user.email}
35 <br/>${_('Using')} ${c.user.email}
36 %else:
36 %else:
37 <br/>${c.user.email}
37 <br/>${c.user.email}
38 %endif
38 %endif
39 </div>
39 </div>
40 </div>
40 </div>
41 <div class="field">
41 <div class="field">
42 <div class="label">
42 <div class="label">
43 <label>${_('API key')}</label> ${c.user.api_key}
43 <label>${_('API key')}</label> ${c.user.api_key}
44 </div>
44 </div>
45 </div>
45 </div>
46
46
47 <div class="fields">
47 <div class="fields">
48 <div class="field">
48 <div class="field">
49 <div class="label">
49 <div class="label">
50 <label for="username">${_('Username')}:</label>
50 <label for="username">${_('Username')}:</label>
51 </div>
51 </div>
52 <div class="input">
52 <div class="input">
53 ${h.text('username',class_='medium')}
53 ${h.text('username',class_='medium')}
54 </div>
54 </div>
55 </div>
55 </div>
56
56
57 <div class="field">
57 <div class="field">
58 <div class="label">
58 <div class="label">
59 <label for="ldap_dn">${_('LDAP DN')}:</label>
59 <label for="ldap_dn">${_('LDAP DN')}:</label>
60 </div>
60 </div>
61 <div class="input">
61 <div class="input">
62 ${h.text('ldap_dn',class_='medium disabled',readonly="readonly")}
62 ${h.text('ldap_dn',class_='medium disabled',readonly="readonly")}
63 </div>
63 </div>
64 </div>
64 </div>
65
65
66 <div class="field">
66 <div class="field">
67 <div class="label">
67 <div class="label">
68 <label for="new_password">${_('New password')}:</label>
68 <label for="new_password">${_('New password')}:</label>
69 </div>
69 </div>
70 <div class="input">
70 <div class="input">
71 ${h.password('new_password',class_='medium',autocomplete="off")}
71 ${h.password('new_password',class_='medium',autocomplete="off")}
72 </div>
72 </div>
73 </div>
73 </div>
74
74
75 <div class="field">
75 <div class="field">
76 <div class="label">
76 <div class="label">
77 <label for="password_confirmation">${_('New password confirmation')}:</label>
77 <label for="password_confirmation">${_('New password confirmation')}:</label>
78 </div>
78 </div>
79 <div class="input">
79 <div class="input">
80 ${h.password('password_confirmation',class_="medium",autocomplete="off")}
80 ${h.password('password_confirmation',class_="medium",autocomplete="off")}
81 </div>
81 </div>
82 </div>
82 </div>
83
83
84 <div class="field">
84 <div class="field">
85 <div class="label">
85 <div class="label">
86 <label for="name">${_('First Name')}:</label>
86 <label for="name">${_('First Name')}:</label>
87 </div>
87 </div>
88 <div class="input">
88 <div class="input">
89 ${h.text('name',class_='medium')}
89 ${h.text('name',class_='medium')}
90 </div>
90 </div>
91 </div>
91 </div>
92
92
93 <div class="field">
93 <div class="field">
94 <div class="label">
94 <div class="label">
95 <label for="lastname">${_('Last Name')}:</label>
95 <label for="lastname">${_('Last Name')}:</label>
96 </div>
96 </div>
97 <div class="input">
97 <div class="input">
98 ${h.text('lastname',class_='medium')}
98 ${h.text('lastname',class_='medium')}
99 </div>
99 </div>
100 </div>
100 </div>
101
101
102 <div class="field">
102 <div class="field">
103 <div class="label">
103 <div class="label">
104 <label for="email">${_('Email')}:</label>
104 <label for="email">${_('Email')}:</label>
105 </div>
105 </div>
106 <div class="input">
106 <div class="input">
107 ${h.text('email',class_='medium')}
107 ${h.text('email',class_='medium')}
108 </div>
108 </div>
109 </div>
109 </div>
110
110
111 <div class="field">
111 <div class="field">
112 <div class="label label-checkbox">
112 <div class="label label-checkbox">
113 <label for="active">${_('Active')}:</label>
113 <label for="active">${_('Active')}:</label>
114 </div>
114 </div>
115 <div class="checkboxes">
115 <div class="checkboxes">
116 ${h.checkbox('active',value=True)}
116 ${h.checkbox('active',value=True)}
117 </div>
117 </div>
118 </div>
118 </div>
119
119
120 <div class="field">
120 <div class="field">
121 <div class="label label-checkbox">
121 <div class="label label-checkbox">
122 <label for="admin">${_('Admin')}:</label>
122 <label for="admin">${_('Admin')}:</label>
123 </div>
123 </div>
124 <div class="checkboxes">
124 <div class="checkboxes">
125 ${h.checkbox('admin',value=True)}
125 ${h.checkbox('admin',value=True)}
126 </div>
126 </div>
127 </div>
127 </div>
128 <div class="buttons">
128 <div class="buttons">
129 ${h.submit('save',_('Save'),class_="ui-button")}
129 ${h.submit('save',_('Save'),class_="ui-button")}
130 ${h.reset('reset',_('Reset'),class_="ui-button")}
130 ${h.reset('reset',_('Reset'),class_="ui-button")}
131 </div>
131 </div>
132 </div>
132 </div>
133 </div>
133 </div>
134 ${h.end_form()}
134 ${h.end_form()}
135 </div>
135 </div>
136 <div class="box box-right">
136 <div class="box box-right">
137 <!-- box / title -->
137 <!-- box / title -->
138 <div class="title">
138 <div class="title">
139 <h5>${_('Permissions')}</h5>
139 <h5>${_('Permissions')}</h5>
140 </div>
140 </div>
141 ${h.form(url('user_perm', id=c.user.user_id),method='put')}
141 ${h.form(url('user_perm', id=c.user.user_id),method='put')}
142 <div class="form">
142 <div class="form">
143 <!-- fields -->
143 <!-- fields -->
144 <div class="fields">
144 <div class="fields">
145 <div class="field">
145 <div class="field">
146 <div class="label label-checkbox">
146 <div class="label label-checkbox">
147 <label for="create_repo_perm">${_('Create repositories')}:</label>
147 <label for="create_repo_perm">${_('Create repositories')}:</label>
148 </div>
148 </div>
149 <div class="checkboxes">
149 <div class="checkboxes">
150 ${h.checkbox('create_repo_perm',value=True)}
150 ${h.checkbox('create_repo_perm',value=True)}
151 </div>
151 </div>
152 </div>
152 </div>
153 <div class="buttons">
153 <div class="buttons">
154 ${h.submit('save',_('Save'),class_="ui-button")}
154 ${h.submit('save',_('Save'),class_="ui-button")}
155 ${h.reset('reset',_('Reset'),class_="ui-button")}
155 ${h.reset('reset',_('Reset'),class_="ui-button")}
156 </div>
156 </div>
157 </div>
157 </div>
158 </div>
158 </div>
159 ${h.end_form()}
159 ${h.end_form()}
160
160
161 ## permissions overview
161 ## permissions overview
162 <div id="perms" class="table">
162 <div id="perms" class="table">
163 %for section in sorted(c.perm_user.permissions.keys()):
163 %for section in sorted(c.perm_user.permissions.keys()):
164 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
164 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
165
165
166 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
166 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
167 <table id="tbl_list_${section}">
167 <table id="tbl_list_${section}">
168 <thead>
168 <thead>
169 <tr>
169 <tr>
170 <th class="left">${_('Name')}</th>
170 <th class="left">${_('Name')}</th>
171 <th class="left">${_('Permission')}</th>
171 <th class="left">${_('Permission')}</th>
172 </thead>
172 </thead>
173 <tbody>
173 <tbody>
174 %for k in c.perm_user.permissions[section]:
174 %for k in c.perm_user.permissions[section]:
175 <%
175 <%
176 if section != 'global':
176 if section != 'global':
177 section_perm = c.perm_user.permissions[section].get(k)
177 section_perm = c.perm_user.permissions[section].get(k)
178 _perm = section_perm.split('.')[-1]
178 _perm = section_perm.split('.')[-1]
179 else:
179 else:
180 _perm = section_perm = None
180 _perm = section_perm = None
181 %>
181 %>
182 <tr>
182 <tr>
183 <td>
183 <td>
184 %if section == 'repositories':
184 %if section == 'repositories':
185 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
185 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
186 %elif section == 'repositories_groups':
186 %elif section == 'repositories_groups':
187 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
187 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
188 %else:
188 %else:
189 ${k}
189 ${k}
190 %endif
190 %endif
191 </td>
191 </td>
192 <td>
192 <td>
193 %if section == 'global':
193 %if section == 'global':
194 ${h.bool2icon(True)}
194 ${h.bool2icon(True)}
195 %else:
195 %else:
196 <span class="perm_tag ${_perm}">${section_perm}</span>
196 <span class="perm_tag ${_perm}">${section_perm}</span>
197 %endif
197 %endif
198 </td>
198 </td>
199 </tr>
199 </tr>
200 %endfor
200 %endfor
201 </tbody>
201 </tbody>
202 </table>
202 </table>
203 </div>
203 </div>
204 %endfor
204 %endfor
205 </div>
205 </div>
206 </div>
206 </div>
207 </%def>
207 </%def>
@@ -1,159 +1,159 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <!DOCTYPE html>
2 <!DOCTYPE html>
3 <html xmlns="http://www.w3.org/1999/xhtml">
3 <html xmlns="http://www.w3.org/1999/xhtml">
4 <head>
4 <head>
5 <title>${self.title()}</title>
5 <title>${self.title()}</title>
6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
7 <meta name="robots" content="index, nofollow"/>
7 <meta name="robots" content="index, nofollow"/>
8 <link rel="icon" href="${h.url('/images/icons/database_gear.png')}" type="image/png" />
8 <link rel="icon" href="${h.url('/images/icons/database_gear.png')}" type="image/png" />
9
9
10 ## CSS ###
10 ## CSS ###
11 <%def name="css()">
11 <%def name="css()">
12 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen"/>
12 <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen"/>
13 <link rel="stylesheet" type="text/css" href="${h.url('/css/pygments.css')}"/>
13 <link rel="stylesheet" type="text/css" href="${h.url('/css/pygments.css')}"/>
14 ## EXTRA FOR CSS
14 ## EXTRA FOR CSS
15 ${self.css_extra()}
15 ${self.css_extra()}
16 </%def>
16 </%def>
17 <%def name="css_extra()">
17 <%def name="css_extra()">
18 </%def>
18 </%def>
19
19
20 ${self.css()}
20 ${self.css()}
21
21
22 %if c.ga_code:
22 %if c.ga_code:
23 <!-- Analytics -->
23 <!-- Analytics -->
24 <script type="text/javascript">
24 <script type="text/javascript">
25 var _gaq = _gaq || [];
25 var _gaq = _gaq || [];
26 _gaq.push(['_setAccount', '${c.ga_code}']);
26 _gaq.push(['_setAccount', '${c.ga_code}']);
27 _gaq.push(['_trackPageview']);
27 _gaq.push(['_trackPageview']);
28
28
29 (function() {
29 (function() {
30 var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
30 var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
31 ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
31 ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
32 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
32 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
33 })();
33 })();
34 </script>
34 </script>
35 %endif
35 %endif
36
36
37 ## JAVASCRIPT ##
37 ## JAVASCRIPT ##
38 <%def name="js()">
38 <%def name="js()">
39 <script type="text/javascript">
39 <script type="text/javascript">
40 //JS translations map
40 //JS translations map
41 var TRANSLATION_MAP = {
41 var TRANSLATION_MAP = {
42 'add another comment':'${_("add another comment")}',
42 'add another comment':'${_("add another comment")}',
43 'Stop following this repository':"${_('Stop following this repository')}",
43 'Stop following this repository':"${_('Stop following this repository')}",
44 'Start following this repository':"${_('Start following this repository')}",
44 'Start following this repository':"${_('Start following this repository')}",
45 'Group':"${_('Group')}",
45 'Group':"${_('Group')}",
46 'members':"${_('members')}",
46 'members':"${_('members')}",
47 'search truncated': "${_('search truncated')}",
47 'search truncated': "${_('search truncated')}",
48 'no matching files': "${_('no matching files')}"
48 'no matching files': "${_('no matching files')}"
49
49
50 };
50 };
51 var _TM = TRANSLATION_MAP;
51 var _TM = TRANSLATION_MAP;
52 </script>
52 </script>
53 <script type="text/javascript" src="${h.url('/js/yui.2.9.js')}"></script>
53 <script type="text/javascript" src="${h.url('/js/yui.2.9.js')}"></script>
54 <!--[if lt IE 9]>
54 <!--[if lt IE 9]>
55 <script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script>
55 <script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script>
56 <![endif]-->
56 <![endif]-->
57 <script type="text/javascript" src="${h.url('/js/yui.flot.js')}"></script>
57 <script type="text/javascript" src="${h.url('/js/yui.flot.js')}"></script>
58 <script type="text/javascript" src="${h.url('/js/rhodecode.js')}"></script>
58 <script type="text/javascript" src="${h.url('/js/rhodecode.js')}"></script>
59 ## EXTRA FOR JS
59 ## EXTRA FOR JS
60 ${self.js_extra()}
60 ${self.js_extra()}
61
61
62 <script type="text/javascript">
62 <script type="text/javascript">
63 var follow_base_url = "${h.url('toggle_following')}";
63 var follow_base_url = "${h.url('toggle_following')}";
64
64
65 var onSuccessFollow = function(target){
65 var onSuccessFollow = function(target){
66 var f = YUD.get(target.id);
66 var f = YUD.get(target.id);
67 var f_cnt = YUD.get('current_followers_count');
67 var f_cnt = YUD.get('current_followers_count');
68
68
69 if(f.getAttribute('class')=='follow'){
69 if(f.getAttribute('class')=='follow'){
70 f.setAttribute('class','following');
70 f.setAttribute('class','following');
71 f.setAttribute('title',_TM['Stop following this repository']);
71 f.setAttribute('title',_TM['Stop following this repository']);
72
72
73 if(f_cnt){
73 if(f_cnt){
74 var cnt = Number(f_cnt.innerHTML)+1;
74 var cnt = Number(f_cnt.innerHTML)+1;
75 f_cnt.innerHTML = cnt;
75 f_cnt.innerHTML = cnt;
76 }
76 }
77 }
77 }
78 else{
78 else{
79 f.setAttribute('class','follow');
79 f.setAttribute('class','follow');
80 f.setAttribute('title',_TM['Start following this repository']);
80 f.setAttribute('title',_TM['Start following this repository']);
81 if(f_cnt){
81 if(f_cnt){
82 var cnt = Number(f_cnt.innerHTML)+1;
82 var cnt = Number(f_cnt.innerHTML)+1;
83 f_cnt.innerHTML = cnt;
83 f_cnt.innerHTML = cnt;
84 }
84 }
85 }
85 }
86 }
86 }
87
87
88 var toggleFollowingUser = function(target,fallows_user_id,token,user_id){
88 var toggleFollowingUser = function(target,fallows_user_id,token,user_id){
89 args = 'follows_user_id='+fallows_user_id;
89 args = 'follows_user_id='+fallows_user_id;
90 args+= '&amp;auth_token='+token;
90 args+= '&amp;auth_token='+token;
91 if(user_id != undefined){
91 if(user_id != undefined){
92 args+="&amp;user_id="+user_id;
92 args+="&amp;user_id="+user_id;
93 }
93 }
94 YUC.asyncRequest('POST',follow_base_url,{
94 YUC.asyncRequest('POST',follow_base_url,{
95 success:function(o){
95 success:function(o){
96 onSuccessFollow(target);
96 onSuccessFollow(target);
97 }
97 }
98 },args);
98 },args);
99 return false;
99 return false;
100 }
100 }
101
101
102 var toggleFollowingRepo = function(target,fallows_repo_id,token,user_id){
102 var toggleFollowingRepo = function(target,fallows_repo_id,token,user_id){
103
103
104 args = 'follows_repo_id='+fallows_repo_id;
104 args = 'follows_repo_id='+fallows_repo_id;
105 args+= '&amp;auth_token='+token;
105 args+= '&amp;auth_token='+token;
106 if(user_id != undefined){
106 if(user_id != undefined){
107 args+="&amp;user_id="+user_id;
107 args+="&amp;user_id="+user_id;
108 }
108 }
109 YUC.asyncRequest('POST',follow_base_url,{
109 YUC.asyncRequest('POST',follow_base_url,{
110 success:function(o){
110 success:function(o){
111 onSuccessFollow(target);
111 onSuccessFollow(target);
112 }
112 }
113 },args);
113 },args);
114 return false;
114 return false;
115 }
115 }
116 YUE.onDOMReady(function(){
116 YUE.onDOMReady(function(){
117 tooltip_activate();
117 tooltip_activate();
118 show_more_event();
118 show_more_event();
119
119
120 YUE.on('quick_login_link','click',function(e){
120 YUE.on('quick_login_link','click',function(e){
121 // make sure we don't redirect
121 // make sure we don't redirect
122 YUE.preventDefault(e);
122 YUE.preventDefault(e);
123
123
124 if(YUD.hasClass('quick_login_link','enabled')){
124 if(YUD.hasClass('quick_login_link','enabled')){
125 YUD.setStyle('quick_login','display','none');
125 YUD.setStyle('quick_login','display','none');
126 YUD.removeClass('quick_login_link','enabled');
126 YUD.removeClass('quick_login_link','enabled');
127 }
127 }
128 else{
128 else{
129 YUD.setStyle('quick_login','display','');
129 YUD.setStyle('quick_login','display','');
130 YUD.addClass('quick_login_link','enabled');
130 YUD.addClass('quick_login_link','enabled');
131 var usr = YUD.get('username');
131 var usr = YUD.get('username');
132 if(usr){
132 if(usr){
133 usr.focus();
133 usr.focus();
134 }
134 }
135 }
135 }
136 });
136 });
137 })
137 })
138 </script>
138 </script>
139 </%def>
139 </%def>
140 <%def name="js_extra()"></%def>
140 <%def name="js_extra()"></%def>
141 ${self.js()}
141 ${self.js()}
142 <%def name="head_extra()"></%def>
142 <%def name="head_extra()"></%def>
143 ${self.head_extra()}
143 ${self.head_extra()}
144 </head>
144 </head>
145 <body id="body">
145 <body id="body">
146 ## IE hacks
146 ## IE hacks
147 <!--[if IE 7]>
147 <!--[if IE 7]>
148 <script>YUD.addClass(document.body,'ie7')</script>
148 <script>YUD.addClass(document.body,'ie7')</script>
149 <![endif]-->
149 <![endif]-->
150 <!--[if IE 8]>
150 <!--[if IE 8]>
151 <script>YUD.addClass(document.body,'ie8')</script>
151 <script>YUD.addClass(document.body,'ie8')</script>
152 <![endif]-->
152 <![endif]-->
153 <!--[if IE 9]>
153 <!--[if IE 9]>
154 <script>YUD.addClass(document.body,'ie9')</script>
154 <script>YUD.addClass(document.body,'ie9')</script>
155 <![endif]-->
155 <![endif]-->
156
156
157 ${next.body()}
157 ${next.body()}
158 </body>
158 </body>
159 </html>
159 </html>
@@ -1,90 +1,90 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('%s Changesets') % c.repo_name} - r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)} -> r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)} - ${c.rhodecode_name}
5 ${_('%s Changesets') % c.repo_name} - r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)} -> r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(u'Home',h.url('/'))}
9 ${h.link_to(u'Home',h.url('/'))}
10 &raquo;
10 &raquo;
11 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
11 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
12 &raquo;
12 &raquo;
13 ${_('Changesets')} - r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)} -> r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)}
13 ${_('Changesets')} - r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)} -> r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)}
14 </%def>
14 </%def>
15
15
16 <%def name="page_nav()">
16 <%def name="page_nav()">
17 ${self.menu('changelog')}
17 ${self.menu('changelog')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box">
21 <div class="box">
22 <!-- box / title -->
22 <!-- box / title -->
23 <div class="title">
23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 </div>
25 </div>
26 <div class="table">
26 <div class="table">
27 <div id="body" class="diffblock">
27 <div id="body" class="diffblock">
28 <div class="code-header cv">
28 <div class="code-header cv">
29 <h3 class="code-header-title">${_('Compare View')}</h3>
29 <h3 class="code-header-title">${_('Compare View')}</h3>
30 <div>
30 <div>
31 ${_('Changesets')} - r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)} -> r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)}
31 ${_('Changesets')} - r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)} -> r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)}
32 </div>
32 </div>
33 </div>
33 </div>
34 </div>
34 </div>
35 <div id="changeset_compare_view_content">
35 <div id="changeset_compare_view_content">
36 <div class="container">
36 <div class="container">
37 <table class="compare_view_commits noborder">
37 <table class="compare_view_commits noborder">
38 %for cs in c.cs_ranges:
38 %for cs in c.cs_ranges:
39 <tr>
39 <tr>
40 <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),14)}"/></div></td>
40 <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),14)}"/></div></td>
41 <td>${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</td>
41 <td>${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</td>
42 <td><div class="author">${h.person(cs.author)}</div></td>
42 <td><div class="author">${h.person(cs.author)}</div></td>
43 <td><span class="tooltip" title="${h.tooltip(h.age(cs.date))}">${h.fmt_date(cs.date)}</span></td>
43 <td><span class="tooltip" title="${h.tooltip(h.age(cs.date))}">${h.fmt_date(cs.date)}</span></td>
44 <td><div class="message">${h.urlify_commit(cs.message, c.repo_name)}</div></td>
44 <td><div class="message">${h.urlify_commit(cs.message, c.repo_name)}</div></td>
45 </tr>
45 </tr>
46 %endfor
46 %endfor
47 </table>
47 </table>
48 </div>
48 </div>
49 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
49 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
50 <div class="cs_files">
50 <div class="cs_files">
51 %for cs in c.cs_ranges:
51 %for cs in c.cs_ranges:
52 <div class="cur_cs">${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
52 <div class="cur_cs">${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
53 %for change,filenode,diff,cs1,cs2,st in c.changes[cs.raw_id]:
53 %for change,filenode,diff,cs1,cs2,st in c.changes[cs.raw_id]:
54 <div class="cs_${change}">${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID(cs.raw_id,filenode.path)))}</div>
54 <div class="cs_${change}">${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID(cs.raw_id,filenode.path)))}</div>
55 %endfor
55 %endfor
56 %endfor
56 %endfor
57 </div>
57 </div>
58 </div>
58 </div>
59
59
60 </div>
60 </div>
61 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
61 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
62 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
62 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
63 %for cs in c.cs_ranges:
63 %for cs in c.cs_ranges:
64 ##${comment.comment_inline_form(cs)}
64 ##${comment.comment_inline_form(cs)}
65 ## diff block
65 ## diff block
66 <h3 style="padding-top:8px;">${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</h3>
66 <h3 style="padding-top:8px;">${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</h3>
67
67
68 ${diff_block.diff_block(c.changes[cs.raw_id])}
68 ${diff_block.diff_block(c.changes[cs.raw_id])}
69 ##${comment.comments(cs)}
69 ##${comment.comments(cs)}
70
70
71 %endfor
71 %endfor
72 <script type="text/javascript">
72 <script type="text/javascript">
73
73
74 YUE.onDOMReady(function(){
74 YUE.onDOMReady(function(){
75
75
76 YUE.on(YUQ('.diff-menu-activate'),'click',function(e){
76 YUE.on(YUQ('.diff-menu-activate'),'click',function(e){
77 var act = e.currentTarget.nextElementSibling;
77 var act = e.currentTarget.nextElementSibling;
78
78
79 if(YUD.hasClass(act,'active')){
79 if(YUD.hasClass(act,'active')){
80 YUD.removeClass(act,'active');
80 YUD.removeClass(act,'active');
81 YUD.setStyle(act,'display','none');
81 YUD.setStyle(act,'display','none');
82 }else{
82 }else{
83 YUD.addClass(act,'active');
83 YUD.addClass(act,'active');
84 YUD.setStyle(act,'display','');
84 YUD.setStyle(act,'display','');
85 }
85 }
86 });
86 });
87 })
87 })
88 </script>
88 </script>
89 </div>
89 </div>
90 </%def>
90 </%def>
@@ -1,111 +1,111 b''
1 """
1 """
2 Module providing backend independent mixin class. It requires that
2 Module providing backend independent mixin class. It requires that
3 InMemoryChangeset class is working properly at backend class.
3 InMemoryChangeset class is working properly at backend class.
4 """
4 """
5 import os
5 import os
6 from rhodecode.lib import vcs
6 from rhodecode.lib import vcs
7 import time
7 import time
8 import shutil
8 import shutil
9 import datetime
9 import datetime
10 from rhodecode.lib.vcs.utils.compat import unittest
10 from rhodecode.lib.vcs.utils.compat import unittest
11
11
12 from conf import SCM_TESTS, get_new_dir
12 from conf import SCM_TESTS, get_new_dir
13
13
14 from rhodecode.lib.vcs.nodes import FileNode
14 from rhodecode.lib.vcs.nodes import FileNode
15
15
16
16
17 class BackendTestMixin(object):
17 class BackendTestMixin(object):
18 """
18 """
19 This is a backend independent test case class which should be created
19 This is a backend independent test case class which should be created
20 with ``type`` method.
20 with ``type`` method.
21
21
22 It is required to set following attributes at subclass:
22 It is required to set following attributes at subclass:
23
23
24 - ``backend_alias``: alias of used backend (see ``vcs.BACKENDS``)
24 - ``backend_alias``: alias of used backend (see ``vcs.BACKENDS``)
25 - ``repo_path``: path to the repository which would be created for set of
25 - ``repo_path``: path to the repository which would be created for set of
26 tests
26 tests
27 - ``recreate_repo_per_test``: If set to ``False``, repo would NOT be created
27 - ``recreate_repo_per_test``: If set to ``False``, repo would NOT be created
28 before every single test. Defaults to ``True``.
28 before every single test. Defaults to ``True``.
29 """
29 """
30 recreate_repo_per_test = True
30 recreate_repo_per_test = True
31
31
32 @classmethod
32 @classmethod
33 def get_backend(cls):
33 def get_backend(cls):
34 return vcs.get_backend(cls.backend_alias)
34 return vcs.get_backend(cls.backend_alias)
35
35
36 @classmethod
36 @classmethod
37 def _get_commits(cls):
37 def _get_commits(cls):
38 commits = [
38 commits = [
39 {
39 {
40 'message': u'Initial commit',
40 'message': u'Initial commit',
41 'author': u'Joe Doe <joe.doe@example.com>',
41 'author': u'Joe Doe <joe.doe@example.com>',
42 'date': datetime.datetime(2010, 1, 1, 20),
42 'date': datetime.datetime(2010, 1, 1, 20),
43 'added': [
43 'added': [
44 FileNode('foobar', content='Foobar'),
44 FileNode('foobar', content='Foobar'),
45 FileNode('foobar2', content='Foobar II'),
45 FileNode('foobar2', content='Foobar II'),
46 FileNode('foo/bar/baz', content='baz here!'),
46 FileNode('foo/bar/baz', content='baz here!'),
47 ],
47 ],
48 },
48 },
49 {
49 {
50 'message': u'Changes...',
50 'message': u'Changes...',
51 'author': u'Jane Doe <jane.doe@example.com>',
51 'author': u'Jane Doe <jane.doe@example.com>',
52 'date': datetime.datetime(2010, 1, 1, 21),
52 'date': datetime.datetime(2010, 1, 1, 21),
53 'added': [
53 'added': [
54 FileNode('some/new.txt', content='news...'),
54 FileNode('some/new.txt', content='news...'),
55 ],
55 ],
56 'changed': [
56 'changed': [
57 FileNode('foobar', 'Foobar I'),
57 FileNode('foobar', 'Foobar I'),
58 ],
58 ],
59 'removed': [],
59 'removed': [],
60 },
60 },
61 ]
61 ]
62 return commits
62 return commits
63
63
64 @classmethod
64 @classmethod
65 def setUpClass(cls):
65 def setUpClass(cls):
66 Backend = cls.get_backend()
66 Backend = cls.get_backend()
67 cls.backend_class = Backend
67 cls.backend_class = Backend
68 cls.repo_path = get_new_dir(str(time.time()))
68 cls.repo_path = get_new_dir(str(time.time()))
69 cls.repo = Backend(cls.repo_path, create=True)
69 cls.repo = Backend(cls.repo_path, create=True)
70 cls.imc = cls.repo.in_memory_changeset
70 cls.imc = cls.repo.in_memory_changeset
71
71
72 for commit in cls._get_commits():
72 for commit in cls._get_commits():
73 for node in commit.get('added', []):
73 for node in commit.get('added', []):
74 cls.imc.add(FileNode(node.path, content=node.content))
74 cls.imc.add(FileNode(node.path, content=node.content))
75 for node in commit.get('changed', []):
75 for node in commit.get('changed', []):
76 cls.imc.change(FileNode(node.path, content=node.content))
76 cls.imc.change(FileNode(node.path, content=node.content))
77 for node in commit.get('removed', []):
77 for node in commit.get('removed', []):
78 cls.imc.remove(FileNode(node.path))
78 cls.imc.remove(FileNode(node.path))
79
79
80 cls.tip = cls.imc.commit(message=unicode(commit['message']),
80 cls.tip = cls.imc.commit(message=unicode(commit['message']),
81 author=unicode(commit['author']),
81 author=unicode(commit['author']),
82 date=commit['date'])
82 date=commit['date'])
83
83
84 @classmethod
84 @classmethod
85 def tearDownClass(cls):
85 def tearDownClass(cls):
86 if not getattr(cls, 'recreate_repo_per_test', False) and \
86 if not getattr(cls, 'recreate_repo_per_test', False) and \
87 'VCS_REMOVE_TEST_DIRS' in os.environ:
87 'VCS_REMOVE_TEST_DIRS' in os.environ:
88 shutil.rmtree(cls.repo_path)
88 shutil.rmtree(cls.repo_path)
89
89
90 def setUp(self):
90 def setUp(self):
91 if getattr(self, 'recreate_repo_per_test', False):
91 if getattr(self, 'recreate_repo_per_test', False):
92 self.__class__.setUpClass()
92 self.__class__.setUpClass()
93
93
94 def tearDown(self):
94 def tearDown(self):
95 if getattr(self, 'recreate_repo_per_test', False) and \
95 if getattr(self, 'recreate_repo_per_test', False) and \
96 'VCS_REMOVE_TEST_DIRS' in os.environ:
96 'VCS_REMOVE_TEST_DIRS' in os.environ:
97 shutil.rmtree(self.repo_path)
97 shutil.rmtree(self.repo_path)
98
98
99
99
100 # For each backend create test case class
100 # For each backend create test case class
101 for alias in SCM_TESTS:
101 for alias in SCM_TESTS:
102 attrs = {
102 attrs = {
103 'backend_alias': alias,
103 'backend_alias': alias,
104 }
104 }
105 cls_name = ''.join(('%s base backend test' % alias).title().split())
105 cls_name = ''.join(('%s base backend test' % alias).title().split())
106 bases = (BackendTestMixin, unittest.TestCase)
106 bases = (BackendTestMixin, unittest.TestCase)
107 globals()[cls_name] = type(cls_name, bases, attrs)
107 globals()[cls_name] = type(cls_name, bases, attrs)
108
108
109
109
110 if __name__ == '__main__':
110 if __name__ == '__main__':
111 unittest.main()
111 unittest.main()
General Comments 0
You need to be logged in to leave comments. Login now