##// END OF EJS Templates
branch selectors: show closed branches too...
Mads Kiilerich -
r4020:218ed589 default
parent child Browse files
Show More
@@ -1,203 +1,207 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.changelog
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 changelog controller for rhodecode
7 7
8 8 :created_on: Apr 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28
29 29 from pylons import request, url, session, tmpl_context as c
30 30 from pylons.controllers.util import redirect
31 31 from pylons.i18n.translation import _
32 32 from webob.exc import HTTPNotFound, HTTPBadRequest
33 33
34 34 import rhodecode.lib.helpers as h
35 35 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
36 36 from rhodecode.lib.base import BaseRepoController, render
37 37 from rhodecode.lib.helpers import RepoPage
38 38 from rhodecode.lib.compat import json
39 39 from rhodecode.lib.graphmod import _colored, _dagwalker
40 40 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError,\
41 41 ChangesetError, NodeDoesNotExistError, EmptyRepositoryError
42 42 from rhodecode.lib.utils2 import safe_int
43 43
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 def _load_changelog_summary():
49 49 p = safe_int(request.GET.get('page'), 1)
50 50 size = safe_int(request.GET.get('size'), 10)
51 51
52 52 def url_generator(**kw):
53 53 return url('changelog_summary_home',
54 54 repo_name=c.rhodecode_db_repo.repo_name, size=size, **kw)
55 55
56 56 collection = c.rhodecode_repo
57 57
58 58 c.repo_changesets = RepoPage(collection, page=p,
59 59 items_per_page=size,
60 60 url=url_generator)
61 61 page_revisions = [x.raw_id for x in list(c.repo_changesets)]
62 62 c.comments = c.rhodecode_db_repo.get_comments(page_revisions)
63 63 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
64 64
65 65
66 66 class ChangelogController(BaseRepoController):
67 67
68 68 def __before__(self):
69 69 super(ChangelogController, self).__before__()
70 70 c.affected_files_cut_off = 60
71 71
72 72 def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
73 73 partial=False):
74 74 """
75 75 Safe way to get changeset if error occur it redirects to changeset with
76 76 proper message. If partial is set then don't do redirect raise Exception
77 77 instead
78 78
79 79 :param rev: revision to fetch
80 80 :param repo: repo instance
81 81 """
82 82
83 83 try:
84 84 return c.rhodecode_repo.get_changeset(rev)
85 85 except EmptyRepositoryError, e:
86 86 if not redirect_after:
87 87 return None
88 88 h.flash(h.literal(_('There are no changesets yet')),
89 89 category='warning')
90 90 redirect(url('changelog_home', repo_name=repo.repo_name))
91 91
92 92 except RepositoryError, e:
93 93 log.error(traceback.format_exc())
94 94 h.flash(str(e), category='warning')
95 95 if not partial:
96 96 redirect(h.url('changelog_home', repo_name=repo.repo_name))
97 97 raise HTTPBadRequest()
98 98
99 99 def _graph(self, repo, revs_int, repo_size, size, p):
100 100 """
101 101 Generates a DAG graph for repo
102 102
103 103 :param repo:
104 104 :param revs_int:
105 105 :param repo_size:
106 106 :param size:
107 107 :param p:
108 108 """
109 109 if not revs_int:
110 110 c.jsdata = json.dumps([])
111 111 return
112 112
113 113 data = []
114 114 revs = revs_int
115 115
116 116 dag = _dagwalker(repo, revs, repo.alias)
117 117 dag = _colored(dag)
118 118 for (id, type, ctx, vtx, edges) in dag:
119 119 data.append(['', vtx, edges])
120 120
121 121 c.jsdata = json.dumps(data)
122 122
123 123 @LoginRequired()
124 124 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
125 125 'repository.admin')
126 126 def index(self, repo_name, revision=None, f_path=None):
127 127 limit = 100
128 128 default = 20
129 129 if request.GET.get('size'):
130 130 c.size = max(min(safe_int(request.GET.get('size')), limit), 1)
131 131 session['changelog_size'] = c.size
132 132 session.save()
133 133 else:
134 134 c.size = int(session.get('changelog_size', default))
135 135 # min size must be 1
136 136 c.size = max(c.size, 1)
137 137 p = safe_int(request.GET.get('page', 1), 1)
138 138 branch_name = request.GET.get('branch', None)
139 139 c.changelog_for_path = f_path
140 140 try:
141 141
142 142 if f_path:
143 143 log.debug('generating changelog for path %s' % f_path)
144 144 # get the history for the file !
145 145 tip_cs = c.rhodecode_repo.get_changeset()
146 146 try:
147 147 collection = tip_cs.get_file_history(f_path)
148 148 except (NodeDoesNotExistError, ChangesetError):
149 149 #this node is not present at tip !
150 150 try:
151 151 cs = self.__get_css_or_redirect(revision, repo_name)
152 152 collection = cs.get_file_history(f_path)
153 153 except RepositoryError, e:
154 154 h.flash(str(e), category='warning')
155 155 redirect(h.url('changelog_home', repo_name=repo_name))
156 156 collection = list(reversed(collection))
157 157 else:
158 158 collection = c.rhodecode_repo.get_changesets(start=0,
159 159 branch_name=branch_name)
160 160 c.total_cs = len(collection)
161 161
162 162 c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
163 163 items_per_page=c.size, branch=branch_name,)
164 164 collection = list(c.pagination)
165 165 page_revisions = [x.raw_id for x in c.pagination]
166 166 c.comments = c.rhodecode_db_repo.get_comments(page_revisions)
167 167 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
168 168 except (EmptyRepositoryError), e:
169 169 h.flash(str(e), category='warning')
170 170 return redirect(url('summary_home', repo_name=c.repo_name))
171 171 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
172 172 log.error(traceback.format_exc())
173 173 h.flash(str(e), category='error')
174 174 return redirect(url('changelog_home', repo_name=c.repo_name))
175 175
176 176 c.branch_name = branch_name
177 177 c.branch_filters = [('', _('All Branches'))] + \
178 178 [(k, k) for k in c.rhodecode_repo.branches.keys()]
179 if c.rhodecode_repo.closed_branches:
180 prefix = _('(closed)') + ' '
181 c.branch_filters += [('-', '-')] + \
182 [(k, prefix + k) for k in c.rhodecode_repo.closed_branches.keys()]
179 183 _revs = []
180 184 if not f_path:
181 185 _revs = [x.revision for x in c.pagination]
182 186 self._graph(c.rhodecode_repo, _revs, c.total_cs, c.size, p)
183 187
184 188 return render('changelog/changelog.html')
185 189
186 190 @LoginRequired()
187 191 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
188 192 'repository.admin')
189 193 def changelog_details(self, cs):
190 194 if request.environ.get('HTTP_X_PARTIAL_XHR'):
191 195 c.cs = c.rhodecode_repo.get_changeset(cs)
192 196 return render('changelog/changelog_details.html')
193 197 raise HTTPNotFound()
194 198
195 199 @LoginRequired()
196 200 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
197 201 'repository.admin')
198 202 def changelog_summary(self, repo_name):
199 203 if request.environ.get('HTTP_X_PARTIAL_XHR'):
200 204 _load_changelog_summary()
201 205
202 206 return render('changelog/changelog_summary_data.html')
203 207 raise HTTPNotFound()
@@ -1,708 +1,712 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 vcs.backends.git.repository
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Git repository 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 urllib
16 16 import urllib2
17 17 import logging
18 18 import posixpath
19 19 import string
20 20
21 21 from dulwich.objects import Tag
22 22 from dulwich.repo import Repo, NotGitRepository
23 23
24 24 from rhodecode.lib.vcs import subprocessio
25 25 from rhodecode.lib.vcs.backends.base import BaseRepository, CollectionGenerator
26 26 from rhodecode.lib.vcs.conf import settings
27 27
28 28 from rhodecode.lib.vcs.exceptions import (
29 29 BranchDoesNotExistError, ChangesetDoesNotExistError, EmptyRepositoryError,
30 30 RepositoryError, TagAlreadyExistError, TagDoesNotExistError
31 31 )
32 32 from rhodecode.lib.vcs.utils import safe_unicode, makedate, date_fromtimestamp
33 33 from rhodecode.lib.vcs.utils.lazy import LazyProperty
34 34 from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
35 35 from rhodecode.lib.vcs.utils.paths import abspath, get_user_home
36 36
37 37 from rhodecode.lib.vcs.utils.hgcompat import (
38 38 hg_url, httpbasicauthhandler, httpdigestauthhandler
39 39 )
40 40
41 41 from .changeset import GitChangeset
42 42 from .config import ConfigFile
43 43 from .inmemory import GitInMemoryChangeset
44 44 from .workdir import GitWorkdir
45 45
46 46 SHA_PATTERN = re.compile(r'^[[0-9a-fA-F]{12}|[0-9a-fA-F]{40}]$')
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50
51 51 class GitRepository(BaseRepository):
52 52 """
53 53 Git repository backend.
54 54 """
55 55 DEFAULT_BRANCH_NAME = 'master'
56 56 scm = 'git'
57 57
58 58 def __init__(self, repo_path, create=False, src_url=None,
59 59 update_after_clone=False, bare=False):
60 60
61 61 self.path = abspath(repo_path)
62 62 repo = self._get_repo(create, src_url, update_after_clone, bare)
63 63 self.bare = repo.bare
64 64
65 65 @property
66 66 def _config_files(self):
67 67 return [
68 68 self.bare and abspath(self.path, 'config')
69 69 or abspath(self.path, '.git', 'config'),
70 70 abspath(get_user_home(), '.gitconfig'),
71 71 ]
72 72
73 73 @property
74 74 def _repo(self):
75 75 return Repo(self.path)
76 76
77 77 @property
78 78 def head(self):
79 79 try:
80 80 return self._repo.head()
81 81 except KeyError:
82 82 return None
83 83
84 84 @property
85 85 def _empty(self):
86 86 """
87 87 Checks if repository is empty ie. without any changesets
88 88 """
89 89
90 90 try:
91 91 self.revisions[0]
92 92 except (KeyError, IndexError):
93 93 return True
94 94 return False
95 95
96 96 @LazyProperty
97 97 def revisions(self):
98 98 """
99 99 Returns list of revisions' ids, in ascending order. Being lazy
100 100 attribute allows external tools to inject shas from cache.
101 101 """
102 102 return self._get_all_revisions()
103 103
104 104 @classmethod
105 105 def _run_git_command(cls, cmd, **opts):
106 106 """
107 107 Runs given ``cmd`` as git command and returns tuple
108 108 (stdout, stderr).
109 109
110 110 :param cmd: git command to be executed
111 111 :param opts: env options to pass into Subprocess command
112 112 """
113 113
114 114 if '_bare' in opts:
115 115 _copts = []
116 116 del opts['_bare']
117 117 else:
118 118 _copts = ['-c', 'core.quotepath=false', ]
119 119 safe_call = False
120 120 if '_safe' in opts:
121 121 #no exc on failure
122 122 del opts['_safe']
123 123 safe_call = True
124 124
125 125 _str_cmd = False
126 126 if isinstance(cmd, basestring):
127 127 cmd = [cmd]
128 128 _str_cmd = True
129 129
130 130 gitenv = os.environ
131 131 # need to clean fix GIT_DIR !
132 132 if 'GIT_DIR' in gitenv:
133 133 del gitenv['GIT_DIR']
134 134 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
135 135
136 136 _git_path = settings.GIT_EXECUTABLE_PATH
137 137 cmd = [_git_path] + _copts + cmd
138 138 if _str_cmd:
139 139 cmd = ' '.join(cmd)
140 140
141 141 try:
142 142 _opts = dict(
143 143 env=gitenv,
144 144 shell=True,
145 145 )
146 146 _opts.update(opts)
147 147 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
148 148 except (EnvironmentError, OSError), err:
149 149 tb_err = ("Couldn't run git command (%s).\n"
150 150 "Original error was:%s\n" % (cmd, err))
151 151 log.error(tb_err)
152 152 if safe_call:
153 153 return '', err
154 154 else:
155 155 raise RepositoryError(tb_err)
156 156
157 157 return ''.join(p.output), ''.join(p.error)
158 158
159 159 def run_git_command(self, cmd):
160 160 opts = {}
161 161 if os.path.isdir(self.path):
162 162 opts['cwd'] = self.path
163 163 return self._run_git_command(cmd, **opts)
164 164
165 165 @classmethod
166 166 def _check_url(cls, url):
167 167 """
168 168 Functon will check given url and try to verify if it's a valid
169 169 link. Sometimes it may happened that mercurial will issue basic
170 170 auth request that can cause whole API to hang when used from python
171 171 or other external calls.
172 172
173 173 On failures it'll raise urllib2.HTTPError
174 174 """
175 175
176 176 # check first if it's not an local url
177 177 if os.path.isdir(url) or url.startswith('file:'):
178 178 return True
179 179
180 180 if('+' in url[:url.find('://')]):
181 181 url = url[url.find('+') + 1:]
182 182
183 183 handlers = []
184 184 test_uri, authinfo = hg_url(url).authinfo()
185 185 if not test_uri.endswith('info/refs'):
186 186 test_uri = test_uri.rstrip('/') + '/info/refs'
187 187 if authinfo:
188 188 #create a password manager
189 189 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
190 190 passmgr.add_password(*authinfo)
191 191
192 192 handlers.extend((httpbasicauthhandler(passmgr),
193 193 httpdigestauthhandler(passmgr)))
194 194
195 195 o = urllib2.build_opener(*handlers)
196 196 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
197 197
198 198 q = {"service": 'git-upload-pack'}
199 199 qs = '?%s' % urllib.urlencode(q)
200 200 cu = "%s%s" % (test_uri, qs)
201 201 req = urllib2.Request(cu, None, {})
202 202
203 203 try:
204 204 resp = o.open(req)
205 205 return resp.code == 200
206 206 except Exception, e:
207 207 # means it cannot be cloned
208 208 raise urllib2.URLError("[%s] %s" % (url, e))
209 209
210 210 def _get_repo(self, create, src_url=None, update_after_clone=False,
211 211 bare=False):
212 212 if create and os.path.exists(self.path):
213 213 raise RepositoryError("Location already exist")
214 214 if src_url and not create:
215 215 raise RepositoryError("Create should be set to True if src_url is "
216 216 "given (clone operation creates repository)")
217 217 try:
218 218 if create and src_url:
219 219 GitRepository._check_url(src_url)
220 220 self.clone(src_url, update_after_clone, bare)
221 221 return Repo(self.path)
222 222 elif create:
223 223 os.mkdir(self.path)
224 224 if bare:
225 225 return Repo.init_bare(self.path)
226 226 else:
227 227 return Repo.init(self.path)
228 228 else:
229 229 return self._repo
230 230 except (NotGitRepository, OSError), err:
231 231 raise RepositoryError(err)
232 232
233 233 def _get_all_revisions(self):
234 234 # we must check if this repo is not empty, since later command
235 235 # fails if it is. And it's cheaper to ask than throw the subprocess
236 236 # errors
237 237 try:
238 238 self._repo.head()
239 239 except KeyError:
240 240 return []
241 241
242 242 rev_filter = _git_path = settings.GIT_REV_FILTER
243 243 cmd = 'rev-list %s --reverse --date-order' % (rev_filter)
244 244 try:
245 245 so, se = self.run_git_command(cmd)
246 246 except RepositoryError:
247 247 # Can be raised for empty repositories
248 248 return []
249 249 return so.splitlines()
250 250
251 251 def _get_all_revisions2(self):
252 252 #alternate implementation using dulwich
253 253 includes = [x[1][0] for x in self._parsed_refs.iteritems()
254 254 if x[1][1] != 'T']
255 255 return [c.commit.id for c in self._repo.get_walker(include=includes)]
256 256
257 257 def _get_revision(self, revision):
258 258 """
259 259 For git backend we always return integer here. This way we ensure
260 260 that changset's revision attribute would become integer.
261 261 """
262 262
263 263 is_null = lambda o: len(o) == revision.count('0')
264 264
265 265 if self._empty:
266 266 raise EmptyRepositoryError("There are no changesets yet")
267 267
268 268 if revision in (None, '', 'tip', 'HEAD', 'head', -1):
269 269 return self.revisions[-1]
270 270
271 271 is_bstr = isinstance(revision, (str, unicode))
272 272 if ((is_bstr and revision.isdigit() and len(revision) < 12)
273 273 or isinstance(revision, int) or is_null(revision)):
274 274 try:
275 275 revision = self.revisions[int(revision)]
276 276 except Exception:
277 277 raise ChangesetDoesNotExistError("Revision %s does not exist "
278 278 "for this repository" % (revision))
279 279
280 280 elif is_bstr:
281 281 # get by branch/tag name
282 282 _ref_revision = self._parsed_refs.get(revision)
283 283 if _ref_revision: # and _ref_revision[1] in ['H', 'RH', 'T']:
284 284 return _ref_revision[0]
285 285
286 286 _tags_shas = self.tags.values()
287 287 # maybe it's a tag ? we don't have them in self.revisions
288 288 if revision in _tags_shas:
289 289 return _tags_shas[_tags_shas.index(revision)]
290 290
291 291 elif not SHA_PATTERN.match(revision) or revision not in self.revisions:
292 292 raise ChangesetDoesNotExistError("Revision %s does not exist "
293 293 "for this repository" % (revision))
294 294
295 295 # Ensure we return full id
296 296 if not SHA_PATTERN.match(str(revision)):
297 297 raise ChangesetDoesNotExistError("Given revision %s not recognized"
298 298 % revision)
299 299 return revision
300 300
301 301 def _get_archives(self, archive_name='tip'):
302 302
303 303 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
304 304 yield {"type": i[0], "extension": i[1], "node": archive_name}
305 305
306 306 def _get_url(self, url):
307 307 """
308 308 Returns normalized url. If schema is not given, would fall to
309 309 filesystem (``file:///``) schema.
310 310 """
311 311 url = str(url)
312 312 if url != 'default' and not '://' in url:
313 313 url = ':///'.join(('file', url))
314 314 return url
315 315
316 316 def get_hook_location(self):
317 317 """
318 318 returns absolute path to location where hooks are stored
319 319 """
320 320 loc = os.path.join(self.path, 'hooks')
321 321 if not self.bare:
322 322 loc = os.path.join(self.path, '.git', 'hooks')
323 323 return loc
324 324
325 325 @LazyProperty
326 326 def name(self):
327 327 return os.path.basename(self.path)
328 328
329 329 @LazyProperty
330 330 def last_change(self):
331 331 """
332 332 Returns last change made on this repository as datetime object
333 333 """
334 334 return date_fromtimestamp(self._get_mtime(), makedate()[1])
335 335
336 336 def _get_mtime(self):
337 337 try:
338 338 return time.mktime(self.get_changeset().date.timetuple())
339 339 except RepositoryError:
340 340 idx_loc = '' if self.bare else '.git'
341 341 # fallback to filesystem
342 342 in_path = os.path.join(self.path, idx_loc, "index")
343 343 he_path = os.path.join(self.path, idx_loc, "HEAD")
344 344 if os.path.exists(in_path):
345 345 return os.stat(in_path).st_mtime
346 346 else:
347 347 return os.stat(he_path).st_mtime
348 348
349 349 @LazyProperty
350 350 def description(self):
351 351 idx_loc = '' if self.bare else '.git'
352 352 undefined_description = u'unknown'
353 353 description_path = os.path.join(self.path, idx_loc, 'description')
354 354 if os.path.isfile(description_path):
355 355 return safe_unicode(open(description_path).read())
356 356 else:
357 357 return undefined_description
358 358
359 359 @LazyProperty
360 360 def contact(self):
361 361 undefined_contact = u'Unknown'
362 362 return undefined_contact
363 363
364 364 @property
365 365 def branches(self):
366 366 if not self.revisions:
367 367 return {}
368 368 sortkey = lambda ctx: ctx[0]
369 369 _branches = [(x[0], x[1][0])
370 370 for x in self._parsed_refs.iteritems() if x[1][1] == 'H']
371 371 return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
372 372
373 373 @LazyProperty
374 def closed_branches(self):
375 return {}
376
377 @LazyProperty
374 378 def tags(self):
375 379 return self._get_tags()
376 380
377 381 def _get_tags(self):
378 382 if not self.revisions:
379 383 return {}
380 384
381 385 sortkey = lambda ctx: ctx[0]
382 386 _tags = [(x[0], x[1][0])
383 387 for x in self._parsed_refs.iteritems() if x[1][1] == 'T']
384 388 return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
385 389
386 390 def tag(self, name, user, revision=None, message=None, date=None,
387 391 **kwargs):
388 392 """
389 393 Creates and returns a tag for the given ``revision``.
390 394
391 395 :param name: name for new tag
392 396 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
393 397 :param revision: changeset id for which new tag would be created
394 398 :param message: message of the tag's commit
395 399 :param date: date of tag's commit
396 400
397 401 :raises TagAlreadyExistError: if tag with same name already exists
398 402 """
399 403 if name in self.tags:
400 404 raise TagAlreadyExistError("Tag %s already exists" % name)
401 405 changeset = self.get_changeset(revision)
402 406 message = message or "Added tag %s for commit %s" % (name,
403 407 changeset.raw_id)
404 408 self._repo.refs["refs/tags/%s" % name] = changeset._commit.id
405 409
406 410 self._parsed_refs = self._get_parsed_refs()
407 411 self.tags = self._get_tags()
408 412 return changeset
409 413
410 414 def remove_tag(self, name, user, message=None, date=None):
411 415 """
412 416 Removes tag with the given ``name``.
413 417
414 418 :param name: name of the tag to be removed
415 419 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
416 420 :param message: message of the tag's removal commit
417 421 :param date: date of tag's removal commit
418 422
419 423 :raises TagDoesNotExistError: if tag with given name does not exists
420 424 """
421 425 if name not in self.tags:
422 426 raise TagDoesNotExistError("Tag %s does not exist" % name)
423 427 tagpath = posixpath.join(self._repo.refs.path, 'refs', 'tags', name)
424 428 try:
425 429 os.remove(tagpath)
426 430 self._parsed_refs = self._get_parsed_refs()
427 431 self.tags = self._get_tags()
428 432 except OSError, e:
429 433 raise RepositoryError(e.strerror)
430 434
431 435 @LazyProperty
432 436 def _parsed_refs(self):
433 437 return self._get_parsed_refs()
434 438
435 439 def _get_parsed_refs(self):
436 440 # cache the property
437 441 _repo = self._repo
438 442 refs = _repo.get_refs()
439 443 keys = [('refs/heads/', 'H'),
440 444 ('refs/remotes/origin/', 'RH'),
441 445 ('refs/tags/', 'T')]
442 446 _refs = {}
443 447 for ref, sha in refs.iteritems():
444 448 for k, type_ in keys:
445 449 if ref.startswith(k):
446 450 _key = ref[len(k):]
447 451 if type_ == 'T':
448 452 obj = _repo.get_object(sha)
449 453 if isinstance(obj, Tag):
450 454 sha = _repo.get_object(sha).object[1]
451 455 _refs[_key] = [sha, type_]
452 456 break
453 457 return _refs
454 458
455 459 def _heads(self, reverse=False):
456 460 refs = self._repo.get_refs()
457 461 heads = {}
458 462
459 463 for key, val in refs.items():
460 464 for ref_key in ['refs/heads/', 'refs/remotes/origin/']:
461 465 if key.startswith(ref_key):
462 466 n = key[len(ref_key):]
463 467 if n not in ['HEAD']:
464 468 heads[n] = val
465 469
466 470 return heads if reverse else dict((y, x) for x, y in heads.iteritems())
467 471
468 472 def get_changeset(self, revision=None):
469 473 """
470 474 Returns ``GitChangeset`` object representing commit from git repository
471 475 at the given revision or head (most recent commit) if None given.
472 476 """
473 477 if isinstance(revision, GitChangeset):
474 478 return revision
475 479 revision = self._get_revision(revision)
476 480 changeset = GitChangeset(repository=self, revision=revision)
477 481 return changeset
478 482
479 483 def get_changesets(self, start=None, end=None, start_date=None,
480 484 end_date=None, branch_name=None, reverse=False):
481 485 """
482 486 Returns iterator of ``GitChangeset`` objects from start to end (both
483 487 are inclusive), in ascending date order (unless ``reverse`` is set).
484 488
485 489 :param start: changeset ID, as str; first returned changeset
486 490 :param end: changeset ID, as str; last returned changeset
487 491 :param start_date: if specified, changesets with commit date less than
488 492 ``start_date`` would be filtered out from returned set
489 493 :param end_date: if specified, changesets with commit date greater than
490 494 ``end_date`` would be filtered out from returned set
491 495 :param branch_name: if specified, changesets not reachable from given
492 496 branch would be filtered out from returned set
493 497 :param reverse: if ``True``, returned generator would be reversed
494 498 (meaning that returned changesets would have descending date order)
495 499
496 500 :raise BranchDoesNotExistError: If given ``branch_name`` does not
497 501 exist.
498 502 :raise ChangesetDoesNotExistError: If changeset for given ``start`` or
499 503 ``end`` could not be found.
500 504
501 505 """
502 506 if branch_name and branch_name not in self.branches:
503 507 raise BranchDoesNotExistError("Branch '%s' not found" \
504 508 % branch_name)
505 509 # actually we should check now if it's not an empty repo to not spaw
506 510 # subprocess commands
507 511 if self._empty:
508 512 raise EmptyRepositoryError("There are no changesets yet")
509 513
510 514 # %H at format means (full) commit hash, initial hashes are retrieved
511 515 # in ascending date order
512 516 cmd_template = 'log --date-order --reverse --pretty=format:"%H"'
513 517 cmd_params = {}
514 518 if start_date:
515 519 cmd_template += ' --since "$since"'
516 520 cmd_params['since'] = start_date.strftime('%m/%d/%y %H:%M:%S')
517 521 if end_date:
518 522 cmd_template += ' --until "$until"'
519 523 cmd_params['until'] = end_date.strftime('%m/%d/%y %H:%M:%S')
520 524 if branch_name:
521 525 cmd_template += ' $branch_name'
522 526 cmd_params['branch_name'] = branch_name
523 527 else:
524 528 rev_filter = _git_path = settings.GIT_REV_FILTER
525 529 cmd_template += ' %s' % (rev_filter)
526 530
527 531 cmd = string.Template(cmd_template).safe_substitute(**cmd_params)
528 532 revs = self.run_git_command(cmd)[0].splitlines()
529 533 start_pos = 0
530 534 end_pos = len(revs)
531 535 if start:
532 536 _start = self._get_revision(start)
533 537 try:
534 538 start_pos = revs.index(_start)
535 539 except ValueError:
536 540 pass
537 541
538 542 if end is not None:
539 543 _end = self._get_revision(end)
540 544 try:
541 545 end_pos = revs.index(_end)
542 546 except ValueError:
543 547 pass
544 548
545 549 if None not in [start, end] and start_pos > end_pos:
546 550 raise RepositoryError('start cannot be after end')
547 551
548 552 if end_pos is not None:
549 553 end_pos += 1
550 554
551 555 revs = revs[start_pos:end_pos]
552 556 if reverse:
553 557 revs = reversed(revs)
554 558 return CollectionGenerator(self, revs)
555 559
556 560 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
557 561 context=3):
558 562 """
559 563 Returns (git like) *diff*, as plain text. Shows changes introduced by
560 564 ``rev2`` since ``rev1``.
561 565
562 566 :param rev1: Entry point from which diff is shown. Can be
563 567 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
564 568 the changes since empty state of the repository until ``rev2``
565 569 :param rev2: Until which revision changes should be shown.
566 570 :param ignore_whitespace: If set to ``True``, would not show whitespace
567 571 changes. Defaults to ``False``.
568 572 :param context: How many lines before/after changed lines should be
569 573 shown. Defaults to ``3``.
570 574 """
571 575 flags = ['-U%s' % context, '--full-index', '--binary', '-p', '-M', '--abbrev=40']
572 576 if ignore_whitespace:
573 577 flags.append('-w')
574 578
575 579 if hasattr(rev1, 'raw_id'):
576 580 rev1 = getattr(rev1, 'raw_id')
577 581
578 582 if hasattr(rev2, 'raw_id'):
579 583 rev2 = getattr(rev2, 'raw_id')
580 584
581 585 if rev1 == self.EMPTY_CHANGESET:
582 586 rev2 = self.get_changeset(rev2).raw_id
583 587 cmd = ' '.join(['show'] + flags + [rev2])
584 588 else:
585 589 rev1 = self.get_changeset(rev1).raw_id
586 590 rev2 = self.get_changeset(rev2).raw_id
587 591 cmd = ' '.join(['diff'] + flags + [rev1, rev2])
588 592
589 593 if path:
590 594 cmd += ' -- "%s"' % path
591 595
592 596 stdout, stderr = self.run_git_command(cmd)
593 597 # If we used 'show' command, strip first few lines (until actual diff
594 598 # starts)
595 599 if rev1 == self.EMPTY_CHANGESET:
596 600 lines = stdout.splitlines()
597 601 x = 0
598 602 for line in lines:
599 603 if line.startswith('diff'):
600 604 break
601 605 x += 1
602 606 # Append new line just like 'diff' command do
603 607 stdout = '\n'.join(lines[x:]) + '\n'
604 608 return stdout
605 609
606 610 @LazyProperty
607 611 def in_memory_changeset(self):
608 612 """
609 613 Returns ``GitInMemoryChangeset`` object for this repository.
610 614 """
611 615 return GitInMemoryChangeset(self)
612 616
613 617 def clone(self, url, update_after_clone=True, bare=False):
614 618 """
615 619 Tries to clone changes from external location.
616 620
617 621 :param update_after_clone: If set to ``False``, git won't checkout
618 622 working directory
619 623 :param bare: If set to ``True``, repository would be cloned into
620 624 *bare* git repository (no working directory at all).
621 625 """
622 626 url = self._get_url(url)
623 627 cmd = ['clone']
624 628 if bare:
625 629 cmd.append('--bare')
626 630 elif not update_after_clone:
627 631 cmd.append('--no-checkout')
628 632 cmd += ['--', '"%s"' % url, '"%s"' % self.path]
629 633 cmd = ' '.join(cmd)
630 634 # If error occurs run_git_command raises RepositoryError already
631 635 self.run_git_command(cmd)
632 636
633 637 def pull(self, url):
634 638 """
635 639 Tries to pull changes from external location.
636 640 """
637 641 url = self._get_url(url)
638 642 cmd = ['pull', "--ff-only", url]
639 643 cmd = ' '.join(cmd)
640 644 # If error occurs run_git_command raises RepositoryError already
641 645 self.run_git_command(cmd)
642 646
643 647 def fetch(self, url):
644 648 """
645 649 Tries to pull changes from external location.
646 650 """
647 651 url = self._get_url(url)
648 652 so, se = self.run_git_command('ls-remote -h %s' % url)
649 653 refs = []
650 654 for line in (x for x in so.splitlines()):
651 655 sha, ref = line.split('\t')
652 656 refs.append(ref)
653 657 refs = ' '.join(('+%s:%s' % (r, r) for r in refs))
654 658 cmd = '''fetch %s -- %s''' % (url, refs)
655 659 self.run_git_command(cmd)
656 660
657 661 @LazyProperty
658 662 def workdir(self):
659 663 """
660 664 Returns ``Workdir`` instance for this repository.
661 665 """
662 666 return GitWorkdir(self)
663 667
664 668 def get_config_value(self, section, name, config_file=None):
665 669 """
666 670 Returns configuration value for a given [``section``] and ``name``.
667 671
668 672 :param section: Section we want to retrieve value from
669 673 :param name: Name of configuration we want to retrieve
670 674 :param config_file: A path to file which should be used to retrieve
671 675 configuration from (might also be a list of file paths)
672 676 """
673 677 if config_file is None:
674 678 config_file = []
675 679 elif isinstance(config_file, basestring):
676 680 config_file = [config_file]
677 681
678 682 def gen_configs():
679 683 for path in config_file + self._config_files:
680 684 try:
681 685 yield ConfigFile.from_path(path)
682 686 except (IOError, OSError, ValueError):
683 687 continue
684 688
685 689 for config in gen_configs():
686 690 try:
687 691 return config.get(section, name)
688 692 except KeyError:
689 693 continue
690 694 return None
691 695
692 696 def get_user_name(self, config_file=None):
693 697 """
694 698 Returns user's name from global configuration file.
695 699
696 700 :param config_file: A path to file which should be used to retrieve
697 701 configuration from (might also be a list of file paths)
698 702 """
699 703 return self.get_config_value('user', 'name', config_file)
700 704
701 705 def get_user_email(self, config_file=None):
702 706 """
703 707 Returns user's email from global configuration file.
704 708
705 709 :param config_file: A path to file which should be used to retrieve
706 710 configuration from (might also be a list of file paths)
707 711 """
708 712 return self.get_config_value('user', 'email', config_file)
@@ -1,572 +1,579 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 vcs.backends.hg.repository
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Mercurial repository 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 time
14 14 import urllib
15 15 import urllib2
16 16 import logging
17 17 import datetime
18 18
19 19
20 20 from rhodecode.lib.vcs.backends.base import BaseRepository, CollectionGenerator
21 21 from rhodecode.lib.vcs.conf import settings
22 22
23 23 from rhodecode.lib.vcs.exceptions import (
24 24 BranchDoesNotExistError, ChangesetDoesNotExistError, EmptyRepositoryError,
25 25 RepositoryError, VCSError, TagAlreadyExistError, TagDoesNotExistError
26 26 )
27 27 from rhodecode.lib.vcs.utils import (
28 28 author_email, author_name, date_fromtimestamp, makedate, safe_unicode
29 29 )
30 30 from rhodecode.lib.vcs.utils.lazy import LazyProperty
31 31 from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
32 32 from rhodecode.lib.vcs.utils.paths import abspath
33 33 from rhodecode.lib.vcs.utils.hgcompat import (
34 34 ui, nullid, match, patch, diffopts, clone, get_contact, pull,
35 35 localrepository, RepoLookupError, Abort, RepoError, hex, scmutil, hg_url,
36 36 httpbasicauthhandler, httpdigestauthhandler, peer
37 37 )
38 38
39 39 from .changeset import MercurialChangeset
40 40 from .inmemory import MercurialInMemoryChangeset
41 41 from .workdir import MercurialWorkdir
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class MercurialRepository(BaseRepository):
47 47 """
48 48 Mercurial repository backend
49 49 """
50 50 DEFAULT_BRANCH_NAME = 'default'
51 51 scm = 'hg'
52 52
53 53 def __init__(self, repo_path, create=False, baseui=None, src_url=None,
54 54 update_after_clone=False):
55 55 """
56 56 Raises RepositoryError if repository could not be find at the given
57 57 ``repo_path``.
58 58
59 59 :param repo_path: local path of the repository
60 60 :param create=False: if set to True, would try to create repository if
61 61 it does not exist rather than raising exception
62 62 :param baseui=None: user data
63 63 :param src_url=None: would try to clone repository from given location
64 64 :param update_after_clone=False: sets update of working copy after
65 65 making a clone
66 66 """
67 67
68 68 if not isinstance(repo_path, str):
69 69 raise VCSError('Mercurial backend requires repository path to '
70 70 'be instance of <str> got %s instead' %
71 71 type(repo_path))
72 72
73 73 self.path = abspath(repo_path)
74 74 self.baseui = baseui or ui.ui()
75 75 # We've set path and ui, now we can set _repo itself
76 76 self._repo = self._get_repo(create, src_url, update_after_clone)
77 77
78 78 @property
79 79 def _empty(self):
80 80 """
81 81 Checks if repository is empty ie. without any changesets
82 82 """
83 83 # TODO: Following raises errors when using InMemoryChangeset...
84 84 # return len(self._repo.changelog) == 0
85 85 return len(self.revisions) == 0
86 86
87 87 @LazyProperty
88 88 def revisions(self):
89 89 """
90 90 Returns list of revisions' ids, in ascending order. Being lazy
91 91 attribute allows external tools to inject shas from cache.
92 92 """
93 93 return self._get_all_revisions()
94 94
95 95 @LazyProperty
96 96 def name(self):
97 97 return os.path.basename(self.path)
98 98
99 99 @LazyProperty
100 100 def branches(self):
101 101 return self._get_branches()
102 102
103 103 @LazyProperty
104 def closed_branches(self):
105 return self._get_branches(normal=False, closed=True)
106
107 @LazyProperty
104 108 def allbranches(self):
105 109 """
106 110 List all branches, including closed branches.
107 111 """
108 112 return self._get_branches(closed=True)
109 113
110 def _get_branches(self, closed=False):
114 def _get_branches(self, normal=True, closed=False):
111 115 """
112 116 Get's branches for this repository
113 117 Returns only not closed branches by default
114 118
115 119 :param closed: return also closed branches for mercurial
120 :param normal: return also normal branches
116 121 """
117 122
118 123 if self._empty:
119 124 return {}
120 125
121 126 def _branchtags(localrepo):
122 127 """
123 128 Patched version of mercurial branchtags to not return the closed
124 129 branches
125 130
126 131 :param localrepo: locarepository instance
127 132 """
128 133
129 134 bt = {}
130 135 bt_closed = {}
131 136 for bn, heads in localrepo.branchmap().iteritems():
132 137 tip = heads[-1]
133 138 if 'close' in localrepo.changelog.read(tip)[5]:
134 139 bt_closed[bn] = tip
135 140 else:
136 141 bt[bn] = tip
137 142
143 if not normal:
144 return bt_closed
138 145 if closed:
139 146 bt.update(bt_closed)
140 147 return bt
141 148
142 149 sortkey = lambda ctx: ctx[0] # sort by name
143 150 _branches = [(safe_unicode(n), hex(h),) for n, h in
144 151 _branchtags(self._repo).items()]
145 152
146 153 return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
147 154
148 155 @LazyProperty
149 156 def tags(self):
150 157 """
151 158 Get's tags for this repository
152 159 """
153 160 return self._get_tags()
154 161
155 162 def _get_tags(self):
156 163 if self._empty:
157 164 return {}
158 165
159 166 sortkey = lambda ctx: ctx[0] # sort by name
160 167 _tags = [(safe_unicode(n), hex(h),) for n, h in
161 168 self._repo.tags().items()]
162 169
163 170 return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
164 171
165 172 def tag(self, name, user, revision=None, message=None, date=None,
166 173 **kwargs):
167 174 """
168 175 Creates and returns a tag for the given ``revision``.
169 176
170 177 :param name: name for new tag
171 178 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
172 179 :param revision: changeset id for which new tag would be created
173 180 :param message: message of the tag's commit
174 181 :param date: date of tag's commit
175 182
176 183 :raises TagAlreadyExistError: if tag with same name already exists
177 184 """
178 185 if name in self.tags:
179 186 raise TagAlreadyExistError("Tag %s already exists" % name)
180 187 changeset = self.get_changeset(revision)
181 188 local = kwargs.setdefault('local', False)
182 189
183 190 if message is None:
184 191 message = "Added tag %s for changeset %s" % (name,
185 192 changeset.short_id)
186 193
187 194 if date is None:
188 195 date = datetime.datetime.now().ctime()
189 196
190 197 try:
191 198 self._repo.tag(name, changeset._ctx.node(), message, local, user,
192 199 date)
193 200 except Abort, e:
194 201 raise RepositoryError(e.message)
195 202
196 203 # Reinitialize tags
197 204 self.tags = self._get_tags()
198 205 tag_id = self.tags[name]
199 206
200 207 return self.get_changeset(revision=tag_id)
201 208
202 209 def remove_tag(self, name, user, message=None, date=None):
203 210 """
204 211 Removes tag with the given ``name``.
205 212
206 213 :param name: name of the tag to be removed
207 214 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
208 215 :param message: message of the tag's removal commit
209 216 :param date: date of tag's removal commit
210 217
211 218 :raises TagDoesNotExistError: if tag with given name does not exists
212 219 """
213 220 if name not in self.tags:
214 221 raise TagDoesNotExistError("Tag %s does not exist" % name)
215 222 if message is None:
216 223 message = "Removed tag %s" % name
217 224 if date is None:
218 225 date = datetime.datetime.now().ctime()
219 226 local = False
220 227
221 228 try:
222 229 self._repo.tag(name, nullid, message, local, user, date)
223 230 self.tags = self._get_tags()
224 231 except Abort, e:
225 232 raise RepositoryError(e.message)
226 233
227 234 @LazyProperty
228 235 def bookmarks(self):
229 236 """
230 237 Get's bookmarks for this repository
231 238 """
232 239 return self._get_bookmarks()
233 240
234 241 def _get_bookmarks(self):
235 242 if self._empty:
236 243 return {}
237 244
238 245 sortkey = lambda ctx: ctx[0] # sort by name
239 246 _bookmarks = [(safe_unicode(n), hex(h),) for n, h in
240 247 self._repo._bookmarks.items()]
241 248 return OrderedDict(sorted(_bookmarks, key=sortkey, reverse=True))
242 249
243 250 def _get_all_revisions(self):
244 251
245 252 return map(lambda x: hex(x[7]), self._repo.changelog.index)[:-1]
246 253
247 254 def get_diff(self, rev1, rev2, path='', ignore_whitespace=False,
248 255 context=3):
249 256 """
250 257 Returns (git like) *diff*, as plain text. Shows changes introduced by
251 258 ``rev2`` since ``rev1``.
252 259
253 260 :param rev1: Entry point from which diff is shown. Can be
254 261 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
255 262 the changes since empty state of the repository until ``rev2``
256 263 :param rev2: Until which revision changes should be shown.
257 264 :param ignore_whitespace: If set to ``True``, would not show whitespace
258 265 changes. Defaults to ``False``.
259 266 :param context: How many lines before/after changed lines should be
260 267 shown. Defaults to ``3``.
261 268 """
262 269 if hasattr(rev1, 'raw_id'):
263 270 rev1 = getattr(rev1, 'raw_id')
264 271
265 272 if hasattr(rev2, 'raw_id'):
266 273 rev2 = getattr(rev2, 'raw_id')
267 274
268 275 # Check if given revisions are present at repository (may raise
269 276 # ChangesetDoesNotExistError)
270 277 if rev1 != self.EMPTY_CHANGESET:
271 278 self.get_changeset(rev1)
272 279 self.get_changeset(rev2)
273 280 if path:
274 281 file_filter = match(self.path, '', [path])
275 282 else:
276 283 file_filter = None
277 284
278 285 return ''.join(patch.diff(self._repo, rev1, rev2, match=file_filter,
279 286 opts=diffopts(git=True,
280 287 ignorews=ignore_whitespace,
281 288 context=context)))
282 289
283 290 @classmethod
284 291 def _check_url(cls, url):
285 292 """
286 293 Function will check given url and try to verify if it's a valid
287 294 link. Sometimes it may happened that mercurial will issue basic
288 295 auth request that can cause whole API to hang when used from python
289 296 or other external calls.
290 297
291 298 On failures it'll raise urllib2.HTTPError, return code 200 if url
292 299 is valid or True if it's a local path
293 300 """
294 301
295 302 # check first if it's not an local url
296 303 if os.path.isdir(url) or url.startswith('file:'):
297 304 return True
298 305
299 306 if('+' in url[:url.find('://')]):
300 307 url = url[url.find('+') + 1:]
301 308
302 309 handlers = []
303 310 test_uri, authinfo = hg_url(url).authinfo()
304 311
305 312 if authinfo:
306 313 #create a password manager
307 314 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
308 315 passmgr.add_password(*authinfo)
309 316
310 317 handlers.extend((httpbasicauthhandler(passmgr),
311 318 httpdigestauthhandler(passmgr)))
312 319
313 320 o = urllib2.build_opener(*handlers)
314 321 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
315 322 ('Accept', 'application/mercurial-0.1')]
316 323
317 324 q = {"cmd": 'between'}
318 325 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
319 326 qs = '?%s' % urllib.urlencode(q)
320 327 cu = "%s%s" % (test_uri, qs)
321 328 req = urllib2.Request(cu, None, {})
322 329
323 330 try:
324 331 resp = o.open(req)
325 332 return resp.code == 200
326 333 except Exception, e:
327 334 # means it cannot be cloned
328 335 raise urllib2.URLError("[%s] %s" % (url, e))
329 336
330 337 def _get_repo(self, create, src_url=None, update_after_clone=False):
331 338 """
332 339 Function will check for mercurial repository in given path and return
333 340 a localrepo object. If there is no repository in that path it will
334 341 raise an exception unless ``create`` parameter is set to True - in
335 342 that case repository would be created and returned.
336 343 If ``src_url`` is given, would try to clone repository from the
337 344 location at given clone_point. Additionally it'll make update to
338 345 working copy accordingly to ``update_after_clone`` flag
339 346 """
340 347
341 348 try:
342 349 if src_url:
343 350 url = str(self._get_url(src_url))
344 351 opts = {}
345 352 if not update_after_clone:
346 353 opts.update({'noupdate': True})
347 354 try:
348 355 MercurialRepository._check_url(url)
349 356 clone(self.baseui, url, self.path, **opts)
350 357 # except urllib2.URLError:
351 358 # raise Abort("Got HTTP 404 error")
352 359 except Exception:
353 360 raise
354 361
355 362 # Don't try to create if we've already cloned repo
356 363 create = False
357 364 return localrepository(self.baseui, self.path, create=create)
358 365 except (Abort, RepoError), err:
359 366 if create:
360 367 msg = "Cannot create repository at %s. Original error was %s"\
361 368 % (self.path, err)
362 369 else:
363 370 msg = "Not valid repository at %s. Original error was %s"\
364 371 % (self.path, err)
365 372 raise RepositoryError(msg)
366 373
367 374 @LazyProperty
368 375 def in_memory_changeset(self):
369 376 return MercurialInMemoryChangeset(self)
370 377
371 378 @LazyProperty
372 379 def description(self):
373 380 undefined_description = u'unknown'
374 381 return safe_unicode(self._repo.ui.config('web', 'description',
375 382 undefined_description, untrusted=True))
376 383
377 384 @LazyProperty
378 385 def contact(self):
379 386 undefined_contact = u'Unknown'
380 387 return safe_unicode(get_contact(self._repo.ui.config)
381 388 or undefined_contact)
382 389
383 390 @LazyProperty
384 391 def last_change(self):
385 392 """
386 393 Returns last change made on this repository as datetime object
387 394 """
388 395 return date_fromtimestamp(self._get_mtime(), makedate()[1])
389 396
390 397 def _get_mtime(self):
391 398 try:
392 399 return time.mktime(self.get_changeset().date.timetuple())
393 400 except RepositoryError:
394 401 #fallback to filesystem
395 402 cl_path = os.path.join(self.path, '.hg', "00changelog.i")
396 403 st_path = os.path.join(self.path, '.hg', "store")
397 404 if os.path.exists(cl_path):
398 405 return os.stat(cl_path).st_mtime
399 406 else:
400 407 return os.stat(st_path).st_mtime
401 408
402 409 def _get_hidden(self):
403 410 return self._repo.ui.configbool("web", "hidden", untrusted=True)
404 411
405 412 def _get_revision(self, revision):
406 413 """
407 414 Get's an ID revision given as str. This will always return a fill
408 415 40 char revision number
409 416
410 417 :param revision: str or int or None
411 418 """
412 419
413 420 if self._empty:
414 421 raise EmptyRepositoryError("There are no changesets yet")
415 422
416 423 if revision in [-1, 'tip', None]:
417 424 revision = 'tip'
418 425
419 426 try:
420 427 revision = hex(self._repo.lookup(revision))
421 428 except (IndexError, ValueError, RepoLookupError, TypeError):
422 429 raise ChangesetDoesNotExistError("Revision %s does not "
423 430 "exist for this repository"
424 431 % (revision))
425 432 return revision
426 433
427 434 def _get_archives(self, archive_name='tip'):
428 435 allowed = self.baseui.configlist("web", "allow_archive",
429 436 untrusted=True)
430 437 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
431 438 if i[0] in allowed or self._repo.ui.configbool("web",
432 439 "allow" + i[0],
433 440 untrusted=True):
434 441 yield {"type": i[0], "extension": i[1], "node": archive_name}
435 442
436 443 def _get_url(self, url):
437 444 """
438 445 Returns normalized url. If schema is not given, would fall
439 446 to filesystem
440 447 (``file:///``) schema.
441 448 """
442 449 url = str(url)
443 450 if url != 'default' and not '://' in url:
444 451 url = "file:" + urllib.pathname2url(url)
445 452 return url
446 453
447 454 def get_hook_location(self):
448 455 """
449 456 returns absolute path to location where hooks are stored
450 457 """
451 458 return os.path.join(self.path, '.hg', '.hgrc')
452 459
453 460 def get_changeset(self, revision=None):
454 461 """
455 462 Returns ``MercurialChangeset`` object representing repository's
456 463 changeset at the given ``revision``.
457 464 """
458 465 revision = self._get_revision(revision)
459 466 changeset = MercurialChangeset(repository=self, revision=revision)
460 467 return changeset
461 468
462 469 def get_changesets(self, start=None, end=None, start_date=None,
463 470 end_date=None, branch_name=None, reverse=False):
464 471 """
465 472 Returns iterator of ``MercurialChangeset`` objects from start to end
466 473 (both are inclusive)
467 474
468 475 :param start: None, str, int or mercurial lookup format
469 476 :param end: None, str, int or mercurial lookup format
470 477 :param start_date:
471 478 :param end_date:
472 479 :param branch_name:
473 480 :param reversed: return changesets in reversed order
474 481 """
475 482
476 483 start_raw_id = self._get_revision(start)
477 484 start_pos = self.revisions.index(start_raw_id) if start else None
478 485 end_raw_id = self._get_revision(end)
479 486 end_pos = self.revisions.index(end_raw_id) if end else None
480 487
481 488 if None not in [start, end] and start_pos > end_pos:
482 489 raise RepositoryError("Start revision '%s' cannot be "
483 490 "after end revision '%s'" % (start, end))
484 491
485 492 if branch_name and branch_name not in self.allbranches.keys():
486 493 raise BranchDoesNotExistError('Branch %s not found in'
487 494 ' this repository' % branch_name)
488 495 if end_pos is not None:
489 496 end_pos += 1
490 497 #filter branches
491 498 filter_ = []
492 499 if branch_name:
493 500 filter_.append('branch("%s")' % (branch_name))
494 501
495 502 if start_date and not end_date:
496 503 filter_.append('date(">%s")' % start_date)
497 504 if end_date and not start_date:
498 505 filter_.append('date("<%s")' % end_date)
499 506 if start_date and end_date:
500 507 filter_.append('date(">%s") and date("<%s")' % (start_date, end_date))
501 508 if filter_:
502 509 revisions = scmutil.revrange(self._repo, filter_)
503 510 else:
504 511 revisions = self.revisions
505 512
506 513 revs = revisions[start_pos:end_pos]
507 514 if reverse:
508 515 revs = reversed(revs)
509 516
510 517 return CollectionGenerator(self, revs)
511 518
512 519 def pull(self, url):
513 520 """
514 521 Tries to pull changes from external location.
515 522 """
516 523 url = self._get_url(url)
517 524 try:
518 525 other = peer(self._repo, {}, url)
519 526 self._repo.pull(other, heads=None, force=None)
520 527 except Abort, err:
521 528 # Propagate error but with vcs's type
522 529 raise RepositoryError(str(err))
523 530
524 531 @LazyProperty
525 532 def workdir(self):
526 533 """
527 534 Returns ``Workdir`` instance for this repository.
528 535 """
529 536 return MercurialWorkdir(self)
530 537
531 538 def get_config_value(self, section, name=None, config_file=None):
532 539 """
533 540 Returns configuration value for a given [``section``] and ``name``.
534 541
535 542 :param section: Section we want to retrieve value from
536 543 :param name: Name of configuration we want to retrieve
537 544 :param config_file: A path to file which should be used to retrieve
538 545 configuration from (might also be a list of file paths)
539 546 """
540 547 if config_file is None:
541 548 config_file = []
542 549 elif isinstance(config_file, basestring):
543 550 config_file = [config_file]
544 551
545 552 config = self._repo.ui
546 553 for path in config_file:
547 554 config.readconfig(path)
548 555 return config.config(section, name)
549 556
550 557 def get_user_name(self, config_file=None):
551 558 """
552 559 Returns user's name from global configuration file.
553 560
554 561 :param config_file: A path to file which should be used to retrieve
555 562 configuration from (might also be a list of file paths)
556 563 """
557 564 username = self.get_config_value('ui', 'username')
558 565 if username:
559 566 return author_name(username)
560 567 return None
561 568
562 569 def get_user_email(self, config_file=None):
563 570 """
564 571 Returns user's email from global configuration file.
565 572
566 573 :param config_file: A path to file which should be used to retrieve
567 574 configuration from (might also be a list of file paths)
568 575 """
569 576 username = self.get_config_value('ui', 'username')
570 577 if username:
571 578 return author_email(username)
572 579 return None
@@ -1,39 +1,50 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <li>
3 3 ${h.link_to('%s (%s)' % (_('Branches'),len(c.rhodecode_repo.branches.values()),),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
4 4 <ul>
5 5 %if c.rhodecode_repo.branches.values():
6 6 %for cnt,branch in enumerate(c.rhodecode_repo.branches.items()):
7 7 <li><div><pre>${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=(branch[0] if '/' not in branch[0] else branch[1]), at=branch[0]))}</pre></div></li>
8 8 %endfor
9 9 %else:
10 10 <li>${h.link_to(_('There are no branches yet'),'#')}</li>
11 11 %endif
12 12 </ul>
13 13 </li>
14 %if c.rhodecode_repo.closed_branches.values():
15 <li>
16 ${h.link_to('%s (%s)' % (_('Closed Branches'),len(c.rhodecode_repo.closed_branches.values()),),h.url('branches_home',repo_name=c.repo_name),class_='branches childs')}
17 <ul>
18 <li><a>-</a></li>
19 %for cnt,branch in enumerate(c.rhodecode_repo.closed_branches.items()):
20 <li><div><pre>${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=(branch[0] if '/' not in branch[0] else branch[1]), at=branch[0]))}</pre></div></li>
21 %endfor
22 </ul>
23 </li>
24 %endif
14 25 <li>
15 26 ${h.link_to('%s (%s)' % (_('Tags'),len(c.rhodecode_repo.tags.values()),),h.url('tags_home',repo_name=c.repo_name),class_='tags childs')}
16 27 <ul>
17 28 %if c.rhodecode_repo.tags.values():
18 29 %for cnt,tag in enumerate(c.rhodecode_repo.tags.items()):
19 30 <li><div><pre>${h.link_to('%s - %s' % (tag[0],h.short_id(tag[1])),h.url('files_home',repo_name=c.repo_name,revision=(tag[0] if '/' not in tag[0] else tag[1]), at=tag[0]))}</pre></div></li>
20 31 %endfor
21 32 %else:
22 33 <li>${h.link_to(_('There are no tags yet'),'#')}</li>
23 34 %endif
24 35 </ul>
25 36 </li>
26 37 %if c.rhodecode_repo.alias == 'hg':
27 38 <li>
28 39 ${h.link_to('%s (%s)' % (_('Bookmarks'),len(c.rhodecode_repo.bookmarks.values()),),h.url('bookmarks_home',repo_name=c.repo_name),class_='bookmarks childs')}
29 40 <ul>
30 41 %if c.rhodecode_repo.bookmarks.values():
31 42 %for cnt,book in enumerate(c.rhodecode_repo.bookmarks.items()):
32 43 <li><div><pre>${h.link_to('%s - %s' % (book[0],h.short_id(book[1])),h.url('files_home',repo_name=c.repo_name,revision=(book[0] if '/' not in book[0] else book[1]), at=book[0]))}</pre></div></li>
33 44 %endfor
34 45 %else:
35 46 <li>${h.link_to(_('There are no bookmarks yet'),'#')}</li>
36 47 %endif
37 48 </ul>
38 49 </li>
39 50 %endif
General Comments 0
You need to be logged in to leave comments. Login now