##// END OF EJS Templates
git: use bool condition to check if the git path is fine. Using exceptions...
marcink -
r2452:c74b82e7 default
parent child Browse files
Show More
@@ -1,947 +1,951 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2017 RhodeCode GmbH
3 # Copyright (C) 2014-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 GIT repository module
22 GIT repository module
23 """
23 """
24
24
25 import logging
25 import logging
26 import os
26 import os
27 import re
27 import re
28 import shutil
28 import shutil
29
29
30 from zope.cachedescriptors.property import Lazy as LazyProperty
30 from zope.cachedescriptors.property import Lazy as LazyProperty
31
31
32 from rhodecode.lib.compat import OrderedDict
32 from rhodecode.lib.compat import OrderedDict
33 from rhodecode.lib.datelib import (
33 from rhodecode.lib.datelib import (
34 utcdate_fromtimestamp, makedate, date_astimestamp)
34 utcdate_fromtimestamp, makedate, date_astimestamp)
35 from rhodecode.lib.utils import safe_unicode, safe_str
35 from rhodecode.lib.utils import safe_unicode, safe_str
36 from rhodecode.lib.vcs import connection, path as vcspath
36 from rhodecode.lib.vcs import connection, path as vcspath
37 from rhodecode.lib.vcs.backends.base import (
37 from rhodecode.lib.vcs.backends.base import (
38 BaseRepository, CollectionGenerator, Config, MergeResponse,
38 BaseRepository, CollectionGenerator, Config, MergeResponse,
39 MergeFailureReason, Reference)
39 MergeFailureReason, Reference)
40 from rhodecode.lib.vcs.backends.git.commit import GitCommit
40 from rhodecode.lib.vcs.backends.git.commit import GitCommit
41 from rhodecode.lib.vcs.backends.git.diff import GitDiff
41 from rhodecode.lib.vcs.backends.git.diff import GitDiff
42 from rhodecode.lib.vcs.backends.git.inmemory import GitInMemoryCommit
42 from rhodecode.lib.vcs.backends.git.inmemory import GitInMemoryCommit
43 from rhodecode.lib.vcs.exceptions import (
43 from rhodecode.lib.vcs.exceptions import (
44 CommitDoesNotExistError, EmptyRepositoryError,
44 CommitDoesNotExistError, EmptyRepositoryError,
45 RepositoryError, TagAlreadyExistError, TagDoesNotExistError, VCSError)
45 RepositoryError, TagAlreadyExistError, TagDoesNotExistError, VCSError)
46
46
47
47
48 SHA_PATTERN = re.compile(r'^[[0-9a-fA-F]{12}|[0-9a-fA-F]{40}]$')
48 SHA_PATTERN = re.compile(r'^[[0-9a-fA-F]{12}|[0-9a-fA-F]{40}]$')
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 class GitRepository(BaseRepository):
53 class GitRepository(BaseRepository):
54 """
54 """
55 Git repository backend.
55 Git repository backend.
56 """
56 """
57 DEFAULT_BRANCH_NAME = 'master'
57 DEFAULT_BRANCH_NAME = 'master'
58
58
59 contact = BaseRepository.DEFAULT_CONTACT
59 contact = BaseRepository.DEFAULT_CONTACT
60
60
61 def __init__(self, repo_path, config=None, create=False, src_url=None,
61 def __init__(self, repo_path, config=None, create=False, src_url=None,
62 update_after_clone=False, with_wire=None, bare=False):
62 update_after_clone=False, with_wire=None, bare=False):
63
63
64 self.path = safe_str(os.path.abspath(repo_path))
64 self.path = safe_str(os.path.abspath(repo_path))
65 self.config = config if config else Config()
65 self.config = config if config else Config()
66 self._remote = connection.Git(
66 self._remote = connection.Git(
67 self.path, self.config, with_wire=with_wire)
67 self.path, self.config, with_wire=with_wire)
68
68
69 self._init_repo(create, src_url, update_after_clone, bare)
69 self._init_repo(create, src_url, update_after_clone, bare)
70
70
71 # caches
71 # caches
72 self._commit_ids = {}
72 self._commit_ids = {}
73
73
74 self.bookmarks = {}
74 self.bookmarks = {}
75
75
76 @LazyProperty
76 @LazyProperty
77 def bare(self):
77 def bare(self):
78 return self._remote.bare()
78 return self._remote.bare()
79
79
80 @LazyProperty
80 @LazyProperty
81 def head(self):
81 def head(self):
82 return self._remote.head()
82 return self._remote.head()
83
83
84 @LazyProperty
84 @LazyProperty
85 def commit_ids(self):
85 def commit_ids(self):
86 """
86 """
87 Returns list of commit ids, in ascending order. Being lazy
87 Returns list of commit ids, in ascending order. Being lazy
88 attribute allows external tools to inject commit ids from cache.
88 attribute allows external tools to inject commit ids from cache.
89 """
89 """
90 commit_ids = self._get_all_commit_ids()
90 commit_ids = self._get_all_commit_ids()
91 self._rebuild_cache(commit_ids)
91 self._rebuild_cache(commit_ids)
92 return commit_ids
92 return commit_ids
93
93
94 def _rebuild_cache(self, commit_ids):
94 def _rebuild_cache(self, commit_ids):
95 self._commit_ids = dict((commit_id, index)
95 self._commit_ids = dict((commit_id, index)
96 for index, commit_id in enumerate(commit_ids))
96 for index, commit_id in enumerate(commit_ids))
97
97
98 def run_git_command(self, cmd, **opts):
98 def run_git_command(self, cmd, **opts):
99 """
99 """
100 Runs given ``cmd`` as git command and returns tuple
100 Runs given ``cmd`` as git command and returns tuple
101 (stdout, stderr).
101 (stdout, stderr).
102
102
103 :param cmd: git command to be executed
103 :param cmd: git command to be executed
104 :param opts: env options to pass into Subprocess command
104 :param opts: env options to pass into Subprocess command
105 """
105 """
106 if not isinstance(cmd, list):
106 if not isinstance(cmd, list):
107 raise ValueError('cmd must be a list, got %s instead' % type(cmd))
107 raise ValueError('cmd must be a list, got %s instead' % type(cmd))
108
108
109 out, err = self._remote.run_git_command(cmd, **opts)
109 out, err = self._remote.run_git_command(cmd, **opts)
110 if err:
110 if err:
111 log.debug('Stderr output of git command "%s":\n%s', cmd, err)
111 log.debug('Stderr output of git command "%s":\n%s', cmd, err)
112 return out, err
112 return out, err
113
113
114 @staticmethod
114 @staticmethod
115 def check_url(url, config):
115 def check_url(url, config):
116 """
116 """
117 Function will check given url and try to verify if it's a valid
117 Function will check given url and try to verify if it's a valid
118 link. Sometimes it may happened that git will issue basic
118 link. Sometimes it may happened that git will issue basic
119 auth request that can cause whole API to hang when used from python
119 auth request that can cause whole API to hang when used from python
120 or other external calls.
120 or other external calls.
121
121
122 On failures it'll raise urllib2.HTTPError, exception is also thrown
122 On failures it'll raise urllib2.HTTPError, exception is also thrown
123 when the return code is non 200
123 when the return code is non 200
124 """
124 """
125 # check first if it's not an url
125 # check first if it's not an url
126 if os.path.isdir(url) or url.startswith('file:'):
126 if os.path.isdir(url) or url.startswith('file:'):
127 return True
127 return True
128
128
129 if '+' in url.split('://', 1)[0]:
129 if '+' in url.split('://', 1)[0]:
130 url = url.split('+', 1)[1]
130 url = url.split('+', 1)[1]
131
131
132 # Request the _remote to verify the url
132 # Request the _remote to verify the url
133 return connection.Git.check_url(url, config.serialize())
133 return connection.Git.check_url(url, config.serialize())
134
134
135 @staticmethod
135 @staticmethod
136 def is_valid_repository(path):
136 def is_valid_repository(path):
137 if os.path.isdir(os.path.join(path, '.git')):
137 if os.path.isdir(os.path.join(path, '.git')):
138 return True
138 return True
139 # check case of bare repository
139 # check case of bare repository
140 try:
140 try:
141 GitRepository(path)
141 GitRepository(path)
142 return True
142 return True
143 except VCSError:
143 except VCSError:
144 pass
144 pass
145 return False
145 return False
146
146
147 def _init_repo(self, create, src_url=None, update_after_clone=False,
147 def _init_repo(self, create, src_url=None, update_after_clone=False,
148 bare=False):
148 bare=False):
149 if create and os.path.exists(self.path):
149 if create and os.path.exists(self.path):
150 raise RepositoryError(
150 raise RepositoryError(
151 "Cannot create repository at %s, location already exist"
151 "Cannot create repository at %s, location already exist"
152 % self.path)
152 % self.path)
153
153
154 try:
154 try:
155 if create and src_url:
155 if create and src_url:
156 GitRepository.check_url(src_url, self.config)
156 GitRepository.check_url(src_url, self.config)
157 self.clone(src_url, update_after_clone, bare)
157 self.clone(src_url, update_after_clone, bare)
158 elif create:
158 elif create:
159 os.makedirs(self.path, mode=0755)
159 os.makedirs(self.path, mode=0755)
160
160
161 if bare:
161 if bare:
162 self._remote.init_bare()
162 self._remote.init_bare()
163 else:
163 else:
164 self._remote.init()
164 self._remote.init()
165 else:
165 else:
166 self._remote.assert_correct_path()
166 if not self._remote.assert_correct_path():
167 raise RepositoryError(
168 'Path "%s" does not contain a Git repository' %
169 (self.path,))
170
167 # TODO: johbo: check if we have to translate the OSError here
171 # TODO: johbo: check if we have to translate the OSError here
168 except OSError as err:
172 except OSError as err:
169 raise RepositoryError(err)
173 raise RepositoryError(err)
170
174
171 def _get_all_commit_ids(self, filters=None):
175 def _get_all_commit_ids(self, filters=None):
172 # we must check if this repo is not empty, since later command
176 # we must check if this repo is not empty, since later command
173 # fails if it is. And it's cheaper to ask than throw the subprocess
177 # fails if it is. And it's cheaper to ask than throw the subprocess
174 # errors
178 # errors
175 try:
179 try:
176 self._remote.head()
180 self._remote.head()
177 except KeyError:
181 except KeyError:
178 return []
182 return []
179
183
180 rev_filter = ['--branches', '--tags']
184 rev_filter = ['--branches', '--tags']
181 extra_filter = []
185 extra_filter = []
182
186
183 if filters:
187 if filters:
184 if filters.get('since'):
188 if filters.get('since'):
185 extra_filter.append('--since=%s' % (filters['since']))
189 extra_filter.append('--since=%s' % (filters['since']))
186 if filters.get('until'):
190 if filters.get('until'):
187 extra_filter.append('--until=%s' % (filters['until']))
191 extra_filter.append('--until=%s' % (filters['until']))
188 if filters.get('branch_name'):
192 if filters.get('branch_name'):
189 rev_filter = ['--tags']
193 rev_filter = ['--tags']
190 extra_filter.append(filters['branch_name'])
194 extra_filter.append(filters['branch_name'])
191 rev_filter.extend(extra_filter)
195 rev_filter.extend(extra_filter)
192
196
193 # if filters.get('start') or filters.get('end'):
197 # if filters.get('start') or filters.get('end'):
194 # # skip is offset, max-count is limit
198 # # skip is offset, max-count is limit
195 # if filters.get('start'):
199 # if filters.get('start'):
196 # extra_filter += ' --skip=%s' % filters['start']
200 # extra_filter += ' --skip=%s' % filters['start']
197 # if filters.get('end'):
201 # if filters.get('end'):
198 # extra_filter += ' --max-count=%s' % (filters['end'] - (filters['start'] or 0))
202 # extra_filter += ' --max-count=%s' % (filters['end'] - (filters['start'] or 0))
199
203
200 cmd = ['rev-list', '--reverse', '--date-order'] + rev_filter
204 cmd = ['rev-list', '--reverse', '--date-order'] + rev_filter
201 try:
205 try:
202 output, __ = self.run_git_command(cmd)
206 output, __ = self.run_git_command(cmd)
203 except RepositoryError:
207 except RepositoryError:
204 # Can be raised for empty repositories
208 # Can be raised for empty repositories
205 return []
209 return []
206 return output.splitlines()
210 return output.splitlines()
207
211
208 def _get_commit_id(self, commit_id_or_idx):
212 def _get_commit_id(self, commit_id_or_idx):
209 def is_null(value):
213 def is_null(value):
210 return len(value) == commit_id_or_idx.count('0')
214 return len(value) == commit_id_or_idx.count('0')
211
215
212 if self.is_empty():
216 if self.is_empty():
213 raise EmptyRepositoryError("There are no commits yet")
217 raise EmptyRepositoryError("There are no commits yet")
214
218
215 if commit_id_or_idx in (None, '', 'tip', 'HEAD', 'head', -1):
219 if commit_id_or_idx in (None, '', 'tip', 'HEAD', 'head', -1):
216 return self.commit_ids[-1]
220 return self.commit_ids[-1]
217
221
218 is_bstr = isinstance(commit_id_or_idx, (str, unicode))
222 is_bstr = isinstance(commit_id_or_idx, (str, unicode))
219 if ((is_bstr and commit_id_or_idx.isdigit() and len(commit_id_or_idx) < 12)
223 if ((is_bstr and commit_id_or_idx.isdigit() and len(commit_id_or_idx) < 12)
220 or isinstance(commit_id_or_idx, int) or is_null(commit_id_or_idx)):
224 or isinstance(commit_id_or_idx, int) or is_null(commit_id_or_idx)):
221 try:
225 try:
222 commit_id_or_idx = self.commit_ids[int(commit_id_or_idx)]
226 commit_id_or_idx = self.commit_ids[int(commit_id_or_idx)]
223 except Exception:
227 except Exception:
224 msg = "Commit %s does not exist for %s" % (
228 msg = "Commit %s does not exist for %s" % (
225 commit_id_or_idx, self)
229 commit_id_or_idx, self)
226 raise CommitDoesNotExistError(msg)
230 raise CommitDoesNotExistError(msg)
227
231
228 elif is_bstr:
232 elif is_bstr:
229 # check full path ref, eg. refs/heads/master
233 # check full path ref, eg. refs/heads/master
230 ref_id = self._refs.get(commit_id_or_idx)
234 ref_id = self._refs.get(commit_id_or_idx)
231 if ref_id:
235 if ref_id:
232 return ref_id
236 return ref_id
233
237
234 # check branch name
238 # check branch name
235 branch_ids = self.branches.values()
239 branch_ids = self.branches.values()
236 ref_id = self._refs.get('refs/heads/%s' % commit_id_or_idx)
240 ref_id = self._refs.get('refs/heads/%s' % commit_id_or_idx)
237 if ref_id:
241 if ref_id:
238 return ref_id
242 return ref_id
239
243
240 # check tag name
244 # check tag name
241 ref_id = self._refs.get('refs/tags/%s' % commit_id_or_idx)
245 ref_id = self._refs.get('refs/tags/%s' % commit_id_or_idx)
242 if ref_id:
246 if ref_id:
243 return ref_id
247 return ref_id
244
248
245 if (not SHA_PATTERN.match(commit_id_or_idx) or
249 if (not SHA_PATTERN.match(commit_id_or_idx) or
246 commit_id_or_idx not in self.commit_ids):
250 commit_id_or_idx not in self.commit_ids):
247 msg = "Commit %s does not exist for %s" % (
251 msg = "Commit %s does not exist for %s" % (
248 commit_id_or_idx, self)
252 commit_id_or_idx, self)
249 raise CommitDoesNotExistError(msg)
253 raise CommitDoesNotExistError(msg)
250
254
251 # Ensure we return full id
255 # Ensure we return full id
252 if not SHA_PATTERN.match(str(commit_id_or_idx)):
256 if not SHA_PATTERN.match(str(commit_id_or_idx)):
253 raise CommitDoesNotExistError(
257 raise CommitDoesNotExistError(
254 "Given commit id %s not recognized" % commit_id_or_idx)
258 "Given commit id %s not recognized" % commit_id_or_idx)
255 return commit_id_or_idx
259 return commit_id_or_idx
256
260
257 def get_hook_location(self):
261 def get_hook_location(self):
258 """
262 """
259 returns absolute path to location where hooks are stored
263 returns absolute path to location where hooks are stored
260 """
264 """
261 loc = os.path.join(self.path, 'hooks')
265 loc = os.path.join(self.path, 'hooks')
262 if not self.bare:
266 if not self.bare:
263 loc = os.path.join(self.path, '.git', 'hooks')
267 loc = os.path.join(self.path, '.git', 'hooks')
264 return loc
268 return loc
265
269
266 @LazyProperty
270 @LazyProperty
267 def last_change(self):
271 def last_change(self):
268 """
272 """
269 Returns last change made on this repository as
273 Returns last change made on this repository as
270 `datetime.datetime` object.
274 `datetime.datetime` object.
271 """
275 """
272 try:
276 try:
273 return self.get_commit().date
277 return self.get_commit().date
274 except RepositoryError:
278 except RepositoryError:
275 tzoffset = makedate()[1]
279 tzoffset = makedate()[1]
276 return utcdate_fromtimestamp(self._get_fs_mtime(), tzoffset)
280 return utcdate_fromtimestamp(self._get_fs_mtime(), tzoffset)
277
281
278 def _get_fs_mtime(self):
282 def _get_fs_mtime(self):
279 idx_loc = '' if self.bare else '.git'
283 idx_loc = '' if self.bare else '.git'
280 # fallback to filesystem
284 # fallback to filesystem
281 in_path = os.path.join(self.path, idx_loc, "index")
285 in_path = os.path.join(self.path, idx_loc, "index")
282 he_path = os.path.join(self.path, idx_loc, "HEAD")
286 he_path = os.path.join(self.path, idx_loc, "HEAD")
283 if os.path.exists(in_path):
287 if os.path.exists(in_path):
284 return os.stat(in_path).st_mtime
288 return os.stat(in_path).st_mtime
285 else:
289 else:
286 return os.stat(he_path).st_mtime
290 return os.stat(he_path).st_mtime
287
291
288 @LazyProperty
292 @LazyProperty
289 def description(self):
293 def description(self):
290 description = self._remote.get_description()
294 description = self._remote.get_description()
291 return safe_unicode(description or self.DEFAULT_DESCRIPTION)
295 return safe_unicode(description or self.DEFAULT_DESCRIPTION)
292
296
293 def _get_refs_entries(self, prefix='', reverse=False, strip_prefix=True):
297 def _get_refs_entries(self, prefix='', reverse=False, strip_prefix=True):
294 if self.is_empty():
298 if self.is_empty():
295 return OrderedDict()
299 return OrderedDict()
296
300
297 result = []
301 result = []
298 for ref, sha in self._refs.iteritems():
302 for ref, sha in self._refs.iteritems():
299 if ref.startswith(prefix):
303 if ref.startswith(prefix):
300 ref_name = ref
304 ref_name = ref
301 if strip_prefix:
305 if strip_prefix:
302 ref_name = ref[len(prefix):]
306 ref_name = ref[len(prefix):]
303 result.append((safe_unicode(ref_name), sha))
307 result.append((safe_unicode(ref_name), sha))
304
308
305 def get_name(entry):
309 def get_name(entry):
306 return entry[0]
310 return entry[0]
307
311
308 return OrderedDict(sorted(result, key=get_name, reverse=reverse))
312 return OrderedDict(sorted(result, key=get_name, reverse=reverse))
309
313
310 def _get_branches(self):
314 def _get_branches(self):
311 return self._get_refs_entries(prefix='refs/heads/', strip_prefix=True)
315 return self._get_refs_entries(prefix='refs/heads/', strip_prefix=True)
312
316
313 @LazyProperty
317 @LazyProperty
314 def branches(self):
318 def branches(self):
315 return self._get_branches()
319 return self._get_branches()
316
320
317 @LazyProperty
321 @LazyProperty
318 def branches_closed(self):
322 def branches_closed(self):
319 return {}
323 return {}
320
324
321 @LazyProperty
325 @LazyProperty
322 def branches_all(self):
326 def branches_all(self):
323 all_branches = {}
327 all_branches = {}
324 all_branches.update(self.branches)
328 all_branches.update(self.branches)
325 all_branches.update(self.branches_closed)
329 all_branches.update(self.branches_closed)
326 return all_branches
330 return all_branches
327
331
328 @LazyProperty
332 @LazyProperty
329 def tags(self):
333 def tags(self):
330 return self._get_tags()
334 return self._get_tags()
331
335
332 def _get_tags(self):
336 def _get_tags(self):
333 return self._get_refs_entries(
337 return self._get_refs_entries(
334 prefix='refs/tags/', strip_prefix=True, reverse=True)
338 prefix='refs/tags/', strip_prefix=True, reverse=True)
335
339
336 def tag(self, name, user, commit_id=None, message=None, date=None,
340 def tag(self, name, user, commit_id=None, message=None, date=None,
337 **kwargs):
341 **kwargs):
338 # TODO: fix this method to apply annotated tags correct with message
342 # TODO: fix this method to apply annotated tags correct with message
339 """
343 """
340 Creates and returns a tag for the given ``commit_id``.
344 Creates and returns a tag for the given ``commit_id``.
341
345
342 :param name: name for new tag
346 :param name: name for new tag
343 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
347 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
344 :param commit_id: commit id for which new tag would be created
348 :param commit_id: commit id for which new tag would be created
345 :param message: message of the tag's commit
349 :param message: message of the tag's commit
346 :param date: date of tag's commit
350 :param date: date of tag's commit
347
351
348 :raises TagAlreadyExistError: if tag with same name already exists
352 :raises TagAlreadyExistError: if tag with same name already exists
349 """
353 """
350 if name in self.tags:
354 if name in self.tags:
351 raise TagAlreadyExistError("Tag %s already exists" % name)
355 raise TagAlreadyExistError("Tag %s already exists" % name)
352 commit = self.get_commit(commit_id=commit_id)
356 commit = self.get_commit(commit_id=commit_id)
353 message = message or "Added tag %s for commit %s" % (
357 message = message or "Added tag %s for commit %s" % (
354 name, commit.raw_id)
358 name, commit.raw_id)
355 self._remote.set_refs('refs/tags/%s' % name, commit._commit['id'])
359 self._remote.set_refs('refs/tags/%s' % name, commit._commit['id'])
356
360
357 self._refs = self._get_refs()
361 self._refs = self._get_refs()
358 self.tags = self._get_tags()
362 self.tags = self._get_tags()
359 return commit
363 return commit
360
364
361 def remove_tag(self, name, user, message=None, date=None):
365 def remove_tag(self, name, user, message=None, date=None):
362 """
366 """
363 Removes tag with the given ``name``.
367 Removes tag with the given ``name``.
364
368
365 :param name: name of the tag to be removed
369 :param name: name of the tag to be removed
366 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
370 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
367 :param message: message of the tag's removal commit
371 :param message: message of the tag's removal commit
368 :param date: date of tag's removal commit
372 :param date: date of tag's removal commit
369
373
370 :raises TagDoesNotExistError: if tag with given name does not exists
374 :raises TagDoesNotExistError: if tag with given name does not exists
371 """
375 """
372 if name not in self.tags:
376 if name not in self.tags:
373 raise TagDoesNotExistError("Tag %s does not exist" % name)
377 raise TagDoesNotExistError("Tag %s does not exist" % name)
374 tagpath = vcspath.join(
378 tagpath = vcspath.join(
375 self._remote.get_refs_path(), 'refs', 'tags', name)
379 self._remote.get_refs_path(), 'refs', 'tags', name)
376 try:
380 try:
377 os.remove(tagpath)
381 os.remove(tagpath)
378 self._refs = self._get_refs()
382 self._refs = self._get_refs()
379 self.tags = self._get_tags()
383 self.tags = self._get_tags()
380 except OSError as e:
384 except OSError as e:
381 raise RepositoryError(e.strerror)
385 raise RepositoryError(e.strerror)
382
386
383 def _get_refs(self):
387 def _get_refs(self):
384 return self._remote.get_refs()
388 return self._remote.get_refs()
385
389
386 @LazyProperty
390 @LazyProperty
387 def _refs(self):
391 def _refs(self):
388 return self._get_refs()
392 return self._get_refs()
389
393
390 @property
394 @property
391 def _ref_tree(self):
395 def _ref_tree(self):
392 node = tree = {}
396 node = tree = {}
393 for ref, sha in self._refs.iteritems():
397 for ref, sha in self._refs.iteritems():
394 path = ref.split('/')
398 path = ref.split('/')
395 for bit in path[:-1]:
399 for bit in path[:-1]:
396 node = node.setdefault(bit, {})
400 node = node.setdefault(bit, {})
397 node[path[-1]] = sha
401 node[path[-1]] = sha
398 node = tree
402 node = tree
399 return tree
403 return tree
400
404
401 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
405 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
402 """
406 """
403 Returns `GitCommit` object representing commit from git repository
407 Returns `GitCommit` object representing commit from git repository
404 at the given `commit_id` or head (most recent commit) if None given.
408 at the given `commit_id` or head (most recent commit) if None given.
405 """
409 """
406 if commit_id is not None:
410 if commit_id is not None:
407 self._validate_commit_id(commit_id)
411 self._validate_commit_id(commit_id)
408 elif commit_idx is not None:
412 elif commit_idx is not None:
409 self._validate_commit_idx(commit_idx)
413 self._validate_commit_idx(commit_idx)
410 commit_id = commit_idx
414 commit_id = commit_idx
411 commit_id = self._get_commit_id(commit_id)
415 commit_id = self._get_commit_id(commit_id)
412 try:
416 try:
413 # Need to call remote to translate id for tagging scenario
417 # Need to call remote to translate id for tagging scenario
414 commit_id = self._remote.get_object(commit_id)["commit_id"]
418 commit_id = self._remote.get_object(commit_id)["commit_id"]
415 idx = self._commit_ids[commit_id]
419 idx = self._commit_ids[commit_id]
416 except KeyError:
420 except KeyError:
417 raise RepositoryError("Cannot get object with id %s" % commit_id)
421 raise RepositoryError("Cannot get object with id %s" % commit_id)
418
422
419 return GitCommit(self, commit_id, idx, pre_load=pre_load)
423 return GitCommit(self, commit_id, idx, pre_load=pre_load)
420
424
421 def get_commits(
425 def get_commits(
422 self, start_id=None, end_id=None, start_date=None, end_date=None,
426 self, start_id=None, end_id=None, start_date=None, end_date=None,
423 branch_name=None, show_hidden=False, pre_load=None):
427 branch_name=None, show_hidden=False, pre_load=None):
424 """
428 """
425 Returns generator of `GitCommit` objects from start to end (both
429 Returns generator of `GitCommit` objects from start to end (both
426 are inclusive), in ascending date order.
430 are inclusive), in ascending date order.
427
431
428 :param start_id: None, str(commit_id)
432 :param start_id: None, str(commit_id)
429 :param end_id: None, str(commit_id)
433 :param end_id: None, str(commit_id)
430 :param start_date: if specified, commits with commit date less than
434 :param start_date: if specified, commits with commit date less than
431 ``start_date`` would be filtered out from returned set
435 ``start_date`` would be filtered out from returned set
432 :param end_date: if specified, commits with commit date greater than
436 :param end_date: if specified, commits with commit date greater than
433 ``end_date`` would be filtered out from returned set
437 ``end_date`` would be filtered out from returned set
434 :param branch_name: if specified, commits not reachable from given
438 :param branch_name: if specified, commits not reachable from given
435 branch would be filtered out from returned set
439 branch would be filtered out from returned set
436 :param show_hidden: Show hidden commits such as obsolete or hidden from
440 :param show_hidden: Show hidden commits such as obsolete or hidden from
437 Mercurial evolve
441 Mercurial evolve
438 :raise BranchDoesNotExistError: If given `branch_name` does not
442 :raise BranchDoesNotExistError: If given `branch_name` does not
439 exist.
443 exist.
440 :raise CommitDoesNotExistError: If commits for given `start` or
444 :raise CommitDoesNotExistError: If commits for given `start` or
441 `end` could not be found.
445 `end` could not be found.
442
446
443 """
447 """
444 if self.is_empty():
448 if self.is_empty():
445 raise EmptyRepositoryError("There are no commits yet")
449 raise EmptyRepositoryError("There are no commits yet")
446 self._validate_branch_name(branch_name)
450 self._validate_branch_name(branch_name)
447
451
448 if start_id is not None:
452 if start_id is not None:
449 self._validate_commit_id(start_id)
453 self._validate_commit_id(start_id)
450 if end_id is not None:
454 if end_id is not None:
451 self._validate_commit_id(end_id)
455 self._validate_commit_id(end_id)
452
456
453 start_raw_id = self._get_commit_id(start_id)
457 start_raw_id = self._get_commit_id(start_id)
454 start_pos = self._commit_ids[start_raw_id] if start_id else None
458 start_pos = self._commit_ids[start_raw_id] if start_id else None
455 end_raw_id = self._get_commit_id(end_id)
459 end_raw_id = self._get_commit_id(end_id)
456 end_pos = max(0, self._commit_ids[end_raw_id]) if end_id else None
460 end_pos = max(0, self._commit_ids[end_raw_id]) if end_id else None
457
461
458 if None not in [start_id, end_id] and start_pos > end_pos:
462 if None not in [start_id, end_id] and start_pos > end_pos:
459 raise RepositoryError(
463 raise RepositoryError(
460 "Start commit '%s' cannot be after end commit '%s'" %
464 "Start commit '%s' cannot be after end commit '%s'" %
461 (start_id, end_id))
465 (start_id, end_id))
462
466
463 if end_pos is not None:
467 if end_pos is not None:
464 end_pos += 1
468 end_pos += 1
465
469
466 filter_ = []
470 filter_ = []
467 if branch_name:
471 if branch_name:
468 filter_.append({'branch_name': branch_name})
472 filter_.append({'branch_name': branch_name})
469 if start_date and not end_date:
473 if start_date and not end_date:
470 filter_.append({'since': start_date})
474 filter_.append({'since': start_date})
471 if end_date and not start_date:
475 if end_date and not start_date:
472 filter_.append({'until': end_date})
476 filter_.append({'until': end_date})
473 if start_date and end_date:
477 if start_date and end_date:
474 filter_.append({'since': start_date})
478 filter_.append({'since': start_date})
475 filter_.append({'until': end_date})
479 filter_.append({'until': end_date})
476
480
477 # if start_pos or end_pos:
481 # if start_pos or end_pos:
478 # filter_.append({'start': start_pos})
482 # filter_.append({'start': start_pos})
479 # filter_.append({'end': end_pos})
483 # filter_.append({'end': end_pos})
480
484
481 if filter_:
485 if filter_:
482 revfilters = {
486 revfilters = {
483 'branch_name': branch_name,
487 'branch_name': branch_name,
484 'since': start_date.strftime('%m/%d/%y %H:%M:%S') if start_date else None,
488 'since': start_date.strftime('%m/%d/%y %H:%M:%S') if start_date else None,
485 'until': end_date.strftime('%m/%d/%y %H:%M:%S') if end_date else None,
489 'until': end_date.strftime('%m/%d/%y %H:%M:%S') if end_date else None,
486 'start': start_pos,
490 'start': start_pos,
487 'end': end_pos,
491 'end': end_pos,
488 }
492 }
489 commit_ids = self._get_all_commit_ids(filters=revfilters)
493 commit_ids = self._get_all_commit_ids(filters=revfilters)
490
494
491 # pure python stuff, it's slow due to walker walking whole repo
495 # pure python stuff, it's slow due to walker walking whole repo
492 # def get_revs(walker):
496 # def get_revs(walker):
493 # for walker_entry in walker:
497 # for walker_entry in walker:
494 # yield walker_entry.commit.id
498 # yield walker_entry.commit.id
495 # revfilters = {}
499 # revfilters = {}
496 # commit_ids = list(reversed(list(get_revs(self._repo.get_walker(**revfilters)))))
500 # commit_ids = list(reversed(list(get_revs(self._repo.get_walker(**revfilters)))))
497 else:
501 else:
498 commit_ids = self.commit_ids
502 commit_ids = self.commit_ids
499
503
500 if start_pos or end_pos:
504 if start_pos or end_pos:
501 commit_ids = commit_ids[start_pos: end_pos]
505 commit_ids = commit_ids[start_pos: end_pos]
502
506
503 return CollectionGenerator(self, commit_ids, pre_load=pre_load)
507 return CollectionGenerator(self, commit_ids, pre_load=pre_load)
504
508
505 def get_diff(
509 def get_diff(
506 self, commit1, commit2, path='', ignore_whitespace=False,
510 self, commit1, commit2, path='', ignore_whitespace=False,
507 context=3, path1=None):
511 context=3, path1=None):
508 """
512 """
509 Returns (git like) *diff*, as plain text. Shows changes introduced by
513 Returns (git like) *diff*, as plain text. Shows changes introduced by
510 ``commit2`` since ``commit1``.
514 ``commit2`` since ``commit1``.
511
515
512 :param commit1: Entry point from which diff is shown. Can be
516 :param commit1: Entry point from which diff is shown. Can be
513 ``self.EMPTY_COMMIT`` - in this case, patch showing all
517 ``self.EMPTY_COMMIT`` - in this case, patch showing all
514 the changes since empty state of the repository until ``commit2``
518 the changes since empty state of the repository until ``commit2``
515 :param commit2: Until which commits changes should be shown.
519 :param commit2: Until which commits changes should be shown.
516 :param ignore_whitespace: If set to ``True``, would not show whitespace
520 :param ignore_whitespace: If set to ``True``, would not show whitespace
517 changes. Defaults to ``False``.
521 changes. Defaults to ``False``.
518 :param context: How many lines before/after changed lines should be
522 :param context: How many lines before/after changed lines should be
519 shown. Defaults to ``3``.
523 shown. Defaults to ``3``.
520 """
524 """
521 self._validate_diff_commits(commit1, commit2)
525 self._validate_diff_commits(commit1, commit2)
522 if path1 is not None and path1 != path:
526 if path1 is not None and path1 != path:
523 raise ValueError("Diff of two different paths not supported.")
527 raise ValueError("Diff of two different paths not supported.")
524
528
525 flags = [
529 flags = [
526 '-U%s' % context, '--full-index', '--binary', '-p',
530 '-U%s' % context, '--full-index', '--binary', '-p',
527 '-M', '--abbrev=40']
531 '-M', '--abbrev=40']
528 if ignore_whitespace:
532 if ignore_whitespace:
529 flags.append('-w')
533 flags.append('-w')
530
534
531 if commit1 == self.EMPTY_COMMIT:
535 if commit1 == self.EMPTY_COMMIT:
532 cmd = ['show'] + flags + [commit2.raw_id]
536 cmd = ['show'] + flags + [commit2.raw_id]
533 else:
537 else:
534 cmd = ['diff'] + flags + [commit1.raw_id, commit2.raw_id]
538 cmd = ['diff'] + flags + [commit1.raw_id, commit2.raw_id]
535
539
536 if path:
540 if path:
537 cmd.extend(['--', path])
541 cmd.extend(['--', path])
538
542
539 stdout, __ = self.run_git_command(cmd)
543 stdout, __ = self.run_git_command(cmd)
540 # If we used 'show' command, strip first few lines (until actual diff
544 # If we used 'show' command, strip first few lines (until actual diff
541 # starts)
545 # starts)
542 if commit1 == self.EMPTY_COMMIT:
546 if commit1 == self.EMPTY_COMMIT:
543 lines = stdout.splitlines()
547 lines = stdout.splitlines()
544 x = 0
548 x = 0
545 for line in lines:
549 for line in lines:
546 if line.startswith('diff'):
550 if line.startswith('diff'):
547 break
551 break
548 x += 1
552 x += 1
549 # Append new line just like 'diff' command do
553 # Append new line just like 'diff' command do
550 stdout = '\n'.join(lines[x:]) + '\n'
554 stdout = '\n'.join(lines[x:]) + '\n'
551 return GitDiff(stdout)
555 return GitDiff(stdout)
552
556
553 def strip(self, commit_id, branch_name):
557 def strip(self, commit_id, branch_name):
554 commit = self.get_commit(commit_id=commit_id)
558 commit = self.get_commit(commit_id=commit_id)
555 if commit.merge:
559 if commit.merge:
556 raise Exception('Cannot reset to merge commit')
560 raise Exception('Cannot reset to merge commit')
557
561
558 # parent is going to be the new head now
562 # parent is going to be the new head now
559 commit = commit.parents[0]
563 commit = commit.parents[0]
560 self._remote.set_refs('refs/heads/%s' % branch_name, commit.raw_id)
564 self._remote.set_refs('refs/heads/%s' % branch_name, commit.raw_id)
561
565
562 self.commit_ids = self._get_all_commit_ids()
566 self.commit_ids = self._get_all_commit_ids()
563 self._rebuild_cache(self.commit_ids)
567 self._rebuild_cache(self.commit_ids)
564
568
565 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
569 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
566 if commit_id1 == commit_id2:
570 if commit_id1 == commit_id2:
567 return commit_id1
571 return commit_id1
568
572
569 if self != repo2:
573 if self != repo2:
570 commits = self._remote.get_missing_revs(
574 commits = self._remote.get_missing_revs(
571 commit_id1, commit_id2, repo2.path)
575 commit_id1, commit_id2, repo2.path)
572 if commits:
576 if commits:
573 commit = repo2.get_commit(commits[-1])
577 commit = repo2.get_commit(commits[-1])
574 if commit.parents:
578 if commit.parents:
575 ancestor_id = commit.parents[0].raw_id
579 ancestor_id = commit.parents[0].raw_id
576 else:
580 else:
577 ancestor_id = None
581 ancestor_id = None
578 else:
582 else:
579 # no commits from other repo, ancestor_id is the commit_id2
583 # no commits from other repo, ancestor_id is the commit_id2
580 ancestor_id = commit_id2
584 ancestor_id = commit_id2
581 else:
585 else:
582 output, __ = self.run_git_command(
586 output, __ = self.run_git_command(
583 ['merge-base', commit_id1, commit_id2])
587 ['merge-base', commit_id1, commit_id2])
584 ancestor_id = re.findall(r'[0-9a-fA-F]{40}', output)[0]
588 ancestor_id = re.findall(r'[0-9a-fA-F]{40}', output)[0]
585
589
586 return ancestor_id
590 return ancestor_id
587
591
588 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
592 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
589 repo1 = self
593 repo1 = self
590 ancestor_id = None
594 ancestor_id = None
591
595
592 if commit_id1 == commit_id2:
596 if commit_id1 == commit_id2:
593 commits = []
597 commits = []
594 elif repo1 != repo2:
598 elif repo1 != repo2:
595 missing_ids = self._remote.get_missing_revs(commit_id1, commit_id2,
599 missing_ids = self._remote.get_missing_revs(commit_id1, commit_id2,
596 repo2.path)
600 repo2.path)
597 commits = [
601 commits = [
598 repo2.get_commit(commit_id=commit_id, pre_load=pre_load)
602 repo2.get_commit(commit_id=commit_id, pre_load=pre_load)
599 for commit_id in reversed(missing_ids)]
603 for commit_id in reversed(missing_ids)]
600 else:
604 else:
601 output, __ = repo1.run_git_command(
605 output, __ = repo1.run_git_command(
602 ['log', '--reverse', '--pretty=format: %H', '-s',
606 ['log', '--reverse', '--pretty=format: %H', '-s',
603 '%s..%s' % (commit_id1, commit_id2)])
607 '%s..%s' % (commit_id1, commit_id2)])
604 commits = [
608 commits = [
605 repo1.get_commit(commit_id=commit_id, pre_load=pre_load)
609 repo1.get_commit(commit_id=commit_id, pre_load=pre_load)
606 for commit_id in re.findall(r'[0-9a-fA-F]{40}', output)]
610 for commit_id in re.findall(r'[0-9a-fA-F]{40}', output)]
607
611
608 return commits
612 return commits
609
613
610 @LazyProperty
614 @LazyProperty
611 def in_memory_commit(self):
615 def in_memory_commit(self):
612 """
616 """
613 Returns ``GitInMemoryCommit`` object for this repository.
617 Returns ``GitInMemoryCommit`` object for this repository.
614 """
618 """
615 return GitInMemoryCommit(self)
619 return GitInMemoryCommit(self)
616
620
617 def clone(self, url, update_after_clone=True, bare=False):
621 def clone(self, url, update_after_clone=True, bare=False):
618 """
622 """
619 Tries to clone commits from external location.
623 Tries to clone commits from external location.
620
624
621 :param update_after_clone: If set to ``False``, git won't checkout
625 :param update_after_clone: If set to ``False``, git won't checkout
622 working directory
626 working directory
623 :param bare: If set to ``True``, repository would be cloned into
627 :param bare: If set to ``True``, repository would be cloned into
624 *bare* git repository (no working directory at all).
628 *bare* git repository (no working directory at all).
625 """
629 """
626 # init_bare and init expect empty dir created to proceed
630 # init_bare and init expect empty dir created to proceed
627 if not os.path.exists(self.path):
631 if not os.path.exists(self.path):
628 os.mkdir(self.path)
632 os.mkdir(self.path)
629
633
630 if bare:
634 if bare:
631 self._remote.init_bare()
635 self._remote.init_bare()
632 else:
636 else:
633 self._remote.init()
637 self._remote.init()
634
638
635 deferred = '^{}'
639 deferred = '^{}'
636 valid_refs = ('refs/heads', 'refs/tags', 'HEAD')
640 valid_refs = ('refs/heads', 'refs/tags', 'HEAD')
637
641
638 return self._remote.clone(
642 return self._remote.clone(
639 url, deferred, valid_refs, update_after_clone)
643 url, deferred, valid_refs, update_after_clone)
640
644
641 def pull(self, url, commit_ids=None):
645 def pull(self, url, commit_ids=None):
642 """
646 """
643 Tries to pull changes from external location. We use fetch here since
647 Tries to pull changes from external location. We use fetch here since
644 pull in get does merges and we want to be compatible with hg backend so
648 pull in get does merges and we want to be compatible with hg backend so
645 pull == fetch in this case
649 pull == fetch in this case
646 """
650 """
647 self.fetch(url, commit_ids=commit_ids)
651 self.fetch(url, commit_ids=commit_ids)
648
652
649 def fetch(self, url, commit_ids=None):
653 def fetch(self, url, commit_ids=None):
650 """
654 """
651 Tries to fetch changes from external location.
655 Tries to fetch changes from external location.
652 """
656 """
653 refs = None
657 refs = None
654
658
655 if commit_ids is not None:
659 if commit_ids is not None:
656 remote_refs = self._remote.get_remote_refs(url)
660 remote_refs = self._remote.get_remote_refs(url)
657 refs = [
661 refs = [
658 ref for ref in remote_refs if remote_refs[ref] in commit_ids]
662 ref for ref in remote_refs if remote_refs[ref] in commit_ids]
659 self._remote.fetch(url, refs=refs)
663 self._remote.fetch(url, refs=refs)
660
664
661 def set_refs(self, ref_name, commit_id):
665 def set_refs(self, ref_name, commit_id):
662 self._remote.set_refs(ref_name, commit_id)
666 self._remote.set_refs(ref_name, commit_id)
663
667
664 def remove_ref(self, ref_name):
668 def remove_ref(self, ref_name):
665 self._remote.remove_ref(ref_name)
669 self._remote.remove_ref(ref_name)
666
670
667 def _update_server_info(self):
671 def _update_server_info(self):
668 """
672 """
669 runs gits update-server-info command in this repo instance
673 runs gits update-server-info command in this repo instance
670 """
674 """
671 self._remote.update_server_info()
675 self._remote.update_server_info()
672
676
673 def _current_branch(self):
677 def _current_branch(self):
674 """
678 """
675 Return the name of the current branch.
679 Return the name of the current branch.
676
680
677 It only works for non bare repositories (i.e. repositories with a
681 It only works for non bare repositories (i.e. repositories with a
678 working copy)
682 working copy)
679 """
683 """
680 if self.bare:
684 if self.bare:
681 raise RepositoryError('Bare git repos do not have active branches')
685 raise RepositoryError('Bare git repos do not have active branches')
682
686
683 if self.is_empty():
687 if self.is_empty():
684 return None
688 return None
685
689
686 stdout, _ = self.run_git_command(['rev-parse', '--abbrev-ref', 'HEAD'])
690 stdout, _ = self.run_git_command(['rev-parse', '--abbrev-ref', 'HEAD'])
687 return stdout.strip()
691 return stdout.strip()
688
692
689 def _checkout(self, branch_name, create=False):
693 def _checkout(self, branch_name, create=False):
690 """
694 """
691 Checkout a branch in the working directory.
695 Checkout a branch in the working directory.
692
696
693 It tries to create the branch if create is True, failing if the branch
697 It tries to create the branch if create is True, failing if the branch
694 already exists.
698 already exists.
695
699
696 It only works for non bare repositories (i.e. repositories with a
700 It only works for non bare repositories (i.e. repositories with a
697 working copy)
701 working copy)
698 """
702 """
699 if self.bare:
703 if self.bare:
700 raise RepositoryError('Cannot checkout branches in a bare git repo')
704 raise RepositoryError('Cannot checkout branches in a bare git repo')
701
705
702 cmd = ['checkout']
706 cmd = ['checkout']
703 if create:
707 if create:
704 cmd.append('-b')
708 cmd.append('-b')
705 cmd.append(branch_name)
709 cmd.append(branch_name)
706 self.run_git_command(cmd, fail_on_stderr=False)
710 self.run_git_command(cmd, fail_on_stderr=False)
707
711
708 def _identify(self):
712 def _identify(self):
709 """
713 """
710 Return the current state of the working directory.
714 Return the current state of the working directory.
711 """
715 """
712 if self.bare:
716 if self.bare:
713 raise RepositoryError('Bare git repos do not have active branches')
717 raise RepositoryError('Bare git repos do not have active branches')
714
718
715 if self.is_empty():
719 if self.is_empty():
716 return None
720 return None
717
721
718 stdout, _ = self.run_git_command(['rev-parse', 'HEAD'])
722 stdout, _ = self.run_git_command(['rev-parse', 'HEAD'])
719 return stdout.strip()
723 return stdout.strip()
720
724
721 def _local_clone(self, clone_path, branch_name):
725 def _local_clone(self, clone_path, branch_name):
722 """
726 """
723 Create a local clone of the current repo.
727 Create a local clone of the current repo.
724 """
728 """
725 # N.B.(skreft): the --branch option is required as otherwise the shallow
729 # N.B.(skreft): the --branch option is required as otherwise the shallow
726 # clone will only fetch the active branch.
730 # clone will only fetch the active branch.
727 cmd = ['clone', '--branch', branch_name, '--single-branch',
731 cmd = ['clone', '--branch', branch_name, '--single-branch',
728 self.path, os.path.abspath(clone_path)]
732 self.path, os.path.abspath(clone_path)]
729 self.run_git_command(cmd, fail_on_stderr=False)
733 self.run_git_command(cmd, fail_on_stderr=False)
730
734
731 def _local_fetch(self, repository_path, branch_name):
735 def _local_fetch(self, repository_path, branch_name):
732 """
736 """
733 Fetch a branch from a local repository.
737 Fetch a branch from a local repository.
734 """
738 """
735 repository_path = os.path.abspath(repository_path)
739 repository_path = os.path.abspath(repository_path)
736 if repository_path == self.path:
740 if repository_path == self.path:
737 raise ValueError('Cannot fetch from the same repository')
741 raise ValueError('Cannot fetch from the same repository')
738
742
739 cmd = ['fetch', '--no-tags', repository_path, branch_name]
743 cmd = ['fetch', '--no-tags', repository_path, branch_name]
740 self.run_git_command(cmd, fail_on_stderr=False)
744 self.run_git_command(cmd, fail_on_stderr=False)
741
745
742 def _last_fetch_heads(self):
746 def _last_fetch_heads(self):
743 """
747 """
744 Return the last fetched heads that need merging.
748 Return the last fetched heads that need merging.
745
749
746 The algorithm is defined at
750 The algorithm is defined at
747 https://github.com/git/git/blob/v2.1.3/git-pull.sh#L283
751 https://github.com/git/git/blob/v2.1.3/git-pull.sh#L283
748 """
752 """
749 if not self.bare:
753 if not self.bare:
750 fetch_heads_path = os.path.join(self.path, '.git', 'FETCH_HEAD')
754 fetch_heads_path = os.path.join(self.path, '.git', 'FETCH_HEAD')
751 else:
755 else:
752 fetch_heads_path = os.path.join(self.path, 'FETCH_HEAD')
756 fetch_heads_path = os.path.join(self.path, 'FETCH_HEAD')
753
757
754 heads = []
758 heads = []
755 with open(fetch_heads_path) as f:
759 with open(fetch_heads_path) as f:
756 for line in f:
760 for line in f:
757 if ' not-for-merge ' in line:
761 if ' not-for-merge ' in line:
758 continue
762 continue
759 line = re.sub('\t.*', '', line, flags=re.DOTALL)
763 line = re.sub('\t.*', '', line, flags=re.DOTALL)
760 heads.append(line)
764 heads.append(line)
761
765
762 return heads
766 return heads
763
767
764 def _get_shadow_instance(self, shadow_repository_path, enable_hooks=False):
768 def _get_shadow_instance(self, shadow_repository_path, enable_hooks=False):
765 return GitRepository(shadow_repository_path)
769 return GitRepository(shadow_repository_path)
766
770
767 def _local_pull(self, repository_path, branch_name):
771 def _local_pull(self, repository_path, branch_name):
768 """
772 """
769 Pull a branch from a local repository.
773 Pull a branch from a local repository.
770 """
774 """
771 if self.bare:
775 if self.bare:
772 raise RepositoryError('Cannot pull into a bare git repository')
776 raise RepositoryError('Cannot pull into a bare git repository')
773 # N.B.(skreft): The --ff-only option is to make sure this is a
777 # N.B.(skreft): The --ff-only option is to make sure this is a
774 # fast-forward (i.e., we are only pulling new changes and there are no
778 # fast-forward (i.e., we are only pulling new changes and there are no
775 # conflicts with our current branch)
779 # conflicts with our current branch)
776 # Additionally, that option needs to go before --no-tags, otherwise git
780 # Additionally, that option needs to go before --no-tags, otherwise git
777 # pull complains about it being an unknown flag.
781 # pull complains about it being an unknown flag.
778 cmd = ['pull', '--ff-only', '--no-tags', repository_path, branch_name]
782 cmd = ['pull', '--ff-only', '--no-tags', repository_path, branch_name]
779 self.run_git_command(cmd, fail_on_stderr=False)
783 self.run_git_command(cmd, fail_on_stderr=False)
780
784
781 def _local_merge(self, merge_message, user_name, user_email, heads):
785 def _local_merge(self, merge_message, user_name, user_email, heads):
782 """
786 """
783 Merge the given head into the checked out branch.
787 Merge the given head into the checked out branch.
784
788
785 It will force a merge commit.
789 It will force a merge commit.
786
790
787 Currently it raises an error if the repo is empty, as it is not possible
791 Currently it raises an error if the repo is empty, as it is not possible
788 to create a merge commit in an empty repo.
792 to create a merge commit in an empty repo.
789
793
790 :param merge_message: The message to use for the merge commit.
794 :param merge_message: The message to use for the merge commit.
791 :param heads: the heads to merge.
795 :param heads: the heads to merge.
792 """
796 """
793 if self.bare:
797 if self.bare:
794 raise RepositoryError('Cannot merge into a bare git repository')
798 raise RepositoryError('Cannot merge into a bare git repository')
795
799
796 if not heads:
800 if not heads:
797 return
801 return
798
802
799 if self.is_empty():
803 if self.is_empty():
800 # TODO(skreft): do somehting more robust in this case.
804 # TODO(skreft): do somehting more robust in this case.
801 raise RepositoryError(
805 raise RepositoryError(
802 'Do not know how to merge into empty repositories yet')
806 'Do not know how to merge into empty repositories yet')
803
807
804 # N.B.(skreft): the --no-ff option is used to enforce the creation of a
808 # N.B.(skreft): the --no-ff option is used to enforce the creation of a
805 # commit message. We also specify the user who is doing the merge.
809 # commit message. We also specify the user who is doing the merge.
806 cmd = ['-c', 'user.name=%s' % safe_str(user_name),
810 cmd = ['-c', 'user.name=%s' % safe_str(user_name),
807 '-c', 'user.email=%s' % safe_str(user_email),
811 '-c', 'user.email=%s' % safe_str(user_email),
808 'merge', '--no-ff', '-m', safe_str(merge_message)]
812 'merge', '--no-ff', '-m', safe_str(merge_message)]
809 cmd.extend(heads)
813 cmd.extend(heads)
810 try:
814 try:
811 self.run_git_command(cmd, fail_on_stderr=False)
815 self.run_git_command(cmd, fail_on_stderr=False)
812 except RepositoryError:
816 except RepositoryError:
813 # Cleanup any merge leftovers
817 # Cleanup any merge leftovers
814 self.run_git_command(['merge', '--abort'], fail_on_stderr=False)
818 self.run_git_command(['merge', '--abort'], fail_on_stderr=False)
815 raise
819 raise
816
820
817 def _local_push(
821 def _local_push(
818 self, source_branch, repository_path, target_branch,
822 self, source_branch, repository_path, target_branch,
819 enable_hooks=False, rc_scm_data=None):
823 enable_hooks=False, rc_scm_data=None):
820 """
824 """
821 Push the source_branch to the given repository and target_branch.
825 Push the source_branch to the given repository and target_branch.
822
826
823 Currently it if the target_branch is not master and the target repo is
827 Currently it if the target_branch is not master and the target repo is
824 empty, the push will work, but then GitRepository won't be able to find
828 empty, the push will work, but then GitRepository won't be able to find
825 the pushed branch or the commits. As the HEAD will be corrupted (i.e.,
829 the pushed branch or the commits. As the HEAD will be corrupted (i.e.,
826 pointing to master, which does not exist).
830 pointing to master, which does not exist).
827
831
828 It does not run the hooks in the target repo.
832 It does not run the hooks in the target repo.
829 """
833 """
830 # TODO(skreft): deal with the case in which the target repo is empty,
834 # TODO(skreft): deal with the case in which the target repo is empty,
831 # and the target_branch is not master.
835 # and the target_branch is not master.
832 target_repo = GitRepository(repository_path)
836 target_repo = GitRepository(repository_path)
833 if (not target_repo.bare and
837 if (not target_repo.bare and
834 target_repo._current_branch() == target_branch):
838 target_repo._current_branch() == target_branch):
835 # Git prevents pushing to the checked out branch, so simulate it by
839 # Git prevents pushing to the checked out branch, so simulate it by
836 # pulling into the target repository.
840 # pulling into the target repository.
837 target_repo._local_pull(self.path, source_branch)
841 target_repo._local_pull(self.path, source_branch)
838 else:
842 else:
839 cmd = ['push', os.path.abspath(repository_path),
843 cmd = ['push', os.path.abspath(repository_path),
840 '%s:%s' % (source_branch, target_branch)]
844 '%s:%s' % (source_branch, target_branch)]
841 gitenv = {}
845 gitenv = {}
842 if rc_scm_data:
846 if rc_scm_data:
843 gitenv.update({'RC_SCM_DATA': rc_scm_data})
847 gitenv.update({'RC_SCM_DATA': rc_scm_data})
844
848
845 if not enable_hooks:
849 if not enable_hooks:
846 gitenv['RC_SKIP_HOOKS'] = '1'
850 gitenv['RC_SKIP_HOOKS'] = '1'
847 self.run_git_command(cmd, fail_on_stderr=False, extra_env=gitenv)
851 self.run_git_command(cmd, fail_on_stderr=False, extra_env=gitenv)
848
852
849 def _get_new_pr_branch(self, source_branch, target_branch):
853 def _get_new_pr_branch(self, source_branch, target_branch):
850 prefix = 'pr_%s-%s_' % (source_branch, target_branch)
854 prefix = 'pr_%s-%s_' % (source_branch, target_branch)
851 pr_branches = []
855 pr_branches = []
852 for branch in self.branches:
856 for branch in self.branches:
853 if branch.startswith(prefix):
857 if branch.startswith(prefix):
854 pr_branches.append(int(branch[len(prefix):]))
858 pr_branches.append(int(branch[len(prefix):]))
855
859
856 if not pr_branches:
860 if not pr_branches:
857 branch_id = 0
861 branch_id = 0
858 else:
862 else:
859 branch_id = max(pr_branches) + 1
863 branch_id = max(pr_branches) + 1
860
864
861 return '%s%d' % (prefix, branch_id)
865 return '%s%d' % (prefix, branch_id)
862
866
863 def _merge_repo(self, shadow_repository_path, target_ref,
867 def _merge_repo(self, shadow_repository_path, target_ref,
864 source_repo, source_ref, merge_message,
868 source_repo, source_ref, merge_message,
865 merger_name, merger_email, dry_run=False,
869 merger_name, merger_email, dry_run=False,
866 use_rebase=False, close_branch=False):
870 use_rebase=False, close_branch=False):
867 if target_ref.commit_id != self.branches[target_ref.name]:
871 if target_ref.commit_id != self.branches[target_ref.name]:
868 return MergeResponse(
872 return MergeResponse(
869 False, False, None, MergeFailureReason.TARGET_IS_NOT_HEAD)
873 False, False, None, MergeFailureReason.TARGET_IS_NOT_HEAD)
870
874
871 shadow_repo = GitRepository(shadow_repository_path)
875 shadow_repo = GitRepository(shadow_repository_path)
872 shadow_repo._checkout(target_ref.name)
876 shadow_repo._checkout(target_ref.name)
873 shadow_repo._local_pull(self.path, target_ref.name)
877 shadow_repo._local_pull(self.path, target_ref.name)
874 # Need to reload repo to invalidate the cache, or otherwise we cannot
878 # Need to reload repo to invalidate the cache, or otherwise we cannot
875 # retrieve the last target commit.
879 # retrieve the last target commit.
876 shadow_repo = GitRepository(shadow_repository_path)
880 shadow_repo = GitRepository(shadow_repository_path)
877 if target_ref.commit_id != shadow_repo.branches[target_ref.name]:
881 if target_ref.commit_id != shadow_repo.branches[target_ref.name]:
878 return MergeResponse(
882 return MergeResponse(
879 False, False, None, MergeFailureReason.TARGET_IS_NOT_HEAD)
883 False, False, None, MergeFailureReason.TARGET_IS_NOT_HEAD)
880
884
881 pr_branch = shadow_repo._get_new_pr_branch(
885 pr_branch = shadow_repo._get_new_pr_branch(
882 source_ref.name, target_ref.name)
886 source_ref.name, target_ref.name)
883 shadow_repo._checkout(pr_branch, create=True)
887 shadow_repo._checkout(pr_branch, create=True)
884 try:
888 try:
885 shadow_repo._local_fetch(source_repo.path, source_ref.name)
889 shadow_repo._local_fetch(source_repo.path, source_ref.name)
886 except RepositoryError:
890 except RepositoryError:
887 log.exception('Failure when doing local fetch on git shadow repo')
891 log.exception('Failure when doing local fetch on git shadow repo')
888 return MergeResponse(
892 return MergeResponse(
889 False, False, None, MergeFailureReason.MISSING_SOURCE_REF)
893 False, False, None, MergeFailureReason.MISSING_SOURCE_REF)
890
894
891 merge_ref = None
895 merge_ref = None
892 merge_failure_reason = MergeFailureReason.NONE
896 merge_failure_reason = MergeFailureReason.NONE
893 try:
897 try:
894 shadow_repo._local_merge(merge_message, merger_name, merger_email,
898 shadow_repo._local_merge(merge_message, merger_name, merger_email,
895 [source_ref.commit_id])
899 [source_ref.commit_id])
896 merge_possible = True
900 merge_possible = True
897
901
898 # Need to reload repo to invalidate the cache, or otherwise we
902 # Need to reload repo to invalidate the cache, or otherwise we
899 # cannot retrieve the merge commit.
903 # cannot retrieve the merge commit.
900 shadow_repo = GitRepository(shadow_repository_path)
904 shadow_repo = GitRepository(shadow_repository_path)
901 merge_commit_id = shadow_repo.branches[pr_branch]
905 merge_commit_id = shadow_repo.branches[pr_branch]
902
906
903 # Set a reference pointing to the merge commit. This reference may
907 # Set a reference pointing to the merge commit. This reference may
904 # be used to easily identify the last successful merge commit in
908 # be used to easily identify the last successful merge commit in
905 # the shadow repository.
909 # the shadow repository.
906 shadow_repo.set_refs('refs/heads/pr-merge', merge_commit_id)
910 shadow_repo.set_refs('refs/heads/pr-merge', merge_commit_id)
907 merge_ref = Reference('branch', 'pr-merge', merge_commit_id)
911 merge_ref = Reference('branch', 'pr-merge', merge_commit_id)
908 except RepositoryError:
912 except RepositoryError:
909 log.exception('Failure when doing local merge on git shadow repo')
913 log.exception('Failure when doing local merge on git shadow repo')
910 merge_possible = False
914 merge_possible = False
911 merge_failure_reason = MergeFailureReason.MERGE_FAILED
915 merge_failure_reason = MergeFailureReason.MERGE_FAILED
912
916
913 if merge_possible and not dry_run:
917 if merge_possible and not dry_run:
914 try:
918 try:
915 shadow_repo._local_push(
919 shadow_repo._local_push(
916 pr_branch, self.path, target_ref.name, enable_hooks=True,
920 pr_branch, self.path, target_ref.name, enable_hooks=True,
917 rc_scm_data=self.config.get('rhodecode', 'RC_SCM_DATA'))
921 rc_scm_data=self.config.get('rhodecode', 'RC_SCM_DATA'))
918 merge_succeeded = True
922 merge_succeeded = True
919 except RepositoryError:
923 except RepositoryError:
920 log.exception(
924 log.exception(
921 'Failure when doing local push on git shadow repo')
925 'Failure when doing local push on git shadow repo')
922 merge_succeeded = False
926 merge_succeeded = False
923 merge_failure_reason = MergeFailureReason.PUSH_FAILED
927 merge_failure_reason = MergeFailureReason.PUSH_FAILED
924 else:
928 else:
925 merge_succeeded = False
929 merge_succeeded = False
926
930
927 return MergeResponse(
931 return MergeResponse(
928 merge_possible, merge_succeeded, merge_ref,
932 merge_possible, merge_succeeded, merge_ref,
929 merge_failure_reason)
933 merge_failure_reason)
930
934
931 def _get_shadow_repository_path(self, workspace_id):
935 def _get_shadow_repository_path(self, workspace_id):
932 # The name of the shadow repository must start with '.', so it is
936 # The name of the shadow repository must start with '.', so it is
933 # skipped by 'rhodecode.lib.utils.get_filesystem_repos'.
937 # skipped by 'rhodecode.lib.utils.get_filesystem_repos'.
934 return os.path.join(
938 return os.path.join(
935 os.path.dirname(self.path),
939 os.path.dirname(self.path),
936 '.__shadow_%s_%s' % (os.path.basename(self.path), workspace_id))
940 '.__shadow_%s_%s' % (os.path.basename(self.path), workspace_id))
937
941
938 def _maybe_prepare_merge_workspace(self, workspace_id, target_ref):
942 def _maybe_prepare_merge_workspace(self, workspace_id, target_ref):
939 shadow_repository_path = self._get_shadow_repository_path(workspace_id)
943 shadow_repository_path = self._get_shadow_repository_path(workspace_id)
940 if not os.path.exists(shadow_repository_path):
944 if not os.path.exists(shadow_repository_path):
941 self._local_clone(shadow_repository_path, target_ref.name)
945 self._local_clone(shadow_repository_path, target_ref.name)
942
946
943 return shadow_repository_path
947 return shadow_repository_path
944
948
945 def cleanup_merge_workspace(self, workspace_id):
949 def cleanup_merge_workspace(self, workspace_id):
946 shadow_repository_path = self._get_shadow_repository_path(workspace_id)
950 shadow_repository_path = self._get_shadow_repository_path(workspace_id)
947 shutil.rmtree(shadow_repository_path, ignore_errors=True)
951 shutil.rmtree(shadow_repository_path, ignore_errors=True)
@@ -1,1269 +1,1269 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import datetime
21 import datetime
22 import mock
22 import mock
23 import os
23 import os
24 import sys
24 import sys
25 import shutil
25 import shutil
26
26
27 import pytest
27 import pytest
28
28
29 from rhodecode.lib.utils import make_db_config
29 from rhodecode.lib.utils import make_db_config
30 from rhodecode.lib.vcs.backends.base import Reference
30 from rhodecode.lib.vcs.backends.base import Reference
31 from rhodecode.lib.vcs.backends.git import (
31 from rhodecode.lib.vcs.backends.git import (
32 GitRepository, GitCommit, discover_git_version)
32 GitRepository, GitCommit, discover_git_version)
33 from rhodecode.lib.vcs.exceptions import (
33 from rhodecode.lib.vcs.exceptions import (
34 RepositoryError, VCSError, NodeDoesNotExistError)
34 RepositoryError, VCSError, NodeDoesNotExistError)
35 from rhodecode.lib.vcs.nodes import (
35 from rhodecode.lib.vcs.nodes import (
36 NodeKind, FileNode, DirNode, NodeState, SubModuleNode)
36 NodeKind, FileNode, DirNode, NodeState, SubModuleNode)
37 from rhodecode.tests import TEST_GIT_REPO, TEST_GIT_REPO_CLONE, get_new_dir
37 from rhodecode.tests import TEST_GIT_REPO, TEST_GIT_REPO_CLONE, get_new_dir
38 from rhodecode.tests.vcs.base import BackendTestMixin
38 from rhodecode.tests.vcs.base import BackendTestMixin
39
39
40
40
41 pytestmark = pytest.mark.backends("git")
41 pytestmark = pytest.mark.backends("git")
42
42
43
43
44 def repo_path_generator():
44 def repo_path_generator():
45 """
45 """
46 Return a different path to be used for cloning repos.
46 Return a different path to be used for cloning repos.
47 """
47 """
48 i = 0
48 i = 0
49 while True:
49 while True:
50 i += 1
50 i += 1
51 yield '%s-%d' % (TEST_GIT_REPO_CLONE, i)
51 yield '%s-%d' % (TEST_GIT_REPO_CLONE, i)
52
52
53
53
54 REPO_PATH_GENERATOR = repo_path_generator()
54 REPO_PATH_GENERATOR = repo_path_generator()
55
55
56
56
57 class TestGitRepository:
57 class TestGitRepository:
58
58
59 # pylint: disable=protected-access
59 # pylint: disable=protected-access
60
60
61 def __check_for_existing_repo(self):
61 def __check_for_existing_repo(self):
62 if os.path.exists(TEST_GIT_REPO_CLONE):
62 if os.path.exists(TEST_GIT_REPO_CLONE):
63 self.fail('Cannot test git clone repo as location %s already '
63 self.fail('Cannot test git clone repo as location %s already '
64 'exists. You should manually remove it first.'
64 'exists. You should manually remove it first.'
65 % TEST_GIT_REPO_CLONE)
65 % TEST_GIT_REPO_CLONE)
66
66
67 @pytest.fixture(autouse=True)
67 @pytest.fixture(autouse=True)
68 def prepare(self, request, baseapp):
68 def prepare(self, request, baseapp):
69 self.repo = GitRepository(TEST_GIT_REPO, bare=True)
69 self.repo = GitRepository(TEST_GIT_REPO, bare=True)
70
70
71 def get_clone_repo(self):
71 def get_clone_repo(self):
72 """
72 """
73 Return a non bare clone of the base repo.
73 Return a non bare clone of the base repo.
74 """
74 """
75 clone_path = next(REPO_PATH_GENERATOR)
75 clone_path = next(REPO_PATH_GENERATOR)
76 repo_clone = GitRepository(
76 repo_clone = GitRepository(
77 clone_path, create=True, src_url=self.repo.path, bare=False)
77 clone_path, create=True, src_url=self.repo.path, bare=False)
78
78
79 return repo_clone
79 return repo_clone
80
80
81 def get_empty_repo(self, bare=False):
81 def get_empty_repo(self, bare=False):
82 """
82 """
83 Return a non bare empty repo.
83 Return a non bare empty repo.
84 """
84 """
85 return GitRepository(next(REPO_PATH_GENERATOR), create=True, bare=bare)
85 return GitRepository(next(REPO_PATH_GENERATOR), create=True, bare=bare)
86
86
87 def test_wrong_repo_path(self):
87 def test_wrong_repo_path(self):
88 wrong_repo_path = '/tmp/errorrepo'
88 wrong_repo_path = '/tmp/errorrepo_git'
89 with pytest.raises(RepositoryError):
89 with pytest.raises(RepositoryError):
90 GitRepository(wrong_repo_path)
90 GitRepository(wrong_repo_path)
91
91
92 def test_repo_clone(self):
92 def test_repo_clone(self):
93 self.__check_for_existing_repo()
93 self.__check_for_existing_repo()
94 repo = GitRepository(TEST_GIT_REPO)
94 repo = GitRepository(TEST_GIT_REPO)
95 repo_clone = GitRepository(
95 repo_clone = GitRepository(
96 TEST_GIT_REPO_CLONE,
96 TEST_GIT_REPO_CLONE,
97 src_url=TEST_GIT_REPO, create=True, update_after_clone=True)
97 src_url=TEST_GIT_REPO, create=True, update_after_clone=True)
98 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
98 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
99 # Checking hashes of commits should be enough
99 # Checking hashes of commits should be enough
100 for commit in repo.get_commits():
100 for commit in repo.get_commits():
101 raw_id = commit.raw_id
101 raw_id = commit.raw_id
102 assert raw_id == repo_clone.get_commit(raw_id).raw_id
102 assert raw_id == repo_clone.get_commit(raw_id).raw_id
103
103
104 def test_repo_clone_without_create(self):
104 def test_repo_clone_without_create(self):
105 with pytest.raises(RepositoryError):
105 with pytest.raises(RepositoryError):
106 GitRepository(
106 GitRepository(
107 TEST_GIT_REPO_CLONE + '_wo_create', src_url=TEST_GIT_REPO)
107 TEST_GIT_REPO_CLONE + '_wo_create', src_url=TEST_GIT_REPO)
108
108
109 def test_repo_clone_with_update(self):
109 def test_repo_clone_with_update(self):
110 repo = GitRepository(TEST_GIT_REPO)
110 repo = GitRepository(TEST_GIT_REPO)
111 clone_path = TEST_GIT_REPO_CLONE + '_with_update'
111 clone_path = TEST_GIT_REPO_CLONE + '_with_update'
112 repo_clone = GitRepository(
112 repo_clone = GitRepository(
113 clone_path,
113 clone_path,
114 create=True, src_url=TEST_GIT_REPO, update_after_clone=True)
114 create=True, src_url=TEST_GIT_REPO, update_after_clone=True)
115 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
115 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
116
116
117 # check if current workdir was updated
117 # check if current workdir was updated
118 fpath = os.path.join(clone_path, 'MANIFEST.in')
118 fpath = os.path.join(clone_path, 'MANIFEST.in')
119 assert os.path.isfile(fpath)
119 assert os.path.isfile(fpath)
120
120
121 def test_repo_clone_without_update(self):
121 def test_repo_clone_without_update(self):
122 repo = GitRepository(TEST_GIT_REPO)
122 repo = GitRepository(TEST_GIT_REPO)
123 clone_path = TEST_GIT_REPO_CLONE + '_without_update'
123 clone_path = TEST_GIT_REPO_CLONE + '_without_update'
124 repo_clone = GitRepository(
124 repo_clone = GitRepository(
125 clone_path,
125 clone_path,
126 create=True, src_url=TEST_GIT_REPO, update_after_clone=False)
126 create=True, src_url=TEST_GIT_REPO, update_after_clone=False)
127 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
127 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
128 # check if current workdir was *NOT* updated
128 # check if current workdir was *NOT* updated
129 fpath = os.path.join(clone_path, 'MANIFEST.in')
129 fpath = os.path.join(clone_path, 'MANIFEST.in')
130 # Make sure it's not bare repo
130 # Make sure it's not bare repo
131 assert not repo_clone.bare
131 assert not repo_clone.bare
132 assert not os.path.isfile(fpath)
132 assert not os.path.isfile(fpath)
133
133
134 def test_repo_clone_into_bare_repo(self):
134 def test_repo_clone_into_bare_repo(self):
135 repo = GitRepository(TEST_GIT_REPO)
135 repo = GitRepository(TEST_GIT_REPO)
136 clone_path = TEST_GIT_REPO_CLONE + '_bare.git'
136 clone_path = TEST_GIT_REPO_CLONE + '_bare.git'
137 repo_clone = GitRepository(
137 repo_clone = GitRepository(
138 clone_path, create=True, src_url=repo.path, bare=True)
138 clone_path, create=True, src_url=repo.path, bare=True)
139 assert repo_clone.bare
139 assert repo_clone.bare
140
140
141 def test_create_repo_is_not_bare_by_default(self):
141 def test_create_repo_is_not_bare_by_default(self):
142 repo = GitRepository(get_new_dir('not-bare-by-default'), create=True)
142 repo = GitRepository(get_new_dir('not-bare-by-default'), create=True)
143 assert not repo.bare
143 assert not repo.bare
144
144
145 def test_create_bare_repo(self):
145 def test_create_bare_repo(self):
146 repo = GitRepository(get_new_dir('bare-repo'), create=True, bare=True)
146 repo = GitRepository(get_new_dir('bare-repo'), create=True, bare=True)
147 assert repo.bare
147 assert repo.bare
148
148
149 def test_update_server_info(self):
149 def test_update_server_info(self):
150 self.repo._update_server_info()
150 self.repo._update_server_info()
151
151
152 def test_fetch(self, vcsbackend_git):
152 def test_fetch(self, vcsbackend_git):
153 # Note: This is a git specific part of the API, it's only implemented
153 # Note: This is a git specific part of the API, it's only implemented
154 # by the git backend.
154 # by the git backend.
155 source_repo = vcsbackend_git.repo
155 source_repo = vcsbackend_git.repo
156 target_repo = vcsbackend_git.create_repo()
156 target_repo = vcsbackend_git.create_repo()
157 target_repo.fetch(source_repo.path)
157 target_repo.fetch(source_repo.path)
158 # Note: Get a fresh instance, avoids caching trouble
158 # Note: Get a fresh instance, avoids caching trouble
159 target_repo = vcsbackend_git.backend(target_repo.path)
159 target_repo = vcsbackend_git.backend(target_repo.path)
160 assert len(source_repo.commit_ids) == len(target_repo.commit_ids)
160 assert len(source_repo.commit_ids) == len(target_repo.commit_ids)
161
161
162 def test_commit_ids(self):
162 def test_commit_ids(self):
163 # there are 112 commits (by now)
163 # there are 112 commits (by now)
164 # so we can assume they would be available from now on
164 # so we can assume they would be available from now on
165 subset = set([
165 subset = set([
166 'c1214f7e79e02fc37156ff215cd71275450cffc3',
166 'c1214f7e79e02fc37156ff215cd71275450cffc3',
167 '38b5fe81f109cb111f549bfe9bb6b267e10bc557',
167 '38b5fe81f109cb111f549bfe9bb6b267e10bc557',
168 'fa6600f6848800641328adbf7811fd2372c02ab2',
168 'fa6600f6848800641328adbf7811fd2372c02ab2',
169 '102607b09cdd60e2793929c4f90478be29f85a17',
169 '102607b09cdd60e2793929c4f90478be29f85a17',
170 '49d3fd156b6f7db46313fac355dca1a0b94a0017',
170 '49d3fd156b6f7db46313fac355dca1a0b94a0017',
171 '2d1028c054665b962fa3d307adfc923ddd528038',
171 '2d1028c054665b962fa3d307adfc923ddd528038',
172 'd7e0d30fbcae12c90680eb095a4f5f02505ce501',
172 'd7e0d30fbcae12c90680eb095a4f5f02505ce501',
173 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
173 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
174 'dd80b0f6cf5052f17cc738c2951c4f2070200d7f',
174 'dd80b0f6cf5052f17cc738c2951c4f2070200d7f',
175 '8430a588b43b5d6da365400117c89400326e7992',
175 '8430a588b43b5d6da365400117c89400326e7992',
176 'd955cd312c17b02143c04fa1099a352b04368118',
176 'd955cd312c17b02143c04fa1099a352b04368118',
177 'f67b87e5c629c2ee0ba58f85197e423ff28d735b',
177 'f67b87e5c629c2ee0ba58f85197e423ff28d735b',
178 'add63e382e4aabc9e1afdc4bdc24506c269b7618',
178 'add63e382e4aabc9e1afdc4bdc24506c269b7618',
179 'f298fe1189f1b69779a4423f40b48edf92a703fc',
179 'f298fe1189f1b69779a4423f40b48edf92a703fc',
180 'bd9b619eb41994cac43d67cf4ccc8399c1125808',
180 'bd9b619eb41994cac43d67cf4ccc8399c1125808',
181 '6e125e7c890379446e98980d8ed60fba87d0f6d1',
181 '6e125e7c890379446e98980d8ed60fba87d0f6d1',
182 'd4a54db9f745dfeba6933bf5b1e79e15d0af20bd',
182 'd4a54db9f745dfeba6933bf5b1e79e15d0af20bd',
183 '0b05e4ed56c802098dfc813cbe779b2f49e92500',
183 '0b05e4ed56c802098dfc813cbe779b2f49e92500',
184 '191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
184 '191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
185 '45223f8f114c64bf4d6f853e3c35a369a6305520',
185 '45223f8f114c64bf4d6f853e3c35a369a6305520',
186 'ca1eb7957a54bce53b12d1a51b13452f95bc7c7e',
186 'ca1eb7957a54bce53b12d1a51b13452f95bc7c7e',
187 'f5ea29fc42ef67a2a5a7aecff10e1566699acd68',
187 'f5ea29fc42ef67a2a5a7aecff10e1566699acd68',
188 '27d48942240f5b91dfda77accd2caac94708cc7d',
188 '27d48942240f5b91dfda77accd2caac94708cc7d',
189 '622f0eb0bafd619d2560c26f80f09e3b0b0d78af',
189 '622f0eb0bafd619d2560c26f80f09e3b0b0d78af',
190 'e686b958768ee96af8029fe19c6050b1a8dd3b2b'])
190 'e686b958768ee96af8029fe19c6050b1a8dd3b2b'])
191 assert subset.issubset(set(self.repo.commit_ids))
191 assert subset.issubset(set(self.repo.commit_ids))
192
192
193 def test_slicing(self):
193 def test_slicing(self):
194 # 4 1 5 10 95
194 # 4 1 5 10 95
195 for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5),
195 for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5),
196 (10, 20, 10), (5, 100, 95)]:
196 (10, 20, 10), (5, 100, 95)]:
197 commit_ids = list(self.repo[sfrom:sto])
197 commit_ids = list(self.repo[sfrom:sto])
198 assert len(commit_ids) == size
198 assert len(commit_ids) == size
199 assert commit_ids[0] == self.repo.get_commit(commit_idx=sfrom)
199 assert commit_ids[0] == self.repo.get_commit(commit_idx=sfrom)
200 assert commit_ids[-1] == self.repo.get_commit(commit_idx=sto - 1)
200 assert commit_ids[-1] == self.repo.get_commit(commit_idx=sto - 1)
201
201
202 def test_branches(self):
202 def test_branches(self):
203 # TODO: Need more tests here
203 # TODO: Need more tests here
204 # Removed (those are 'remotes' branches for cloned repo)
204 # Removed (those are 'remotes' branches for cloned repo)
205 # assert 'master' in self.repo.branches
205 # assert 'master' in self.repo.branches
206 # assert 'gittree' in self.repo.branches
206 # assert 'gittree' in self.repo.branches
207 # assert 'web-branch' in self.repo.branches
207 # assert 'web-branch' in self.repo.branches
208 for __, commit_id in self.repo.branches.items():
208 for __, commit_id in self.repo.branches.items():
209 assert isinstance(self.repo.get_commit(commit_id), GitCommit)
209 assert isinstance(self.repo.get_commit(commit_id), GitCommit)
210
210
211 def test_tags(self):
211 def test_tags(self):
212 # TODO: Need more tests here
212 # TODO: Need more tests here
213 assert 'v0.1.1' in self.repo.tags
213 assert 'v0.1.1' in self.repo.tags
214 assert 'v0.1.2' in self.repo.tags
214 assert 'v0.1.2' in self.repo.tags
215 for __, commit_id in self.repo.tags.items():
215 for __, commit_id in self.repo.tags.items():
216 assert isinstance(self.repo.get_commit(commit_id), GitCommit)
216 assert isinstance(self.repo.get_commit(commit_id), GitCommit)
217
217
218 def _test_single_commit_cache(self, commit_id):
218 def _test_single_commit_cache(self, commit_id):
219 commit = self.repo.get_commit(commit_id)
219 commit = self.repo.get_commit(commit_id)
220 assert commit_id in self.repo.commits
220 assert commit_id in self.repo.commits
221 assert commit is self.repo.commits[commit_id]
221 assert commit is self.repo.commits[commit_id]
222
222
223 def test_initial_commit(self):
223 def test_initial_commit(self):
224 commit_id = self.repo.commit_ids[0]
224 commit_id = self.repo.commit_ids[0]
225 init_commit = self.repo.get_commit(commit_id)
225 init_commit = self.repo.get_commit(commit_id)
226 init_author = init_commit.author
226 init_author = init_commit.author
227
227
228 assert init_commit.message == 'initial import\n'
228 assert init_commit.message == 'initial import\n'
229 assert init_author == 'Marcin Kuzminski <marcin@python-blog.com>'
229 assert init_author == 'Marcin Kuzminski <marcin@python-blog.com>'
230 assert init_author == init_commit.committer
230 assert init_author == init_commit.committer
231 for path in ('vcs/__init__.py',
231 for path in ('vcs/__init__.py',
232 'vcs/backends/BaseRepository.py',
232 'vcs/backends/BaseRepository.py',
233 'vcs/backends/__init__.py'):
233 'vcs/backends/__init__.py'):
234 assert isinstance(init_commit.get_node(path), FileNode)
234 assert isinstance(init_commit.get_node(path), FileNode)
235 for path in ('', 'vcs', 'vcs/backends'):
235 for path in ('', 'vcs', 'vcs/backends'):
236 assert isinstance(init_commit.get_node(path), DirNode)
236 assert isinstance(init_commit.get_node(path), DirNode)
237
237
238 with pytest.raises(NodeDoesNotExistError):
238 with pytest.raises(NodeDoesNotExistError):
239 init_commit.get_node(path='foobar')
239 init_commit.get_node(path='foobar')
240
240
241 node = init_commit.get_node('vcs/')
241 node = init_commit.get_node('vcs/')
242 assert hasattr(node, 'kind')
242 assert hasattr(node, 'kind')
243 assert node.kind == NodeKind.DIR
243 assert node.kind == NodeKind.DIR
244
244
245 node = init_commit.get_node('vcs')
245 node = init_commit.get_node('vcs')
246 assert hasattr(node, 'kind')
246 assert hasattr(node, 'kind')
247 assert node.kind == NodeKind.DIR
247 assert node.kind == NodeKind.DIR
248
248
249 node = init_commit.get_node('vcs/__init__.py')
249 node = init_commit.get_node('vcs/__init__.py')
250 assert hasattr(node, 'kind')
250 assert hasattr(node, 'kind')
251 assert node.kind == NodeKind.FILE
251 assert node.kind == NodeKind.FILE
252
252
253 def test_not_existing_commit(self):
253 def test_not_existing_commit(self):
254 with pytest.raises(RepositoryError):
254 with pytest.raises(RepositoryError):
255 self.repo.get_commit('f' * 40)
255 self.repo.get_commit('f' * 40)
256
256
257 def test_commit10(self):
257 def test_commit10(self):
258
258
259 commit10 = self.repo.get_commit(self.repo.commit_ids[9])
259 commit10 = self.repo.get_commit(self.repo.commit_ids[9])
260 README = """===
260 README = """===
261 VCS
261 VCS
262 ===
262 ===
263
263
264 Various Version Control System management abstraction layer for Python.
264 Various Version Control System management abstraction layer for Python.
265
265
266 Introduction
266 Introduction
267 ------------
267 ------------
268
268
269 TODO: To be written...
269 TODO: To be written...
270
270
271 """
271 """
272 node = commit10.get_node('README.rst')
272 node = commit10.get_node('README.rst')
273 assert node.kind == NodeKind.FILE
273 assert node.kind == NodeKind.FILE
274 assert node.content == README
274 assert node.content == README
275
275
276 def test_head(self):
276 def test_head(self):
277 assert self.repo.head == self.repo.get_commit().raw_id
277 assert self.repo.head == self.repo.get_commit().raw_id
278
278
279 def test_checkout_with_create(self):
279 def test_checkout_with_create(self):
280 repo_clone = self.get_clone_repo()
280 repo_clone = self.get_clone_repo()
281
281
282 new_branch = 'new_branch'
282 new_branch = 'new_branch'
283 assert repo_clone._current_branch() == 'master'
283 assert repo_clone._current_branch() == 'master'
284 assert set(repo_clone.branches) == set(('master',))
284 assert set(repo_clone.branches) == set(('master',))
285 repo_clone._checkout(new_branch, create=True)
285 repo_clone._checkout(new_branch, create=True)
286
286
287 # Branches is a lazy property so we need to recrete the Repo object.
287 # Branches is a lazy property so we need to recrete the Repo object.
288 repo_clone = GitRepository(repo_clone.path)
288 repo_clone = GitRepository(repo_clone.path)
289 assert set(repo_clone.branches) == set(('master', new_branch))
289 assert set(repo_clone.branches) == set(('master', new_branch))
290 assert repo_clone._current_branch() == new_branch
290 assert repo_clone._current_branch() == new_branch
291
291
292 def test_checkout(self):
292 def test_checkout(self):
293 repo_clone = self.get_clone_repo()
293 repo_clone = self.get_clone_repo()
294
294
295 repo_clone._checkout('new_branch', create=True)
295 repo_clone._checkout('new_branch', create=True)
296 repo_clone._checkout('master')
296 repo_clone._checkout('master')
297
297
298 assert repo_clone._current_branch() == 'master'
298 assert repo_clone._current_branch() == 'master'
299
299
300 def test_checkout_same_branch(self):
300 def test_checkout_same_branch(self):
301 repo_clone = self.get_clone_repo()
301 repo_clone = self.get_clone_repo()
302
302
303 repo_clone._checkout('master')
303 repo_clone._checkout('master')
304 assert repo_clone._current_branch() == 'master'
304 assert repo_clone._current_branch() == 'master'
305
305
306 def test_checkout_branch_already_exists(self):
306 def test_checkout_branch_already_exists(self):
307 repo_clone = self.get_clone_repo()
307 repo_clone = self.get_clone_repo()
308
308
309 with pytest.raises(RepositoryError):
309 with pytest.raises(RepositoryError):
310 repo_clone._checkout('master', create=True)
310 repo_clone._checkout('master', create=True)
311
311
312 def test_checkout_bare_repo(self):
312 def test_checkout_bare_repo(self):
313 with pytest.raises(RepositoryError):
313 with pytest.raises(RepositoryError):
314 self.repo._checkout('master')
314 self.repo._checkout('master')
315
315
316 def test_current_branch_bare_repo(self):
316 def test_current_branch_bare_repo(self):
317 with pytest.raises(RepositoryError):
317 with pytest.raises(RepositoryError):
318 self.repo._current_branch()
318 self.repo._current_branch()
319
319
320 def test_current_branch_empty_repo(self):
320 def test_current_branch_empty_repo(self):
321 repo = self.get_empty_repo()
321 repo = self.get_empty_repo()
322 assert repo._current_branch() is None
322 assert repo._current_branch() is None
323
323
324 def test_local_clone(self):
324 def test_local_clone(self):
325 clone_path = next(REPO_PATH_GENERATOR)
325 clone_path = next(REPO_PATH_GENERATOR)
326 self.repo._local_clone(clone_path, 'master')
326 self.repo._local_clone(clone_path, 'master')
327 repo_clone = GitRepository(clone_path)
327 repo_clone = GitRepository(clone_path)
328
328
329 assert self.repo.commit_ids == repo_clone.commit_ids
329 assert self.repo.commit_ids == repo_clone.commit_ids
330
330
331 def test_local_clone_with_specific_branch(self):
331 def test_local_clone_with_specific_branch(self):
332 source_repo = self.get_clone_repo()
332 source_repo = self.get_clone_repo()
333
333
334 # Create a new branch in source repo
334 # Create a new branch in source repo
335 new_branch_commit = source_repo.commit_ids[-3]
335 new_branch_commit = source_repo.commit_ids[-3]
336 source_repo._checkout(new_branch_commit)
336 source_repo._checkout(new_branch_commit)
337 source_repo._checkout('new_branch', create=True)
337 source_repo._checkout('new_branch', create=True)
338
338
339 clone_path = next(REPO_PATH_GENERATOR)
339 clone_path = next(REPO_PATH_GENERATOR)
340 source_repo._local_clone(clone_path, 'new_branch')
340 source_repo._local_clone(clone_path, 'new_branch')
341 repo_clone = GitRepository(clone_path)
341 repo_clone = GitRepository(clone_path)
342
342
343 assert source_repo.commit_ids[:-3 + 1] == repo_clone.commit_ids
343 assert source_repo.commit_ids[:-3 + 1] == repo_clone.commit_ids
344
344
345 clone_path = next(REPO_PATH_GENERATOR)
345 clone_path = next(REPO_PATH_GENERATOR)
346 source_repo._local_clone(clone_path, 'master')
346 source_repo._local_clone(clone_path, 'master')
347 repo_clone = GitRepository(clone_path)
347 repo_clone = GitRepository(clone_path)
348
348
349 assert source_repo.commit_ids == repo_clone.commit_ids
349 assert source_repo.commit_ids == repo_clone.commit_ids
350
350
351 def test_local_clone_fails_if_target_exists(self):
351 def test_local_clone_fails_if_target_exists(self):
352 with pytest.raises(RepositoryError):
352 with pytest.raises(RepositoryError):
353 self.repo._local_clone(self.repo.path, 'master')
353 self.repo._local_clone(self.repo.path, 'master')
354
354
355 def test_local_fetch(self):
355 def test_local_fetch(self):
356 target_repo = self.get_empty_repo()
356 target_repo = self.get_empty_repo()
357 source_repo = self.get_clone_repo()
357 source_repo = self.get_clone_repo()
358
358
359 # Create a new branch in source repo
359 # Create a new branch in source repo
360 master_commit = source_repo.commit_ids[-1]
360 master_commit = source_repo.commit_ids[-1]
361 new_branch_commit = source_repo.commit_ids[-3]
361 new_branch_commit = source_repo.commit_ids[-3]
362 source_repo._checkout(new_branch_commit)
362 source_repo._checkout(new_branch_commit)
363 source_repo._checkout('new_branch', create=True)
363 source_repo._checkout('new_branch', create=True)
364
364
365 target_repo._local_fetch(source_repo.path, 'new_branch')
365 target_repo._local_fetch(source_repo.path, 'new_branch')
366 assert target_repo._last_fetch_heads() == [new_branch_commit]
366 assert target_repo._last_fetch_heads() == [new_branch_commit]
367
367
368 target_repo._local_fetch(source_repo.path, 'master')
368 target_repo._local_fetch(source_repo.path, 'master')
369 assert target_repo._last_fetch_heads() == [master_commit]
369 assert target_repo._last_fetch_heads() == [master_commit]
370
370
371 def test_local_fetch_from_bare_repo(self):
371 def test_local_fetch_from_bare_repo(self):
372 target_repo = self.get_empty_repo()
372 target_repo = self.get_empty_repo()
373 target_repo._local_fetch(self.repo.path, 'master')
373 target_repo._local_fetch(self.repo.path, 'master')
374
374
375 master_commit = self.repo.commit_ids[-1]
375 master_commit = self.repo.commit_ids[-1]
376 assert target_repo._last_fetch_heads() == [master_commit]
376 assert target_repo._last_fetch_heads() == [master_commit]
377
377
378 def test_local_fetch_from_same_repo(self):
378 def test_local_fetch_from_same_repo(self):
379 with pytest.raises(ValueError):
379 with pytest.raises(ValueError):
380 self.repo._local_fetch(self.repo.path, 'master')
380 self.repo._local_fetch(self.repo.path, 'master')
381
381
382 def test_local_fetch_branch_does_not_exist(self):
382 def test_local_fetch_branch_does_not_exist(self):
383 target_repo = self.get_empty_repo()
383 target_repo = self.get_empty_repo()
384
384
385 with pytest.raises(RepositoryError):
385 with pytest.raises(RepositoryError):
386 target_repo._local_fetch(self.repo.path, 'new_branch')
386 target_repo._local_fetch(self.repo.path, 'new_branch')
387
387
388 def test_local_pull(self):
388 def test_local_pull(self):
389 target_repo = self.get_empty_repo()
389 target_repo = self.get_empty_repo()
390 source_repo = self.get_clone_repo()
390 source_repo = self.get_clone_repo()
391
391
392 # Create a new branch in source repo
392 # Create a new branch in source repo
393 master_commit = source_repo.commit_ids[-1]
393 master_commit = source_repo.commit_ids[-1]
394 new_branch_commit = source_repo.commit_ids[-3]
394 new_branch_commit = source_repo.commit_ids[-3]
395 source_repo._checkout(new_branch_commit)
395 source_repo._checkout(new_branch_commit)
396 source_repo._checkout('new_branch', create=True)
396 source_repo._checkout('new_branch', create=True)
397
397
398 target_repo._local_pull(source_repo.path, 'new_branch')
398 target_repo._local_pull(source_repo.path, 'new_branch')
399 target_repo = GitRepository(target_repo.path)
399 target_repo = GitRepository(target_repo.path)
400 assert target_repo.head == new_branch_commit
400 assert target_repo.head == new_branch_commit
401
401
402 target_repo._local_pull(source_repo.path, 'master')
402 target_repo._local_pull(source_repo.path, 'master')
403 target_repo = GitRepository(target_repo.path)
403 target_repo = GitRepository(target_repo.path)
404 assert target_repo.head == master_commit
404 assert target_repo.head == master_commit
405
405
406 def test_local_pull_in_bare_repo(self):
406 def test_local_pull_in_bare_repo(self):
407 with pytest.raises(RepositoryError):
407 with pytest.raises(RepositoryError):
408 self.repo._local_pull(self.repo.path, 'master')
408 self.repo._local_pull(self.repo.path, 'master')
409
409
410 def test_local_merge(self):
410 def test_local_merge(self):
411 target_repo = self.get_empty_repo()
411 target_repo = self.get_empty_repo()
412 source_repo = self.get_clone_repo()
412 source_repo = self.get_clone_repo()
413
413
414 # Create a new branch in source repo
414 # Create a new branch in source repo
415 master_commit = source_repo.commit_ids[-1]
415 master_commit = source_repo.commit_ids[-1]
416 new_branch_commit = source_repo.commit_ids[-3]
416 new_branch_commit = source_repo.commit_ids[-3]
417 source_repo._checkout(new_branch_commit)
417 source_repo._checkout(new_branch_commit)
418 source_repo._checkout('new_branch', create=True)
418 source_repo._checkout('new_branch', create=True)
419
419
420 # This is required as one cannot do a -ff-only merge in an empty repo.
420 # This is required as one cannot do a -ff-only merge in an empty repo.
421 target_repo._local_pull(source_repo.path, 'new_branch')
421 target_repo._local_pull(source_repo.path, 'new_branch')
422
422
423 target_repo._local_fetch(source_repo.path, 'master')
423 target_repo._local_fetch(source_repo.path, 'master')
424 merge_message = 'Merge message\n\nDescription:...'
424 merge_message = 'Merge message\n\nDescription:...'
425 user_name = 'Albert Einstein'
425 user_name = 'Albert Einstein'
426 user_email = 'albert@einstein.com'
426 user_email = 'albert@einstein.com'
427 target_repo._local_merge(merge_message, user_name, user_email,
427 target_repo._local_merge(merge_message, user_name, user_email,
428 target_repo._last_fetch_heads())
428 target_repo._last_fetch_heads())
429
429
430 target_repo = GitRepository(target_repo.path)
430 target_repo = GitRepository(target_repo.path)
431 assert target_repo.commit_ids[-2] == master_commit
431 assert target_repo.commit_ids[-2] == master_commit
432 last_commit = target_repo.get_commit(target_repo.head)
432 last_commit = target_repo.get_commit(target_repo.head)
433 assert last_commit.message.strip() == merge_message
433 assert last_commit.message.strip() == merge_message
434 assert last_commit.author == '%s <%s>' % (user_name, user_email)
434 assert last_commit.author == '%s <%s>' % (user_name, user_email)
435
435
436 assert not os.path.exists(
436 assert not os.path.exists(
437 os.path.join(target_repo.path, '.git', 'MERGE_HEAD'))
437 os.path.join(target_repo.path, '.git', 'MERGE_HEAD'))
438
438
439 def test_local_merge_raises_exception_on_conflict(self, vcsbackend_git):
439 def test_local_merge_raises_exception_on_conflict(self, vcsbackend_git):
440 target_repo = vcsbackend_git.create_repo(number_of_commits=1)
440 target_repo = vcsbackend_git.create_repo(number_of_commits=1)
441 vcsbackend_git.ensure_file('README', 'I will conflict with you!!!')
441 vcsbackend_git.ensure_file('README', 'I will conflict with you!!!')
442
442
443 target_repo._local_fetch(self.repo.path, 'master')
443 target_repo._local_fetch(self.repo.path, 'master')
444 with pytest.raises(RepositoryError):
444 with pytest.raises(RepositoryError):
445 target_repo._local_merge(
445 target_repo._local_merge(
446 'merge_message', 'user name', 'user@name.com',
446 'merge_message', 'user name', 'user@name.com',
447 target_repo._last_fetch_heads())
447 target_repo._last_fetch_heads())
448
448
449 # Check we are not left in an intermediate merge state
449 # Check we are not left in an intermediate merge state
450 assert not os.path.exists(
450 assert not os.path.exists(
451 os.path.join(target_repo.path, '.git', 'MERGE_HEAD'))
451 os.path.join(target_repo.path, '.git', 'MERGE_HEAD'))
452
452
453 def test_local_merge_into_empty_repo(self):
453 def test_local_merge_into_empty_repo(self):
454 target_repo = self.get_empty_repo()
454 target_repo = self.get_empty_repo()
455
455
456 # This is required as one cannot do a -ff-only merge in an empty repo.
456 # This is required as one cannot do a -ff-only merge in an empty repo.
457 target_repo._local_fetch(self.repo.path, 'master')
457 target_repo._local_fetch(self.repo.path, 'master')
458 with pytest.raises(RepositoryError):
458 with pytest.raises(RepositoryError):
459 target_repo._local_merge(
459 target_repo._local_merge(
460 'merge_message', 'user name', 'user@name.com',
460 'merge_message', 'user name', 'user@name.com',
461 target_repo._last_fetch_heads())
461 target_repo._last_fetch_heads())
462
462
463 def test_local_merge_in_bare_repo(self):
463 def test_local_merge_in_bare_repo(self):
464 with pytest.raises(RepositoryError):
464 with pytest.raises(RepositoryError):
465 self.repo._local_merge(
465 self.repo._local_merge(
466 'merge_message', 'user name', 'user@name.com', None)
466 'merge_message', 'user name', 'user@name.com', None)
467
467
468 def test_local_push_non_bare(self):
468 def test_local_push_non_bare(self):
469 target_repo = self.get_empty_repo()
469 target_repo = self.get_empty_repo()
470
470
471 pushed_branch = 'pushed_branch'
471 pushed_branch = 'pushed_branch'
472 self.repo._local_push('master', target_repo.path, pushed_branch)
472 self.repo._local_push('master', target_repo.path, pushed_branch)
473 # Fix the HEAD of the target repo, or otherwise GitRepository won't
473 # Fix the HEAD of the target repo, or otherwise GitRepository won't
474 # report any branches.
474 # report any branches.
475 with open(os.path.join(target_repo.path, '.git', 'HEAD'), 'w') as f:
475 with open(os.path.join(target_repo.path, '.git', 'HEAD'), 'w') as f:
476 f.write('ref: refs/heads/%s' % pushed_branch)
476 f.write('ref: refs/heads/%s' % pushed_branch)
477
477
478 target_repo = GitRepository(target_repo.path)
478 target_repo = GitRepository(target_repo.path)
479
479
480 assert (target_repo.branches[pushed_branch] ==
480 assert (target_repo.branches[pushed_branch] ==
481 self.repo.branches['master'])
481 self.repo.branches['master'])
482
482
483 def test_local_push_bare(self):
483 def test_local_push_bare(self):
484 target_repo = self.get_empty_repo(bare=True)
484 target_repo = self.get_empty_repo(bare=True)
485
485
486 pushed_branch = 'pushed_branch'
486 pushed_branch = 'pushed_branch'
487 self.repo._local_push('master', target_repo.path, pushed_branch)
487 self.repo._local_push('master', target_repo.path, pushed_branch)
488 # Fix the HEAD of the target repo, or otherwise GitRepository won't
488 # Fix the HEAD of the target repo, or otherwise GitRepository won't
489 # report any branches.
489 # report any branches.
490 with open(os.path.join(target_repo.path, 'HEAD'), 'w') as f:
490 with open(os.path.join(target_repo.path, 'HEAD'), 'w') as f:
491 f.write('ref: refs/heads/%s' % pushed_branch)
491 f.write('ref: refs/heads/%s' % pushed_branch)
492
492
493 target_repo = GitRepository(target_repo.path)
493 target_repo = GitRepository(target_repo.path)
494
494
495 assert (target_repo.branches[pushed_branch] ==
495 assert (target_repo.branches[pushed_branch] ==
496 self.repo.branches['master'])
496 self.repo.branches['master'])
497
497
498 def test_local_push_non_bare_target_branch_is_checked_out(self):
498 def test_local_push_non_bare_target_branch_is_checked_out(self):
499 target_repo = self.get_clone_repo()
499 target_repo = self.get_clone_repo()
500
500
501 pushed_branch = 'pushed_branch'
501 pushed_branch = 'pushed_branch'
502 # Create a new branch in source repo
502 # Create a new branch in source repo
503 new_branch_commit = target_repo.commit_ids[-3]
503 new_branch_commit = target_repo.commit_ids[-3]
504 target_repo._checkout(new_branch_commit)
504 target_repo._checkout(new_branch_commit)
505 target_repo._checkout(pushed_branch, create=True)
505 target_repo._checkout(pushed_branch, create=True)
506
506
507 self.repo._local_push('master', target_repo.path, pushed_branch)
507 self.repo._local_push('master', target_repo.path, pushed_branch)
508
508
509 target_repo = GitRepository(target_repo.path)
509 target_repo = GitRepository(target_repo.path)
510
510
511 assert (target_repo.branches[pushed_branch] ==
511 assert (target_repo.branches[pushed_branch] ==
512 self.repo.branches['master'])
512 self.repo.branches['master'])
513
513
514 def test_local_push_raises_exception_on_conflict(self, vcsbackend_git):
514 def test_local_push_raises_exception_on_conflict(self, vcsbackend_git):
515 target_repo = vcsbackend_git.create_repo(number_of_commits=1)
515 target_repo = vcsbackend_git.create_repo(number_of_commits=1)
516 with pytest.raises(RepositoryError):
516 with pytest.raises(RepositoryError):
517 self.repo._local_push('master', target_repo.path, 'master')
517 self.repo._local_push('master', target_repo.path, 'master')
518
518
519 def test_hooks_can_be_enabled_via_env_variable_for_local_push(self):
519 def test_hooks_can_be_enabled_via_env_variable_for_local_push(self):
520 target_repo = self.get_empty_repo(bare=True)
520 target_repo = self.get_empty_repo(bare=True)
521
521
522 with mock.patch.object(self.repo, 'run_git_command') as run_mock:
522 with mock.patch.object(self.repo, 'run_git_command') as run_mock:
523 self.repo._local_push(
523 self.repo._local_push(
524 'master', target_repo.path, 'master', enable_hooks=True)
524 'master', target_repo.path, 'master', enable_hooks=True)
525 env = run_mock.call_args[1]['extra_env']
525 env = run_mock.call_args[1]['extra_env']
526 assert 'RC_SKIP_HOOKS' not in env
526 assert 'RC_SKIP_HOOKS' not in env
527
527
528 def _add_failing_hook(self, repo_path, hook_name, bare=False):
528 def _add_failing_hook(self, repo_path, hook_name, bare=False):
529 path_components = (
529 path_components = (
530 ['hooks', hook_name] if bare else ['.git', 'hooks', hook_name])
530 ['hooks', hook_name] if bare else ['.git', 'hooks', hook_name])
531 hook_path = os.path.join(repo_path, *path_components)
531 hook_path = os.path.join(repo_path, *path_components)
532 with open(hook_path, 'w') as f:
532 with open(hook_path, 'w') as f:
533 script_lines = [
533 script_lines = [
534 '#!%s' % sys.executable,
534 '#!%s' % sys.executable,
535 'import os',
535 'import os',
536 'import sys',
536 'import sys',
537 'if os.environ.get("RC_SKIP_HOOKS"):',
537 'if os.environ.get("RC_SKIP_HOOKS"):',
538 ' sys.exit(0)',
538 ' sys.exit(0)',
539 'sys.exit(1)',
539 'sys.exit(1)',
540 ]
540 ]
541 f.write('\n'.join(script_lines))
541 f.write('\n'.join(script_lines))
542 os.chmod(hook_path, 0755)
542 os.chmod(hook_path, 0755)
543
543
544 def test_local_push_does_not_execute_hook(self):
544 def test_local_push_does_not_execute_hook(self):
545 target_repo = self.get_empty_repo()
545 target_repo = self.get_empty_repo()
546
546
547 pushed_branch = 'pushed_branch'
547 pushed_branch = 'pushed_branch'
548 self._add_failing_hook(target_repo.path, 'pre-receive')
548 self._add_failing_hook(target_repo.path, 'pre-receive')
549 self.repo._local_push('master', target_repo.path, pushed_branch)
549 self.repo._local_push('master', target_repo.path, pushed_branch)
550 # Fix the HEAD of the target repo, or otherwise GitRepository won't
550 # Fix the HEAD of the target repo, or otherwise GitRepository won't
551 # report any branches.
551 # report any branches.
552 with open(os.path.join(target_repo.path, '.git', 'HEAD'), 'w') as f:
552 with open(os.path.join(target_repo.path, '.git', 'HEAD'), 'w') as f:
553 f.write('ref: refs/heads/%s' % pushed_branch)
553 f.write('ref: refs/heads/%s' % pushed_branch)
554
554
555 target_repo = GitRepository(target_repo.path)
555 target_repo = GitRepository(target_repo.path)
556
556
557 assert (target_repo.branches[pushed_branch] ==
557 assert (target_repo.branches[pushed_branch] ==
558 self.repo.branches['master'])
558 self.repo.branches['master'])
559
559
560 def test_local_push_executes_hook(self):
560 def test_local_push_executes_hook(self):
561 target_repo = self.get_empty_repo(bare=True)
561 target_repo = self.get_empty_repo(bare=True)
562 self._add_failing_hook(target_repo.path, 'pre-receive', bare=True)
562 self._add_failing_hook(target_repo.path, 'pre-receive', bare=True)
563 with pytest.raises(RepositoryError):
563 with pytest.raises(RepositoryError):
564 self.repo._local_push(
564 self.repo._local_push(
565 'master', target_repo.path, 'master', enable_hooks=True)
565 'master', target_repo.path, 'master', enable_hooks=True)
566
566
567 def test_maybe_prepare_merge_workspace(self):
567 def test_maybe_prepare_merge_workspace(self):
568 workspace = self.repo._maybe_prepare_merge_workspace(
568 workspace = self.repo._maybe_prepare_merge_workspace(
569 'pr2', Reference('branch', 'master', 'unused'))
569 'pr2', Reference('branch', 'master', 'unused'))
570
570
571 assert os.path.isdir(workspace)
571 assert os.path.isdir(workspace)
572 workspace_repo = GitRepository(workspace)
572 workspace_repo = GitRepository(workspace)
573 assert workspace_repo.branches == self.repo.branches
573 assert workspace_repo.branches == self.repo.branches
574
574
575 # Calling it a second time should also succeed
575 # Calling it a second time should also succeed
576 workspace = self.repo._maybe_prepare_merge_workspace(
576 workspace = self.repo._maybe_prepare_merge_workspace(
577 'pr2', Reference('branch', 'master', 'unused'))
577 'pr2', Reference('branch', 'master', 'unused'))
578 assert os.path.isdir(workspace)
578 assert os.path.isdir(workspace)
579
579
580 def test_cleanup_merge_workspace(self):
580 def test_cleanup_merge_workspace(self):
581 workspace = self.repo._maybe_prepare_merge_workspace(
581 workspace = self.repo._maybe_prepare_merge_workspace(
582 'pr3', Reference('branch', 'master', 'unused'))
582 'pr3', Reference('branch', 'master', 'unused'))
583 self.repo.cleanup_merge_workspace('pr3')
583 self.repo.cleanup_merge_workspace('pr3')
584
584
585 assert not os.path.exists(workspace)
585 assert not os.path.exists(workspace)
586
586
587 def test_cleanup_merge_workspace_invalid_workspace_id(self):
587 def test_cleanup_merge_workspace_invalid_workspace_id(self):
588 # No assert: because in case of an inexistent workspace this function
588 # No assert: because in case of an inexistent workspace this function
589 # should still succeed.
589 # should still succeed.
590 self.repo.cleanup_merge_workspace('pr4')
590 self.repo.cleanup_merge_workspace('pr4')
591
591
592 def test_set_refs(self):
592 def test_set_refs(self):
593 test_ref = 'refs/test-refs/abcde'
593 test_ref = 'refs/test-refs/abcde'
594 test_commit_id = 'ecb86e1f424f2608262b130db174a7dfd25a6623'
594 test_commit_id = 'ecb86e1f424f2608262b130db174a7dfd25a6623'
595
595
596 self.repo.set_refs(test_ref, test_commit_id)
596 self.repo.set_refs(test_ref, test_commit_id)
597 stdout, _ = self.repo.run_git_command(['show-ref'])
597 stdout, _ = self.repo.run_git_command(['show-ref'])
598 assert test_ref in stdout
598 assert test_ref in stdout
599 assert test_commit_id in stdout
599 assert test_commit_id in stdout
600
600
601 def test_remove_ref(self):
601 def test_remove_ref(self):
602 test_ref = 'refs/test-refs/abcde'
602 test_ref = 'refs/test-refs/abcde'
603 test_commit_id = 'ecb86e1f424f2608262b130db174a7dfd25a6623'
603 test_commit_id = 'ecb86e1f424f2608262b130db174a7dfd25a6623'
604 self.repo.set_refs(test_ref, test_commit_id)
604 self.repo.set_refs(test_ref, test_commit_id)
605 stdout, _ = self.repo.run_git_command(['show-ref'])
605 stdout, _ = self.repo.run_git_command(['show-ref'])
606 assert test_ref in stdout
606 assert test_ref in stdout
607 assert test_commit_id in stdout
607 assert test_commit_id in stdout
608
608
609 self.repo.remove_ref(test_ref)
609 self.repo.remove_ref(test_ref)
610 stdout, _ = self.repo.run_git_command(['show-ref'])
610 stdout, _ = self.repo.run_git_command(['show-ref'])
611 assert test_ref not in stdout
611 assert test_ref not in stdout
612 assert test_commit_id not in stdout
612 assert test_commit_id not in stdout
613
613
614
614
615 class TestGitCommit(object):
615 class TestGitCommit(object):
616
616
617 @pytest.fixture(autouse=True)
617 @pytest.fixture(autouse=True)
618 def prepare(self):
618 def prepare(self):
619 self.repo = GitRepository(TEST_GIT_REPO)
619 self.repo = GitRepository(TEST_GIT_REPO)
620
620
621 def test_default_commit(self):
621 def test_default_commit(self):
622 tip = self.repo.get_commit()
622 tip = self.repo.get_commit()
623 assert tip == self.repo.get_commit(None)
623 assert tip == self.repo.get_commit(None)
624 assert tip == self.repo.get_commit('tip')
624 assert tip == self.repo.get_commit('tip')
625
625
626 def test_root_node(self):
626 def test_root_node(self):
627 tip = self.repo.get_commit()
627 tip = self.repo.get_commit()
628 assert tip.root is tip.get_node('')
628 assert tip.root is tip.get_node('')
629
629
630 def test_lazy_fetch(self):
630 def test_lazy_fetch(self):
631 """
631 """
632 Test if commit's nodes expands and are cached as we walk through
632 Test if commit's nodes expands and are cached as we walk through
633 the commit. This test is somewhat hard to write as order of tests
633 the commit. This test is somewhat hard to write as order of tests
634 is a key here. Written by running command after command in a shell.
634 is a key here. Written by running command after command in a shell.
635 """
635 """
636 commit_id = '2a13f185e4525f9d4b59882791a2d397b90d5ddc'
636 commit_id = '2a13f185e4525f9d4b59882791a2d397b90d5ddc'
637 assert commit_id in self.repo.commit_ids
637 assert commit_id in self.repo.commit_ids
638 commit = self.repo.get_commit(commit_id)
638 commit = self.repo.get_commit(commit_id)
639 assert len(commit.nodes) == 0
639 assert len(commit.nodes) == 0
640 root = commit.root
640 root = commit.root
641 assert len(commit.nodes) == 1
641 assert len(commit.nodes) == 1
642 assert len(root.nodes) == 8
642 assert len(root.nodes) == 8
643 # accessing root.nodes updates commit.nodes
643 # accessing root.nodes updates commit.nodes
644 assert len(commit.nodes) == 9
644 assert len(commit.nodes) == 9
645
645
646 docs = root.get_node('docs')
646 docs = root.get_node('docs')
647 # we haven't yet accessed anything new as docs dir was already cached
647 # we haven't yet accessed anything new as docs dir was already cached
648 assert len(commit.nodes) == 9
648 assert len(commit.nodes) == 9
649 assert len(docs.nodes) == 8
649 assert len(docs.nodes) == 8
650 # accessing docs.nodes updates commit.nodes
650 # accessing docs.nodes updates commit.nodes
651 assert len(commit.nodes) == 17
651 assert len(commit.nodes) == 17
652
652
653 assert docs is commit.get_node('docs')
653 assert docs is commit.get_node('docs')
654 assert docs is root.nodes[0]
654 assert docs is root.nodes[0]
655 assert docs is root.dirs[0]
655 assert docs is root.dirs[0]
656 assert docs is commit.get_node('docs')
656 assert docs is commit.get_node('docs')
657
657
658 def test_nodes_with_commit(self):
658 def test_nodes_with_commit(self):
659 commit_id = '2a13f185e4525f9d4b59882791a2d397b90d5ddc'
659 commit_id = '2a13f185e4525f9d4b59882791a2d397b90d5ddc'
660 commit = self.repo.get_commit(commit_id)
660 commit = self.repo.get_commit(commit_id)
661 root = commit.root
661 root = commit.root
662 docs = root.get_node('docs')
662 docs = root.get_node('docs')
663 assert docs is commit.get_node('docs')
663 assert docs is commit.get_node('docs')
664 api = docs.get_node('api')
664 api = docs.get_node('api')
665 assert api is commit.get_node('docs/api')
665 assert api is commit.get_node('docs/api')
666 index = api.get_node('index.rst')
666 index = api.get_node('index.rst')
667 assert index is commit.get_node('docs/api/index.rst')
667 assert index is commit.get_node('docs/api/index.rst')
668 assert index is commit.get_node('docs')\
668 assert index is commit.get_node('docs')\
669 .get_node('api')\
669 .get_node('api')\
670 .get_node('index.rst')
670 .get_node('index.rst')
671
671
672 def test_branch_and_tags(self):
672 def test_branch_and_tags(self):
673 """
673 """
674 rev0 = self.repo.commit_ids[0]
674 rev0 = self.repo.commit_ids[0]
675 commit0 = self.repo.get_commit(rev0)
675 commit0 = self.repo.get_commit(rev0)
676 assert commit0.branch == 'master'
676 assert commit0.branch == 'master'
677 assert commit0.tags == []
677 assert commit0.tags == []
678
678
679 rev10 = self.repo.commit_ids[10]
679 rev10 = self.repo.commit_ids[10]
680 commit10 = self.repo.get_commit(rev10)
680 commit10 = self.repo.get_commit(rev10)
681 assert commit10.branch == 'master'
681 assert commit10.branch == 'master'
682 assert commit10.tags == []
682 assert commit10.tags == []
683
683
684 rev44 = self.repo.commit_ids[44]
684 rev44 = self.repo.commit_ids[44]
685 commit44 = self.repo.get_commit(rev44)
685 commit44 = self.repo.get_commit(rev44)
686 assert commit44.branch == 'web-branch'
686 assert commit44.branch == 'web-branch'
687
687
688 tip = self.repo.get_commit('tip')
688 tip = self.repo.get_commit('tip')
689 assert 'tip' in tip.tags
689 assert 'tip' in tip.tags
690 """
690 """
691 # Those tests would fail - branches are now going
691 # Those tests would fail - branches are now going
692 # to be changed at main API in order to support git backend
692 # to be changed at main API in order to support git backend
693 pass
693 pass
694
694
695 def test_file_size(self):
695 def test_file_size(self):
696 to_check = (
696 to_check = (
697 ('c1214f7e79e02fc37156ff215cd71275450cffc3',
697 ('c1214f7e79e02fc37156ff215cd71275450cffc3',
698 'vcs/backends/BaseRepository.py', 502),
698 'vcs/backends/BaseRepository.py', 502),
699 ('d7e0d30fbcae12c90680eb095a4f5f02505ce501',
699 ('d7e0d30fbcae12c90680eb095a4f5f02505ce501',
700 'vcs/backends/hg.py', 854),
700 'vcs/backends/hg.py', 854),
701 ('6e125e7c890379446e98980d8ed60fba87d0f6d1',
701 ('6e125e7c890379446e98980d8ed60fba87d0f6d1',
702 'setup.py', 1068),
702 'setup.py', 1068),
703
703
704 ('d955cd312c17b02143c04fa1099a352b04368118',
704 ('d955cd312c17b02143c04fa1099a352b04368118',
705 'vcs/backends/base.py', 2921),
705 'vcs/backends/base.py', 2921),
706 ('ca1eb7957a54bce53b12d1a51b13452f95bc7c7e',
706 ('ca1eb7957a54bce53b12d1a51b13452f95bc7c7e',
707 'vcs/backends/base.py', 3936),
707 'vcs/backends/base.py', 3936),
708 ('f50f42baeed5af6518ef4b0cb2f1423f3851a941',
708 ('f50f42baeed5af6518ef4b0cb2f1423f3851a941',
709 'vcs/backends/base.py', 6189),
709 'vcs/backends/base.py', 6189),
710 )
710 )
711 for commit_id, path, size in to_check:
711 for commit_id, path, size in to_check:
712 node = self.repo.get_commit(commit_id).get_node(path)
712 node = self.repo.get_commit(commit_id).get_node(path)
713 assert node.is_file()
713 assert node.is_file()
714 assert node.size == size
714 assert node.size == size
715
715
716 def test_file_history_from_commits(self):
716 def test_file_history_from_commits(self):
717 node = self.repo[10].get_node('setup.py')
717 node = self.repo[10].get_node('setup.py')
718 commit_ids = [commit.raw_id for commit in node.history]
718 commit_ids = [commit.raw_id for commit in node.history]
719 assert ['ff7ca51e58c505fec0dd2491de52c622bb7a806b'] == commit_ids
719 assert ['ff7ca51e58c505fec0dd2491de52c622bb7a806b'] == commit_ids
720
720
721 node = self.repo[20].get_node('setup.py')
721 node = self.repo[20].get_node('setup.py')
722 node_ids = [commit.raw_id for commit in node.history]
722 node_ids = [commit.raw_id for commit in node.history]
723 assert ['191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
723 assert ['191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
724 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'] == node_ids
724 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'] == node_ids
725
725
726 # special case we check history from commit that has this particular
726 # special case we check history from commit that has this particular
727 # file changed this means we check if it's included as well
727 # file changed this means we check if it's included as well
728 node = self.repo.get_commit('191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e') \
728 node = self.repo.get_commit('191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e') \
729 .get_node('setup.py')
729 .get_node('setup.py')
730 node_ids = [commit.raw_id for commit in node.history]
730 node_ids = [commit.raw_id for commit in node.history]
731 assert ['191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
731 assert ['191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
732 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'] == node_ids
732 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'] == node_ids
733
733
734 def test_file_history(self):
734 def test_file_history(self):
735 # we can only check if those commits are present in the history
735 # we can only check if those commits are present in the history
736 # as we cannot update this test every time file is changed
736 # as we cannot update this test every time file is changed
737 files = {
737 files = {
738 'setup.py': [
738 'setup.py': [
739 '54386793436c938cff89326944d4c2702340037d',
739 '54386793436c938cff89326944d4c2702340037d',
740 '51d254f0ecf5df2ce50c0b115741f4cf13985dab',
740 '51d254f0ecf5df2ce50c0b115741f4cf13985dab',
741 '998ed409c795fec2012b1c0ca054d99888b22090',
741 '998ed409c795fec2012b1c0ca054d99888b22090',
742 '5e0eb4c47f56564395f76333f319d26c79e2fb09',
742 '5e0eb4c47f56564395f76333f319d26c79e2fb09',
743 '0115510b70c7229dbc5dc49036b32e7d91d23acd',
743 '0115510b70c7229dbc5dc49036b32e7d91d23acd',
744 '7cb3fd1b6d8c20ba89e2264f1c8baebc8a52d36e',
744 '7cb3fd1b6d8c20ba89e2264f1c8baebc8a52d36e',
745 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
745 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
746 '191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
746 '191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e',
747 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
747 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
748 ],
748 ],
749 'vcs/nodes.py': [
749 'vcs/nodes.py': [
750 '33fa3223355104431402a888fa77a4e9956feb3e',
750 '33fa3223355104431402a888fa77a4e9956feb3e',
751 'fa014c12c26d10ba682fadb78f2a11c24c8118e1',
751 'fa014c12c26d10ba682fadb78f2a11c24c8118e1',
752 'e686b958768ee96af8029fe19c6050b1a8dd3b2b',
752 'e686b958768ee96af8029fe19c6050b1a8dd3b2b',
753 'ab5721ca0a081f26bf43d9051e615af2cc99952f',
753 'ab5721ca0a081f26bf43d9051e615af2cc99952f',
754 'c877b68d18e792a66b7f4c529ea02c8f80801542',
754 'c877b68d18e792a66b7f4c529ea02c8f80801542',
755 '4313566d2e417cb382948f8d9d7c765330356054',
755 '4313566d2e417cb382948f8d9d7c765330356054',
756 '6c2303a793671e807d1cfc70134c9ca0767d98c2',
756 '6c2303a793671e807d1cfc70134c9ca0767d98c2',
757 '54386793436c938cff89326944d4c2702340037d',
757 '54386793436c938cff89326944d4c2702340037d',
758 '54000345d2e78b03a99d561399e8e548de3f3203',
758 '54000345d2e78b03a99d561399e8e548de3f3203',
759 '1c6b3677b37ea064cb4b51714d8f7498f93f4b2b',
759 '1c6b3677b37ea064cb4b51714d8f7498f93f4b2b',
760 '2d03ca750a44440fb5ea8b751176d1f36f8e8f46',
760 '2d03ca750a44440fb5ea8b751176d1f36f8e8f46',
761 '2a08b128c206db48c2f0b8f70df060e6db0ae4f8',
761 '2a08b128c206db48c2f0b8f70df060e6db0ae4f8',
762 '30c26513ff1eb8e5ce0e1c6b477ee5dc50e2f34b',
762 '30c26513ff1eb8e5ce0e1c6b477ee5dc50e2f34b',
763 'ac71e9503c2ca95542839af0ce7b64011b72ea7c',
763 'ac71e9503c2ca95542839af0ce7b64011b72ea7c',
764 '12669288fd13adba2a9b7dd5b870cc23ffab92d2',
764 '12669288fd13adba2a9b7dd5b870cc23ffab92d2',
765 '5a0c84f3e6fe3473e4c8427199d5a6fc71a9b382',
765 '5a0c84f3e6fe3473e4c8427199d5a6fc71a9b382',
766 '12f2f5e2b38e6ff3fbdb5d722efed9aa72ecb0d5',
766 '12f2f5e2b38e6ff3fbdb5d722efed9aa72ecb0d5',
767 '5eab1222a7cd4bfcbabc218ca6d04276d4e27378',
767 '5eab1222a7cd4bfcbabc218ca6d04276d4e27378',
768 'f50f42baeed5af6518ef4b0cb2f1423f3851a941',
768 'f50f42baeed5af6518ef4b0cb2f1423f3851a941',
769 'd7e390a45f6aa96f04f5e7f583ad4f867431aa25',
769 'd7e390a45f6aa96f04f5e7f583ad4f867431aa25',
770 'f15c21f97864b4f071cddfbf2750ec2e23859414',
770 'f15c21f97864b4f071cddfbf2750ec2e23859414',
771 'e906ef056cf539a4e4e5fc8003eaf7cf14dd8ade',
771 'e906ef056cf539a4e4e5fc8003eaf7cf14dd8ade',
772 'ea2b108b48aa8f8c9c4a941f66c1a03315ca1c3b',
772 'ea2b108b48aa8f8c9c4a941f66c1a03315ca1c3b',
773 '84dec09632a4458f79f50ddbbd155506c460b4f9',
773 '84dec09632a4458f79f50ddbbd155506c460b4f9',
774 '0115510b70c7229dbc5dc49036b32e7d91d23acd',
774 '0115510b70c7229dbc5dc49036b32e7d91d23acd',
775 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
775 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
776 '3bf1c5868e570e39569d094f922d33ced2fa3b2b',
776 '3bf1c5868e570e39569d094f922d33ced2fa3b2b',
777 'b8d04012574729d2c29886e53b1a43ef16dd00a1',
777 'b8d04012574729d2c29886e53b1a43ef16dd00a1',
778 '6970b057cffe4aab0a792aa634c89f4bebf01441',
778 '6970b057cffe4aab0a792aa634c89f4bebf01441',
779 'dd80b0f6cf5052f17cc738c2951c4f2070200d7f',
779 'dd80b0f6cf5052f17cc738c2951c4f2070200d7f',
780 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
780 'ff7ca51e58c505fec0dd2491de52c622bb7a806b',
781 ],
781 ],
782 'vcs/backends/git.py': [
782 'vcs/backends/git.py': [
783 '4cf116ad5a457530381135e2f4c453e68a1b0105',
783 '4cf116ad5a457530381135e2f4c453e68a1b0105',
784 '9a751d84d8e9408e736329767387f41b36935153',
784 '9a751d84d8e9408e736329767387f41b36935153',
785 'cb681fb539c3faaedbcdf5ca71ca413425c18f01',
785 'cb681fb539c3faaedbcdf5ca71ca413425c18f01',
786 '428f81bb652bcba8d631bce926e8834ff49bdcc6',
786 '428f81bb652bcba8d631bce926e8834ff49bdcc6',
787 '180ab15aebf26f98f714d8c68715e0f05fa6e1c7',
787 '180ab15aebf26f98f714d8c68715e0f05fa6e1c7',
788 '2b8e07312a2e89e92b90426ab97f349f4bce2a3a',
788 '2b8e07312a2e89e92b90426ab97f349f4bce2a3a',
789 '50e08c506174d8645a4bb517dd122ac946a0f3bf',
789 '50e08c506174d8645a4bb517dd122ac946a0f3bf',
790 '54000345d2e78b03a99d561399e8e548de3f3203',
790 '54000345d2e78b03a99d561399e8e548de3f3203',
791 ],
791 ],
792 }
792 }
793 for path, commit_ids in files.items():
793 for path, commit_ids in files.items():
794 node = self.repo.get_commit(commit_ids[0]).get_node(path)
794 node = self.repo.get_commit(commit_ids[0]).get_node(path)
795 node_ids = [commit.raw_id for commit in node.history]
795 node_ids = [commit.raw_id for commit in node.history]
796 assert set(commit_ids).issubset(set(node_ids)), (
796 assert set(commit_ids).issubset(set(node_ids)), (
797 "We assumed that %s is subset of commit_ids for which file %s "
797 "We assumed that %s is subset of commit_ids for which file %s "
798 "has been changed, and history of that node returned: %s"
798 "has been changed, and history of that node returned: %s"
799 % (commit_ids, path, node_ids))
799 % (commit_ids, path, node_ids))
800
800
801 def test_file_annotate(self):
801 def test_file_annotate(self):
802 files = {
802 files = {
803 'vcs/backends/__init__.py': {
803 'vcs/backends/__init__.py': {
804 'c1214f7e79e02fc37156ff215cd71275450cffc3': {
804 'c1214f7e79e02fc37156ff215cd71275450cffc3': {
805 'lines_no': 1,
805 'lines_no': 1,
806 'commits': [
806 'commits': [
807 'c1214f7e79e02fc37156ff215cd71275450cffc3',
807 'c1214f7e79e02fc37156ff215cd71275450cffc3',
808 ],
808 ],
809 },
809 },
810 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647': {
810 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647': {
811 'lines_no': 21,
811 'lines_no': 21,
812 'commits': [
812 'commits': [
813 '49d3fd156b6f7db46313fac355dca1a0b94a0017',
813 '49d3fd156b6f7db46313fac355dca1a0b94a0017',
814 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
814 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
815 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
815 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
816 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
816 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
817 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
817 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
818 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
818 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
819 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
819 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
820 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
820 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
821 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
821 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
822 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
822 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
823 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
823 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
824 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
824 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
825 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
825 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
826 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
826 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
827 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
827 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
828 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
828 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
829 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
829 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
830 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
830 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
831 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
831 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
832 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
832 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
833 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
833 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
834 ],
834 ],
835 },
835 },
836 'e29b67bd158580fc90fc5e9111240b90e6e86064': {
836 'e29b67bd158580fc90fc5e9111240b90e6e86064': {
837 'lines_no': 32,
837 'lines_no': 32,
838 'commits': [
838 'commits': [
839 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
839 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
840 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
840 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
841 '5eab1222a7cd4bfcbabc218ca6d04276d4e27378',
841 '5eab1222a7cd4bfcbabc218ca6d04276d4e27378',
842 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
842 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
843 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
843 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
844 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
844 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
845 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
845 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
846 '54000345d2e78b03a99d561399e8e548de3f3203',
846 '54000345d2e78b03a99d561399e8e548de3f3203',
847 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
847 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
848 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
848 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
849 '78c3f0c23b7ee935ec276acb8b8212444c33c396',
849 '78c3f0c23b7ee935ec276acb8b8212444c33c396',
850 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
850 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
851 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
851 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
852 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
852 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
853 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
853 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
854 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
854 '2a13f185e4525f9d4b59882791a2d397b90d5ddc',
855 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
855 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
856 '78c3f0c23b7ee935ec276acb8b8212444c33c396',
856 '78c3f0c23b7ee935ec276acb8b8212444c33c396',
857 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
857 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
858 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
858 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
859 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
859 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
860 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
860 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
861 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
861 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
862 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
862 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
863 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
863 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
864 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
864 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
865 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
865 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
866 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
866 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
867 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
867 '992f38217b979d0b0987d0bae3cc26dac85d9b19',
868 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
868 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
869 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
869 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
870 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
870 '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647',
871 ],
871 ],
872 },
872 },
873 },
873 },
874 }
874 }
875
875
876 for fname, commit_dict in files.items():
876 for fname, commit_dict in files.items():
877 for commit_id, __ in commit_dict.items():
877 for commit_id, __ in commit_dict.items():
878 commit = self.repo.get_commit(commit_id)
878 commit = self.repo.get_commit(commit_id)
879
879
880 l1_1 = [x[1] for x in commit.get_file_annotate(fname)]
880 l1_1 = [x[1] for x in commit.get_file_annotate(fname)]
881 l1_2 = [x[2]().raw_id for x in commit.get_file_annotate(fname)]
881 l1_2 = [x[2]().raw_id for x in commit.get_file_annotate(fname)]
882 assert l1_1 == l1_2
882 assert l1_1 == l1_2
883 l1 = l1_1
883 l1 = l1_1
884 l2 = files[fname][commit_id]['commits']
884 l2 = files[fname][commit_id]['commits']
885 assert l1 == l2, (
885 assert l1 == l2, (
886 "The lists of commit_ids for %s@commit_id %s"
886 "The lists of commit_ids for %s@commit_id %s"
887 "from annotation list should match each other, "
887 "from annotation list should match each other, "
888 "got \n%s \nvs \n%s " % (fname, commit_id, l1, l2))
888 "got \n%s \nvs \n%s " % (fname, commit_id, l1, l2))
889
889
890 def test_files_state(self):
890 def test_files_state(self):
891 """
891 """
892 Tests state of FileNodes.
892 Tests state of FileNodes.
893 """
893 """
894 node = self.repo\
894 node = self.repo\
895 .get_commit('e6ea6d16e2f26250124a1f4b4fe37a912f9d86a0')\
895 .get_commit('e6ea6d16e2f26250124a1f4b4fe37a912f9d86a0')\
896 .get_node('vcs/utils/diffs.py')
896 .get_node('vcs/utils/diffs.py')
897 assert node.state, NodeState.ADDED
897 assert node.state, NodeState.ADDED
898 assert node.added
898 assert node.added
899 assert not node.changed
899 assert not node.changed
900 assert not node.not_changed
900 assert not node.not_changed
901 assert not node.removed
901 assert not node.removed
902
902
903 node = self.repo\
903 node = self.repo\
904 .get_commit('33fa3223355104431402a888fa77a4e9956feb3e')\
904 .get_commit('33fa3223355104431402a888fa77a4e9956feb3e')\
905 .get_node('.hgignore')
905 .get_node('.hgignore')
906 assert node.state, NodeState.CHANGED
906 assert node.state, NodeState.CHANGED
907 assert not node.added
907 assert not node.added
908 assert node.changed
908 assert node.changed
909 assert not node.not_changed
909 assert not node.not_changed
910 assert not node.removed
910 assert not node.removed
911
911
912 node = self.repo\
912 node = self.repo\
913 .get_commit('e29b67bd158580fc90fc5e9111240b90e6e86064')\
913 .get_commit('e29b67bd158580fc90fc5e9111240b90e6e86064')\
914 .get_node('setup.py')
914 .get_node('setup.py')
915 assert node.state, NodeState.NOT_CHANGED
915 assert node.state, NodeState.NOT_CHANGED
916 assert not node.added
916 assert not node.added
917 assert not node.changed
917 assert not node.changed
918 assert node.not_changed
918 assert node.not_changed
919 assert not node.removed
919 assert not node.removed
920
920
921 # If node has REMOVED state then trying to fetch it would raise
921 # If node has REMOVED state then trying to fetch it would raise
922 # CommitError exception
922 # CommitError exception
923 commit = self.repo.get_commit(
923 commit = self.repo.get_commit(
924 'fa6600f6848800641328adbf7811fd2372c02ab2')
924 'fa6600f6848800641328adbf7811fd2372c02ab2')
925 path = 'vcs/backends/BaseRepository.py'
925 path = 'vcs/backends/BaseRepository.py'
926 with pytest.raises(NodeDoesNotExistError):
926 with pytest.raises(NodeDoesNotExistError):
927 commit.get_node(path)
927 commit.get_node(path)
928 # but it would be one of ``removed`` (commit's attribute)
928 # but it would be one of ``removed`` (commit's attribute)
929 assert path in [rf.path for rf in commit.removed]
929 assert path in [rf.path for rf in commit.removed]
930
930
931 commit = self.repo.get_commit(
931 commit = self.repo.get_commit(
932 '54386793436c938cff89326944d4c2702340037d')
932 '54386793436c938cff89326944d4c2702340037d')
933 changed = [
933 changed = [
934 'setup.py', 'tests/test_nodes.py', 'vcs/backends/hg.py',
934 'setup.py', 'tests/test_nodes.py', 'vcs/backends/hg.py',
935 'vcs/nodes.py']
935 'vcs/nodes.py']
936 assert set(changed) == set([f.path for f in commit.changed])
936 assert set(changed) == set([f.path for f in commit.changed])
937
937
938 def test_unicode_branch_refs(self):
938 def test_unicode_branch_refs(self):
939 unicode_branches = {
939 unicode_branches = {
940 'refs/heads/unicode': '6c0ce52b229aa978889e91b38777f800e85f330b',
940 'refs/heads/unicode': '6c0ce52b229aa978889e91b38777f800e85f330b',
941 u'refs/heads/uniΓ§ΓΆβˆ‚e': 'ΓΌrl',
941 u'refs/heads/uniΓ§ΓΆβˆ‚e': 'ΓΌrl',
942 }
942 }
943 with mock.patch(
943 with mock.patch(
944 ("rhodecode.lib.vcs.backends.git.repository"
944 ("rhodecode.lib.vcs.backends.git.repository"
945 ".GitRepository._refs"),
945 ".GitRepository._refs"),
946 unicode_branches):
946 unicode_branches):
947 branches = self.repo.branches
947 branches = self.repo.branches
948
948
949 assert 'unicode' in branches
949 assert 'unicode' in branches
950 assert u'uniΓ§ΓΆβˆ‚e' in branches
950 assert u'uniΓ§ΓΆβˆ‚e' in branches
951
951
952 def test_unicode_tag_refs(self):
952 def test_unicode_tag_refs(self):
953 unicode_tags = {
953 unicode_tags = {
954 'refs/tags/unicode': '6c0ce52b229aa978889e91b38777f800e85f330b',
954 'refs/tags/unicode': '6c0ce52b229aa978889e91b38777f800e85f330b',
955 u'refs/tags/uniΓ§ΓΆβˆ‚e': '6c0ce52b229aa978889e91b38777f800e85f330b',
955 u'refs/tags/uniΓ§ΓΆβˆ‚e': '6c0ce52b229aa978889e91b38777f800e85f330b',
956 }
956 }
957 with mock.patch(
957 with mock.patch(
958 ("rhodecode.lib.vcs.backends.git.repository"
958 ("rhodecode.lib.vcs.backends.git.repository"
959 ".GitRepository._refs"),
959 ".GitRepository._refs"),
960 unicode_tags):
960 unicode_tags):
961 tags = self.repo.tags
961 tags = self.repo.tags
962
962
963 assert 'unicode' in tags
963 assert 'unicode' in tags
964 assert u'uniΓ§ΓΆβˆ‚e' in tags
964 assert u'uniΓ§ΓΆβˆ‚e' in tags
965
965
966 def test_commit_message_is_unicode(self):
966 def test_commit_message_is_unicode(self):
967 for commit in self.repo:
967 for commit in self.repo:
968 assert type(commit.message) == unicode
968 assert type(commit.message) == unicode
969
969
970 def test_commit_author_is_unicode(self):
970 def test_commit_author_is_unicode(self):
971 for commit in self.repo:
971 for commit in self.repo:
972 assert type(commit.author) == unicode
972 assert type(commit.author) == unicode
973
973
974 def test_repo_files_content_is_unicode(self):
974 def test_repo_files_content_is_unicode(self):
975 commit = self.repo.get_commit()
975 commit = self.repo.get_commit()
976 for node in commit.get_node('/'):
976 for node in commit.get_node('/'):
977 if node.is_file():
977 if node.is_file():
978 assert type(node.content) == unicode
978 assert type(node.content) == unicode
979
979
980 def test_wrong_path(self):
980 def test_wrong_path(self):
981 # There is 'setup.py' in the root dir but not there:
981 # There is 'setup.py' in the root dir but not there:
982 path = 'foo/bar/setup.py'
982 path = 'foo/bar/setup.py'
983 tip = self.repo.get_commit()
983 tip = self.repo.get_commit()
984 with pytest.raises(VCSError):
984 with pytest.raises(VCSError):
985 tip.get_node(path)
985 tip.get_node(path)
986
986
987 @pytest.mark.parametrize("author_email, commit_id", [
987 @pytest.mark.parametrize("author_email, commit_id", [
988 ('marcin@python-blog.com', 'c1214f7e79e02fc37156ff215cd71275450cffc3'),
988 ('marcin@python-blog.com', 'c1214f7e79e02fc37156ff215cd71275450cffc3'),
989 ('lukasz.balcerzak@python-center.pl',
989 ('lukasz.balcerzak@python-center.pl',
990 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'),
990 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'),
991 ('none@none', '8430a588b43b5d6da365400117c89400326e7992'),
991 ('none@none', '8430a588b43b5d6da365400117c89400326e7992'),
992 ])
992 ])
993 def test_author_email(self, author_email, commit_id):
993 def test_author_email(self, author_email, commit_id):
994 commit = self.repo.get_commit(commit_id)
994 commit = self.repo.get_commit(commit_id)
995 assert author_email == commit.author_email
995 assert author_email == commit.author_email
996
996
997 @pytest.mark.parametrize("author, commit_id", [
997 @pytest.mark.parametrize("author, commit_id", [
998 ('Marcin Kuzminski', 'c1214f7e79e02fc37156ff215cd71275450cffc3'),
998 ('Marcin Kuzminski', 'c1214f7e79e02fc37156ff215cd71275450cffc3'),
999 ('Lukasz Balcerzak', 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'),
999 ('Lukasz Balcerzak', 'ff7ca51e58c505fec0dd2491de52c622bb7a806b'),
1000 ('marcink', '8430a588b43b5d6da365400117c89400326e7992'),
1000 ('marcink', '8430a588b43b5d6da365400117c89400326e7992'),
1001 ])
1001 ])
1002 def test_author_username(self, author, commit_id):
1002 def test_author_username(self, author, commit_id):
1003 commit = self.repo.get_commit(commit_id)
1003 commit = self.repo.get_commit(commit_id)
1004 assert author == commit.author_name
1004 assert author == commit.author_name
1005
1005
1006
1006
1007 class TestLargeFileRepo(object):
1007 class TestLargeFileRepo(object):
1008
1008
1009 def test_large_file(self, backend_git):
1009 def test_large_file(self, backend_git):
1010 conf = make_db_config()
1010 conf = make_db_config()
1011 repo = backend_git.create_test_repo('largefiles', conf)
1011 repo = backend_git.create_test_repo('largefiles', conf)
1012
1012
1013 tip = repo.scm_instance().get_commit()
1013 tip = repo.scm_instance().get_commit()
1014
1014
1015 # extract stored LF node into the origin cache
1015 # extract stored LF node into the origin cache
1016 lfs_store = os.path.join(repo.repo_path, repo.repo_name, 'lfs_store')
1016 lfs_store = os.path.join(repo.repo_path, repo.repo_name, 'lfs_store')
1017
1017
1018 oid = '7b331c02e313c7599d5a90212e17e6d3cb729bd2e1c9b873c302a63c95a2f9bf'
1018 oid = '7b331c02e313c7599d5a90212e17e6d3cb729bd2e1c9b873c302a63c95a2f9bf'
1019 oid_path = os.path.join(lfs_store, oid)
1019 oid_path = os.path.join(lfs_store, oid)
1020 oid_destination = os.path.join(
1020 oid_destination = os.path.join(
1021 conf.get('vcs_git_lfs', 'store_location'), oid)
1021 conf.get('vcs_git_lfs', 'store_location'), oid)
1022 shutil.copy(oid_path, oid_destination)
1022 shutil.copy(oid_path, oid_destination)
1023
1023
1024 node = tip.get_node('1MB.zip')
1024 node = tip.get_node('1MB.zip')
1025
1025
1026 lf_node = node.get_largefile_node()
1026 lf_node = node.get_largefile_node()
1027
1027
1028 assert lf_node.is_largefile() is True
1028 assert lf_node.is_largefile() is True
1029 assert lf_node.size == 1024000
1029 assert lf_node.size == 1024000
1030 assert lf_node.name == '1MB.zip'
1030 assert lf_node.name == '1MB.zip'
1031
1031
1032
1032
1033 class TestGitSpecificWithRepo(BackendTestMixin):
1033 class TestGitSpecificWithRepo(BackendTestMixin):
1034
1034
1035 @classmethod
1035 @classmethod
1036 def _get_commits(cls):
1036 def _get_commits(cls):
1037 return [
1037 return [
1038 {
1038 {
1039 'message': 'Initial',
1039 'message': 'Initial',
1040 'author': 'Joe Doe <joe.doe@example.com>',
1040 'author': 'Joe Doe <joe.doe@example.com>',
1041 'date': datetime.datetime(2010, 1, 1, 20),
1041 'date': datetime.datetime(2010, 1, 1, 20),
1042 'added': [
1042 'added': [
1043 FileNode('foobar/static/js/admin/base.js', content='base'),
1043 FileNode('foobar/static/js/admin/base.js', content='base'),
1044 FileNode(
1044 FileNode(
1045 'foobar/static/admin', content='admin',
1045 'foobar/static/admin', content='admin',
1046 mode=0120000), # this is a link
1046 mode=0120000), # this is a link
1047 FileNode('foo', content='foo'),
1047 FileNode('foo', content='foo'),
1048 ],
1048 ],
1049 },
1049 },
1050 {
1050 {
1051 'message': 'Second',
1051 'message': 'Second',
1052 'author': 'Joe Doe <joe.doe@example.com>',
1052 'author': 'Joe Doe <joe.doe@example.com>',
1053 'date': datetime.datetime(2010, 1, 1, 22),
1053 'date': datetime.datetime(2010, 1, 1, 22),
1054 'added': [
1054 'added': [
1055 FileNode('foo2', content='foo2'),
1055 FileNode('foo2', content='foo2'),
1056 ],
1056 ],
1057 },
1057 },
1058 ]
1058 ]
1059
1059
1060 def test_paths_slow_traversing(self):
1060 def test_paths_slow_traversing(self):
1061 commit = self.repo.get_commit()
1061 commit = self.repo.get_commit()
1062 assert commit.get_node('foobar').get_node('static').get_node('js')\
1062 assert commit.get_node('foobar').get_node('static').get_node('js')\
1063 .get_node('admin').get_node('base.js').content == 'base'
1063 .get_node('admin').get_node('base.js').content == 'base'
1064
1064
1065 def test_paths_fast_traversing(self):
1065 def test_paths_fast_traversing(self):
1066 commit = self.repo.get_commit()
1066 commit = self.repo.get_commit()
1067 assert (
1067 assert (
1068 commit.get_node('foobar/static/js/admin/base.js').content ==
1068 commit.get_node('foobar/static/js/admin/base.js').content ==
1069 'base')
1069 'base')
1070
1070
1071 def test_get_diff_runs_git_command_with_hashes(self):
1071 def test_get_diff_runs_git_command_with_hashes(self):
1072 self.repo.run_git_command = mock.Mock(return_value=['', ''])
1072 self.repo.run_git_command = mock.Mock(return_value=['', ''])
1073 self.repo.get_diff(self.repo[0], self.repo[1])
1073 self.repo.get_diff(self.repo[0], self.repo[1])
1074 self.repo.run_git_command.assert_called_once_with(
1074 self.repo.run_git_command.assert_called_once_with(
1075 ['diff', '-U3', '--full-index', '--binary', '-p', '-M',
1075 ['diff', '-U3', '--full-index', '--binary', '-p', '-M',
1076 '--abbrev=40', self.repo._get_commit_id(0),
1076 '--abbrev=40', self.repo._get_commit_id(0),
1077 self.repo._get_commit_id(1)])
1077 self.repo._get_commit_id(1)])
1078
1078
1079 def test_get_diff_runs_git_command_with_str_hashes(self):
1079 def test_get_diff_runs_git_command_with_str_hashes(self):
1080 self.repo.run_git_command = mock.Mock(return_value=['', ''])
1080 self.repo.run_git_command = mock.Mock(return_value=['', ''])
1081 self.repo.get_diff(self.repo.EMPTY_COMMIT, self.repo[1])
1081 self.repo.get_diff(self.repo.EMPTY_COMMIT, self.repo[1])
1082 self.repo.run_git_command.assert_called_once_with(
1082 self.repo.run_git_command.assert_called_once_with(
1083 ['show', '-U3', '--full-index', '--binary', '-p', '-M',
1083 ['show', '-U3', '--full-index', '--binary', '-p', '-M',
1084 '--abbrev=40', self.repo._get_commit_id(1)])
1084 '--abbrev=40', self.repo._get_commit_id(1)])
1085
1085
1086 def test_get_diff_runs_git_command_with_path_if_its_given(self):
1086 def test_get_diff_runs_git_command_with_path_if_its_given(self):
1087 self.repo.run_git_command = mock.Mock(return_value=['', ''])
1087 self.repo.run_git_command = mock.Mock(return_value=['', ''])
1088 self.repo.get_diff(self.repo[0], self.repo[1], 'foo')
1088 self.repo.get_diff(self.repo[0], self.repo[1], 'foo')
1089 self.repo.run_git_command.assert_called_once_with(
1089 self.repo.run_git_command.assert_called_once_with(
1090 ['diff', '-U3', '--full-index', '--binary', '-p', '-M',
1090 ['diff', '-U3', '--full-index', '--binary', '-p', '-M',
1091 '--abbrev=40', self.repo._get_commit_id(0),
1091 '--abbrev=40', self.repo._get_commit_id(0),
1092 self.repo._get_commit_id(1), '--', 'foo'])
1092 self.repo._get_commit_id(1), '--', 'foo'])
1093
1093
1094
1094
1095 class TestGitRegression(BackendTestMixin):
1095 class TestGitRegression(BackendTestMixin):
1096
1096
1097 @classmethod
1097 @classmethod
1098 def _get_commits(cls):
1098 def _get_commits(cls):
1099 return [
1099 return [
1100 {
1100 {
1101 'message': 'Initial',
1101 'message': 'Initial',
1102 'author': 'Joe Doe <joe.doe@example.com>',
1102 'author': 'Joe Doe <joe.doe@example.com>',
1103 'date': datetime.datetime(2010, 1, 1, 20),
1103 'date': datetime.datetime(2010, 1, 1, 20),
1104 'added': [
1104 'added': [
1105 FileNode('bot/__init__.py', content='base'),
1105 FileNode('bot/__init__.py', content='base'),
1106 FileNode('bot/templates/404.html', content='base'),
1106 FileNode('bot/templates/404.html', content='base'),
1107 FileNode('bot/templates/500.html', content='base'),
1107 FileNode('bot/templates/500.html', content='base'),
1108 ],
1108 ],
1109 },
1109 },
1110 {
1110 {
1111 'message': 'Second',
1111 'message': 'Second',
1112 'author': 'Joe Doe <joe.doe@example.com>',
1112 'author': 'Joe Doe <joe.doe@example.com>',
1113 'date': datetime.datetime(2010, 1, 1, 22),
1113 'date': datetime.datetime(2010, 1, 1, 22),
1114 'added': [
1114 'added': [
1115 FileNode('bot/build/migrations/1.py', content='foo2'),
1115 FileNode('bot/build/migrations/1.py', content='foo2'),
1116 FileNode('bot/build/migrations/2.py', content='foo2'),
1116 FileNode('bot/build/migrations/2.py', content='foo2'),
1117 FileNode(
1117 FileNode(
1118 'bot/build/static/templates/f.html', content='foo2'),
1118 'bot/build/static/templates/f.html', content='foo2'),
1119 FileNode(
1119 FileNode(
1120 'bot/build/static/templates/f1.html', content='foo2'),
1120 'bot/build/static/templates/f1.html', content='foo2'),
1121 FileNode('bot/build/templates/err.html', content='foo2'),
1121 FileNode('bot/build/templates/err.html', content='foo2'),
1122 FileNode('bot/build/templates/err2.html', content='foo2'),
1122 FileNode('bot/build/templates/err2.html', content='foo2'),
1123 ],
1123 ],
1124 },
1124 },
1125 ]
1125 ]
1126
1126
1127 @pytest.mark.parametrize("path, expected_paths", [
1127 @pytest.mark.parametrize("path, expected_paths", [
1128 ('bot', [
1128 ('bot', [
1129 'bot/build',
1129 'bot/build',
1130 'bot/templates',
1130 'bot/templates',
1131 'bot/__init__.py']),
1131 'bot/__init__.py']),
1132 ('bot/build', [
1132 ('bot/build', [
1133 'bot/build/migrations',
1133 'bot/build/migrations',
1134 'bot/build/static',
1134 'bot/build/static',
1135 'bot/build/templates']),
1135 'bot/build/templates']),
1136 ('bot/build/static', [
1136 ('bot/build/static', [
1137 'bot/build/static/templates']),
1137 'bot/build/static/templates']),
1138 ('bot/build/static/templates', [
1138 ('bot/build/static/templates', [
1139 'bot/build/static/templates/f.html',
1139 'bot/build/static/templates/f.html',
1140 'bot/build/static/templates/f1.html']),
1140 'bot/build/static/templates/f1.html']),
1141 ('bot/build/templates', [
1141 ('bot/build/templates', [
1142 'bot/build/templates/err.html',
1142 'bot/build/templates/err.html',
1143 'bot/build/templates/err2.html']),
1143 'bot/build/templates/err2.html']),
1144 ('bot/templates/', [
1144 ('bot/templates/', [
1145 'bot/templates/404.html',
1145 'bot/templates/404.html',
1146 'bot/templates/500.html']),
1146 'bot/templates/500.html']),
1147 ])
1147 ])
1148 def test_similar_paths(self, path, expected_paths):
1148 def test_similar_paths(self, path, expected_paths):
1149 commit = self.repo.get_commit()
1149 commit = self.repo.get_commit()
1150 paths = [n.path for n in commit.get_nodes(path)]
1150 paths = [n.path for n in commit.get_nodes(path)]
1151 assert paths == expected_paths
1151 assert paths == expected_paths
1152
1152
1153
1153
1154 class TestDiscoverGitVersion:
1154 class TestDiscoverGitVersion:
1155
1155
1156 def test_returns_git_version(self, baseapp):
1156 def test_returns_git_version(self, baseapp):
1157 version = discover_git_version()
1157 version = discover_git_version()
1158 assert version
1158 assert version
1159
1159
1160 def test_returns_empty_string_without_vcsserver(self):
1160 def test_returns_empty_string_without_vcsserver(self):
1161 mock_connection = mock.Mock()
1161 mock_connection = mock.Mock()
1162 mock_connection.discover_git_version = mock.Mock(
1162 mock_connection.discover_git_version = mock.Mock(
1163 side_effect=Exception)
1163 side_effect=Exception)
1164 with mock.patch('rhodecode.lib.vcs.connection.Git', mock_connection):
1164 with mock.patch('rhodecode.lib.vcs.connection.Git', mock_connection):
1165 version = discover_git_version()
1165 version = discover_git_version()
1166 assert version == ''
1166 assert version == ''
1167
1167
1168
1168
1169 class TestGetSubmoduleUrl(object):
1169 class TestGetSubmoduleUrl(object):
1170 def test_submodules_file_found(self):
1170 def test_submodules_file_found(self):
1171 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1171 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1172 node = mock.Mock()
1172 node = mock.Mock()
1173 with mock.patch.object(
1173 with mock.patch.object(
1174 commit, 'get_node', return_value=node) as get_node_mock:
1174 commit, 'get_node', return_value=node) as get_node_mock:
1175 node.content = (
1175 node.content = (
1176 '[submodule "subrepo1"]\n'
1176 '[submodule "subrepo1"]\n'
1177 '\tpath = subrepo1\n'
1177 '\tpath = subrepo1\n'
1178 '\turl = https://code.rhodecode.com/dulwich\n'
1178 '\turl = https://code.rhodecode.com/dulwich\n'
1179 )
1179 )
1180 result = commit._get_submodule_url('subrepo1')
1180 result = commit._get_submodule_url('subrepo1')
1181 get_node_mock.assert_called_once_with('.gitmodules')
1181 get_node_mock.assert_called_once_with('.gitmodules')
1182 assert result == 'https://code.rhodecode.com/dulwich'
1182 assert result == 'https://code.rhodecode.com/dulwich'
1183
1183
1184 def test_complex_submodule_path(self):
1184 def test_complex_submodule_path(self):
1185 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1185 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1186 node = mock.Mock()
1186 node = mock.Mock()
1187 with mock.patch.object(
1187 with mock.patch.object(
1188 commit, 'get_node', return_value=node) as get_node_mock:
1188 commit, 'get_node', return_value=node) as get_node_mock:
1189 node.content = (
1189 node.content = (
1190 '[submodule "complex/subrepo/path"]\n'
1190 '[submodule "complex/subrepo/path"]\n'
1191 '\tpath = complex/subrepo/path\n'
1191 '\tpath = complex/subrepo/path\n'
1192 '\turl = https://code.rhodecode.com/dulwich\n'
1192 '\turl = https://code.rhodecode.com/dulwich\n'
1193 )
1193 )
1194 result = commit._get_submodule_url('complex/subrepo/path')
1194 result = commit._get_submodule_url('complex/subrepo/path')
1195 get_node_mock.assert_called_once_with('.gitmodules')
1195 get_node_mock.assert_called_once_with('.gitmodules')
1196 assert result == 'https://code.rhodecode.com/dulwich'
1196 assert result == 'https://code.rhodecode.com/dulwich'
1197
1197
1198 def test_submodules_file_not_found(self):
1198 def test_submodules_file_not_found(self):
1199 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1199 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1200 with mock.patch.object(
1200 with mock.patch.object(
1201 commit, 'get_node', side_effect=NodeDoesNotExistError):
1201 commit, 'get_node', side_effect=NodeDoesNotExistError):
1202 result = commit._get_submodule_url('complex/subrepo/path')
1202 result = commit._get_submodule_url('complex/subrepo/path')
1203 assert result is None
1203 assert result is None
1204
1204
1205 def test_path_not_found(self):
1205 def test_path_not_found(self):
1206 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1206 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1207 node = mock.Mock()
1207 node = mock.Mock()
1208 with mock.patch.object(
1208 with mock.patch.object(
1209 commit, 'get_node', return_value=node) as get_node_mock:
1209 commit, 'get_node', return_value=node) as get_node_mock:
1210 node.content = (
1210 node.content = (
1211 '[submodule "subrepo1"]\n'
1211 '[submodule "subrepo1"]\n'
1212 '\tpath = subrepo1\n'
1212 '\tpath = subrepo1\n'
1213 '\turl = https://code.rhodecode.com/dulwich\n'
1213 '\turl = https://code.rhodecode.com/dulwich\n'
1214 )
1214 )
1215 result = commit._get_submodule_url('subrepo2')
1215 result = commit._get_submodule_url('subrepo2')
1216 get_node_mock.assert_called_once_with('.gitmodules')
1216 get_node_mock.assert_called_once_with('.gitmodules')
1217 assert result is None
1217 assert result is None
1218
1218
1219 def test_returns_cached_values(self):
1219 def test_returns_cached_values(self):
1220 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1220 commit = GitCommit(repository=mock.Mock(), raw_id='abcdef12', idx=1)
1221 node = mock.Mock()
1221 node = mock.Mock()
1222 with mock.patch.object(
1222 with mock.patch.object(
1223 commit, 'get_node', return_value=node) as get_node_mock:
1223 commit, 'get_node', return_value=node) as get_node_mock:
1224 node.content = (
1224 node.content = (
1225 '[submodule "subrepo1"]\n'
1225 '[submodule "subrepo1"]\n'
1226 '\tpath = subrepo1\n'
1226 '\tpath = subrepo1\n'
1227 '\turl = https://code.rhodecode.com/dulwich\n'
1227 '\turl = https://code.rhodecode.com/dulwich\n'
1228 )
1228 )
1229 for _ in range(3):
1229 for _ in range(3):
1230 commit._get_submodule_url('subrepo1')
1230 commit._get_submodule_url('subrepo1')
1231 get_node_mock.assert_called_once_with('.gitmodules')
1231 get_node_mock.assert_called_once_with('.gitmodules')
1232
1232
1233 def test_get_node_returns_a_link(self):
1233 def test_get_node_returns_a_link(self):
1234 repository = mock.Mock()
1234 repository = mock.Mock()
1235 repository.alias = 'git'
1235 repository.alias = 'git'
1236 commit = GitCommit(repository=repository, raw_id='abcdef12', idx=1)
1236 commit = GitCommit(repository=repository, raw_id='abcdef12', idx=1)
1237 submodule_url = 'https://code.rhodecode.com/dulwich'
1237 submodule_url = 'https://code.rhodecode.com/dulwich'
1238 get_id_patch = mock.patch.object(
1238 get_id_patch = mock.patch.object(
1239 commit, '_get_id_for_path', return_value=(1, 'link'))
1239 commit, '_get_id_for_path', return_value=(1, 'link'))
1240 get_submodule_patch = mock.patch.object(
1240 get_submodule_patch = mock.patch.object(
1241 commit, '_get_submodule_url', return_value=submodule_url)
1241 commit, '_get_submodule_url', return_value=submodule_url)
1242
1242
1243 with get_id_patch, get_submodule_patch as submodule_mock:
1243 with get_id_patch, get_submodule_patch as submodule_mock:
1244 node = commit.get_node('/abcde')
1244 node = commit.get_node('/abcde')
1245
1245
1246 submodule_mock.assert_called_once_with('/abcde')
1246 submodule_mock.assert_called_once_with('/abcde')
1247 assert type(node) == SubModuleNode
1247 assert type(node) == SubModuleNode
1248 assert node.url == submodule_url
1248 assert node.url == submodule_url
1249
1249
1250 def test_get_nodes_returns_links(self):
1250 def test_get_nodes_returns_links(self):
1251 repository = mock.MagicMock()
1251 repository = mock.MagicMock()
1252 repository.alias = 'git'
1252 repository.alias = 'git'
1253 repository._remote.tree_items.return_value = [
1253 repository._remote.tree_items.return_value = [
1254 ('subrepo', 'stat', 1, 'link')
1254 ('subrepo', 'stat', 1, 'link')
1255 ]
1255 ]
1256 commit = GitCommit(repository=repository, raw_id='abcdef12', idx=1)
1256 commit = GitCommit(repository=repository, raw_id='abcdef12', idx=1)
1257 submodule_url = 'https://code.rhodecode.com/dulwich'
1257 submodule_url = 'https://code.rhodecode.com/dulwich'
1258 get_id_patch = mock.patch.object(
1258 get_id_patch = mock.patch.object(
1259 commit, '_get_id_for_path', return_value=(1, 'tree'))
1259 commit, '_get_id_for_path', return_value=(1, 'tree'))
1260 get_submodule_patch = mock.patch.object(
1260 get_submodule_patch = mock.patch.object(
1261 commit, '_get_submodule_url', return_value=submodule_url)
1261 commit, '_get_submodule_url', return_value=submodule_url)
1262
1262
1263 with get_id_patch, get_submodule_patch as submodule_mock:
1263 with get_id_patch, get_submodule_patch as submodule_mock:
1264 nodes = commit.get_nodes('/abcde')
1264 nodes = commit.get_nodes('/abcde')
1265
1265
1266 submodule_mock.assert_called_once_with('/abcde/subrepo')
1266 submodule_mock.assert_called_once_with('/abcde/subrepo')
1267 assert len(nodes) == 1
1267 assert len(nodes) == 1
1268 assert type(nodes[0]) == SubModuleNode
1268 assert type(nodes[0]) == SubModuleNode
1269 assert nodes[0].url == submodule_url
1269 assert nodes[0].url == submodule_url
@@ -1,1180 +1,1180 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22
22
23 import mock
23 import mock
24 import pytest
24 import pytest
25
25
26 from rhodecode.lib.utils import make_db_config
26 from rhodecode.lib.utils import make_db_config
27 from rhodecode.lib.vcs import backends
27 from rhodecode.lib.vcs import backends
28 from rhodecode.lib.vcs.backends.base import (
28 from rhodecode.lib.vcs.backends.base import (
29 Reference, MergeResponse, MergeFailureReason)
29 Reference, MergeResponse, MergeFailureReason)
30 from rhodecode.lib.vcs.backends.hg import MercurialRepository, MercurialCommit
30 from rhodecode.lib.vcs.backends.hg import MercurialRepository, MercurialCommit
31 from rhodecode.lib.vcs.exceptions import (
31 from rhodecode.lib.vcs.exceptions import (
32 RepositoryError, VCSError, NodeDoesNotExistError, CommitDoesNotExistError)
32 RepositoryError, VCSError, NodeDoesNotExistError, CommitDoesNotExistError)
33 from rhodecode.lib.vcs.nodes import FileNode, NodeKind, NodeState
33 from rhodecode.lib.vcs.nodes import FileNode, NodeKind, NodeState
34 from rhodecode.tests import TEST_HG_REPO, TEST_HG_REPO_CLONE
34 from rhodecode.tests import TEST_HG_REPO, TEST_HG_REPO_CLONE
35
35
36
36
37 pytestmark = pytest.mark.backends("hg")
37 pytestmark = pytest.mark.backends("hg")
38
38
39
39
40 def repo_path_generator():
40 def repo_path_generator():
41 """
41 """
42 Return a different path to be used for cloning repos.
42 Return a different path to be used for cloning repos.
43 """
43 """
44 i = 0
44 i = 0
45 while True:
45 while True:
46 i += 1
46 i += 1
47 yield '%s-%d' % (TEST_HG_REPO_CLONE, i)
47 yield '%s-%d' % (TEST_HG_REPO_CLONE, i)
48
48
49
49
50 REPO_PATH_GENERATOR = repo_path_generator()
50 REPO_PATH_GENERATOR = repo_path_generator()
51
51
52
52
53 @pytest.fixture(scope='class', autouse=True)
53 @pytest.fixture(scope='class', autouse=True)
54 def repo(request, baseapp):
54 def repo(request, baseapp):
55 repo = MercurialRepository(TEST_HG_REPO)
55 repo = MercurialRepository(TEST_HG_REPO)
56 if request.cls:
56 if request.cls:
57 request.cls.repo = repo
57 request.cls.repo = repo
58 return repo
58 return repo
59
59
60
60
61 class TestMercurialRepository:
61 class TestMercurialRepository:
62
62
63 # pylint: disable=protected-access
63 # pylint: disable=protected-access
64
64
65 def get_clone_repo(self):
65 def get_clone_repo(self):
66 """
66 """
67 Return a clone of the base repo.
67 Return a clone of the base repo.
68 """
68 """
69 clone_path = next(REPO_PATH_GENERATOR)
69 clone_path = next(REPO_PATH_GENERATOR)
70 repo_clone = MercurialRepository(
70 repo_clone = MercurialRepository(
71 clone_path, create=True, src_url=self.repo.path)
71 clone_path, create=True, src_url=self.repo.path)
72
72
73 return repo_clone
73 return repo_clone
74
74
75 def get_empty_repo(self):
75 def get_empty_repo(self):
76 """
76 """
77 Return an empty repo.
77 Return an empty repo.
78 """
78 """
79 return MercurialRepository(next(REPO_PATH_GENERATOR), create=True)
79 return MercurialRepository(next(REPO_PATH_GENERATOR), create=True)
80
80
81 def test_wrong_repo_path(self):
81 def test_wrong_repo_path(self):
82 wrong_repo_path = '/tmp/errorrepo'
82 wrong_repo_path = '/tmp/errorrepo_hg'
83 with pytest.raises(RepositoryError):
83 with pytest.raises(RepositoryError):
84 MercurialRepository(wrong_repo_path)
84 MercurialRepository(wrong_repo_path)
85
85
86 def test_unicode_path_repo(self):
86 def test_unicode_path_repo(self):
87 with pytest.raises(VCSError):
87 with pytest.raises(VCSError):
88 MercurialRepository(u'iShouldFail')
88 MercurialRepository(u'iShouldFail')
89
89
90 def test_unicode_commit_id(self):
90 def test_unicode_commit_id(self):
91 with pytest.raises(CommitDoesNotExistError):
91 with pytest.raises(CommitDoesNotExistError):
92 self.repo.get_commit(u'unicode-commit-id')
92 self.repo.get_commit(u'unicode-commit-id')
93 with pytest.raises(CommitDoesNotExistError):
93 with pytest.raises(CommitDoesNotExistError):
94 self.repo.get_commit(u'unΓ­cΓΈde-spéçial-chΓ€rΓ₯cter-commit-id')
94 self.repo.get_commit(u'unΓ­cΓΈde-spéçial-chΓ€rΓ₯cter-commit-id')
95
95
96 def test_unicode_bookmark(self):
96 def test_unicode_bookmark(self):
97 self.repo.bookmark(u'unicode-bookmark')
97 self.repo.bookmark(u'unicode-bookmark')
98 self.repo.bookmark(u'unΓ­cΓΈde-spéçial-chΓ€rΓ₯cter-bookmark')
98 self.repo.bookmark(u'unΓ­cΓΈde-spéçial-chΓ€rΓ₯cter-bookmark')
99
99
100 def test_unicode_branch(self):
100 def test_unicode_branch(self):
101 with pytest.raises(KeyError):
101 with pytest.raises(KeyError):
102 self.repo.branches[u'unicode-branch']
102 self.repo.branches[u'unicode-branch']
103 with pytest.raises(KeyError):
103 with pytest.raises(KeyError):
104 self.repo.branches[u'unΓ­cΓΈde-spéçial-chΓ€rΓ₯cter-branch']
104 self.repo.branches[u'unΓ­cΓΈde-spéçial-chΓ€rΓ₯cter-branch']
105
105
106 def test_repo_clone(self):
106 def test_repo_clone(self):
107 if os.path.exists(TEST_HG_REPO_CLONE):
107 if os.path.exists(TEST_HG_REPO_CLONE):
108 self.fail(
108 self.fail(
109 'Cannot test mercurial clone repo as location %s already '
109 'Cannot test mercurial clone repo as location %s already '
110 'exists. You should manually remove it first.'
110 'exists. You should manually remove it first.'
111 % TEST_HG_REPO_CLONE)
111 % TEST_HG_REPO_CLONE)
112
112
113 repo = MercurialRepository(TEST_HG_REPO)
113 repo = MercurialRepository(TEST_HG_REPO)
114 repo_clone = MercurialRepository(TEST_HG_REPO_CLONE,
114 repo_clone = MercurialRepository(TEST_HG_REPO_CLONE,
115 src_url=TEST_HG_REPO)
115 src_url=TEST_HG_REPO)
116 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
116 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
117 # Checking hashes of commits should be enough
117 # Checking hashes of commits should be enough
118 for commit in repo.get_commits():
118 for commit in repo.get_commits():
119 raw_id = commit.raw_id
119 raw_id = commit.raw_id
120 assert raw_id == repo_clone.get_commit(raw_id).raw_id
120 assert raw_id == repo_clone.get_commit(raw_id).raw_id
121
121
122 def test_repo_clone_with_update(self):
122 def test_repo_clone_with_update(self):
123 repo = MercurialRepository(TEST_HG_REPO)
123 repo = MercurialRepository(TEST_HG_REPO)
124 repo_clone = MercurialRepository(
124 repo_clone = MercurialRepository(
125 TEST_HG_REPO_CLONE + '_w_update',
125 TEST_HG_REPO_CLONE + '_w_update',
126 src_url=TEST_HG_REPO, update_after_clone=True)
126 src_url=TEST_HG_REPO, update_after_clone=True)
127 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
127 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
128
128
129 # check if current workdir was updated
129 # check if current workdir was updated
130 assert os.path.isfile(
130 assert os.path.isfile(
131 os.path.join(TEST_HG_REPO_CLONE + '_w_update', 'MANIFEST.in'))
131 os.path.join(TEST_HG_REPO_CLONE + '_w_update', 'MANIFEST.in'))
132
132
133 def test_repo_clone_without_update(self):
133 def test_repo_clone_without_update(self):
134 repo = MercurialRepository(TEST_HG_REPO)
134 repo = MercurialRepository(TEST_HG_REPO)
135 repo_clone = MercurialRepository(
135 repo_clone = MercurialRepository(
136 TEST_HG_REPO_CLONE + '_wo_update',
136 TEST_HG_REPO_CLONE + '_wo_update',
137 src_url=TEST_HG_REPO, update_after_clone=False)
137 src_url=TEST_HG_REPO, update_after_clone=False)
138 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
138 assert len(repo.commit_ids) == len(repo_clone.commit_ids)
139 assert not os.path.isfile(
139 assert not os.path.isfile(
140 os.path.join(TEST_HG_REPO_CLONE + '_wo_update', 'MANIFEST.in'))
140 os.path.join(TEST_HG_REPO_CLONE + '_wo_update', 'MANIFEST.in'))
141
141
142 def test_commit_ids(self):
142 def test_commit_ids(self):
143 # there are 21 commits at bitbucket now
143 # there are 21 commits at bitbucket now
144 # so we can assume they would be available from now on
144 # so we can assume they would be available from now on
145 subset = set([
145 subset = set([
146 'b986218ba1c9b0d6a259fac9b050b1724ed8e545',
146 'b986218ba1c9b0d6a259fac9b050b1724ed8e545',
147 '3d8f361e72ab303da48d799ff1ac40d5ac37c67e',
147 '3d8f361e72ab303da48d799ff1ac40d5ac37c67e',
148 '6cba7170863a2411822803fa77a0a264f1310b35',
148 '6cba7170863a2411822803fa77a0a264f1310b35',
149 '56349e29c2af3ac913b28bde9a2c6154436e615b',
149 '56349e29c2af3ac913b28bde9a2c6154436e615b',
150 '2dda4e345facb0ccff1a191052dd1606dba6781d',
150 '2dda4e345facb0ccff1a191052dd1606dba6781d',
151 '6fff84722075f1607a30f436523403845f84cd9e',
151 '6fff84722075f1607a30f436523403845f84cd9e',
152 '7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7',
152 '7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7',
153 '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb',
153 '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb',
154 'dc5d2c0661b61928834a785d3e64a3f80d3aad9c',
154 'dc5d2c0661b61928834a785d3e64a3f80d3aad9c',
155 'be90031137367893f1c406e0a8683010fd115b79',
155 'be90031137367893f1c406e0a8683010fd115b79',
156 'db8e58be770518cbb2b1cdfa69146e47cd481481',
156 'db8e58be770518cbb2b1cdfa69146e47cd481481',
157 '84478366594b424af694a6c784cb991a16b87c21',
157 '84478366594b424af694a6c784cb991a16b87c21',
158 '17f8e105dddb9f339600389c6dc7175d395a535c',
158 '17f8e105dddb9f339600389c6dc7175d395a535c',
159 '20a662e756499bde3095ffc9bc0643d1def2d0eb',
159 '20a662e756499bde3095ffc9bc0643d1def2d0eb',
160 '2e319b85e70a707bba0beff866d9f9de032aa4f9',
160 '2e319b85e70a707bba0beff866d9f9de032aa4f9',
161 '786facd2c61deb9cf91e9534735124fb8fc11842',
161 '786facd2c61deb9cf91e9534735124fb8fc11842',
162 '94593d2128d38210a2fcd1aabff6dda0d6d9edf8',
162 '94593d2128d38210a2fcd1aabff6dda0d6d9edf8',
163 'aa6a0de05b7612707db567078e130a6cd114a9a7',
163 'aa6a0de05b7612707db567078e130a6cd114a9a7',
164 'eada5a770da98ab0dd7325e29d00e0714f228d09'
164 'eada5a770da98ab0dd7325e29d00e0714f228d09'
165 ])
165 ])
166 assert subset.issubset(set(self.repo.commit_ids))
166 assert subset.issubset(set(self.repo.commit_ids))
167
167
168 # check if we have the proper order of commits
168 # check if we have the proper order of commits
169 org = [
169 org = [
170 'b986218ba1c9b0d6a259fac9b050b1724ed8e545',
170 'b986218ba1c9b0d6a259fac9b050b1724ed8e545',
171 '3d8f361e72ab303da48d799ff1ac40d5ac37c67e',
171 '3d8f361e72ab303da48d799ff1ac40d5ac37c67e',
172 '6cba7170863a2411822803fa77a0a264f1310b35',
172 '6cba7170863a2411822803fa77a0a264f1310b35',
173 '56349e29c2af3ac913b28bde9a2c6154436e615b',
173 '56349e29c2af3ac913b28bde9a2c6154436e615b',
174 '2dda4e345facb0ccff1a191052dd1606dba6781d',
174 '2dda4e345facb0ccff1a191052dd1606dba6781d',
175 '6fff84722075f1607a30f436523403845f84cd9e',
175 '6fff84722075f1607a30f436523403845f84cd9e',
176 '7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7',
176 '7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7',
177 '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb',
177 '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb',
178 'dc5d2c0661b61928834a785d3e64a3f80d3aad9c',
178 'dc5d2c0661b61928834a785d3e64a3f80d3aad9c',
179 'be90031137367893f1c406e0a8683010fd115b79',
179 'be90031137367893f1c406e0a8683010fd115b79',
180 'db8e58be770518cbb2b1cdfa69146e47cd481481',
180 'db8e58be770518cbb2b1cdfa69146e47cd481481',
181 '84478366594b424af694a6c784cb991a16b87c21',
181 '84478366594b424af694a6c784cb991a16b87c21',
182 '17f8e105dddb9f339600389c6dc7175d395a535c',
182 '17f8e105dddb9f339600389c6dc7175d395a535c',
183 '20a662e756499bde3095ffc9bc0643d1def2d0eb',
183 '20a662e756499bde3095ffc9bc0643d1def2d0eb',
184 '2e319b85e70a707bba0beff866d9f9de032aa4f9',
184 '2e319b85e70a707bba0beff866d9f9de032aa4f9',
185 '786facd2c61deb9cf91e9534735124fb8fc11842',
185 '786facd2c61deb9cf91e9534735124fb8fc11842',
186 '94593d2128d38210a2fcd1aabff6dda0d6d9edf8',
186 '94593d2128d38210a2fcd1aabff6dda0d6d9edf8',
187 'aa6a0de05b7612707db567078e130a6cd114a9a7',
187 'aa6a0de05b7612707db567078e130a6cd114a9a7',
188 'eada5a770da98ab0dd7325e29d00e0714f228d09',
188 'eada5a770da98ab0dd7325e29d00e0714f228d09',
189 '2c1885c735575ca478bf9e17b0029dca68824458',
189 '2c1885c735575ca478bf9e17b0029dca68824458',
190 'd9bcd465040bf869799b09ad732c04e0eea99fe9',
190 'd9bcd465040bf869799b09ad732c04e0eea99fe9',
191 '469e9c847fe1f6f7a697b8b25b4bc5b48780c1a7',
191 '469e9c847fe1f6f7a697b8b25b4bc5b48780c1a7',
192 '4fb8326d78e5120da2c7468dcf7098997be385da',
192 '4fb8326d78e5120da2c7468dcf7098997be385da',
193 '62b4a097164940bd66030c4db51687f3ec035eed',
193 '62b4a097164940bd66030c4db51687f3ec035eed',
194 '536c1a19428381cfea92ac44985304f6a8049569',
194 '536c1a19428381cfea92ac44985304f6a8049569',
195 '965e8ab3c44b070cdaa5bf727ddef0ada980ecc4',
195 '965e8ab3c44b070cdaa5bf727ddef0ada980ecc4',
196 '9bb326a04ae5d98d437dece54be04f830cf1edd9',
196 '9bb326a04ae5d98d437dece54be04f830cf1edd9',
197 'f8940bcb890a98c4702319fbe36db75ea309b475',
197 'f8940bcb890a98c4702319fbe36db75ea309b475',
198 'ff5ab059786ebc7411e559a2cc309dfae3625a3b',
198 'ff5ab059786ebc7411e559a2cc309dfae3625a3b',
199 '6b6ad5f82ad5bb6190037671bd254bd4e1f4bf08',
199 '6b6ad5f82ad5bb6190037671bd254bd4e1f4bf08',
200 'ee87846a61c12153b51543bf860e1026c6d3dcba',
200 'ee87846a61c12153b51543bf860e1026c6d3dcba',
201 ]
201 ]
202 assert org == self.repo.commit_ids[:31]
202 assert org == self.repo.commit_ids[:31]
203
203
204 def test_iter_slice(self):
204 def test_iter_slice(self):
205 sliced = list(self.repo[:10])
205 sliced = list(self.repo[:10])
206 itered = list(self.repo)[:10]
206 itered = list(self.repo)[:10]
207 assert sliced == itered
207 assert sliced == itered
208
208
209 def test_slicing(self):
209 def test_slicing(self):
210 # 4 1 5 10 95
210 # 4 1 5 10 95
211 for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5),
211 for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5),
212 (10, 20, 10), (5, 100, 95)]:
212 (10, 20, 10), (5, 100, 95)]:
213 indexes = list(self.repo[sfrom:sto])
213 indexes = list(self.repo[sfrom:sto])
214 assert len(indexes) == size
214 assert len(indexes) == size
215 assert indexes[0] == self.repo.get_commit(commit_idx=sfrom)
215 assert indexes[0] == self.repo.get_commit(commit_idx=sfrom)
216 assert indexes[-1] == self.repo.get_commit(commit_idx=sto - 1)
216 assert indexes[-1] == self.repo.get_commit(commit_idx=sto - 1)
217
217
218 def test_branches(self):
218 def test_branches(self):
219 # TODO: Need more tests here
219 # TODO: Need more tests here
220
220
221 # active branches
221 # active branches
222 assert 'default' in self.repo.branches
222 assert 'default' in self.repo.branches
223 assert 'stable' in self.repo.branches
223 assert 'stable' in self.repo.branches
224
224
225 # closed
225 # closed
226 assert 'git' in self.repo._get_branches(closed=True)
226 assert 'git' in self.repo._get_branches(closed=True)
227 assert 'web' in self.repo._get_branches(closed=True)
227 assert 'web' in self.repo._get_branches(closed=True)
228
228
229 for name, id in self.repo.branches.items():
229 for name, id in self.repo.branches.items():
230 assert isinstance(self.repo.get_commit(id), MercurialCommit)
230 assert isinstance(self.repo.get_commit(id), MercurialCommit)
231
231
232 def test_tip_in_tags(self):
232 def test_tip_in_tags(self):
233 # tip is always a tag
233 # tip is always a tag
234 assert 'tip' in self.repo.tags
234 assert 'tip' in self.repo.tags
235
235
236 def test_tip_commit_in_tags(self):
236 def test_tip_commit_in_tags(self):
237 tip = self.repo.get_commit()
237 tip = self.repo.get_commit()
238 assert self.repo.tags['tip'] == tip.raw_id
238 assert self.repo.tags['tip'] == tip.raw_id
239
239
240 def test_initial_commit(self):
240 def test_initial_commit(self):
241 init_commit = self.repo.get_commit(commit_idx=0)
241 init_commit = self.repo.get_commit(commit_idx=0)
242 init_author = init_commit.author
242 init_author = init_commit.author
243
243
244 assert init_commit.message == 'initial import'
244 assert init_commit.message == 'initial import'
245 assert init_author == 'Marcin Kuzminski <marcin@python-blog.com>'
245 assert init_author == 'Marcin Kuzminski <marcin@python-blog.com>'
246 assert init_author == init_commit.committer
246 assert init_author == init_commit.committer
247 assert sorted(init_commit._file_paths) == sorted([
247 assert sorted(init_commit._file_paths) == sorted([
248 'vcs/__init__.py',
248 'vcs/__init__.py',
249 'vcs/backends/BaseRepository.py',
249 'vcs/backends/BaseRepository.py',
250 'vcs/backends/__init__.py',
250 'vcs/backends/__init__.py',
251 ])
251 ])
252 assert sorted(init_commit._dir_paths) == sorted(
252 assert sorted(init_commit._dir_paths) == sorted(
253 ['', 'vcs', 'vcs/backends'])
253 ['', 'vcs', 'vcs/backends'])
254
254
255 assert init_commit._dir_paths + init_commit._file_paths == \
255 assert init_commit._dir_paths + init_commit._file_paths == \
256 init_commit._paths
256 init_commit._paths
257
257
258 with pytest.raises(NodeDoesNotExistError):
258 with pytest.raises(NodeDoesNotExistError):
259 init_commit.get_node(path='foobar')
259 init_commit.get_node(path='foobar')
260
260
261 node = init_commit.get_node('vcs/')
261 node = init_commit.get_node('vcs/')
262 assert hasattr(node, 'kind')
262 assert hasattr(node, 'kind')
263 assert node.kind == NodeKind.DIR
263 assert node.kind == NodeKind.DIR
264
264
265 node = init_commit.get_node('vcs')
265 node = init_commit.get_node('vcs')
266 assert hasattr(node, 'kind')
266 assert hasattr(node, 'kind')
267 assert node.kind == NodeKind.DIR
267 assert node.kind == NodeKind.DIR
268
268
269 node = init_commit.get_node('vcs/__init__.py')
269 node = init_commit.get_node('vcs/__init__.py')
270 assert hasattr(node, 'kind')
270 assert hasattr(node, 'kind')
271 assert node.kind == NodeKind.FILE
271 assert node.kind == NodeKind.FILE
272
272
273 def test_not_existing_commit(self):
273 def test_not_existing_commit(self):
274 # rawid
274 # rawid
275 with pytest.raises(RepositoryError):
275 with pytest.raises(RepositoryError):
276 self.repo.get_commit('abcd' * 10)
276 self.repo.get_commit('abcd' * 10)
277 # shortid
277 # shortid
278 with pytest.raises(RepositoryError):
278 with pytest.raises(RepositoryError):
279 self.repo.get_commit('erro' * 4)
279 self.repo.get_commit('erro' * 4)
280 # numeric
280 # numeric
281 with pytest.raises(RepositoryError):
281 with pytest.raises(RepositoryError):
282 self.repo.get_commit(commit_idx=self.repo.count() + 1)
282 self.repo.get_commit(commit_idx=self.repo.count() + 1)
283
283
284 # Small chance we ever get to this one
284 # Small chance we ever get to this one
285 idx = pow(2, 30)
285 idx = pow(2, 30)
286 with pytest.raises(RepositoryError):
286 with pytest.raises(RepositoryError):
287 self.repo.get_commit(commit_idx=idx)
287 self.repo.get_commit(commit_idx=idx)
288
288
289 def test_commit10(self):
289 def test_commit10(self):
290 commit10 = self.repo.get_commit(commit_idx=10)
290 commit10 = self.repo.get_commit(commit_idx=10)
291 README = """===
291 README = """===
292 VCS
292 VCS
293 ===
293 ===
294
294
295 Various Version Control System management abstraction layer for Python.
295 Various Version Control System management abstraction layer for Python.
296
296
297 Introduction
297 Introduction
298 ------------
298 ------------
299
299
300 TODO: To be written...
300 TODO: To be written...
301
301
302 """
302 """
303 node = commit10.get_node('README.rst')
303 node = commit10.get_node('README.rst')
304 assert node.kind == NodeKind.FILE
304 assert node.kind == NodeKind.FILE
305 assert node.content == README
305 assert node.content == README
306
306
307 def test_local_clone(self):
307 def test_local_clone(self):
308 clone_path = next(REPO_PATH_GENERATOR)
308 clone_path = next(REPO_PATH_GENERATOR)
309 self.repo._local_clone(clone_path)
309 self.repo._local_clone(clone_path)
310 repo_clone = MercurialRepository(clone_path)
310 repo_clone = MercurialRepository(clone_path)
311
311
312 assert self.repo.commit_ids == repo_clone.commit_ids
312 assert self.repo.commit_ids == repo_clone.commit_ids
313
313
314 def test_local_clone_fails_if_target_exists(self):
314 def test_local_clone_fails_if_target_exists(self):
315 with pytest.raises(RepositoryError):
315 with pytest.raises(RepositoryError):
316 self.repo._local_clone(self.repo.path)
316 self.repo._local_clone(self.repo.path)
317
317
318 def test_update(self):
318 def test_update(self):
319 repo_clone = self.get_clone_repo()
319 repo_clone = self.get_clone_repo()
320 branches = repo_clone.branches
320 branches = repo_clone.branches
321
321
322 repo_clone._update('default')
322 repo_clone._update('default')
323 assert branches['default'] == repo_clone._identify()
323 assert branches['default'] == repo_clone._identify()
324 repo_clone._update('stable')
324 repo_clone._update('stable')
325 assert branches['stable'] == repo_clone._identify()
325 assert branches['stable'] == repo_clone._identify()
326
326
327 def test_local_pull_branch(self):
327 def test_local_pull_branch(self):
328 target_repo = self.get_empty_repo()
328 target_repo = self.get_empty_repo()
329 source_repo = self.get_clone_repo()
329 source_repo = self.get_clone_repo()
330
330
331 default = Reference(
331 default = Reference(
332 'branch', 'default', source_repo.branches['default'])
332 'branch', 'default', source_repo.branches['default'])
333 target_repo._local_pull(source_repo.path, default)
333 target_repo._local_pull(source_repo.path, default)
334 target_repo = MercurialRepository(target_repo.path)
334 target_repo = MercurialRepository(target_repo.path)
335 assert (target_repo.branches['default'] ==
335 assert (target_repo.branches['default'] ==
336 source_repo.branches['default'])
336 source_repo.branches['default'])
337
337
338 stable = Reference('branch', 'stable', source_repo.branches['stable'])
338 stable = Reference('branch', 'stable', source_repo.branches['stable'])
339 target_repo._local_pull(source_repo.path, stable)
339 target_repo._local_pull(source_repo.path, stable)
340 target_repo = MercurialRepository(target_repo.path)
340 target_repo = MercurialRepository(target_repo.path)
341 assert target_repo.branches['stable'] == source_repo.branches['stable']
341 assert target_repo.branches['stable'] == source_repo.branches['stable']
342
342
343 def test_local_pull_bookmark(self):
343 def test_local_pull_bookmark(self):
344 target_repo = self.get_empty_repo()
344 target_repo = self.get_empty_repo()
345 source_repo = self.get_clone_repo()
345 source_repo = self.get_clone_repo()
346
346
347 commits = list(source_repo.get_commits(branch_name='default'))
347 commits = list(source_repo.get_commits(branch_name='default'))
348 foo1_id = commits[-5].raw_id
348 foo1_id = commits[-5].raw_id
349 foo1 = Reference('book', 'foo1', foo1_id)
349 foo1 = Reference('book', 'foo1', foo1_id)
350 source_repo._update(foo1_id)
350 source_repo._update(foo1_id)
351 source_repo.bookmark('foo1')
351 source_repo.bookmark('foo1')
352
352
353 foo2_id = commits[-3].raw_id
353 foo2_id = commits[-3].raw_id
354 foo2 = Reference('book', 'foo2', foo2_id)
354 foo2 = Reference('book', 'foo2', foo2_id)
355 source_repo._update(foo2_id)
355 source_repo._update(foo2_id)
356 source_repo.bookmark('foo2')
356 source_repo.bookmark('foo2')
357
357
358 target_repo._local_pull(source_repo.path, foo1)
358 target_repo._local_pull(source_repo.path, foo1)
359 target_repo = MercurialRepository(target_repo.path)
359 target_repo = MercurialRepository(target_repo.path)
360 assert target_repo.branches['default'] == commits[-5].raw_id
360 assert target_repo.branches['default'] == commits[-5].raw_id
361
361
362 target_repo._local_pull(source_repo.path, foo2)
362 target_repo._local_pull(source_repo.path, foo2)
363 target_repo = MercurialRepository(target_repo.path)
363 target_repo = MercurialRepository(target_repo.path)
364 assert target_repo.branches['default'] == commits[-3].raw_id
364 assert target_repo.branches['default'] == commits[-3].raw_id
365
365
366 def test_local_pull_commit(self):
366 def test_local_pull_commit(self):
367 target_repo = self.get_empty_repo()
367 target_repo = self.get_empty_repo()
368 source_repo = self.get_clone_repo()
368 source_repo = self.get_clone_repo()
369
369
370 commits = list(source_repo.get_commits(branch_name='default'))
370 commits = list(source_repo.get_commits(branch_name='default'))
371 commit_id = commits[-5].raw_id
371 commit_id = commits[-5].raw_id
372 commit = Reference('rev', commit_id, commit_id)
372 commit = Reference('rev', commit_id, commit_id)
373 target_repo._local_pull(source_repo.path, commit)
373 target_repo._local_pull(source_repo.path, commit)
374 target_repo = MercurialRepository(target_repo.path)
374 target_repo = MercurialRepository(target_repo.path)
375 assert target_repo.branches['default'] == commit_id
375 assert target_repo.branches['default'] == commit_id
376
376
377 commit_id = commits[-3].raw_id
377 commit_id = commits[-3].raw_id
378 commit = Reference('rev', commit_id, commit_id)
378 commit = Reference('rev', commit_id, commit_id)
379 target_repo._local_pull(source_repo.path, commit)
379 target_repo._local_pull(source_repo.path, commit)
380 target_repo = MercurialRepository(target_repo.path)
380 target_repo = MercurialRepository(target_repo.path)
381 assert target_repo.branches['default'] == commit_id
381 assert target_repo.branches['default'] == commit_id
382
382
383 def test_local_pull_from_same_repo(self):
383 def test_local_pull_from_same_repo(self):
384 reference = Reference('branch', 'default', None)
384 reference = Reference('branch', 'default', None)
385 with pytest.raises(ValueError):
385 with pytest.raises(ValueError):
386 self.repo._local_pull(self.repo.path, reference)
386 self.repo._local_pull(self.repo.path, reference)
387
387
388 def test_validate_pull_reference_raises_on_missing_reference(
388 def test_validate_pull_reference_raises_on_missing_reference(
389 self, vcsbackend_hg):
389 self, vcsbackend_hg):
390 target_repo = vcsbackend_hg.create_repo(number_of_commits=1)
390 target_repo = vcsbackend_hg.create_repo(number_of_commits=1)
391 reference = Reference(
391 reference = Reference(
392 'book', 'invalid_reference', 'a' * 40)
392 'book', 'invalid_reference', 'a' * 40)
393
393
394 with pytest.raises(CommitDoesNotExistError):
394 with pytest.raises(CommitDoesNotExistError):
395 target_repo._validate_pull_reference(reference)
395 target_repo._validate_pull_reference(reference)
396
396
397 def test_heads(self):
397 def test_heads(self):
398 assert set(self.repo._heads()) == set(self.repo.branches.values())
398 assert set(self.repo._heads()) == set(self.repo.branches.values())
399
399
400 def test_ancestor(self):
400 def test_ancestor(self):
401 commits = [
401 commits = [
402 c.raw_id for c in self.repo.get_commits(branch_name='default')]
402 c.raw_id for c in self.repo.get_commits(branch_name='default')]
403 assert self.repo._ancestor(commits[-3], commits[-5]) == commits[-5]
403 assert self.repo._ancestor(commits[-3], commits[-5]) == commits[-5]
404 assert self.repo._ancestor(commits[-5], commits[-3]) == commits[-5]
404 assert self.repo._ancestor(commits[-5], commits[-3]) == commits[-5]
405
405
406 def test_local_push(self):
406 def test_local_push(self):
407 target_repo = self.get_empty_repo()
407 target_repo = self.get_empty_repo()
408
408
409 revisions = list(self.repo.get_commits(branch_name='default'))
409 revisions = list(self.repo.get_commits(branch_name='default'))
410 revision = revisions[-5].raw_id
410 revision = revisions[-5].raw_id
411 self.repo._local_push(revision, target_repo.path)
411 self.repo._local_push(revision, target_repo.path)
412
412
413 target_repo = MercurialRepository(target_repo.path)
413 target_repo = MercurialRepository(target_repo.path)
414
414
415 assert target_repo.branches['default'] == revision
415 assert target_repo.branches['default'] == revision
416
416
417 def test_hooks_can_be_enabled_for_local_push(self):
417 def test_hooks_can_be_enabled_for_local_push(self):
418 revision = 'deadbeef'
418 revision = 'deadbeef'
419 repo_path = 'test_group/test_repo'
419 repo_path = 'test_group/test_repo'
420 with mock.patch.object(self.repo, '_remote') as remote_mock:
420 with mock.patch.object(self.repo, '_remote') as remote_mock:
421 self.repo._local_push(revision, repo_path, enable_hooks=True)
421 self.repo._local_push(revision, repo_path, enable_hooks=True)
422 remote_mock.push.assert_called_once_with(
422 remote_mock.push.assert_called_once_with(
423 [revision], repo_path, hooks=True, push_branches=False)
423 [revision], repo_path, hooks=True, push_branches=False)
424
424
425 def test_local_merge(self, vcsbackend_hg):
425 def test_local_merge(self, vcsbackend_hg):
426 target_repo = vcsbackend_hg.create_repo(number_of_commits=1)
426 target_repo = vcsbackend_hg.create_repo(number_of_commits=1)
427 source_repo = vcsbackend_hg.clone_repo(target_repo)
427 source_repo = vcsbackend_hg.clone_repo(target_repo)
428 vcsbackend_hg.add_file(target_repo, 'README_MERGE1', 'Version 1')
428 vcsbackend_hg.add_file(target_repo, 'README_MERGE1', 'Version 1')
429 target_repo = MercurialRepository(target_repo.path)
429 target_repo = MercurialRepository(target_repo.path)
430 target_rev = target_repo.branches['default']
430 target_rev = target_repo.branches['default']
431 target_ref = Reference(
431 target_ref = Reference(
432 type='branch', name='default', commit_id=target_rev)
432 type='branch', name='default', commit_id=target_rev)
433 vcsbackend_hg.add_file(source_repo, 'README_MERGE2', 'Version 2')
433 vcsbackend_hg.add_file(source_repo, 'README_MERGE2', 'Version 2')
434 source_repo = MercurialRepository(source_repo.path)
434 source_repo = MercurialRepository(source_repo.path)
435 source_rev = source_repo.branches['default']
435 source_rev = source_repo.branches['default']
436 source_ref = Reference(
436 source_ref = Reference(
437 type='branch', name='default', commit_id=source_rev)
437 type='branch', name='default', commit_id=source_rev)
438
438
439 target_repo._local_pull(source_repo.path, source_ref)
439 target_repo._local_pull(source_repo.path, source_ref)
440
440
441 merge_message = 'Merge message\n\nDescription:...'
441 merge_message = 'Merge message\n\nDescription:...'
442 user_name = 'Albert Einstein'
442 user_name = 'Albert Einstein'
443 user_email = 'albert@einstein.com'
443 user_email = 'albert@einstein.com'
444 merge_commit_id, needs_push = target_repo._local_merge(
444 merge_commit_id, needs_push = target_repo._local_merge(
445 target_ref, merge_message, user_name, user_email, source_ref)
445 target_ref, merge_message, user_name, user_email, source_ref)
446 assert needs_push
446 assert needs_push
447
447
448 target_repo = MercurialRepository(target_repo.path)
448 target_repo = MercurialRepository(target_repo.path)
449 assert target_repo.commit_ids[-3] == target_rev
449 assert target_repo.commit_ids[-3] == target_rev
450 assert target_repo.commit_ids[-2] == source_rev
450 assert target_repo.commit_ids[-2] == source_rev
451 last_commit = target_repo.get_commit(merge_commit_id)
451 last_commit = target_repo.get_commit(merge_commit_id)
452 assert last_commit.message.strip() == merge_message
452 assert last_commit.message.strip() == merge_message
453 assert last_commit.author == '%s <%s>' % (user_name, user_email)
453 assert last_commit.author == '%s <%s>' % (user_name, user_email)
454
454
455 assert not os.path.exists(
455 assert not os.path.exists(
456 os.path.join(target_repo.path, '.hg', 'merge', 'state'))
456 os.path.join(target_repo.path, '.hg', 'merge', 'state'))
457
457
458 def test_local_merge_source_is_fast_forward(self, vcsbackend_hg):
458 def test_local_merge_source_is_fast_forward(self, vcsbackend_hg):
459 target_repo = vcsbackend_hg.create_repo(number_of_commits=1)
459 target_repo = vcsbackend_hg.create_repo(number_of_commits=1)
460 source_repo = vcsbackend_hg.clone_repo(target_repo)
460 source_repo = vcsbackend_hg.clone_repo(target_repo)
461 target_rev = target_repo.branches['default']
461 target_rev = target_repo.branches['default']
462 target_ref = Reference(
462 target_ref = Reference(
463 type='branch', name='default', commit_id=target_rev)
463 type='branch', name='default', commit_id=target_rev)
464 vcsbackend_hg.add_file(source_repo, 'README_MERGE2', 'Version 2')
464 vcsbackend_hg.add_file(source_repo, 'README_MERGE2', 'Version 2')
465 source_repo = MercurialRepository(source_repo.path)
465 source_repo = MercurialRepository(source_repo.path)
466 source_rev = source_repo.branches['default']
466 source_rev = source_repo.branches['default']
467 source_ref = Reference(
467 source_ref = Reference(
468 type='branch', name='default', commit_id=source_rev)
468 type='branch', name='default', commit_id=source_rev)
469
469
470 target_repo._local_pull(source_repo.path, source_ref)
470 target_repo._local_pull(source_repo.path, source_ref)
471
471
472 merge_message = 'Merge message\n\nDescription:...'
472 merge_message = 'Merge message\n\nDescription:...'
473 user_name = 'Albert Einstein'
473 user_name = 'Albert Einstein'
474 user_email = 'albert@einstein.com'
474 user_email = 'albert@einstein.com'
475 merge_commit_id, needs_push = target_repo._local_merge(
475 merge_commit_id, needs_push = target_repo._local_merge(
476 target_ref, merge_message, user_name, user_email, source_ref)
476 target_ref, merge_message, user_name, user_email, source_ref)
477 assert merge_commit_id == source_rev
477 assert merge_commit_id == source_rev
478 assert needs_push
478 assert needs_push
479
479
480 target_repo = MercurialRepository(target_repo.path)
480 target_repo = MercurialRepository(target_repo.path)
481 assert target_repo.commit_ids[-2] == target_rev
481 assert target_repo.commit_ids[-2] == target_rev
482 assert target_repo.commit_ids[-1] == source_rev
482 assert target_repo.commit_ids[-1] == source_rev
483
483
484 assert not os.path.exists(
484 assert not os.path.exists(
485 os.path.join(target_repo.path, '.hg', 'merge', 'state'))
485 os.path.join(target_repo.path, '.hg', 'merge', 'state'))
486
486
487 def test_local_merge_source_is_integrated(self, vcsbackend_hg):
487 def test_local_merge_source_is_integrated(self, vcsbackend_hg):
488 target_repo = vcsbackend_hg.create_repo(number_of_commits=1)
488 target_repo = vcsbackend_hg.create_repo(number_of_commits=1)
489 target_rev = target_repo.branches['default']
489 target_rev = target_repo.branches['default']
490 target_ref = Reference(
490 target_ref = Reference(
491 type='branch', name='default', commit_id=target_rev)
491 type='branch', name='default', commit_id=target_rev)
492
492
493 merge_message = 'Merge message\n\nDescription:...'
493 merge_message = 'Merge message\n\nDescription:...'
494 user_name = 'Albert Einstein'
494 user_name = 'Albert Einstein'
495 user_email = 'albert@einstein.com'
495 user_email = 'albert@einstein.com'
496 merge_commit_id, needs_push = target_repo._local_merge(
496 merge_commit_id, needs_push = target_repo._local_merge(
497 target_ref, merge_message, user_name, user_email, target_ref)
497 target_ref, merge_message, user_name, user_email, target_ref)
498 assert merge_commit_id == target_rev
498 assert merge_commit_id == target_rev
499 assert not needs_push
499 assert not needs_push
500
500
501 target_repo = MercurialRepository(target_repo.path)
501 target_repo = MercurialRepository(target_repo.path)
502 assert target_repo.commit_ids[-1] == target_rev
502 assert target_repo.commit_ids[-1] == target_rev
503
503
504 assert not os.path.exists(
504 assert not os.path.exists(
505 os.path.join(target_repo.path, '.hg', 'merge', 'state'))
505 os.path.join(target_repo.path, '.hg', 'merge', 'state'))
506
506
507 def test_local_merge_raises_exception_on_conflict(self, vcsbackend_hg):
507 def test_local_merge_raises_exception_on_conflict(self, vcsbackend_hg):
508 target_repo = vcsbackend_hg.create_repo(number_of_commits=1)
508 target_repo = vcsbackend_hg.create_repo(number_of_commits=1)
509 source_repo = vcsbackend_hg.clone_repo(target_repo)
509 source_repo = vcsbackend_hg.clone_repo(target_repo)
510 vcsbackend_hg.add_file(target_repo, 'README_MERGE', 'Version 1')
510 vcsbackend_hg.add_file(target_repo, 'README_MERGE', 'Version 1')
511 target_repo = MercurialRepository(target_repo.path)
511 target_repo = MercurialRepository(target_repo.path)
512 target_rev = target_repo.branches['default']
512 target_rev = target_repo.branches['default']
513 target_ref = Reference(
513 target_ref = Reference(
514 type='branch', name='default', commit_id=target_rev)
514 type='branch', name='default', commit_id=target_rev)
515 vcsbackend_hg.add_file(source_repo, 'README_MERGE', 'Version 2')
515 vcsbackend_hg.add_file(source_repo, 'README_MERGE', 'Version 2')
516 source_repo = MercurialRepository(source_repo.path)
516 source_repo = MercurialRepository(source_repo.path)
517 source_rev = source_repo.branches['default']
517 source_rev = source_repo.branches['default']
518 source_ref = Reference(
518 source_ref = Reference(
519 type='branch', name='default', commit_id=source_rev)
519 type='branch', name='default', commit_id=source_rev)
520
520
521 target_repo._local_pull(source_repo.path, source_ref)
521 target_repo._local_pull(source_repo.path, source_ref)
522 with pytest.raises(RepositoryError):
522 with pytest.raises(RepositoryError):
523 target_repo._local_merge(
523 target_repo._local_merge(
524 target_ref, 'merge_message', 'user name', 'user@name.com',
524 target_ref, 'merge_message', 'user name', 'user@name.com',
525 source_ref)
525 source_ref)
526
526
527 # Check we are not left in an intermediate merge state
527 # Check we are not left in an intermediate merge state
528 assert not os.path.exists(
528 assert not os.path.exists(
529 os.path.join(target_repo.path, '.hg', 'merge', 'state'))
529 os.path.join(target_repo.path, '.hg', 'merge', 'state'))
530
530
531 def test_local_merge_of_two_branches_of_the_same_repo(self, backend_hg):
531 def test_local_merge_of_two_branches_of_the_same_repo(self, backend_hg):
532 commits = [
532 commits = [
533 {'message': 'a'},
533 {'message': 'a'},
534 {'message': 'b', 'branch': 'b'},
534 {'message': 'b', 'branch': 'b'},
535 ]
535 ]
536 repo = backend_hg.create_repo(commits)
536 repo = backend_hg.create_repo(commits)
537 commit_ids = backend_hg.commit_ids
537 commit_ids = backend_hg.commit_ids
538 target_ref = Reference(
538 target_ref = Reference(
539 type='branch', name='default', commit_id=commit_ids['a'])
539 type='branch', name='default', commit_id=commit_ids['a'])
540 source_ref = Reference(
540 source_ref = Reference(
541 type='branch', name='b', commit_id=commit_ids['b'])
541 type='branch', name='b', commit_id=commit_ids['b'])
542 merge_message = 'Merge message\n\nDescription:...'
542 merge_message = 'Merge message\n\nDescription:...'
543 user_name = 'Albert Einstein'
543 user_name = 'Albert Einstein'
544 user_email = 'albert@einstein.com'
544 user_email = 'albert@einstein.com'
545 vcs_repo = repo.scm_instance()
545 vcs_repo = repo.scm_instance()
546 merge_commit_id, needs_push = vcs_repo._local_merge(
546 merge_commit_id, needs_push = vcs_repo._local_merge(
547 target_ref, merge_message, user_name, user_email, source_ref)
547 target_ref, merge_message, user_name, user_email, source_ref)
548 assert merge_commit_id != source_ref.commit_id
548 assert merge_commit_id != source_ref.commit_id
549 assert needs_push is True
549 assert needs_push is True
550 commit = vcs_repo.get_commit(merge_commit_id)
550 commit = vcs_repo.get_commit(merge_commit_id)
551 assert commit.merge is True
551 assert commit.merge is True
552 assert commit.message == merge_message
552 assert commit.message == merge_message
553
553
554 def test_maybe_prepare_merge_workspace(self):
554 def test_maybe_prepare_merge_workspace(self):
555 workspace = self.repo._maybe_prepare_merge_workspace('pr2', 'unused')
555 workspace = self.repo._maybe_prepare_merge_workspace('pr2', 'unused')
556
556
557 assert os.path.isdir(workspace)
557 assert os.path.isdir(workspace)
558 workspace_repo = MercurialRepository(workspace)
558 workspace_repo = MercurialRepository(workspace)
559 assert workspace_repo.branches == self.repo.branches
559 assert workspace_repo.branches == self.repo.branches
560
560
561 # Calling it a second time should also succeed
561 # Calling it a second time should also succeed
562 workspace = self.repo._maybe_prepare_merge_workspace('pr2', 'unused')
562 workspace = self.repo._maybe_prepare_merge_workspace('pr2', 'unused')
563 assert os.path.isdir(workspace)
563 assert os.path.isdir(workspace)
564
564
565 def test_cleanup_merge_workspace(self):
565 def test_cleanup_merge_workspace(self):
566 workspace = self.repo._maybe_prepare_merge_workspace('pr3', 'unused')
566 workspace = self.repo._maybe_prepare_merge_workspace('pr3', 'unused')
567 self.repo.cleanup_merge_workspace('pr3')
567 self.repo.cleanup_merge_workspace('pr3')
568
568
569 assert not os.path.exists(workspace)
569 assert not os.path.exists(workspace)
570
570
571 def test_cleanup_merge_workspace_invalid_workspace_id(self):
571 def test_cleanup_merge_workspace_invalid_workspace_id(self):
572 # No assert: because in case of an inexistent workspace this function
572 # No assert: because in case of an inexistent workspace this function
573 # should still succeed.
573 # should still succeed.
574 self.repo.cleanup_merge_workspace('pr4')
574 self.repo.cleanup_merge_workspace('pr4')
575
575
576 def test_merge_target_is_bookmark(self, vcsbackend_hg):
576 def test_merge_target_is_bookmark(self, vcsbackend_hg):
577 target_repo = vcsbackend_hg.create_repo(number_of_commits=1)
577 target_repo = vcsbackend_hg.create_repo(number_of_commits=1)
578 source_repo = vcsbackend_hg.clone_repo(target_repo)
578 source_repo = vcsbackend_hg.clone_repo(target_repo)
579 vcsbackend_hg.add_file(target_repo, 'README_MERGE1', 'Version 1')
579 vcsbackend_hg.add_file(target_repo, 'README_MERGE1', 'Version 1')
580 vcsbackend_hg.add_file(source_repo, 'README_MERGE2', 'Version 2')
580 vcsbackend_hg.add_file(source_repo, 'README_MERGE2', 'Version 2')
581 imc = source_repo.in_memory_commit
581 imc = source_repo.in_memory_commit
582 imc.add(FileNode('file_x', content=source_repo.name))
582 imc.add(FileNode('file_x', content=source_repo.name))
583 imc.commit(
583 imc.commit(
584 message=u'Automatic commit from repo merge test',
584 message=u'Automatic commit from repo merge test',
585 author=u'Automatic')
585 author=u'Automatic')
586 target_commit = target_repo.get_commit()
586 target_commit = target_repo.get_commit()
587 source_commit = source_repo.get_commit()
587 source_commit = source_repo.get_commit()
588 default_branch = target_repo.DEFAULT_BRANCH_NAME
588 default_branch = target_repo.DEFAULT_BRANCH_NAME
589 bookmark_name = 'bookmark'
589 bookmark_name = 'bookmark'
590 target_repo._update(default_branch)
590 target_repo._update(default_branch)
591 target_repo.bookmark(bookmark_name)
591 target_repo.bookmark(bookmark_name)
592 target_ref = Reference('book', bookmark_name, target_commit.raw_id)
592 target_ref = Reference('book', bookmark_name, target_commit.raw_id)
593 source_ref = Reference('branch', default_branch, source_commit.raw_id)
593 source_ref = Reference('branch', default_branch, source_commit.raw_id)
594 workspace = 'test-merge'
594 workspace = 'test-merge'
595
595
596 merge_response = target_repo.merge(
596 merge_response = target_repo.merge(
597 target_ref, source_repo, source_ref, workspace,
597 target_ref, source_repo, source_ref, workspace,
598 'test user', 'test@rhodecode.com', 'merge message 1',
598 'test user', 'test@rhodecode.com', 'merge message 1',
599 dry_run=False)
599 dry_run=False)
600 expected_merge_response = MergeResponse(
600 expected_merge_response = MergeResponse(
601 True, True, merge_response.merge_ref,
601 True, True, merge_response.merge_ref,
602 MergeFailureReason.NONE)
602 MergeFailureReason.NONE)
603 assert merge_response == expected_merge_response
603 assert merge_response == expected_merge_response
604
604
605 target_repo = backends.get_backend(vcsbackend_hg.alias)(
605 target_repo = backends.get_backend(vcsbackend_hg.alias)(
606 target_repo.path)
606 target_repo.path)
607 target_commits = list(target_repo.get_commits())
607 target_commits = list(target_repo.get_commits())
608 commit_ids = [c.raw_id for c in target_commits[:-1]]
608 commit_ids = [c.raw_id for c in target_commits[:-1]]
609 assert source_ref.commit_id in commit_ids
609 assert source_ref.commit_id in commit_ids
610 assert target_ref.commit_id in commit_ids
610 assert target_ref.commit_id in commit_ids
611
611
612 merge_commit = target_commits[-1]
612 merge_commit = target_commits[-1]
613 assert merge_commit.raw_id == merge_response.merge_ref.commit_id
613 assert merge_commit.raw_id == merge_response.merge_ref.commit_id
614 assert merge_commit.message.strip() == 'merge message 1'
614 assert merge_commit.message.strip() == 'merge message 1'
615 assert merge_commit.author == 'test user <test@rhodecode.com>'
615 assert merge_commit.author == 'test user <test@rhodecode.com>'
616
616
617 # Check the bookmark was updated in the target repo
617 # Check the bookmark was updated in the target repo
618 assert (
618 assert (
619 target_repo.bookmarks[bookmark_name] ==
619 target_repo.bookmarks[bookmark_name] ==
620 merge_response.merge_ref.commit_id)
620 merge_response.merge_ref.commit_id)
621
621
622 def test_merge_source_is_bookmark(self, vcsbackend_hg):
622 def test_merge_source_is_bookmark(self, vcsbackend_hg):
623 target_repo = vcsbackend_hg.create_repo(number_of_commits=1)
623 target_repo = vcsbackend_hg.create_repo(number_of_commits=1)
624 source_repo = vcsbackend_hg.clone_repo(target_repo)
624 source_repo = vcsbackend_hg.clone_repo(target_repo)
625 imc = source_repo.in_memory_commit
625 imc = source_repo.in_memory_commit
626 imc.add(FileNode('file_x', content=source_repo.name))
626 imc.add(FileNode('file_x', content=source_repo.name))
627 imc.commit(
627 imc.commit(
628 message=u'Automatic commit from repo merge test',
628 message=u'Automatic commit from repo merge test',
629 author=u'Automatic')
629 author=u'Automatic')
630 target_commit = target_repo.get_commit()
630 target_commit = target_repo.get_commit()
631 source_commit = source_repo.get_commit()
631 source_commit = source_repo.get_commit()
632 default_branch = target_repo.DEFAULT_BRANCH_NAME
632 default_branch = target_repo.DEFAULT_BRANCH_NAME
633 bookmark_name = 'bookmark'
633 bookmark_name = 'bookmark'
634 target_ref = Reference('branch', default_branch, target_commit.raw_id)
634 target_ref = Reference('branch', default_branch, target_commit.raw_id)
635 source_repo._update(default_branch)
635 source_repo._update(default_branch)
636 source_repo.bookmark(bookmark_name)
636 source_repo.bookmark(bookmark_name)
637 source_ref = Reference('book', bookmark_name, source_commit.raw_id)
637 source_ref = Reference('book', bookmark_name, source_commit.raw_id)
638 workspace = 'test-merge'
638 workspace = 'test-merge'
639
639
640 merge_response = target_repo.merge(
640 merge_response = target_repo.merge(
641 target_ref, source_repo, source_ref, workspace,
641 target_ref, source_repo, source_ref, workspace,
642 'test user', 'test@rhodecode.com', 'merge message 1',
642 'test user', 'test@rhodecode.com', 'merge message 1',
643 dry_run=False)
643 dry_run=False)
644 expected_merge_response = MergeResponse(
644 expected_merge_response = MergeResponse(
645 True, True, merge_response.merge_ref,
645 True, True, merge_response.merge_ref,
646 MergeFailureReason.NONE)
646 MergeFailureReason.NONE)
647 assert merge_response == expected_merge_response
647 assert merge_response == expected_merge_response
648
648
649 target_repo = backends.get_backend(vcsbackend_hg.alias)(
649 target_repo = backends.get_backend(vcsbackend_hg.alias)(
650 target_repo.path)
650 target_repo.path)
651 target_commits = list(target_repo.get_commits())
651 target_commits = list(target_repo.get_commits())
652 commit_ids = [c.raw_id for c in target_commits]
652 commit_ids = [c.raw_id for c in target_commits]
653 assert source_ref.commit_id == commit_ids[-1]
653 assert source_ref.commit_id == commit_ids[-1]
654 assert target_ref.commit_id == commit_ids[-2]
654 assert target_ref.commit_id == commit_ids[-2]
655
655
656 def test_merge_target_has_multiple_heads(self, vcsbackend_hg):
656 def test_merge_target_has_multiple_heads(self, vcsbackend_hg):
657 target_repo = vcsbackend_hg.create_repo(number_of_commits=2)
657 target_repo = vcsbackend_hg.create_repo(number_of_commits=2)
658 source_repo = vcsbackend_hg.clone_repo(target_repo)
658 source_repo = vcsbackend_hg.clone_repo(target_repo)
659 vcsbackend_hg.add_file(target_repo, 'README_MERGE1', 'Version 1')
659 vcsbackend_hg.add_file(target_repo, 'README_MERGE1', 'Version 1')
660 vcsbackend_hg.add_file(source_repo, 'README_MERGE2', 'Version 2')
660 vcsbackend_hg.add_file(source_repo, 'README_MERGE2', 'Version 2')
661
661
662 # add an extra head to the target repo
662 # add an extra head to the target repo
663 imc = target_repo.in_memory_commit
663 imc = target_repo.in_memory_commit
664 imc.add(FileNode('file_x', content='foo'))
664 imc.add(FileNode('file_x', content='foo'))
665 commits = list(target_repo.get_commits())
665 commits = list(target_repo.get_commits())
666 imc.commit(
666 imc.commit(
667 message=u'Automatic commit from repo merge test',
667 message=u'Automatic commit from repo merge test',
668 author=u'Automatic', parents=commits[0:1])
668 author=u'Automatic', parents=commits[0:1])
669
669
670 target_commit = target_repo.get_commit()
670 target_commit = target_repo.get_commit()
671 source_commit = source_repo.get_commit()
671 source_commit = source_repo.get_commit()
672 default_branch = target_repo.DEFAULT_BRANCH_NAME
672 default_branch = target_repo.DEFAULT_BRANCH_NAME
673 target_repo._update(default_branch)
673 target_repo._update(default_branch)
674
674
675 target_ref = Reference('branch', default_branch, target_commit.raw_id)
675 target_ref = Reference('branch', default_branch, target_commit.raw_id)
676 source_ref = Reference('branch', default_branch, source_commit.raw_id)
676 source_ref = Reference('branch', default_branch, source_commit.raw_id)
677 workspace = 'test-merge'
677 workspace = 'test-merge'
678
678
679 assert len(target_repo._heads(branch='default')) == 2
679 assert len(target_repo._heads(branch='default')) == 2
680 expected_merge_response = MergeResponse(
680 expected_merge_response = MergeResponse(
681 False, False, None,
681 False, False, None,
682 MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS)
682 MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS)
683 merge_response = target_repo.merge(
683 merge_response = target_repo.merge(
684 target_ref, source_repo, source_ref, workspace,
684 target_ref, source_repo, source_ref, workspace,
685 'test user', 'test@rhodecode.com', 'merge message 1',
685 'test user', 'test@rhodecode.com', 'merge message 1',
686 dry_run=False)
686 dry_run=False)
687 assert merge_response == expected_merge_response
687 assert merge_response == expected_merge_response
688
688
689 def test_merge_rebase_source_is_updated_bookmark(self, vcsbackend_hg):
689 def test_merge_rebase_source_is_updated_bookmark(self, vcsbackend_hg):
690 target_repo = vcsbackend_hg.create_repo(number_of_commits=1)
690 target_repo = vcsbackend_hg.create_repo(number_of_commits=1)
691 source_repo = vcsbackend_hg.clone_repo(target_repo)
691 source_repo = vcsbackend_hg.clone_repo(target_repo)
692 vcsbackend_hg.add_file(target_repo, 'README_MERGE1', 'Version 1')
692 vcsbackend_hg.add_file(target_repo, 'README_MERGE1', 'Version 1')
693 vcsbackend_hg.add_file(source_repo, 'README_MERGE2', 'Version 2')
693 vcsbackend_hg.add_file(source_repo, 'README_MERGE2', 'Version 2')
694 imc = source_repo.in_memory_commit
694 imc = source_repo.in_memory_commit
695 imc.add(FileNode('file_x', content=source_repo.name))
695 imc.add(FileNode('file_x', content=source_repo.name))
696 imc.commit(
696 imc.commit(
697 message=u'Automatic commit from repo merge test',
697 message=u'Automatic commit from repo merge test',
698 author=u'Automatic')
698 author=u'Automatic')
699 target_commit = target_repo.get_commit()
699 target_commit = target_repo.get_commit()
700 source_commit = source_repo.get_commit()
700 source_commit = source_repo.get_commit()
701
701
702 vcsbackend_hg.add_file(source_repo, 'LICENSE', 'LICENSE Info')
702 vcsbackend_hg.add_file(source_repo, 'LICENSE', 'LICENSE Info')
703
703
704 default_branch = target_repo.DEFAULT_BRANCH_NAME
704 default_branch = target_repo.DEFAULT_BRANCH_NAME
705 bookmark_name = 'bookmark'
705 bookmark_name = 'bookmark'
706 source_repo._update(default_branch)
706 source_repo._update(default_branch)
707 source_repo.bookmark(bookmark_name)
707 source_repo.bookmark(bookmark_name)
708
708
709 target_ref = Reference('branch', default_branch, target_commit.raw_id)
709 target_ref = Reference('branch', default_branch, target_commit.raw_id)
710 source_ref = Reference('book', bookmark_name, source_commit.raw_id)
710 source_ref = Reference('book', bookmark_name, source_commit.raw_id)
711 workspace = 'test-merge'
711 workspace = 'test-merge'
712
712
713 merge_response = target_repo.merge(
713 merge_response = target_repo.merge(
714 target_ref, source_repo, source_ref, workspace,
714 target_ref, source_repo, source_ref, workspace,
715 'test user', 'test@rhodecode.com', 'merge message 1',
715 'test user', 'test@rhodecode.com', 'merge message 1',
716 dry_run=False, use_rebase=True)
716 dry_run=False, use_rebase=True)
717
717
718 expected_merge_response = MergeResponse(
718 expected_merge_response = MergeResponse(
719 True, True, merge_response.merge_ref,
719 True, True, merge_response.merge_ref,
720 MergeFailureReason.NONE)
720 MergeFailureReason.NONE)
721 assert merge_response == expected_merge_response
721 assert merge_response == expected_merge_response
722
722
723 target_repo = backends.get_backend(vcsbackend_hg.alias)(
723 target_repo = backends.get_backend(vcsbackend_hg.alias)(
724 target_repo.path)
724 target_repo.path)
725 last_commit = target_repo.get_commit()
725 last_commit = target_repo.get_commit()
726 assert last_commit.message == source_commit.message
726 assert last_commit.message == source_commit.message
727 assert last_commit.author == source_commit.author
727 assert last_commit.author == source_commit.author
728 # This checks that we effectively did a rebase
728 # This checks that we effectively did a rebase
729 assert last_commit.raw_id != source_commit.raw_id
729 assert last_commit.raw_id != source_commit.raw_id
730
730
731 # Check the target has only 4 commits: 2 were already in target and
731 # Check the target has only 4 commits: 2 were already in target and
732 # only two should have been added
732 # only two should have been added
733 assert len(target_repo.commit_ids) == 2 + 2
733 assert len(target_repo.commit_ids) == 2 + 2
734
734
735
735
736 class TestGetShadowInstance(object):
736 class TestGetShadowInstance(object):
737
737
738 @pytest.fixture
738 @pytest.fixture
739 def repo(self, vcsbackend_hg, monkeypatch):
739 def repo(self, vcsbackend_hg, monkeypatch):
740 repo = vcsbackend_hg.repo
740 repo = vcsbackend_hg.repo
741 monkeypatch.setattr(repo, 'config', mock.Mock())
741 monkeypatch.setattr(repo, 'config', mock.Mock())
742 monkeypatch.setattr('rhodecode.lib.vcs.connection.Hg', mock.Mock())
742 monkeypatch.setattr('rhodecode.lib.vcs.connection.Hg', mock.Mock())
743 return repo
743 return repo
744
744
745 def test_passes_config(self, repo):
745 def test_passes_config(self, repo):
746 shadow = repo._get_shadow_instance(repo.path)
746 shadow = repo._get_shadow_instance(repo.path)
747 assert shadow.config == repo.config.copy()
747 assert shadow.config == repo.config.copy()
748
748
749 def test_disables_hooks(self, repo):
749 def test_disables_hooks(self, repo):
750 shadow = repo._get_shadow_instance(repo.path)
750 shadow = repo._get_shadow_instance(repo.path)
751 shadow.config.clear_section.assert_called_once_with('hooks')
751 shadow.config.clear_section.assert_called_once_with('hooks')
752
752
753 def test_allows_to_keep_hooks(self, repo):
753 def test_allows_to_keep_hooks(self, repo):
754 shadow = repo._get_shadow_instance(repo.path, enable_hooks=True)
754 shadow = repo._get_shadow_instance(repo.path, enable_hooks=True)
755 assert not shadow.config.clear_section.called
755 assert not shadow.config.clear_section.called
756
756
757
757
758 class TestMercurialCommit(object):
758 class TestMercurialCommit(object):
759
759
760 def _test_equality(self, commit):
760 def _test_equality(self, commit):
761 idx = commit.idx
761 idx = commit.idx
762 assert commit == self.repo.get_commit(commit_idx=idx)
762 assert commit == self.repo.get_commit(commit_idx=idx)
763
763
764 def test_equality(self):
764 def test_equality(self):
765 indexes = [0, 10, 20]
765 indexes = [0, 10, 20]
766 commits = [self.repo.get_commit(commit_idx=idx) for idx in indexes]
766 commits = [self.repo.get_commit(commit_idx=idx) for idx in indexes]
767 for commit in commits:
767 for commit in commits:
768 self._test_equality(commit)
768 self._test_equality(commit)
769
769
770 def test_default_commit(self):
770 def test_default_commit(self):
771 tip = self.repo.get_commit('tip')
771 tip = self.repo.get_commit('tip')
772 assert tip == self.repo.get_commit()
772 assert tip == self.repo.get_commit()
773 assert tip == self.repo.get_commit(commit_id=None)
773 assert tip == self.repo.get_commit(commit_id=None)
774 assert tip == self.repo.get_commit(commit_idx=None)
774 assert tip == self.repo.get_commit(commit_idx=None)
775 assert tip == list(self.repo[-1:])[0]
775 assert tip == list(self.repo[-1:])[0]
776
776
777 def test_root_node(self):
777 def test_root_node(self):
778 tip = self.repo.get_commit('tip')
778 tip = self.repo.get_commit('tip')
779 assert tip.root is tip.get_node('')
779 assert tip.root is tip.get_node('')
780
780
781 def test_lazy_fetch(self):
781 def test_lazy_fetch(self):
782 """
782 """
783 Test if commit's nodes expands and are cached as we walk through
783 Test if commit's nodes expands and are cached as we walk through
784 the commit. This test is somewhat hard to write as order of tests
784 the commit. This test is somewhat hard to write as order of tests
785 is a key here. Written by running command after command in a shell.
785 is a key here. Written by running command after command in a shell.
786 """
786 """
787 commit = self.repo.get_commit(commit_idx=45)
787 commit = self.repo.get_commit(commit_idx=45)
788 assert len(commit.nodes) == 0
788 assert len(commit.nodes) == 0
789 root = commit.root
789 root = commit.root
790 assert len(commit.nodes) == 1
790 assert len(commit.nodes) == 1
791 assert len(root.nodes) == 8
791 assert len(root.nodes) == 8
792 # accessing root.nodes updates commit.nodes
792 # accessing root.nodes updates commit.nodes
793 assert len(commit.nodes) == 9
793 assert len(commit.nodes) == 9
794
794
795 docs = root.get_node('docs')
795 docs = root.get_node('docs')
796 # we haven't yet accessed anything new as docs dir was already cached
796 # we haven't yet accessed anything new as docs dir was already cached
797 assert len(commit.nodes) == 9
797 assert len(commit.nodes) == 9
798 assert len(docs.nodes) == 8
798 assert len(docs.nodes) == 8
799 # accessing docs.nodes updates commit.nodes
799 # accessing docs.nodes updates commit.nodes
800 assert len(commit.nodes) == 17
800 assert len(commit.nodes) == 17
801
801
802 assert docs is commit.get_node('docs')
802 assert docs is commit.get_node('docs')
803 assert docs is root.nodes[0]
803 assert docs is root.nodes[0]
804 assert docs is root.dirs[0]
804 assert docs is root.dirs[0]
805 assert docs is commit.get_node('docs')
805 assert docs is commit.get_node('docs')
806
806
807 def test_nodes_with_commit(self):
807 def test_nodes_with_commit(self):
808 commit = self.repo.get_commit(commit_idx=45)
808 commit = self.repo.get_commit(commit_idx=45)
809 root = commit.root
809 root = commit.root
810 docs = root.get_node('docs')
810 docs = root.get_node('docs')
811 assert docs is commit.get_node('docs')
811 assert docs is commit.get_node('docs')
812 api = docs.get_node('api')
812 api = docs.get_node('api')
813 assert api is commit.get_node('docs/api')
813 assert api is commit.get_node('docs/api')
814 index = api.get_node('index.rst')
814 index = api.get_node('index.rst')
815 assert index is commit.get_node('docs/api/index.rst')
815 assert index is commit.get_node('docs/api/index.rst')
816 assert index is commit.get_node(
816 assert index is commit.get_node(
817 'docs').get_node('api').get_node('index.rst')
817 'docs').get_node('api').get_node('index.rst')
818
818
819 def test_branch_and_tags(self):
819 def test_branch_and_tags(self):
820 commit0 = self.repo.get_commit(commit_idx=0)
820 commit0 = self.repo.get_commit(commit_idx=0)
821 assert commit0.branch == 'default'
821 assert commit0.branch == 'default'
822 assert commit0.tags == []
822 assert commit0.tags == []
823
823
824 commit10 = self.repo.get_commit(commit_idx=10)
824 commit10 = self.repo.get_commit(commit_idx=10)
825 assert commit10.branch == 'default'
825 assert commit10.branch == 'default'
826 assert commit10.tags == []
826 assert commit10.tags == []
827
827
828 commit44 = self.repo.get_commit(commit_idx=44)
828 commit44 = self.repo.get_commit(commit_idx=44)
829 assert commit44.branch == 'web'
829 assert commit44.branch == 'web'
830
830
831 tip = self.repo.get_commit('tip')
831 tip = self.repo.get_commit('tip')
832 assert 'tip' in tip.tags
832 assert 'tip' in tip.tags
833
833
834 def test_bookmarks(self):
834 def test_bookmarks(self):
835 commit0 = self.repo.get_commit(commit_idx=0)
835 commit0 = self.repo.get_commit(commit_idx=0)
836 assert commit0.bookmarks == []
836 assert commit0.bookmarks == []
837
837
838 def _test_file_size(self, idx, path, size):
838 def _test_file_size(self, idx, path, size):
839 node = self.repo.get_commit(commit_idx=idx).get_node(path)
839 node = self.repo.get_commit(commit_idx=idx).get_node(path)
840 assert node.is_file()
840 assert node.is_file()
841 assert node.size == size
841 assert node.size == size
842
842
843 def test_file_size(self):
843 def test_file_size(self):
844 to_check = (
844 to_check = (
845 (10, 'setup.py', 1068),
845 (10, 'setup.py', 1068),
846 (20, 'setup.py', 1106),
846 (20, 'setup.py', 1106),
847 (60, 'setup.py', 1074),
847 (60, 'setup.py', 1074),
848
848
849 (10, 'vcs/backends/base.py', 2921),
849 (10, 'vcs/backends/base.py', 2921),
850 (20, 'vcs/backends/base.py', 3936),
850 (20, 'vcs/backends/base.py', 3936),
851 (60, 'vcs/backends/base.py', 6189),
851 (60, 'vcs/backends/base.py', 6189),
852 )
852 )
853 for idx, path, size in to_check:
853 for idx, path, size in to_check:
854 self._test_file_size(idx, path, size)
854 self._test_file_size(idx, path, size)
855
855
856 def test_file_history_from_commits(self):
856 def test_file_history_from_commits(self):
857 node = self.repo[10].get_node('setup.py')
857 node = self.repo[10].get_node('setup.py')
858 commit_ids = [commit.raw_id for commit in node.history]
858 commit_ids = [commit.raw_id for commit in node.history]
859 assert ['3803844fdbd3b711175fc3da9bdacfcd6d29a6fb'] == commit_ids
859 assert ['3803844fdbd3b711175fc3da9bdacfcd6d29a6fb'] == commit_ids
860
860
861 node = self.repo[20].get_node('setup.py')
861 node = self.repo[20].get_node('setup.py')
862 node_ids = [commit.raw_id for commit in node.history]
862 node_ids = [commit.raw_id for commit in node.history]
863 assert ['eada5a770da98ab0dd7325e29d00e0714f228d09',
863 assert ['eada5a770da98ab0dd7325e29d00e0714f228d09',
864 '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb'] == node_ids
864 '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb'] == node_ids
865
865
866 # special case we check history from commit that has this particular
866 # special case we check history from commit that has this particular
867 # file changed this means we check if it's included as well
867 # file changed this means we check if it's included as well
868 node = self.repo.get_commit('eada5a770da98ab0dd7325e29d00e0714f228d09')\
868 node = self.repo.get_commit('eada5a770da98ab0dd7325e29d00e0714f228d09')\
869 .get_node('setup.py')
869 .get_node('setup.py')
870 node_ids = [commit.raw_id for commit in node.history]
870 node_ids = [commit.raw_id for commit in node.history]
871 assert ['eada5a770da98ab0dd7325e29d00e0714f228d09',
871 assert ['eada5a770da98ab0dd7325e29d00e0714f228d09',
872 '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb'] == node_ids
872 '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb'] == node_ids
873
873
874 def test_file_history(self):
874 def test_file_history(self):
875 # we can only check if those commits are present in the history
875 # we can only check if those commits are present in the history
876 # as we cannot update this test every time file is changed
876 # as we cannot update this test every time file is changed
877 files = {
877 files = {
878 'setup.py': [7, 18, 45, 46, 47, 69, 77],
878 'setup.py': [7, 18, 45, 46, 47, 69, 77],
879 'vcs/nodes.py': [
879 'vcs/nodes.py': [
880 7, 8, 24, 26, 30, 45, 47, 49, 56, 57, 58, 59, 60, 61, 73, 76],
880 7, 8, 24, 26, 30, 45, 47, 49, 56, 57, 58, 59, 60, 61, 73, 76],
881 'vcs/backends/hg.py': [
881 'vcs/backends/hg.py': [
882 4, 5, 6, 11, 12, 13, 14, 15, 16, 21, 22, 23, 26, 27, 28, 30,
882 4, 5, 6, 11, 12, 13, 14, 15, 16, 21, 22, 23, 26, 27, 28, 30,
883 31, 33, 35, 36, 37, 38, 39, 40, 41, 44, 45, 47, 48, 49, 53, 54,
883 31, 33, 35, 36, 37, 38, 39, 40, 41, 44, 45, 47, 48, 49, 53, 54,
884 55, 58, 60, 61, 67, 68, 69, 70, 73, 77, 78, 79, 82],
884 55, 58, 60, 61, 67, 68, 69, 70, 73, 77, 78, 79, 82],
885 }
885 }
886 for path, indexes in files.items():
886 for path, indexes in files.items():
887 tip = self.repo.get_commit(commit_idx=indexes[-1])
887 tip = self.repo.get_commit(commit_idx=indexes[-1])
888 node = tip.get_node(path)
888 node = tip.get_node(path)
889 node_indexes = [commit.idx for commit in node.history]
889 node_indexes = [commit.idx for commit in node.history]
890 assert set(indexes).issubset(set(node_indexes)), (
890 assert set(indexes).issubset(set(node_indexes)), (
891 "We assumed that %s is subset of commits for which file %s "
891 "We assumed that %s is subset of commits for which file %s "
892 "has been changed, and history of that node returned: %s"
892 "has been changed, and history of that node returned: %s"
893 % (indexes, path, node_indexes))
893 % (indexes, path, node_indexes))
894
894
895 def test_file_annotate(self):
895 def test_file_annotate(self):
896 files = {
896 files = {
897 'vcs/backends/__init__.py': {
897 'vcs/backends/__init__.py': {
898 89: {
898 89: {
899 'lines_no': 31,
899 'lines_no': 31,
900 'commits': [
900 'commits': [
901 32, 32, 61, 32, 32, 37, 32, 32, 32, 44,
901 32, 32, 61, 32, 32, 37, 32, 32, 32, 44,
902 37, 37, 37, 37, 45, 37, 44, 37, 37, 37,
902 37, 37, 37, 37, 45, 37, 44, 37, 37, 37,
903 32, 32, 32, 32, 37, 32, 37, 37, 32,
903 32, 32, 32, 32, 37, 32, 37, 37, 32,
904 32, 32
904 32, 32
905 ]
905 ]
906 },
906 },
907 20: {
907 20: {
908 'lines_no': 1,
908 'lines_no': 1,
909 'commits': [4]
909 'commits': [4]
910 },
910 },
911 55: {
911 55: {
912 'lines_no': 31,
912 'lines_no': 31,
913 'commits': [
913 'commits': [
914 32, 32, 45, 32, 32, 37, 32, 32, 32, 44,
914 32, 32, 45, 32, 32, 37, 32, 32, 32, 44,
915 37, 37, 37, 37, 45, 37, 44, 37, 37, 37,
915 37, 37, 37, 37, 45, 37, 44, 37, 37, 37,
916 32, 32, 32, 32, 37, 32, 37, 37, 32,
916 32, 32, 32, 32, 37, 32, 37, 37, 32,
917 32, 32
917 32, 32
918 ]
918 ]
919 }
919 }
920 },
920 },
921 'vcs/exceptions.py': {
921 'vcs/exceptions.py': {
922 89: {
922 89: {
923 'lines_no': 18,
923 'lines_no': 18,
924 'commits': [
924 'commits': [
925 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
925 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
926 16, 16, 17, 16, 16, 18, 18, 18
926 16, 16, 17, 16, 16, 18, 18, 18
927 ]
927 ]
928 },
928 },
929 20: {
929 20: {
930 'lines_no': 18,
930 'lines_no': 18,
931 'commits': [
931 'commits': [
932 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
932 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
933 16, 16, 17, 16, 16, 18, 18, 18
933 16, 16, 17, 16, 16, 18, 18, 18
934 ]
934 ]
935 },
935 },
936 55: {
936 55: {
937 'lines_no': 18,
937 'lines_no': 18,
938 'commits': [
938 'commits': [
939 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
939 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
940 17, 16, 16, 18, 18, 18
940 17, 16, 16, 18, 18, 18
941 ]
941 ]
942 }
942 }
943 },
943 },
944 'MANIFEST.in': {
944 'MANIFEST.in': {
945 89: {
945 89: {
946 'lines_no': 5,
946 'lines_no': 5,
947 'commits': [7, 7, 7, 71, 71]
947 'commits': [7, 7, 7, 71, 71]
948 },
948 },
949 20: {
949 20: {
950 'lines_no': 3,
950 'lines_no': 3,
951 'commits': [7, 7, 7]
951 'commits': [7, 7, 7]
952 },
952 },
953 55: {
953 55: {
954 'lines_no': 3,
954 'lines_no': 3,
955 'commits': [7, 7, 7]
955 'commits': [7, 7, 7]
956 }
956 }
957 }
957 }
958 }
958 }
959
959
960 for fname, commit_dict in files.items():
960 for fname, commit_dict in files.items():
961 for idx, __ in commit_dict.items():
961 for idx, __ in commit_dict.items():
962 commit = self.repo.get_commit(commit_idx=idx)
962 commit = self.repo.get_commit(commit_idx=idx)
963 l1_1 = [x[1] for x in commit.get_file_annotate(fname)]
963 l1_1 = [x[1] for x in commit.get_file_annotate(fname)]
964 l1_2 = [x[2]().raw_id for x in commit.get_file_annotate(fname)]
964 l1_2 = [x[2]().raw_id for x in commit.get_file_annotate(fname)]
965 assert l1_1 == l1_2
965 assert l1_1 == l1_2
966 l1 = l1_2 = [
966 l1 = l1_2 = [
967 x[2]().idx for x in commit.get_file_annotate(fname)]
967 x[2]().idx for x in commit.get_file_annotate(fname)]
968 l2 = files[fname][idx]['commits']
968 l2 = files[fname][idx]['commits']
969 assert l1 == l2, (
969 assert l1 == l2, (
970 "The lists of commit for %s@commit_id%s"
970 "The lists of commit for %s@commit_id%s"
971 "from annotation list should match each other,"
971 "from annotation list should match each other,"
972 "got \n%s \nvs \n%s " % (fname, idx, l1, l2))
972 "got \n%s \nvs \n%s " % (fname, idx, l1, l2))
973
973
974 def test_commit_state(self):
974 def test_commit_state(self):
975 """
975 """
976 Tests which files have been added/changed/removed at particular commit
976 Tests which files have been added/changed/removed at particular commit
977 """
977 """
978
978
979 # commit_id 46ad32a4f974:
979 # commit_id 46ad32a4f974:
980 # hg st --rev 46ad32a4f974
980 # hg st --rev 46ad32a4f974
981 # changed: 13
981 # changed: 13
982 # added: 20
982 # added: 20
983 # removed: 1
983 # removed: 1
984 changed = set([
984 changed = set([
985 '.hgignore', 'README.rst', 'docs/conf.py', 'docs/index.rst',
985 '.hgignore', 'README.rst', 'docs/conf.py', 'docs/index.rst',
986 'setup.py', 'tests/test_hg.py', 'tests/test_nodes.py',
986 'setup.py', 'tests/test_hg.py', 'tests/test_nodes.py',
987 'vcs/__init__.py', 'vcs/backends/__init__.py',
987 'vcs/__init__.py', 'vcs/backends/__init__.py',
988 'vcs/backends/base.py', 'vcs/backends/hg.py', 'vcs/nodes.py',
988 'vcs/backends/base.py', 'vcs/backends/hg.py', 'vcs/nodes.py',
989 'vcs/utils/__init__.py'])
989 'vcs/utils/__init__.py'])
990
990
991 added = set([
991 added = set([
992 'docs/api/backends/hg.rst', 'docs/api/backends/index.rst',
992 'docs/api/backends/hg.rst', 'docs/api/backends/index.rst',
993 'docs/api/index.rst', 'docs/api/nodes.rst',
993 'docs/api/index.rst', 'docs/api/nodes.rst',
994 'docs/api/web/index.rst', 'docs/api/web/simplevcs.rst',
994 'docs/api/web/index.rst', 'docs/api/web/simplevcs.rst',
995 'docs/installation.rst', 'docs/quickstart.rst', 'setup.cfg',
995 'docs/installation.rst', 'docs/quickstart.rst', 'setup.cfg',
996 'vcs/utils/baseui_config.py', 'vcs/utils/web.py',
996 'vcs/utils/baseui_config.py', 'vcs/utils/web.py',
997 'vcs/web/__init__.py', 'vcs/web/exceptions.py',
997 'vcs/web/__init__.py', 'vcs/web/exceptions.py',
998 'vcs/web/simplevcs/__init__.py', 'vcs/web/simplevcs/exceptions.py',
998 'vcs/web/simplevcs/__init__.py', 'vcs/web/simplevcs/exceptions.py',
999 'vcs/web/simplevcs/middleware.py', 'vcs/web/simplevcs/models.py',
999 'vcs/web/simplevcs/middleware.py', 'vcs/web/simplevcs/models.py',
1000 'vcs/web/simplevcs/settings.py', 'vcs/web/simplevcs/utils.py',
1000 'vcs/web/simplevcs/settings.py', 'vcs/web/simplevcs/utils.py',
1001 'vcs/web/simplevcs/views.py'])
1001 'vcs/web/simplevcs/views.py'])
1002
1002
1003 removed = set(['docs/api.rst'])
1003 removed = set(['docs/api.rst'])
1004
1004
1005 commit64 = self.repo.get_commit('46ad32a4f974')
1005 commit64 = self.repo.get_commit('46ad32a4f974')
1006 assert set((node.path for node in commit64.added)) == added
1006 assert set((node.path for node in commit64.added)) == added
1007 assert set((node.path for node in commit64.changed)) == changed
1007 assert set((node.path for node in commit64.changed)) == changed
1008 assert set((node.path for node in commit64.removed)) == removed
1008 assert set((node.path for node in commit64.removed)) == removed
1009
1009
1010 # commit_id b090f22d27d6:
1010 # commit_id b090f22d27d6:
1011 # hg st --rev b090f22d27d6
1011 # hg st --rev b090f22d27d6
1012 # changed: 13
1012 # changed: 13
1013 # added: 20
1013 # added: 20
1014 # removed: 1
1014 # removed: 1
1015 commit88 = self.repo.get_commit('b090f22d27d6')
1015 commit88 = self.repo.get_commit('b090f22d27d6')
1016 assert set((node.path for node in commit88.added)) == set()
1016 assert set((node.path for node in commit88.added)) == set()
1017 assert set((node.path for node in commit88.changed)) == \
1017 assert set((node.path for node in commit88.changed)) == \
1018 set(['.hgignore'])
1018 set(['.hgignore'])
1019 assert set((node.path for node in commit88.removed)) == set()
1019 assert set((node.path for node in commit88.removed)) == set()
1020
1020
1021 #
1021 #
1022 # 85:
1022 # 85:
1023 # added: 2 [
1023 # added: 2 [
1024 # 'vcs/utils/diffs.py', 'vcs/web/simplevcs/views/diffs.py']
1024 # 'vcs/utils/diffs.py', 'vcs/web/simplevcs/views/diffs.py']
1025 # changed: 4 ['vcs/web/simplevcs/models.py', ...]
1025 # changed: 4 ['vcs/web/simplevcs/models.py', ...]
1026 # removed: 1 ['vcs/utils/web.py']
1026 # removed: 1 ['vcs/utils/web.py']
1027 commit85 = self.repo.get_commit(commit_idx=85)
1027 commit85 = self.repo.get_commit(commit_idx=85)
1028 assert set((node.path for node in commit85.added)) == set([
1028 assert set((node.path for node in commit85.added)) == set([
1029 'vcs/utils/diffs.py',
1029 'vcs/utils/diffs.py',
1030 'vcs/web/simplevcs/views/diffs.py'])
1030 'vcs/web/simplevcs/views/diffs.py'])
1031 assert set((node.path for node in commit85.changed)) == set([
1031 assert set((node.path for node in commit85.changed)) == set([
1032 'vcs/web/simplevcs/models.py',
1032 'vcs/web/simplevcs/models.py',
1033 'vcs/web/simplevcs/utils.py',
1033 'vcs/web/simplevcs/utils.py',
1034 'vcs/web/simplevcs/views/__init__.py',
1034 'vcs/web/simplevcs/views/__init__.py',
1035 'vcs/web/simplevcs/views/repository.py',
1035 'vcs/web/simplevcs/views/repository.py',
1036 ])
1036 ])
1037 assert set((node.path for node in commit85.removed)) == \
1037 assert set((node.path for node in commit85.removed)) == \
1038 set(['vcs/utils/web.py'])
1038 set(['vcs/utils/web.py'])
1039
1039
1040 def test_files_state(self):
1040 def test_files_state(self):
1041 """
1041 """
1042 Tests state of FileNodes.
1042 Tests state of FileNodes.
1043 """
1043 """
1044 commit = self.repo.get_commit(commit_idx=85)
1044 commit = self.repo.get_commit(commit_idx=85)
1045 node = commit.get_node('vcs/utils/diffs.py')
1045 node = commit.get_node('vcs/utils/diffs.py')
1046 assert node.state, NodeState.ADDED
1046 assert node.state, NodeState.ADDED
1047 assert node.added
1047 assert node.added
1048 assert not node.changed
1048 assert not node.changed
1049 assert not node.not_changed
1049 assert not node.not_changed
1050 assert not node.removed
1050 assert not node.removed
1051
1051
1052 commit = self.repo.get_commit(commit_idx=88)
1052 commit = self.repo.get_commit(commit_idx=88)
1053 node = commit.get_node('.hgignore')
1053 node = commit.get_node('.hgignore')
1054 assert node.state, NodeState.CHANGED
1054 assert node.state, NodeState.CHANGED
1055 assert not node.added
1055 assert not node.added
1056 assert node.changed
1056 assert node.changed
1057 assert not node.not_changed
1057 assert not node.not_changed
1058 assert not node.removed
1058 assert not node.removed
1059
1059
1060 commit = self.repo.get_commit(commit_idx=85)
1060 commit = self.repo.get_commit(commit_idx=85)
1061 node = commit.get_node('setup.py')
1061 node = commit.get_node('setup.py')
1062 assert node.state, NodeState.NOT_CHANGED
1062 assert node.state, NodeState.NOT_CHANGED
1063 assert not node.added
1063 assert not node.added
1064 assert not node.changed
1064 assert not node.changed
1065 assert node.not_changed
1065 assert node.not_changed
1066 assert not node.removed
1066 assert not node.removed
1067
1067
1068 # If node has REMOVED state then trying to fetch it would raise
1068 # If node has REMOVED state then trying to fetch it would raise
1069 # CommitError exception
1069 # CommitError exception
1070 commit = self.repo.get_commit(commit_idx=2)
1070 commit = self.repo.get_commit(commit_idx=2)
1071 path = 'vcs/backends/BaseRepository.py'
1071 path = 'vcs/backends/BaseRepository.py'
1072 with pytest.raises(NodeDoesNotExistError):
1072 with pytest.raises(NodeDoesNotExistError):
1073 commit.get_node(path)
1073 commit.get_node(path)
1074 # but it would be one of ``removed`` (commit's attribute)
1074 # but it would be one of ``removed`` (commit's attribute)
1075 assert path in [rf.path for rf in commit.removed]
1075 assert path in [rf.path for rf in commit.removed]
1076
1076
1077 def test_commit_message_is_unicode(self):
1077 def test_commit_message_is_unicode(self):
1078 for cm in self.repo:
1078 for cm in self.repo:
1079 assert type(cm.message) == unicode
1079 assert type(cm.message) == unicode
1080
1080
1081 def test_commit_author_is_unicode(self):
1081 def test_commit_author_is_unicode(self):
1082 for cm in self.repo:
1082 for cm in self.repo:
1083 assert type(cm.author) == unicode
1083 assert type(cm.author) == unicode
1084
1084
1085 def test_repo_files_content_is_unicode(self):
1085 def test_repo_files_content_is_unicode(self):
1086 test_commit = self.repo.get_commit(commit_idx=100)
1086 test_commit = self.repo.get_commit(commit_idx=100)
1087 for node in test_commit.get_node('/'):
1087 for node in test_commit.get_node('/'):
1088 if node.is_file():
1088 if node.is_file():
1089 assert type(node.content) == unicode
1089 assert type(node.content) == unicode
1090
1090
1091 def test_wrong_path(self):
1091 def test_wrong_path(self):
1092 # There is 'setup.py' in the root dir but not there:
1092 # There is 'setup.py' in the root dir but not there:
1093 path = 'foo/bar/setup.py'
1093 path = 'foo/bar/setup.py'
1094 with pytest.raises(VCSError):
1094 with pytest.raises(VCSError):
1095 self.repo.get_commit().get_node(path)
1095 self.repo.get_commit().get_node(path)
1096
1096
1097 def test_author_email(self):
1097 def test_author_email(self):
1098 assert 'marcin@python-blog.com' == \
1098 assert 'marcin@python-blog.com' == \
1099 self.repo.get_commit('b986218ba1c9').author_email
1099 self.repo.get_commit('b986218ba1c9').author_email
1100 assert 'lukasz.balcerzak@python-center.pl' == \
1100 assert 'lukasz.balcerzak@python-center.pl' == \
1101 self.repo.get_commit('3803844fdbd3').author_email
1101 self.repo.get_commit('3803844fdbd3').author_email
1102 assert '' == self.repo.get_commit('84478366594b').author_email
1102 assert '' == self.repo.get_commit('84478366594b').author_email
1103
1103
1104 def test_author_username(self):
1104 def test_author_username(self):
1105 assert 'Marcin Kuzminski' == \
1105 assert 'Marcin Kuzminski' == \
1106 self.repo.get_commit('b986218ba1c9').author_name
1106 self.repo.get_commit('b986218ba1c9').author_name
1107 assert 'Lukasz Balcerzak' == \
1107 assert 'Lukasz Balcerzak' == \
1108 self.repo.get_commit('3803844fdbd3').author_name
1108 self.repo.get_commit('3803844fdbd3').author_name
1109 assert 'marcink' == \
1109 assert 'marcink' == \
1110 self.repo.get_commit('84478366594b').author_name
1110 self.repo.get_commit('84478366594b').author_name
1111
1111
1112
1112
1113 class TestLargeFileRepo(object):
1113 class TestLargeFileRepo(object):
1114
1114
1115 def test_large_file(self, backend_hg):
1115 def test_large_file(self, backend_hg):
1116 repo = backend_hg.create_test_repo('largefiles', make_db_config())
1116 repo = backend_hg.create_test_repo('largefiles', make_db_config())
1117
1117
1118 tip = repo.scm_instance().get_commit()
1118 tip = repo.scm_instance().get_commit()
1119 node = tip.get_node('.hglf/thisfileislarge')
1119 node = tip.get_node('.hglf/thisfileislarge')
1120
1120
1121 lf_node = node.get_largefile_node()
1121 lf_node = node.get_largefile_node()
1122
1122
1123 assert lf_node.is_largefile() is True
1123 assert lf_node.is_largefile() is True
1124 assert lf_node.size == 1024000
1124 assert lf_node.size == 1024000
1125 assert lf_node.name == '.hglf/thisfileislarge'
1125 assert lf_node.name == '.hglf/thisfileislarge'
1126
1126
1127
1127
1128 class TestGetBranchName(object):
1128 class TestGetBranchName(object):
1129 def test_returns_ref_name_when_type_is_branch(self):
1129 def test_returns_ref_name_when_type_is_branch(self):
1130 ref = self._create_ref('branch', 'fake-name')
1130 ref = self._create_ref('branch', 'fake-name')
1131 result = self.repo._get_branch_name(ref)
1131 result = self.repo._get_branch_name(ref)
1132 assert result == ref.name
1132 assert result == ref.name
1133
1133
1134 @pytest.mark.parametrize("type_", ("book", "tag"))
1134 @pytest.mark.parametrize("type_", ("book", "tag"))
1135 def test_queries_remote_when_type_is_not_branch(self, type_):
1135 def test_queries_remote_when_type_is_not_branch(self, type_):
1136 ref = self._create_ref(type_, 'wrong-fake-name')
1136 ref = self._create_ref(type_, 'wrong-fake-name')
1137 with mock.patch.object(self.repo, "_remote") as remote_mock:
1137 with mock.patch.object(self.repo, "_remote") as remote_mock:
1138 remote_mock.ctx_branch.return_value = "fake-name"
1138 remote_mock.ctx_branch.return_value = "fake-name"
1139 result = self.repo._get_branch_name(ref)
1139 result = self.repo._get_branch_name(ref)
1140 assert result == "fake-name"
1140 assert result == "fake-name"
1141 remote_mock.ctx_branch.assert_called_once_with(ref.commit_id)
1141 remote_mock.ctx_branch.assert_called_once_with(ref.commit_id)
1142
1142
1143 def _create_ref(self, type_, name):
1143 def _create_ref(self, type_, name):
1144 ref = mock.Mock()
1144 ref = mock.Mock()
1145 ref.type = type_
1145 ref.type = type_
1146 ref.name = 'wrong-fake-name'
1146 ref.name = 'wrong-fake-name'
1147 ref.commit_id = "deadbeef"
1147 ref.commit_id = "deadbeef"
1148 return ref
1148 return ref
1149
1149
1150
1150
1151 class TestIsTheSameBranch(object):
1151 class TestIsTheSameBranch(object):
1152 def test_returns_true_when_branches_are_equal(self):
1152 def test_returns_true_when_branches_are_equal(self):
1153 source_ref = mock.Mock(name="source-ref")
1153 source_ref = mock.Mock(name="source-ref")
1154 target_ref = mock.Mock(name="target-ref")
1154 target_ref = mock.Mock(name="target-ref")
1155 branch_name_patcher = mock.patch.object(
1155 branch_name_patcher = mock.patch.object(
1156 self.repo, "_get_branch_name", return_value="default")
1156 self.repo, "_get_branch_name", return_value="default")
1157 with branch_name_patcher as branch_name_mock:
1157 with branch_name_patcher as branch_name_mock:
1158 result = self.repo._is_the_same_branch(source_ref, target_ref)
1158 result = self.repo._is_the_same_branch(source_ref, target_ref)
1159
1159
1160 expected_calls = [mock.call(source_ref), mock.call(target_ref)]
1160 expected_calls = [mock.call(source_ref), mock.call(target_ref)]
1161 assert branch_name_mock.call_args_list == expected_calls
1161 assert branch_name_mock.call_args_list == expected_calls
1162 assert result is True
1162 assert result is True
1163
1163
1164 def test_returns_false_when_branches_are_not_equal(self):
1164 def test_returns_false_when_branches_are_not_equal(self):
1165 source_ref = mock.Mock(name="source-ref")
1165 source_ref = mock.Mock(name="source-ref")
1166 source_ref.name = "source-branch"
1166 source_ref.name = "source-branch"
1167 target_ref = mock.Mock(name="target-ref")
1167 target_ref = mock.Mock(name="target-ref")
1168 source_ref.name = "target-branch"
1168 source_ref.name = "target-branch"
1169
1169
1170 def side_effect(ref):
1170 def side_effect(ref):
1171 return ref.name
1171 return ref.name
1172
1172
1173 branch_name_patcher = mock.patch.object(
1173 branch_name_patcher = mock.patch.object(
1174 self.repo, "_get_branch_name", side_effect=side_effect)
1174 self.repo, "_get_branch_name", side_effect=side_effect)
1175 with branch_name_patcher as branch_name_mock:
1175 with branch_name_patcher as branch_name_mock:
1176 result = self.repo._is_the_same_branch(source_ref, target_ref)
1176 result = self.repo._is_the_same_branch(source_ref, target_ref)
1177
1177
1178 expected_calls = [mock.call(source_ref), mock.call(target_ref)]
1178 expected_calls = [mock.call(source_ref), mock.call(target_ref)]
1179 assert branch_name_mock.call_args_list == expected_calls
1179 assert branch_name_mock.call_args_list == expected_calls
1180 assert result is False
1180 assert result is False
General Comments 0
You need to be logged in to leave comments. Login now