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