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