##// END OF EJS Templates
Don't do git pull on remote repos since they are bare now, we need to use git fetch on them
marcink -
r2383:e576410f beta
parent child Browse files
Show More
@@ -1,554 +1,565 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 vcs.backends.git
3 vcs.backends.git
4 ~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~
5
5
6 Git backend implementation.
6 Git backend implementation.
7
7
8 :created_on: Apr 8, 2010
8 :created_on: Apr 8, 2010
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 """
10 """
11
11
12 import os
12 import os
13 import re
13 import re
14 import time
14 import time
15 import posixpath
15 import posixpath
16 from dulwich.repo import Repo, NotGitRepository
16 from dulwich.repo import Repo, NotGitRepository
17 #from dulwich.config import ConfigFile
17 #from dulwich.config import ConfigFile
18 from string import Template
18 from string import Template
19 from subprocess import Popen, PIPE
19 from subprocess import Popen, PIPE
20 from rhodecode.lib.vcs.backends.base import BaseRepository
20 from rhodecode.lib.vcs.backends.base import BaseRepository
21 from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError
21 from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError
22 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
22 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
23 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
23 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
24 from rhodecode.lib.vcs.exceptions import RepositoryError
24 from rhodecode.lib.vcs.exceptions import RepositoryError
25 from rhodecode.lib.vcs.exceptions import TagAlreadyExistError
25 from rhodecode.lib.vcs.exceptions import TagAlreadyExistError
26 from rhodecode.lib.vcs.exceptions import TagDoesNotExistError
26 from rhodecode.lib.vcs.exceptions import TagDoesNotExistError
27 from rhodecode.lib.vcs.utils import safe_unicode, makedate, date_fromtimestamp
27 from rhodecode.lib.vcs.utils import safe_unicode, makedate, date_fromtimestamp
28 from rhodecode.lib.vcs.utils.lazy import LazyProperty
28 from rhodecode.lib.vcs.utils.lazy import LazyProperty
29 from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
29 from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
30 from rhodecode.lib.vcs.utils.paths import abspath
30 from rhodecode.lib.vcs.utils.paths import abspath
31 from rhodecode.lib.vcs.utils.paths import get_user_home
31 from rhodecode.lib.vcs.utils.paths import get_user_home
32 from .workdir import GitWorkdir
32 from .workdir import GitWorkdir
33 from .changeset import GitChangeset
33 from .changeset import GitChangeset
34 from .inmemory import GitInMemoryChangeset
34 from .inmemory import GitInMemoryChangeset
35 from .config import ConfigFile
35 from .config import ConfigFile
36
36
37
37
38 class GitRepository(BaseRepository):
38 class GitRepository(BaseRepository):
39 """
39 """
40 Git repository backend.
40 Git repository backend.
41 """
41 """
42 DEFAULT_BRANCH_NAME = 'master'
42 DEFAULT_BRANCH_NAME = 'master'
43 scm = 'git'
43 scm = 'git'
44
44
45 def __init__(self, repo_path, create=False, src_url=None,
45 def __init__(self, repo_path, create=False, src_url=None,
46 update_after_clone=False, bare=False):
46 update_after_clone=False, bare=False):
47
47
48 self.path = abspath(repo_path)
48 self.path = abspath(repo_path)
49 self._repo = self._get_repo(create, src_url, update_after_clone, bare)
49 self._repo = self._get_repo(create, src_url, update_after_clone, bare)
50 #temporary set that to now at later we will move it to constructor
50 #temporary set that to now at later we will move it to constructor
51 baseui = None
51 baseui = None
52 if baseui is None:
52 if baseui is None:
53 from mercurial.ui import ui
53 from mercurial.ui import ui
54 baseui = ui()
54 baseui = ui()
55 # patch the instance of GitRepo with an "FAKE" ui object to add
55 # patch the instance of GitRepo with an "FAKE" ui object to add
56 # compatibility layer with Mercurial
56 # compatibility layer with Mercurial
57 setattr(self._repo, 'ui', baseui)
57 setattr(self._repo, 'ui', baseui)
58
58
59 try:
59 try:
60 self.head = self._repo.head()
60 self.head = self._repo.head()
61 except KeyError:
61 except KeyError:
62 self.head = None
62 self.head = None
63
63
64 self._config_files = [
64 self._config_files = [
65 bare and abspath(self.path, 'config') or abspath(self.path, '.git',
65 bare and abspath(self.path, 'config') or abspath(self.path, '.git',
66 'config'),
66 'config'),
67 abspath(get_user_home(), '.gitconfig'),
67 abspath(get_user_home(), '.gitconfig'),
68 ]
68 ]
69 self.bare = self._repo.bare
69 self.bare = self._repo.bare
70
70
71 @LazyProperty
71 @LazyProperty
72 def revisions(self):
72 def revisions(self):
73 """
73 """
74 Returns list of revisions' ids, in ascending order. Being lazy
74 Returns list of revisions' ids, in ascending order. Being lazy
75 attribute allows external tools to inject shas from cache.
75 attribute allows external tools to inject shas from cache.
76 """
76 """
77 return self._get_all_revisions()
77 return self._get_all_revisions()
78
78
79 def run_git_command(self, cmd):
79 def run_git_command(self, cmd):
80 """
80 """
81 Runs given ``cmd`` as git command and returns tuple
81 Runs given ``cmd`` as git command and returns tuple
82 (returncode, stdout, stderr).
82 (returncode, stdout, stderr).
83
83
84 .. note::
84 .. note::
85 This method exists only until log/blame functionality is implemented
85 This method exists only until log/blame functionality is implemented
86 at Dulwich (see https://bugs.launchpad.net/bugs/645142). Parsing
86 at Dulwich (see https://bugs.launchpad.net/bugs/645142). Parsing
87 os command's output is road to hell...
87 os command's output is road to hell...
88
88
89 :param cmd: git command to be executed
89 :param cmd: git command to be executed
90 """
90 """
91
91
92 _copts = ['-c', 'core.quotepath=false', ]
92 _copts = ['-c', 'core.quotepath=false', ]
93 _str_cmd = False
93 _str_cmd = False
94 if isinstance(cmd, basestring):
94 if isinstance(cmd, basestring):
95 cmd = [cmd]
95 cmd = [cmd]
96 _str_cmd = True
96 _str_cmd = True
97
97
98 gitenv = os.environ
98 gitenv = os.environ
99 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
99 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
100
100
101 cmd = ['git'] + _copts + cmd
101 cmd = ['git'] + _copts + cmd
102 if _str_cmd:
102 if _str_cmd:
103 cmd = ' '.join(cmd)
103 cmd = ' '.join(cmd)
104 try:
104 try:
105 opts = dict(
105 opts = dict(
106 shell=isinstance(cmd, basestring),
106 shell=isinstance(cmd, basestring),
107 stdout=PIPE,
107 stdout=PIPE,
108 stderr=PIPE,
108 stderr=PIPE,
109 env=gitenv,
109 env=gitenv,
110 )
110 )
111 if os.path.isdir(self.path):
111 if os.path.isdir(self.path):
112 opts['cwd'] = self.path
112 opts['cwd'] = self.path
113 p = Popen(cmd, **opts)
113 p = Popen(cmd, **opts)
114 except OSError, err:
114 except OSError, err:
115 raise RepositoryError("Couldn't run git command (%s).\n"
115 raise RepositoryError("Couldn't run git command (%s).\n"
116 "Original error was:%s" % (cmd, err))
116 "Original error was:%s" % (cmd, err))
117 so, se = p.communicate()
117 so, se = p.communicate()
118 if not se.startswith("fatal: bad default revision 'HEAD'") and \
118 if not se.startswith("fatal: bad default revision 'HEAD'") and \
119 p.returncode != 0:
119 p.returncode != 0:
120 raise RepositoryError("Couldn't run git command (%s).\n"
120 raise RepositoryError("Couldn't run git command (%s).\n"
121 "stderr:\n%s" % (cmd, se))
121 "stderr:\n%s" % (cmd, se))
122 return so, se
122 return so, se
123
123
124 def _check_url(self, url):
124 def _check_url(self, url):
125 """
125 """
126 Functon will check given url and try to verify if it's a valid
126 Functon will check given url and try to verify if it's a valid
127 link. Sometimes it may happened that mercurial will issue basic
127 link. Sometimes it may happened that mercurial will issue basic
128 auth request that can cause whole API to hang when used from python
128 auth request that can cause whole API to hang when used from python
129 or other external calls.
129 or other external calls.
130
130
131 On failures it'll raise urllib2.HTTPError
131 On failures it'll raise urllib2.HTTPError
132 """
132 """
133
133
134 #TODO: implement this
134 #TODO: implement this
135 pass
135 pass
136
136
137 def _get_repo(self, create, src_url=None, update_after_clone=False,
137 def _get_repo(self, create, src_url=None, update_after_clone=False,
138 bare=False):
138 bare=False):
139 if create and os.path.exists(self.path):
139 if create and os.path.exists(self.path):
140 raise RepositoryError("Location already exist")
140 raise RepositoryError("Location already exist")
141 if src_url and not create:
141 if src_url and not create:
142 raise RepositoryError("Create should be set to True if src_url is "
142 raise RepositoryError("Create should be set to True if src_url is "
143 "given (clone operation creates repository)")
143 "given (clone operation creates repository)")
144 try:
144 try:
145 if create and src_url:
145 if create and src_url:
146 self._check_url(src_url)
146 self._check_url(src_url)
147 self.clone(src_url, update_after_clone, bare)
147 self.clone(src_url, update_after_clone, bare)
148 return Repo(self.path)
148 return Repo(self.path)
149 elif create:
149 elif create:
150 os.mkdir(self.path)
150 os.mkdir(self.path)
151 if bare:
151 if bare:
152 return Repo.init_bare(self.path)
152 return Repo.init_bare(self.path)
153 else:
153 else:
154 return Repo.init(self.path)
154 return Repo.init(self.path)
155 else:
155 else:
156 return Repo(self.path)
156 return Repo(self.path)
157 except (NotGitRepository, OSError), err:
157 except (NotGitRepository, OSError), err:
158 raise RepositoryError(err)
158 raise RepositoryError(err)
159
159
160 def _get_all_revisions(self):
160 def _get_all_revisions(self):
161 cmd = 'rev-list --all --date-order'
161 cmd = 'rev-list --all --date-order'
162 try:
162 try:
163 so, se = self.run_git_command(cmd)
163 so, se = self.run_git_command(cmd)
164 except RepositoryError:
164 except RepositoryError:
165 # Can be raised for empty repositories
165 # Can be raised for empty repositories
166 return []
166 return []
167 revisions = so.splitlines()
167 revisions = so.splitlines()
168 revisions.reverse()
168 revisions.reverse()
169 return revisions
169 return revisions
170
170
171 def _get_revision(self, revision):
171 def _get_revision(self, revision):
172 """
172 """
173 For git backend we always return integer here. This way we ensure
173 For git backend we always return integer here. This way we ensure
174 that changset's revision attribute would become integer.
174 that changset's revision attribute would become integer.
175 """
175 """
176 pattern = re.compile(r'^[[0-9a-fA-F]{12}|[0-9a-fA-F]{40}]$')
176 pattern = re.compile(r'^[[0-9a-fA-F]{12}|[0-9a-fA-F]{40}]$')
177 is_bstr = lambda o: isinstance(o, (str, unicode))
177 is_bstr = lambda o: isinstance(o, (str, unicode))
178 is_null = lambda o: len(o) == revision.count('0')
178 is_null = lambda o: len(o) == revision.count('0')
179
179
180 if len(self.revisions) == 0:
180 if len(self.revisions) == 0:
181 raise EmptyRepositoryError("There are no changesets yet")
181 raise EmptyRepositoryError("There are no changesets yet")
182
182
183 if revision in (None, '', 'tip', 'HEAD', 'head', -1):
183 if revision in (None, '', 'tip', 'HEAD', 'head', -1):
184 revision = self.revisions[-1]
184 revision = self.revisions[-1]
185
185
186 if ((is_bstr(revision) and revision.isdigit() and len(revision) < 12)
186 if ((is_bstr(revision) and revision.isdigit() and len(revision) < 12)
187 or isinstance(revision, int) or is_null(revision)):
187 or isinstance(revision, int) or is_null(revision)):
188 try:
188 try:
189 revision = self.revisions[int(revision)]
189 revision = self.revisions[int(revision)]
190 except:
190 except:
191 raise ChangesetDoesNotExistError("Revision %r does not exist "
191 raise ChangesetDoesNotExistError("Revision %r does not exist "
192 "for this repository %s" % (revision, self))
192 "for this repository %s" % (revision, self))
193
193
194 elif is_bstr(revision):
194 elif is_bstr(revision):
195 if not pattern.match(revision) or revision not in self.revisions:
195 if not pattern.match(revision) or revision not in self.revisions:
196 raise ChangesetDoesNotExistError("Revision %r does not exist "
196 raise ChangesetDoesNotExistError("Revision %r does not exist "
197 "for this repository %s" % (revision, self))
197 "for this repository %s" % (revision, self))
198
198
199 # Ensure we return full id
199 # Ensure we return full id
200 if not pattern.match(str(revision)):
200 if not pattern.match(str(revision)):
201 raise ChangesetDoesNotExistError("Given revision %r not recognized"
201 raise ChangesetDoesNotExistError("Given revision %r not recognized"
202 % revision)
202 % revision)
203 return revision
203 return revision
204
204
205 def _get_archives(self, archive_name='tip'):
205 def _get_archives(self, archive_name='tip'):
206
206
207 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
207 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
208 yield {"type": i[0], "extension": i[1], "node": archive_name}
208 yield {"type": i[0], "extension": i[1], "node": archive_name}
209
209
210 def _get_url(self, url):
210 def _get_url(self, url):
211 """
211 """
212 Returns normalized url. If schema is not given, would fall to
212 Returns normalized url. If schema is not given, would fall to
213 filesystem (``file:///``) schema.
213 filesystem (``file:///``) schema.
214 """
214 """
215 url = str(url)
215 url = str(url)
216 if url != 'default' and not '://' in url:
216 if url != 'default' and not '://' in url:
217 url = ':///'.join(('file', url))
217 url = ':///'.join(('file', url))
218 return url
218 return url
219
219
220 @LazyProperty
220 @LazyProperty
221 def name(self):
221 def name(self):
222 return os.path.basename(self.path)
222 return os.path.basename(self.path)
223
223
224 @LazyProperty
224 @LazyProperty
225 def last_change(self):
225 def last_change(self):
226 """
226 """
227 Returns last change made on this repository as datetime object
227 Returns last change made on this repository as datetime object
228 """
228 """
229 return date_fromtimestamp(self._get_mtime(), makedate()[1])
229 return date_fromtimestamp(self._get_mtime(), makedate()[1])
230
230
231 def _get_mtime(self):
231 def _get_mtime(self):
232 try:
232 try:
233 return time.mktime(self.get_changeset().date.timetuple())
233 return time.mktime(self.get_changeset().date.timetuple())
234 except RepositoryError:
234 except RepositoryError:
235 idx_loc = '' if self.bare else '.git'
235 idx_loc = '' if self.bare else '.git'
236 # fallback to filesystem
236 # fallback to filesystem
237 in_path = os.path.join(self.path, idx_loc, "index")
237 in_path = os.path.join(self.path, idx_loc, "index")
238 he_path = os.path.join(self.path, idx_loc, "HEAD")
238 he_path = os.path.join(self.path, idx_loc, "HEAD")
239 if os.path.exists(in_path):
239 if os.path.exists(in_path):
240 return os.stat(in_path).st_mtime
240 return os.stat(in_path).st_mtime
241 else:
241 else:
242 return os.stat(he_path).st_mtime
242 return os.stat(he_path).st_mtime
243
243
244 @LazyProperty
244 @LazyProperty
245 def description(self):
245 def description(self):
246 idx_loc = '' if self.bare else '.git'
246 idx_loc = '' if self.bare else '.git'
247 undefined_description = u'unknown'
247 undefined_description = u'unknown'
248 description_path = os.path.join(self.path, idx_loc, 'description')
248 description_path = os.path.join(self.path, idx_loc, 'description')
249 if os.path.isfile(description_path):
249 if os.path.isfile(description_path):
250 return safe_unicode(open(description_path).read())
250 return safe_unicode(open(description_path).read())
251 else:
251 else:
252 return undefined_description
252 return undefined_description
253
253
254 @LazyProperty
254 @LazyProperty
255 def contact(self):
255 def contact(self):
256 undefined_contact = u'Unknown'
256 undefined_contact = u'Unknown'
257 return undefined_contact
257 return undefined_contact
258
258
259 @property
259 @property
260 def branches(self):
260 def branches(self):
261 if not self.revisions:
261 if not self.revisions:
262 return {}
262 return {}
263 refs = self._repo.refs.as_dict()
263 refs = self._repo.refs.as_dict()
264 sortkey = lambda ctx: ctx[0]
264 sortkey = lambda ctx: ctx[0]
265 _branches = [('/'.join(ref.split('/')[2:]), head)
265 _branches = [('/'.join(ref.split('/')[2:]), head)
266 for ref, head in refs.items()
266 for ref, head in refs.items()
267 if ref.startswith('refs/heads/') and not ref.endswith('/HEAD')]
267 if ref.startswith('refs/heads/') and not ref.endswith('/HEAD')]
268 return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
268 return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
269
269
270 def _heads(self, reverse=False):
270 def _heads(self, reverse=False):
271 refs = self._repo.get_refs()
271 refs = self._repo.get_refs()
272 heads = {}
272 heads = {}
273
273
274 for key, val in refs.items():
274 for key, val in refs.items():
275 for ref_key in ['refs/heads/', 'refs/remotes/origin/']:
275 for ref_key in ['refs/heads/', 'refs/remotes/origin/']:
276 if key.startswith(ref_key):
276 if key.startswith(ref_key):
277 n = key[len(ref_key):]
277 n = key[len(ref_key):]
278 if n not in ['HEAD']:
278 if n not in ['HEAD']:
279 heads[n] = val
279 heads[n] = val
280
280
281 return heads if reverse else dict((y,x) for x,y in heads.iteritems())
281 return heads if reverse else dict((y,x) for x,y in heads.iteritems())
282
282
283 def _get_tags(self):
283 def _get_tags(self):
284 if not self.revisions:
284 if not self.revisions:
285 return {}
285 return {}
286 sortkey = lambda ctx: ctx[0]
286 sortkey = lambda ctx: ctx[0]
287 _tags = [('/'.join(ref.split('/')[2:]), head) for ref, head in
287 _tags = [('/'.join(ref.split('/')[2:]), head) for ref, head in
288 self._repo.get_refs().items() if ref.startswith('refs/tags/')]
288 self._repo.get_refs().items() if ref.startswith('refs/tags/')]
289 return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
289 return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
290
290
291 @LazyProperty
291 @LazyProperty
292 def tags(self):
292 def tags(self):
293 return self._get_tags()
293 return self._get_tags()
294
294
295 def tag(self, name, user, revision=None, message=None, date=None,
295 def tag(self, name, user, revision=None, message=None, date=None,
296 **kwargs):
296 **kwargs):
297 """
297 """
298 Creates and returns a tag for the given ``revision``.
298 Creates and returns a tag for the given ``revision``.
299
299
300 :param name: name for new tag
300 :param name: name for new tag
301 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
301 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
302 :param revision: changeset id for which new tag would be created
302 :param revision: changeset id for which new tag would be created
303 :param message: message of the tag's commit
303 :param message: message of the tag's commit
304 :param date: date of tag's commit
304 :param date: date of tag's commit
305
305
306 :raises TagAlreadyExistError: if tag with same name already exists
306 :raises TagAlreadyExistError: if tag with same name already exists
307 """
307 """
308 if name in self.tags:
308 if name in self.tags:
309 raise TagAlreadyExistError("Tag %s already exists" % name)
309 raise TagAlreadyExistError("Tag %s already exists" % name)
310 changeset = self.get_changeset(revision)
310 changeset = self.get_changeset(revision)
311 message = message or "Added tag %s for commit %s" % (name,
311 message = message or "Added tag %s for commit %s" % (name,
312 changeset.raw_id)
312 changeset.raw_id)
313 self._repo.refs["refs/tags/%s" % name] = changeset._commit.id
313 self._repo.refs["refs/tags/%s" % name] = changeset._commit.id
314
314
315 self.tags = self._get_tags()
315 self.tags = self._get_tags()
316 return changeset
316 return changeset
317
317
318 def remove_tag(self, name, user, message=None, date=None):
318 def remove_tag(self, name, user, message=None, date=None):
319 """
319 """
320 Removes tag with the given ``name``.
320 Removes tag with the given ``name``.
321
321
322 :param name: name of the tag to be removed
322 :param name: name of the tag to be removed
323 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
323 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
324 :param message: message of the tag's removal commit
324 :param message: message of the tag's removal commit
325 :param date: date of tag's removal commit
325 :param date: date of tag's removal commit
326
326
327 :raises TagDoesNotExistError: if tag with given name does not exists
327 :raises TagDoesNotExistError: if tag with given name does not exists
328 """
328 """
329 if name not in self.tags:
329 if name not in self.tags:
330 raise TagDoesNotExistError("Tag %s does not exist" % name)
330 raise TagDoesNotExistError("Tag %s does not exist" % name)
331 tagpath = posixpath.join(self._repo.refs.path, 'refs', 'tags', name)
331 tagpath = posixpath.join(self._repo.refs.path, 'refs', 'tags', name)
332 try:
332 try:
333 os.remove(tagpath)
333 os.remove(tagpath)
334 self.tags = self._get_tags()
334 self.tags = self._get_tags()
335 except OSError, e:
335 except OSError, e:
336 raise RepositoryError(e.strerror)
336 raise RepositoryError(e.strerror)
337
337
338 def get_changeset(self, revision=None):
338 def get_changeset(self, revision=None):
339 """
339 """
340 Returns ``GitChangeset`` object representing commit from git repository
340 Returns ``GitChangeset`` object representing commit from git repository
341 at the given revision or head (most recent commit) if None given.
341 at the given revision or head (most recent commit) if None given.
342 """
342 """
343 if isinstance(revision, GitChangeset):
343 if isinstance(revision, GitChangeset):
344 return revision
344 return revision
345 revision = self._get_revision(revision)
345 revision = self._get_revision(revision)
346 changeset = GitChangeset(repository=self, revision=revision)
346 changeset = GitChangeset(repository=self, revision=revision)
347 return changeset
347 return changeset
348
348
349 def get_changesets(self, start=None, end=None, start_date=None,
349 def get_changesets(self, start=None, end=None, start_date=None,
350 end_date=None, branch_name=None, reverse=False):
350 end_date=None, branch_name=None, reverse=False):
351 """
351 """
352 Returns iterator of ``GitChangeset`` objects from start to end (both
352 Returns iterator of ``GitChangeset`` objects from start to end (both
353 are inclusive), in ascending date order (unless ``reverse`` is set).
353 are inclusive), in ascending date order (unless ``reverse`` is set).
354
354
355 :param start: changeset ID, as str; first returned changeset
355 :param start: changeset ID, as str; first returned changeset
356 :param end: changeset ID, as str; last returned changeset
356 :param end: changeset ID, as str; last returned changeset
357 :param start_date: if specified, changesets with commit date less than
357 :param start_date: if specified, changesets with commit date less than
358 ``start_date`` would be filtered out from returned set
358 ``start_date`` would be filtered out from returned set
359 :param end_date: if specified, changesets with commit date greater than
359 :param end_date: if specified, changesets with commit date greater than
360 ``end_date`` would be filtered out from returned set
360 ``end_date`` would be filtered out from returned set
361 :param branch_name: if specified, changesets not reachable from given
361 :param branch_name: if specified, changesets not reachable from given
362 branch would be filtered out from returned set
362 branch would be filtered out from returned set
363 :param reverse: if ``True``, returned generator would be reversed
363 :param reverse: if ``True``, returned generator would be reversed
364 (meaning that returned changesets would have descending date order)
364 (meaning that returned changesets would have descending date order)
365
365
366 :raise BranchDoesNotExistError: If given ``branch_name`` does not
366 :raise BranchDoesNotExistError: If given ``branch_name`` does not
367 exist.
367 exist.
368 :raise ChangesetDoesNotExistError: If changeset for given ``start`` or
368 :raise ChangesetDoesNotExistError: If changeset for given ``start`` or
369 ``end`` could not be found.
369 ``end`` could not be found.
370
370
371 """
371 """
372 if branch_name and branch_name not in self.branches:
372 if branch_name and branch_name not in self.branches:
373 raise BranchDoesNotExistError("Branch '%s' not found" \
373 raise BranchDoesNotExistError("Branch '%s' not found" \
374 % branch_name)
374 % branch_name)
375 # %H at format means (full) commit hash, initial hashes are retrieved
375 # %H at format means (full) commit hash, initial hashes are retrieved
376 # in ascending date order
376 # in ascending date order
377 cmd_template = 'log --date-order --reverse --pretty=format:"%H"'
377 cmd_template = 'log --date-order --reverse --pretty=format:"%H"'
378 cmd_params = {}
378 cmd_params = {}
379 if start_date:
379 if start_date:
380 cmd_template += ' --since "$since"'
380 cmd_template += ' --since "$since"'
381 cmd_params['since'] = start_date.strftime('%m/%d/%y %H:%M:%S')
381 cmd_params['since'] = start_date.strftime('%m/%d/%y %H:%M:%S')
382 if end_date:
382 if end_date:
383 cmd_template += ' --until "$until"'
383 cmd_template += ' --until "$until"'
384 cmd_params['until'] = end_date.strftime('%m/%d/%y %H:%M:%S')
384 cmd_params['until'] = end_date.strftime('%m/%d/%y %H:%M:%S')
385 if branch_name:
385 if branch_name:
386 cmd_template += ' $branch_name'
386 cmd_template += ' $branch_name'
387 cmd_params['branch_name'] = branch_name
387 cmd_params['branch_name'] = branch_name
388 else:
388 else:
389 cmd_template += ' --all'
389 cmd_template += ' --all'
390
390
391 cmd = Template(cmd_template).safe_substitute(**cmd_params)
391 cmd = Template(cmd_template).safe_substitute(**cmd_params)
392 revs = self.run_git_command(cmd)[0].splitlines()
392 revs = self.run_git_command(cmd)[0].splitlines()
393 start_pos = 0
393 start_pos = 0
394 end_pos = len(revs)
394 end_pos = len(revs)
395 if start:
395 if start:
396 _start = self._get_revision(start)
396 _start = self._get_revision(start)
397 try:
397 try:
398 start_pos = revs.index(_start)
398 start_pos = revs.index(_start)
399 except ValueError:
399 except ValueError:
400 pass
400 pass
401
401
402 if end is not None:
402 if end is not None:
403 _end = self._get_revision(end)
403 _end = self._get_revision(end)
404 try:
404 try:
405 end_pos = revs.index(_end)
405 end_pos = revs.index(_end)
406 except ValueError:
406 except ValueError:
407 pass
407 pass
408
408
409 if None not in [start, end] and start_pos > end_pos:
409 if None not in [start, end] and start_pos > end_pos:
410 raise RepositoryError('start cannot be after end')
410 raise RepositoryError('start cannot be after end')
411
411
412 if end_pos is not None:
412 if end_pos is not None:
413 end_pos += 1
413 end_pos += 1
414
414
415 revs = revs[start_pos:end_pos]
415 revs = revs[start_pos:end_pos]
416 if reverse:
416 if reverse:
417 revs = reversed(revs)
417 revs = reversed(revs)
418 for rev in revs:
418 for rev in revs:
419 yield self.get_changeset(rev)
419 yield self.get_changeset(rev)
420
420
421 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
421 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
422 context=3):
422 context=3):
423 """
423 """
424 Returns (git like) *diff*, as plain text. Shows changes introduced by
424 Returns (git like) *diff*, as plain text. Shows changes introduced by
425 ``rev2`` since ``rev1``.
425 ``rev2`` since ``rev1``.
426
426
427 :param rev1: Entry point from which diff is shown. Can be
427 :param rev1: Entry point from which diff is shown. Can be
428 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
428 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
429 the changes since empty state of the repository until ``rev2``
429 the changes since empty state of the repository until ``rev2``
430 :param rev2: Until which revision changes should be shown.
430 :param rev2: Until which revision changes should be shown.
431 :param ignore_whitespace: If set to ``True``, would not show whitespace
431 :param ignore_whitespace: If set to ``True``, would not show whitespace
432 changes. Defaults to ``False``.
432 changes. Defaults to ``False``.
433 :param context: How many lines before/after changed lines should be
433 :param context: How many lines before/after changed lines should be
434 shown. Defaults to ``3``.
434 shown. Defaults to ``3``.
435 """
435 """
436 flags = ['-U%s' % context]
436 flags = ['-U%s' % context]
437 if ignore_whitespace:
437 if ignore_whitespace:
438 flags.append('-w')
438 flags.append('-w')
439
439
440 if rev1 == self.EMPTY_CHANGESET:
440 if rev1 == self.EMPTY_CHANGESET:
441 rev2 = self.get_changeset(rev2).raw_id
441 rev2 = self.get_changeset(rev2).raw_id
442 cmd = ' '.join(['show'] + flags + [rev2])
442 cmd = ' '.join(['show'] + flags + [rev2])
443 else:
443 else:
444 rev1 = self.get_changeset(rev1).raw_id
444 rev1 = self.get_changeset(rev1).raw_id
445 rev2 = self.get_changeset(rev2).raw_id
445 rev2 = self.get_changeset(rev2).raw_id
446 cmd = ' '.join(['diff'] + flags + [rev1, rev2])
446 cmd = ' '.join(['diff'] + flags + [rev1, rev2])
447
447
448 if path:
448 if path:
449 cmd += ' -- "%s"' % path
449 cmd += ' -- "%s"' % path
450 stdout, stderr = self.run_git_command(cmd)
450 stdout, stderr = self.run_git_command(cmd)
451 # If we used 'show' command, strip first few lines (until actual diff
451 # If we used 'show' command, strip first few lines (until actual diff
452 # starts)
452 # starts)
453 if rev1 == self.EMPTY_CHANGESET:
453 if rev1 == self.EMPTY_CHANGESET:
454 lines = stdout.splitlines()
454 lines = stdout.splitlines()
455 x = 0
455 x = 0
456 for line in lines:
456 for line in lines:
457 if line.startswith('diff'):
457 if line.startswith('diff'):
458 break
458 break
459 x += 1
459 x += 1
460 # Append new line just like 'diff' command do
460 # Append new line just like 'diff' command do
461 stdout = '\n'.join(lines[x:]) + '\n'
461 stdout = '\n'.join(lines[x:]) + '\n'
462 return stdout
462 return stdout
463
463
464 @LazyProperty
464 @LazyProperty
465 def in_memory_changeset(self):
465 def in_memory_changeset(self):
466 """
466 """
467 Returns ``GitInMemoryChangeset`` object for this repository.
467 Returns ``GitInMemoryChangeset`` object for this repository.
468 """
468 """
469 return GitInMemoryChangeset(self)
469 return GitInMemoryChangeset(self)
470
470
471 def clone(self, url, update_after_clone=True, bare=False):
471 def clone(self, url, update_after_clone=True, bare=False):
472 """
472 """
473 Tries to clone changes from external location.
473 Tries to clone changes from external location.
474
474
475 :param update_after_clone: If set to ``False``, git won't checkout
475 :param update_after_clone: If set to ``False``, git won't checkout
476 working directory
476 working directory
477 :param bare: If set to ``True``, repository would be cloned into
477 :param bare: If set to ``True``, repository would be cloned into
478 *bare* git repository (no working directory at all).
478 *bare* git repository (no working directory at all).
479 """
479 """
480 url = self._get_url(url)
480 url = self._get_url(url)
481 cmd = ['clone']
481 cmd = ['clone']
482 if bare:
482 if bare:
483 cmd.append('--bare')
483 cmd.append('--bare')
484 elif not update_after_clone:
484 elif not update_after_clone:
485 cmd.append('--no-checkout')
485 cmd.append('--no-checkout')
486 cmd += ['--', '"%s"' % url, '"%s"' % self.path]
486 cmd += ['--', '"%s"' % url, '"%s"' % self.path]
487 cmd = ' '.join(cmd)
487 cmd = ' '.join(cmd)
488 # If error occurs run_git_command raises RepositoryError already
488 # If error occurs run_git_command raises RepositoryError already
489 self.run_git_command(cmd)
489 self.run_git_command(cmd)
490
490
491 def pull(self, url):
491 def pull(self, url):
492 """
492 """
493 Tries to pull changes from external location.
493 Tries to pull changes from external location.
494 """
494 """
495 url = self._get_url(url)
495 url = self._get_url(url)
496 cmd = ['pull']
496 cmd = ['pull']
497 cmd.append("--ff-only")
497 cmd.append("--ff-only")
498 cmd.append(url)
498 cmd.append(url)
499 cmd = ' '.join(cmd)
499 cmd = ' '.join(cmd)
500 # If error occurs run_git_command raises RepositoryError already
500 # If error occurs run_git_command raises RepositoryError already
501 self.run_git_command(cmd)
501 self.run_git_command(cmd)
502
502
503 def fetch(self, url):
504 """
505 Tries to pull changes from external location.
506 """
507 url = self._get_url(url)
508 cmd = ['fetch']
509 cmd.append(url)
510 cmd = ' '.join(cmd)
511 # If error occurs run_git_command raises RepositoryError already
512 self.run_git_command(cmd)
513
503 @LazyProperty
514 @LazyProperty
504 def workdir(self):
515 def workdir(self):
505 """
516 """
506 Returns ``Workdir`` instance for this repository.
517 Returns ``Workdir`` instance for this repository.
507 """
518 """
508 return GitWorkdir(self)
519 return GitWorkdir(self)
509
520
510 def get_config_value(self, section, name, config_file=None):
521 def get_config_value(self, section, name, config_file=None):
511 """
522 """
512 Returns configuration value for a given [``section``] and ``name``.
523 Returns configuration value for a given [``section``] and ``name``.
513
524
514 :param section: Section we want to retrieve value from
525 :param section: Section we want to retrieve value from
515 :param name: Name of configuration we want to retrieve
526 :param name: Name of configuration we want to retrieve
516 :param config_file: A path to file which should be used to retrieve
527 :param config_file: A path to file which should be used to retrieve
517 configuration from (might also be a list of file paths)
528 configuration from (might also be a list of file paths)
518 """
529 """
519 if config_file is None:
530 if config_file is None:
520 config_file = []
531 config_file = []
521 elif isinstance(config_file, basestring):
532 elif isinstance(config_file, basestring):
522 config_file = [config_file]
533 config_file = [config_file]
523
534
524 def gen_configs():
535 def gen_configs():
525 for path in config_file + self._config_files:
536 for path in config_file + self._config_files:
526 try:
537 try:
527 yield ConfigFile.from_path(path)
538 yield ConfigFile.from_path(path)
528 except (IOError, OSError, ValueError):
539 except (IOError, OSError, ValueError):
529 continue
540 continue
530
541
531 for config in gen_configs():
542 for config in gen_configs():
532 try:
543 try:
533 return config.get(section, name)
544 return config.get(section, name)
534 except KeyError:
545 except KeyError:
535 continue
546 continue
536 return None
547 return None
537
548
538 def get_user_name(self, config_file=None):
549 def get_user_name(self, config_file=None):
539 """
550 """
540 Returns user's name from global configuration file.
551 Returns user's name from global configuration file.
541
552
542 :param config_file: A path to file which should be used to retrieve
553 :param config_file: A path to file which should be used to retrieve
543 configuration from (might also be a list of file paths)
554 configuration from (might also be a list of file paths)
544 """
555 """
545 return self.get_config_value('user', 'name', config_file)
556 return self.get_config_value('user', 'name', config_file)
546
557
547 def get_user_email(self, config_file=None):
558 def get_user_email(self, config_file=None):
548 """
559 """
549 Returns user's email from global configuration file.
560 Returns user's email from global configuration file.
550
561
551 :param config_file: A path to file which should be used to retrieve
562 :param config_file: A path to file which should be used to retrieve
552 configuration from (might also be a list of file paths)
563 configuration from (might also be a list of file paths)
553 """
564 """
554 return self.get_config_value('user', 'email', config_file)
565 return self.get_config_value('user', 'email', config_file)
@@ -1,472 +1,474 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.scm
3 rhodecode.model.scm
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Scm model for RhodeCode
6 Scm model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 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 import os
25 import os
26 import time
26 import time
27 import traceback
27 import traceback
28 import logging
28 import logging
29 import cStringIO
29 import cStringIO
30
30
31 from sqlalchemy import func
31 from sqlalchemy import func
32
32
33 from rhodecode.lib.vcs import get_backend
33 from rhodecode.lib.vcs import get_backend
34 from rhodecode.lib.vcs.exceptions import RepositoryError
34 from rhodecode.lib.vcs.exceptions import RepositoryError
35 from rhodecode.lib.vcs.utils.lazy import LazyProperty
35 from rhodecode.lib.vcs.utils.lazy import LazyProperty
36 from rhodecode.lib.vcs.nodes import FileNode
36 from rhodecode.lib.vcs.nodes import FileNode
37
37
38 from rhodecode import BACKENDS
38 from rhodecode import BACKENDS
39 from rhodecode.lib import helpers as h
39 from rhodecode.lib import helpers as h
40 from rhodecode.lib.utils2 import safe_str, safe_unicode
40 from rhodecode.lib.utils2 import safe_str, safe_unicode
41 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
41 from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
42 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
42 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
43 action_logger, EmptyChangeset, REMOVED_REPO_PAT
43 action_logger, EmptyChangeset, REMOVED_REPO_PAT
44 from rhodecode.model import BaseModel
44 from rhodecode.model import BaseModel
45 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
45 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
46 UserFollowing, UserLog, User, RepoGroup
46 UserFollowing, UserLog, User, RepoGroup
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50
50
51 class UserTemp(object):
51 class UserTemp(object):
52 def __init__(self, user_id):
52 def __init__(self, user_id):
53 self.user_id = user_id
53 self.user_id = user_id
54
54
55 def __repr__(self):
55 def __repr__(self):
56 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
56 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
57
57
58
58
59 class RepoTemp(object):
59 class RepoTemp(object):
60 def __init__(self, repo_id):
60 def __init__(self, repo_id):
61 self.repo_id = repo_id
61 self.repo_id = repo_id
62
62
63 def __repr__(self):
63 def __repr__(self):
64 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
64 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
65
65
66
66
67 class CachedRepoList(object):
67 class CachedRepoList(object):
68
68
69 def __init__(self, db_repo_list, repos_path, order_by=None):
69 def __init__(self, db_repo_list, repos_path, order_by=None):
70 self.db_repo_list = db_repo_list
70 self.db_repo_list = db_repo_list
71 self.repos_path = repos_path
71 self.repos_path = repos_path
72 self.order_by = order_by
72 self.order_by = order_by
73 self.reversed = (order_by or '').startswith('-')
73 self.reversed = (order_by or '').startswith('-')
74
74
75 def __len__(self):
75 def __len__(self):
76 return len(self.db_repo_list)
76 return len(self.db_repo_list)
77
77
78 def __repr__(self):
78 def __repr__(self):
79 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
79 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
80
80
81 def __iter__(self):
81 def __iter__(self):
82 # pre-propagated cache_map to save executing select statements
82 # pre-propagated cache_map to save executing select statements
83 # for each repo
83 # for each repo
84 cache_map = CacheInvalidation.get_cache_map()
84 cache_map = CacheInvalidation.get_cache_map()
85
85
86 for dbr in self.db_repo_list:
86 for dbr in self.db_repo_list:
87 scmr = dbr.scm_instance_cached(cache_map)
87 scmr = dbr.scm_instance_cached(cache_map)
88 # check permission at this level
88 # check permission at this level
89 if not HasRepoPermissionAny(
89 if not HasRepoPermissionAny(
90 'repository.read', 'repository.write', 'repository.admin'
90 'repository.read', 'repository.write', 'repository.admin'
91 )(dbr.repo_name, 'get repo check'):
91 )(dbr.repo_name, 'get repo check'):
92 continue
92 continue
93
93
94 if scmr is None:
94 if scmr is None:
95 log.error(
95 log.error(
96 '%s this repository is present in database but it '
96 '%s this repository is present in database but it '
97 'cannot be created as an scm instance' % dbr.repo_name
97 'cannot be created as an scm instance' % dbr.repo_name
98 )
98 )
99 continue
99 continue
100
100
101 last_change = scmr.last_change
101 last_change = scmr.last_change
102 tip = h.get_changeset_safe(scmr, 'tip')
102 tip = h.get_changeset_safe(scmr, 'tip')
103
103
104 tmp_d = {}
104 tmp_d = {}
105 tmp_d['name'] = dbr.repo_name
105 tmp_d['name'] = dbr.repo_name
106 tmp_d['name_sort'] = tmp_d['name'].lower()
106 tmp_d['name_sort'] = tmp_d['name'].lower()
107 tmp_d['description'] = dbr.description
107 tmp_d['description'] = dbr.description
108 tmp_d['description_sort'] = tmp_d['description']
108 tmp_d['description_sort'] = tmp_d['description']
109 tmp_d['last_change'] = last_change
109 tmp_d['last_change'] = last_change
110 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
110 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
111 tmp_d['tip'] = tip.raw_id
111 tmp_d['tip'] = tip.raw_id
112 tmp_d['tip_sort'] = tip.revision
112 tmp_d['tip_sort'] = tip.revision
113 tmp_d['rev'] = tip.revision
113 tmp_d['rev'] = tip.revision
114 tmp_d['contact'] = dbr.user.full_contact
114 tmp_d['contact'] = dbr.user.full_contact
115 tmp_d['contact_sort'] = tmp_d['contact']
115 tmp_d['contact_sort'] = tmp_d['contact']
116 tmp_d['owner_sort'] = tmp_d['contact']
116 tmp_d['owner_sort'] = tmp_d['contact']
117 tmp_d['repo_archives'] = list(scmr._get_archives())
117 tmp_d['repo_archives'] = list(scmr._get_archives())
118 tmp_d['last_msg'] = tip.message
118 tmp_d['last_msg'] = tip.message
119 tmp_d['author'] = tip.author
119 tmp_d['author'] = tip.author
120 tmp_d['dbrepo'] = dbr.get_dict()
120 tmp_d['dbrepo'] = dbr.get_dict()
121 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
121 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
122 yield tmp_d
122 yield tmp_d
123
123
124
124
125 class GroupList(object):
125 class GroupList(object):
126
126
127 def __init__(self, db_repo_group_list):
127 def __init__(self, db_repo_group_list):
128 self.db_repo_group_list = db_repo_group_list
128 self.db_repo_group_list = db_repo_group_list
129
129
130 def __len__(self):
130 def __len__(self):
131 return len(self.db_repo_group_list)
131 return len(self.db_repo_group_list)
132
132
133 def __repr__(self):
133 def __repr__(self):
134 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
134 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
135
135
136 def __iter__(self):
136 def __iter__(self):
137 for dbgr in self.db_repo_group_list:
137 for dbgr in self.db_repo_group_list:
138 # check permission at this level
138 # check permission at this level
139 if not HasReposGroupPermissionAny(
139 if not HasReposGroupPermissionAny(
140 'group.read', 'group.write', 'group.admin'
140 'group.read', 'group.write', 'group.admin'
141 )(dbgr.group_name, 'get group repo check'):
141 )(dbgr.group_name, 'get group repo check'):
142 continue
142 continue
143
143
144 yield dbgr
144 yield dbgr
145
145
146
146
147 class ScmModel(BaseModel):
147 class ScmModel(BaseModel):
148 """
148 """
149 Generic Scm Model
149 Generic Scm Model
150 """
150 """
151
151
152 def __get_repo(self, instance):
152 def __get_repo(self, instance):
153 cls = Repository
153 cls = Repository
154 if isinstance(instance, cls):
154 if isinstance(instance, cls):
155 return instance
155 return instance
156 elif isinstance(instance, int) or str(instance).isdigit():
156 elif isinstance(instance, int) or str(instance).isdigit():
157 return cls.get(instance)
157 return cls.get(instance)
158 elif isinstance(instance, basestring):
158 elif isinstance(instance, basestring):
159 return cls.get_by_repo_name(instance)
159 return cls.get_by_repo_name(instance)
160 elif instance:
160 elif instance:
161 raise Exception('given object must be int, basestr or Instance'
161 raise Exception('given object must be int, basestr or Instance'
162 ' of %s got %s' % (type(cls), type(instance)))
162 ' of %s got %s' % (type(cls), type(instance)))
163
163
164 @LazyProperty
164 @LazyProperty
165 def repos_path(self):
165 def repos_path(self):
166 """
166 """
167 Get's the repositories root path from database
167 Get's the repositories root path from database
168 """
168 """
169
169
170 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
170 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
171
171
172 return q.ui_value
172 return q.ui_value
173
173
174 def repo_scan(self, repos_path=None):
174 def repo_scan(self, repos_path=None):
175 """
175 """
176 Listing of repositories in given path. This path should not be a
176 Listing of repositories in given path. This path should not be a
177 repository itself. Return a dictionary of repository objects
177 repository itself. Return a dictionary of repository objects
178
178
179 :param repos_path: path to directory containing repositories
179 :param repos_path: path to directory containing repositories
180 """
180 """
181
181
182 if repos_path is None:
182 if repos_path is None:
183 repos_path = self.repos_path
183 repos_path = self.repos_path
184
184
185 log.info('scanning for repositories in %s' % repos_path)
185 log.info('scanning for repositories in %s' % repos_path)
186
186
187 baseui = make_ui('db')
187 baseui = make_ui('db')
188 repos = {}
188 repos = {}
189
189
190 for name, path in get_filesystem_repos(repos_path, recursive=True):
190 for name, path in get_filesystem_repos(repos_path, recursive=True):
191 # skip removed repos
191 # skip removed repos
192 if REMOVED_REPO_PAT.match(name):
192 if REMOVED_REPO_PAT.match(name):
193 continue
193 continue
194
194
195 # name need to be decomposed and put back together using the /
195 # name need to be decomposed and put back together using the /
196 # since this is internal storage separator for rhodecode
196 # since this is internal storage separator for rhodecode
197 name = Repository.url_sep().join(name.split(os.sep))
197 name = Repository.url_sep().join(name.split(os.sep))
198
198
199 try:
199 try:
200 if name in repos:
200 if name in repos:
201 raise RepositoryError('Duplicate repository name %s '
201 raise RepositoryError('Duplicate repository name %s '
202 'found in %s' % (name, path))
202 'found in %s' % (name, path))
203 else:
203 else:
204
204
205 klass = get_backend(path[0])
205 klass = get_backend(path[0])
206
206
207 if path[0] == 'hg' and path[0] in BACKENDS.keys():
207 if path[0] == 'hg' and path[0] in BACKENDS.keys():
208 repos[name] = klass(safe_str(path[1]), baseui=baseui)
208 repos[name] = klass(safe_str(path[1]), baseui=baseui)
209
209
210 if path[0] == 'git' and path[0] in BACKENDS.keys():
210 if path[0] == 'git' and path[0] in BACKENDS.keys():
211 repos[name] = klass(path[1])
211 repos[name] = klass(path[1])
212 except OSError:
212 except OSError:
213 continue
213 continue
214
214
215 return repos
215 return repos
216
216
217 def get_repos(self, all_repos=None, sort_key=None):
217 def get_repos(self, all_repos=None, sort_key=None):
218 """
218 """
219 Get all repos from db and for each repo create it's
219 Get all repos from db and for each repo create it's
220 backend instance and fill that backed with information from database
220 backend instance and fill that backed with information from database
221
221
222 :param all_repos: list of repository names as strings
222 :param all_repos: list of repository names as strings
223 give specific repositories list, good for filtering
223 give specific repositories list, good for filtering
224 """
224 """
225 if all_repos is None:
225 if all_repos is None:
226 all_repos = self.sa.query(Repository)\
226 all_repos = self.sa.query(Repository)\
227 .filter(Repository.group_id == None)\
227 .filter(Repository.group_id == None)\
228 .order_by(func.lower(Repository.repo_name)).all()
228 .order_by(func.lower(Repository.repo_name)).all()
229
229
230 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
230 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
231 order_by=sort_key)
231 order_by=sort_key)
232
232
233 return repo_iter
233 return repo_iter
234
234
235 def get_repos_groups(self, all_groups=None):
235 def get_repos_groups(self, all_groups=None):
236 if all_groups is None:
236 if all_groups is None:
237 all_groups = RepoGroup.query()\
237 all_groups = RepoGroup.query()\
238 .filter(RepoGroup.group_parent_id == None).all()
238 .filter(RepoGroup.group_parent_id == None).all()
239 group_iter = GroupList(all_groups)
239 group_iter = GroupList(all_groups)
240
240
241 return group_iter
241 return group_iter
242
242
243 def mark_for_invalidation(self, repo_name):
243 def mark_for_invalidation(self, repo_name):
244 """
244 """
245 Puts cache invalidation task into db for
245 Puts cache invalidation task into db for
246 further global cache invalidation
246 further global cache invalidation
247
247
248 :param repo_name: this repo that should invalidation take place
248 :param repo_name: this repo that should invalidation take place
249 """
249 """
250 CacheInvalidation.set_invalidate(repo_name)
250 CacheInvalidation.set_invalidate(repo_name)
251
251
252 def toggle_following_repo(self, follow_repo_id, user_id):
252 def toggle_following_repo(self, follow_repo_id, user_id):
253
253
254 f = self.sa.query(UserFollowing)\
254 f = self.sa.query(UserFollowing)\
255 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
255 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
256 .filter(UserFollowing.user_id == user_id).scalar()
256 .filter(UserFollowing.user_id == user_id).scalar()
257
257
258 if f is not None:
258 if f is not None:
259 try:
259 try:
260 self.sa.delete(f)
260 self.sa.delete(f)
261 action_logger(UserTemp(user_id),
261 action_logger(UserTemp(user_id),
262 'stopped_following_repo',
262 'stopped_following_repo',
263 RepoTemp(follow_repo_id))
263 RepoTemp(follow_repo_id))
264 return
264 return
265 except:
265 except:
266 log.error(traceback.format_exc())
266 log.error(traceback.format_exc())
267 raise
267 raise
268
268
269 try:
269 try:
270 f = UserFollowing()
270 f = UserFollowing()
271 f.user_id = user_id
271 f.user_id = user_id
272 f.follows_repo_id = follow_repo_id
272 f.follows_repo_id = follow_repo_id
273 self.sa.add(f)
273 self.sa.add(f)
274
274
275 action_logger(UserTemp(user_id),
275 action_logger(UserTemp(user_id),
276 'started_following_repo',
276 'started_following_repo',
277 RepoTemp(follow_repo_id))
277 RepoTemp(follow_repo_id))
278 except:
278 except:
279 log.error(traceback.format_exc())
279 log.error(traceback.format_exc())
280 raise
280 raise
281
281
282 def toggle_following_user(self, follow_user_id, user_id):
282 def toggle_following_user(self, follow_user_id, user_id):
283 f = self.sa.query(UserFollowing)\
283 f = self.sa.query(UserFollowing)\
284 .filter(UserFollowing.follows_user_id == follow_user_id)\
284 .filter(UserFollowing.follows_user_id == follow_user_id)\
285 .filter(UserFollowing.user_id == user_id).scalar()
285 .filter(UserFollowing.user_id == user_id).scalar()
286
286
287 if f is not None:
287 if f is not None:
288 try:
288 try:
289 self.sa.delete(f)
289 self.sa.delete(f)
290 return
290 return
291 except:
291 except:
292 log.error(traceback.format_exc())
292 log.error(traceback.format_exc())
293 raise
293 raise
294
294
295 try:
295 try:
296 f = UserFollowing()
296 f = UserFollowing()
297 f.user_id = user_id
297 f.user_id = user_id
298 f.follows_user_id = follow_user_id
298 f.follows_user_id = follow_user_id
299 self.sa.add(f)
299 self.sa.add(f)
300 except:
300 except:
301 log.error(traceback.format_exc())
301 log.error(traceback.format_exc())
302 raise
302 raise
303
303
304 def is_following_repo(self, repo_name, user_id, cache=False):
304 def is_following_repo(self, repo_name, user_id, cache=False):
305 r = self.sa.query(Repository)\
305 r = self.sa.query(Repository)\
306 .filter(Repository.repo_name == repo_name).scalar()
306 .filter(Repository.repo_name == repo_name).scalar()
307
307
308 f = self.sa.query(UserFollowing)\
308 f = self.sa.query(UserFollowing)\
309 .filter(UserFollowing.follows_repository == r)\
309 .filter(UserFollowing.follows_repository == r)\
310 .filter(UserFollowing.user_id == user_id).scalar()
310 .filter(UserFollowing.user_id == user_id).scalar()
311
311
312 return f is not None
312 return f is not None
313
313
314 def is_following_user(self, username, user_id, cache=False):
314 def is_following_user(self, username, user_id, cache=False):
315 u = User.get_by_username(username)
315 u = User.get_by_username(username)
316
316
317 f = self.sa.query(UserFollowing)\
317 f = self.sa.query(UserFollowing)\
318 .filter(UserFollowing.follows_user == u)\
318 .filter(UserFollowing.follows_user == u)\
319 .filter(UserFollowing.user_id == user_id).scalar()
319 .filter(UserFollowing.user_id == user_id).scalar()
320
320
321 return f is not None
321 return f is not None
322
322
323 def get_followers(self, repo_id):
323 def get_followers(self, repo_id):
324 if not isinstance(repo_id, int):
324 if not isinstance(repo_id, int):
325 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
325 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
326
326
327 return self.sa.query(UserFollowing)\
327 return self.sa.query(UserFollowing)\
328 .filter(UserFollowing.follows_repo_id == repo_id).count()
328 .filter(UserFollowing.follows_repo_id == repo_id).count()
329
329
330 def get_forks(self, repo_id):
330 def get_forks(self, repo_id):
331 if not isinstance(repo_id, int):
331 if not isinstance(repo_id, int):
332 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
332 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
333
333
334 return self.sa.query(Repository)\
334 return self.sa.query(Repository)\
335 .filter(Repository.fork_id == repo_id).count()
335 .filter(Repository.fork_id == repo_id).count()
336
336
337 def mark_as_fork(self, repo, fork, user):
337 def mark_as_fork(self, repo, fork, user):
338 repo = self.__get_repo(repo)
338 repo = self.__get_repo(repo)
339 fork = self.__get_repo(fork)
339 fork = self.__get_repo(fork)
340 repo.fork = fork
340 repo.fork = fork
341 self.sa.add(repo)
341 self.sa.add(repo)
342 return repo
342 return repo
343
343
344 def pull_changes(self, repo_name, username):
344 def pull_changes(self, repo_name, username):
345 dbrepo = Repository.get_by_repo_name(repo_name)
345 dbrepo = Repository.get_by_repo_name(repo_name)
346 clone_uri = dbrepo.clone_uri
346 clone_uri = dbrepo.clone_uri
347 if not clone_uri:
347 if not clone_uri:
348 raise Exception("This repository doesn't have a clone uri")
348 raise Exception("This repository doesn't have a clone uri")
349
349
350 repo = dbrepo.scm_instance
350 repo = dbrepo.scm_instance
351 try:
351 try:
352 extras = {
352 extras = {
353 'ip': '',
353 'ip': '',
354 'username': username,
354 'username': username,
355 'action': 'push_remote',
355 'action': 'push_remote',
356 'repository': repo_name,
356 'repository': repo_name,
357 'scm': repo.alias,
357 'scm': repo.alias,
358 }
358 }
359
359
360 # inject ui extra param to log this action via push logger
360 # inject ui extra param to log this action via push logger
361 for k, v in extras.items():
361 for k, v in extras.items():
362 repo._repo.ui.setconfig('rhodecode_extras', k, v)
362 repo._repo.ui.setconfig('rhodecode_extras', k, v)
363
363 if repo.alias == 'git':
364 repo.pull(clone_uri)
364 repo.fetch(clone_uri)
365 else:
366 repo.pull(clone_uri)
365 self.mark_for_invalidation(repo_name)
367 self.mark_for_invalidation(repo_name)
366 except:
368 except:
367 log.error(traceback.format_exc())
369 log.error(traceback.format_exc())
368 raise
370 raise
369
371
370 def commit_change(self, repo, repo_name, cs, user, author, message,
372 def commit_change(self, repo, repo_name, cs, user, author, message,
371 content, f_path):
373 content, f_path):
372
374
373 if repo.alias == 'hg':
375 if repo.alias == 'hg':
374 from rhodecode.lib.vcs.backends.hg import \
376 from rhodecode.lib.vcs.backends.hg import \
375 MercurialInMemoryChangeset as IMC
377 MercurialInMemoryChangeset as IMC
376 elif repo.alias == 'git':
378 elif repo.alias == 'git':
377 from rhodecode.lib.vcs.backends.git import \
379 from rhodecode.lib.vcs.backends.git import \
378 GitInMemoryChangeset as IMC
380 GitInMemoryChangeset as IMC
379
381
380 # decoding here will force that we have proper encoded values
382 # decoding here will force that we have proper encoded values
381 # in any other case this will throw exceptions and deny commit
383 # in any other case this will throw exceptions and deny commit
382 content = safe_str(content)
384 content = safe_str(content)
383 path = safe_str(f_path)
385 path = safe_str(f_path)
384 # message and author needs to be unicode
386 # message and author needs to be unicode
385 # proper backend should then translate that into required type
387 # proper backend should then translate that into required type
386 message = safe_unicode(message)
388 message = safe_unicode(message)
387 author = safe_unicode(author)
389 author = safe_unicode(author)
388 m = IMC(repo)
390 m = IMC(repo)
389 m.change(FileNode(path, content))
391 m.change(FileNode(path, content))
390 tip = m.commit(message=message,
392 tip = m.commit(message=message,
391 author=author,
393 author=author,
392 parents=[cs], branch=cs.branch)
394 parents=[cs], branch=cs.branch)
393
395
394 new_cs = tip.short_id
396 new_cs = tip.short_id
395 action = 'push_local:%s' % new_cs
397 action = 'push_local:%s' % new_cs
396
398
397 action_logger(user, action, repo_name)
399 action_logger(user, action, repo_name)
398
400
399 self.mark_for_invalidation(repo_name)
401 self.mark_for_invalidation(repo_name)
400
402
401 def create_node(self, repo, repo_name, cs, user, author, message, content,
403 def create_node(self, repo, repo_name, cs, user, author, message, content,
402 f_path):
404 f_path):
403 if repo.alias == 'hg':
405 if repo.alias == 'hg':
404 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
406 from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
405 elif repo.alias == 'git':
407 elif repo.alias == 'git':
406 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
408 from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
407 # decoding here will force that we have proper encoded values
409 # decoding here will force that we have proper encoded values
408 # in any other case this will throw exceptions and deny commit
410 # in any other case this will throw exceptions and deny commit
409
411
410 if isinstance(content, (basestring,)):
412 if isinstance(content, (basestring,)):
411 content = safe_str(content)
413 content = safe_str(content)
412 elif isinstance(content, (file, cStringIO.OutputType,)):
414 elif isinstance(content, (file, cStringIO.OutputType,)):
413 content = content.read()
415 content = content.read()
414 else:
416 else:
415 raise Exception('Content is of unrecognized type %s' % (
417 raise Exception('Content is of unrecognized type %s' % (
416 type(content)
418 type(content)
417 ))
419 ))
418
420
419 message = safe_unicode(message)
421 message = safe_unicode(message)
420 author = safe_unicode(author)
422 author = safe_unicode(author)
421 path = safe_str(f_path)
423 path = safe_str(f_path)
422 m = IMC(repo)
424 m = IMC(repo)
423
425
424 if isinstance(cs, EmptyChangeset):
426 if isinstance(cs, EmptyChangeset):
425 # EmptyChangeset means we we're editing empty repository
427 # EmptyChangeset means we we're editing empty repository
426 parents = None
428 parents = None
427 else:
429 else:
428 parents = [cs]
430 parents = [cs]
429
431
430 m.add(FileNode(path, content=content))
432 m.add(FileNode(path, content=content))
431 tip = m.commit(message=message,
433 tip = m.commit(message=message,
432 author=author,
434 author=author,
433 parents=parents, branch=cs.branch)
435 parents=parents, branch=cs.branch)
434 new_cs = tip.short_id
436 new_cs = tip.short_id
435 action = 'push_local:%s' % new_cs
437 action = 'push_local:%s' % new_cs
436
438
437 action_logger(user, action, repo_name)
439 action_logger(user, action, repo_name)
438
440
439 self.mark_for_invalidation(repo_name)
441 self.mark_for_invalidation(repo_name)
440
442
441 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
443 def get_nodes(self, repo_name, revision, root_path='/', flat=True):
442 """
444 """
443 recursive walk in root dir and return a set of all path in that dir
445 recursive walk in root dir and return a set of all path in that dir
444 based on repository walk function
446 based on repository walk function
445
447
446 :param repo_name: name of repository
448 :param repo_name: name of repository
447 :param revision: revision for which to list nodes
449 :param revision: revision for which to list nodes
448 :param root_path: root path to list
450 :param root_path: root path to list
449 :param flat: return as a list, if False returns a dict with decription
451 :param flat: return as a list, if False returns a dict with decription
450
452
451 """
453 """
452 _files = list()
454 _files = list()
453 _dirs = list()
455 _dirs = list()
454 try:
456 try:
455 _repo = self.__get_repo(repo_name)
457 _repo = self.__get_repo(repo_name)
456 changeset = _repo.scm_instance.get_changeset(revision)
458 changeset = _repo.scm_instance.get_changeset(revision)
457 root_path = root_path.lstrip('/')
459 root_path = root_path.lstrip('/')
458 for topnode, dirs, files in changeset.walk(root_path):
460 for topnode, dirs, files in changeset.walk(root_path):
459 for f in files:
461 for f in files:
460 _files.append(f.path if flat else {"name": f.path,
462 _files.append(f.path if flat else {"name": f.path,
461 "type": "file"})
463 "type": "file"})
462 for d in dirs:
464 for d in dirs:
463 _dirs.append(d.path if flat else {"name": d.path,
465 _dirs.append(d.path if flat else {"name": d.path,
464 "type": "dir"})
466 "type": "dir"})
465 except RepositoryError:
467 except RepositoryError:
466 log.debug(traceback.format_exc())
468 log.debug(traceback.format_exc())
467 raise
469 raise
468
470
469 return _dirs, _files
471 return _dirs, _files
470
472
471 def get_unread_journal(self):
473 def get_unread_journal(self):
472 return self.sa.query(UserLog).count()
474 return self.sa.query(UserLog).count()
General Comments 0
You need to be logged in to leave comments. Login now