Show More
@@ -0,0 +1,46 b'' | |||||
|
1 | # Copyright (C) 2014-2024 RhodeCode GmbH | |||
|
2 | # | |||
|
3 | # This program is free software: you can redistribute it and/or modify | |||
|
4 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
5 | # (only), as published by the Free Software Foundation. | |||
|
6 | # | |||
|
7 | # This program is distributed in the hope that it will be useful, | |||
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
10 | # GNU General Public License for more details. | |||
|
11 | # | |||
|
12 | # You should have received a copy of the GNU Affero General Public License | |||
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
14 | # | |||
|
15 | # This program is dual-licensed. If you wish to learn more about the | |||
|
16 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
18 | ||||
|
19 | ||||
|
20 | """ | |||
|
21 | Common VCS module for rhodecode and vcsserver | |||
|
22 | """ | |||
|
23 | ||||
|
24 | ||||
|
25 | import enum | |||
|
26 | ||||
|
27 | FILEMODE_DEFAULT = 0o100644 | |||
|
28 | FILEMODE_EXECUTABLE = 0o100755 | |||
|
29 | FILEMODE_LINK = 0o120000 | |||
|
30 | ||||
|
31 | ||||
|
32 | class NodeKind(int, enum.Enum): | |||
|
33 | SUBMODULE = -1 | |||
|
34 | DIR = 1 | |||
|
35 | FILE = 2 | |||
|
36 | LARGE_FILE = 3 | |||
|
37 | ||||
|
38 | ||||
|
39 | def map_git_obj_type(obj_type): | |||
|
40 | if obj_type == "blob": | |||
|
41 | return NodeKind.FILE | |||
|
42 | elif obj_type == "tree": | |||
|
43 | return NodeKind.DIR | |||
|
44 | elif obj_type == "link": | |||
|
45 | return NodeKind.SUBMODULE | |||
|
46 | return None |
@@ -0,0 +1,227 b'' | |||||
|
1 | # Copyright (C) 2010-2024 RhodeCode GmbH | |||
|
2 | # | |||
|
3 | # This program is free software: you can redistribute it and/or modify | |||
|
4 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
5 | # (only), as published by the Free Software Foundation. | |||
|
6 | # | |||
|
7 | # This program is distributed in the hope that it will be useful, | |||
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
10 | # GNU General Public License for more details. | |||
|
11 | # | |||
|
12 | # You should have received a copy of the GNU Affero General Public License | |||
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
14 | # | |||
|
15 | # This program is dual-licensed. If you wish to learn more about the | |||
|
16 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
18 | ||||
|
19 | import datetime | |||
|
20 | ||||
|
21 | import pytest | |||
|
22 | ||||
|
23 | from rhodecode.lib.str_utils import safe_bytes | |||
|
24 | from rhodecode.lib.vcs.nodes import FileNode | |||
|
25 | from rhodecode.lib.vcs_common import NodeKind | |||
|
26 | from rhodecode.tests.vcs.conftest import BackendTestMixin | |||
|
27 | ||||
|
28 | ||||
|
29 | class TestFileNodesListingAndCaches: | |||
|
30 | def test_filenode_get_root_node(self, vcsbackend): | |||
|
31 | repo = vcsbackend.repo | |||
|
32 | commit = repo.get_commit() | |||
|
33 | ||||
|
34 | # check if we start with empty nodes cache | |||
|
35 | assert commit.nodes == {} | |||
|
36 | assert commit._path_mode_cache == {} | |||
|
37 | assert commit._path_type_cache == {} | |||
|
38 | ||||
|
39 | top_dir = commit.get_node(b"") | |||
|
40 | ||||
|
41 | assert top_dir.is_dir() | |||
|
42 | ||||
|
43 | assert list(commit.nodes.keys()) == [b""] | |||
|
44 | assert list(commit._path_type_cache.keys()) == [b""] | |||
|
45 | assert commit._path_mode_cache == {} | |||
|
46 | ||||
|
47 | def test_filenode_get_file_node(self, vcsbackend): | |||
|
48 | repo = vcsbackend.repo | |||
|
49 | commit = repo.get_commit() | |||
|
50 | ||||
|
51 | # check if we start with empty nodes cache | |||
|
52 | assert commit.nodes == {} | |||
|
53 | assert commit._path_mode_cache == {} | |||
|
54 | assert commit._path_type_cache == {} | |||
|
55 | ||||
|
56 | file_node = commit.get_node(b"README.rst") | |||
|
57 | ||||
|
58 | assert file_node.is_file() | |||
|
59 | assert file_node.last_commit | |||
|
60 | ||||
|
61 | assert list(commit.nodes.keys()) == [b"README.rst"] | |||
|
62 | if repo.alias == "hg": | |||
|
63 | assert commit._path_type_cache == {b"README.rst": NodeKind.FILE} | |||
|
64 | assert commit._path_mode_cache == {b"README.rst": 33188} | |||
|
65 | ||||
|
66 | if repo.alias == "git": | |||
|
67 | assert commit._path_type_cache == { | |||
|
68 | b"README.rst": ["8f111ab5152b9fd34f52b0fb288e1216b5d2f55e", NodeKind.FILE] | |||
|
69 | } | |||
|
70 | assert commit._path_mode_cache == {b"README.rst": 33188} | |||
|
71 | ||||
|
72 | if repo.alias == "svn": | |||
|
73 | assert commit._path_type_cache == {b"README.rst": NodeKind.FILE} | |||
|
74 | assert commit._path_mode_cache == {b"README.rst": 33188} | |||
|
75 | ||||
|
76 | def test_filenode_get_nodes_from_top_dir(self, vcsbackend): | |||
|
77 | repo = vcsbackend.repo | |||
|
78 | commit = repo.get_commit() | |||
|
79 | ||||
|
80 | # check if we start with empty nodes cache | |||
|
81 | assert commit.nodes == {} | |||
|
82 | assert commit._path_mode_cache == {} | |||
|
83 | assert commit._path_type_cache == {} | |||
|
84 | ||||
|
85 | node_list = commit.get_nodes(b"") | |||
|
86 | ||||
|
87 | for node in node_list: | |||
|
88 | assert node | |||
|
89 | ||||
|
90 | if repo.alias == "svn": | |||
|
91 | assert list(commit.nodes.keys()) == [ | |||
|
92 | b"README", | |||
|
93 | b".hgignore", | |||
|
94 | b"MANIFEST.in", | |||
|
95 | b".travis.yml", | |||
|
96 | b"vcs", | |||
|
97 | b"tox.ini", | |||
|
98 | b"setup.py", | |||
|
99 | b"setup.cfg", | |||
|
100 | b"docs", | |||
|
101 | b"fakefile", | |||
|
102 | b"examples", | |||
|
103 | b"test_and_report.sh", | |||
|
104 | b"bin", | |||
|
105 | b".gitignore", | |||
|
106 | b".hgtags", | |||
|
107 | b"README.rst", | |||
|
108 | ] | |||
|
109 | assert commit._path_type_cache == { | |||
|
110 | b"": NodeKind.DIR, | |||
|
111 | b"README": 2, | |||
|
112 | b".hgignore": 2, | |||
|
113 | b"MANIFEST.in": 2, | |||
|
114 | b".travis.yml": 2, | |||
|
115 | b"vcs": 1, | |||
|
116 | b"tox.ini": 2, | |||
|
117 | b"setup.py": 2, | |||
|
118 | b"setup.cfg": 2, | |||
|
119 | b"docs": 1, | |||
|
120 | b"fakefile": 2, | |||
|
121 | b"examples": 1, | |||
|
122 | b"test_and_report.sh": 2, | |||
|
123 | b"bin": 1, | |||
|
124 | b".gitignore": 2, | |||
|
125 | b".hgtags": 2, | |||
|
126 | b"README.rst": 2, | |||
|
127 | } | |||
|
128 | assert commit._path_mode_cache == {} | |||
|
129 | ||||
|
130 | if repo.alias == "hg": | |||
|
131 | assert list(commit.nodes.keys()) == [ | |||
|
132 | b".gitignore", | |||
|
133 | b".hgignore", | |||
|
134 | b".hgtags", | |||
|
135 | b".travis.yml", | |||
|
136 | b"MANIFEST.in", | |||
|
137 | b"README", | |||
|
138 | b"README.rst", | |||
|
139 | b"docs", | |||
|
140 | b"run_test_and_report.sh", | |||
|
141 | b"setup.cfg", | |||
|
142 | b"setup.py", | |||
|
143 | b"test_and_report.sh", | |||
|
144 | b"tox.ini", | |||
|
145 | b"vcs", | |||
|
146 | ] | |||
|
147 | ||||
|
148 | assert commit._path_type_cache == { | |||
|
149 | b"": NodeKind.DIR, | |||
|
150 | b".gitignore": 2, | |||
|
151 | b".hgignore": 2, | |||
|
152 | b".hgtags": 2, | |||
|
153 | b".travis.yml": 2, | |||
|
154 | b"MANIFEST.in": 2, | |||
|
155 | b"README": 2, | |||
|
156 | b"README.rst": 2, | |||
|
157 | b"docs": 1, | |||
|
158 | b"run_test_and_report.sh": 2, | |||
|
159 | b"setup.cfg": 2, | |||
|
160 | b"setup.py": 2, | |||
|
161 | b"test_and_report.sh": 2, | |||
|
162 | b"tox.ini": 2, | |||
|
163 | b"vcs": 1, | |||
|
164 | } | |||
|
165 | assert commit._path_mode_cache == {} | |||
|
166 | ||||
|
167 | if repo.alias == "git": | |||
|
168 | assert list(commit.nodes.keys()) == [ | |||
|
169 | b".gitignore", | |||
|
170 | b".hgignore", | |||
|
171 | b".hgtags", | |||
|
172 | b"MANIFEST.in", | |||
|
173 | b"README", | |||
|
174 | b"README.rst", | |||
|
175 | b"docs", | |||
|
176 | b"run_test_and_report.sh", | |||
|
177 | b"setup.cfg", | |||
|
178 | b"setup.py", | |||
|
179 | b"test_and_report.sh", | |||
|
180 | b"tox.ini", | |||
|
181 | b"vcs", | |||
|
182 | ] | |||
|
183 | assert commit._path_type_cache == { | |||
|
184 | b"": ["2dab7f7377e36948b5633ae0877310380fdf64ba", NodeKind.DIR], | |||
|
185 | b".gitignore": ["7f95b0917e31b0cce1ad96a033b1a814c420cde3", 2], | |||
|
186 | b".hgignore": ["9bdee5ed67ceed7c4b5589ba26dada65df151aed", 2], | |||
|
187 | b".hgtags": ["63aa2e892deebdfdef1845fe52e2418ade03557b", 2], | |||
|
188 | b"MANIFEST.in": ["6d65aca5321c26589ae197b81511445cfb353c95", 2], | |||
|
189 | b"README": ["92cacd285355271487b7e379dba6ca60f9a554a4", 2], | |||
|
190 | b"README.rst": ["8f111ab5152b9fd34f52b0fb288e1216b5d2f55e", 2], | |||
|
191 | b"docs": ["eaa8cef76ec4e1baf06f515ab03bfc01719a913d", 1], | |||
|
192 | b"run_test_and_report.sh": ["e17011bdddc8812fda96e891a1087c73054f1f25", 2], | |||
|
193 | b"setup.cfg": ["2e76b65bc9106ed466d71fb2cfe710352eadfb49", 2], | |||
|
194 | b"setup.py": ["ccda2bd076eca961059deba6e39fb591d014052a", 2], | |||
|
195 | b"test_and_report.sh": ["0b14056dd7dc759a9719adef3d7bb529141a740e", 2], | |||
|
196 | b"tox.ini": ["c1735231e7af32f089b95455162db092824715a4", 2], | |||
|
197 | b"vcs": ["5868c69a8d74ccf3fdc655af8f3f185bc0ff2ef6", 1], | |||
|
198 | } | |||
|
199 | assert commit._path_mode_cache == { | |||
|
200 | b".gitignore": 33188, | |||
|
201 | b".hgignore": 33188, | |||
|
202 | b".hgtags": 33188, | |||
|
203 | b"MANIFEST.in": 33188, | |||
|
204 | b"README": 40960, | |||
|
205 | b"README.rst": 33188, | |||
|
206 | b"docs": 16384, | |||
|
207 | b"run_test_and_report.sh": 33261, | |||
|
208 | b"setup.cfg": 33188, | |||
|
209 | b"setup.py": 33188, | |||
|
210 | b"test_and_report.sh": 33261, | |||
|
211 | b"tox.ini": 33188, | |||
|
212 | b"vcs": 16384, | |||
|
213 | } | |||
|
214 | ||||
|
215 | def test_filenode_get_nodes_from_docs_dir(self, vcsbackend): | |||
|
216 | repo = vcsbackend.repo | |||
|
217 | commit = repo.get_commit() | |||
|
218 | ||||
|
219 | # check if we start with empty nodes cache | |||
|
220 | assert commit.nodes == {} | |||
|
221 | assert commit._path_mode_cache == {} | |||
|
222 | assert commit._path_type_cache == {} | |||
|
223 | ||||
|
224 | node_list = commit.get_nodes(b"docs") | |||
|
225 | ||||
|
226 | for node in node_list: | |||
|
227 | assert node |
@@ -45,13 +45,20 b' CELERY_EAGER = False' | |||||
45 | # link to config for pyramid |
|
45 | # link to config for pyramid | |
46 | CONFIG = {} |
|
46 | CONFIG = {} | |
47 |
|
47 | |||
|
48 | class NotGivenMeta: | |||
|
49 | ||||
|
50 | def __repr__(self): | |||
|
51 | return 'NotGivenObject()' | |||
|
52 | __str__ = __repr__ | |||
|
53 | ||||
|
54 | NotGiven = NotGivenMeta() | |||
48 |
|
55 | |||
49 | class ConfigGet: |
|
56 | class ConfigGet: | |
50 | NotGiven = object() |
|
|||
51 |
|
57 | |||
52 | def _get_val_or_missing(self, key, missing): |
|
58 | @classmethod | |
|
59 | def _get_val_or_missing(cls, key, missing): | |||
53 | if key not in CONFIG: |
|
60 | if key not in CONFIG: | |
54 |
if missing |
|
61 | if missing != NotGiven: | |
55 | return missing |
|
62 | return missing | |
56 | # we don't get key, we don't get missing value, return nothing similar as config.get(key) |
|
63 | # we don't get key, we don't get missing value, return nothing similar as config.get(key) | |
57 | return None |
|
64 | return None | |
@@ -74,6 +81,12 b' class ConfigGet:' | |||||
74 | val = self._get_val_or_missing(key, missing) |
|
81 | val = self._get_val_or_missing(key, missing) | |
75 | return str2bool(val) |
|
82 | return str2bool(val) | |
76 |
|
83 | |||
|
84 | def get_list(self, key, missing=NotGiven): | |||
|
85 | from rhodecode.lib.type_utils import aslist | |||
|
86 | val = self._get_val_or_missing(key, missing) | |||
|
87 | return aslist(val, sep=',') | |||
|
88 | ||||
|
89 | ||||
77 | # Populated with the settings dictionary from application init in |
|
90 | # Populated with the settings dictionary from application init in | |
78 | # rhodecode.conf.environment.load_pyramid_environment |
|
91 | # rhodecode.conf.environment.load_pyramid_environment | |
79 | PYRAMID_SETTINGS = {} |
|
92 | PYRAMID_SETTINGS = {} |
@@ -19,6 +19,7 b'' | |||||
19 |
|
19 | |||
20 | import pytest |
|
20 | import pytest | |
21 |
|
21 | |||
|
22 | from rhodecode.lib.str_utils import safe_str | |||
22 | from rhodecode.model.db import User, ChangesetComment |
|
23 | from rhodecode.model.db import User, ChangesetComment | |
23 | from rhodecode.model.meta import Session |
|
24 | from rhodecode.model.meta import Session | |
24 | from rhodecode.model.comment import CommentsModel |
|
25 | from rhodecode.model.comment import CommentsModel | |
@@ -36,7 +37,7 b' def make_repo_comments_factory(request):' | |||||
36 | commit = repo.scm_instance()[0] |
|
37 | commit = repo.scm_instance()[0] | |
37 |
|
38 | |||
38 | commit_id = commit.raw_id |
|
39 | commit_id = commit.raw_id | |
39 | file_0 = commit.affected_files[0] |
|
40 | file_0 = safe_str(commit.affected_files[0]) | |
40 | comments = [] |
|
41 | comments = [] | |
41 |
|
42 | |||
42 | # general |
|
43 | # general |
@@ -317,8 +317,7 b' def get_repo_changeset(request, apiuser,' | |||||
317 | ','.join(_changes_details_types))) |
|
317 | ','.join(_changes_details_types))) | |
318 |
|
318 | |||
319 | vcs_repo = repo.scm_instance() |
|
319 | vcs_repo = repo.scm_instance() | |
320 | pre_load = ['author', 'branch', 'date', 'message', 'parents', |
|
320 | pre_load = ['author', 'branch', 'date', 'message', 'parents', 'status', '_commit'] | |
321 | 'status', '_commit', '_file_paths'] |
|
|||
322 |
|
321 | |||
323 | try: |
|
322 | try: | |
324 | commit = repo.get_commit(commit_id=revision, pre_load=pre_load) |
|
323 | commit = repo.get_commit(commit_id=revision, pre_load=pre_load) | |
@@ -376,8 +375,7 b' def get_repo_changesets(request, apiuser' | |||||
376 | ','.join(_changes_details_types))) |
|
375 | ','.join(_changes_details_types))) | |
377 |
|
376 | |||
378 | limit = int(limit) |
|
377 | limit = int(limit) | |
379 | pre_load = ['author', 'branch', 'date', 'message', 'parents', |
|
378 | pre_load = ['author', 'branch', 'date', 'message', 'parents', 'status', '_commit'] | |
380 | 'status', '_commit', '_file_paths'] |
|
|||
381 |
|
379 | |||
382 | vcs_repo = repo.scm_instance() |
|
380 | vcs_repo = repo.scm_instance() | |
383 | # SVN needs a special case to distinguish its index and commit id |
|
381 | # SVN needs a special case to distinguish its index and commit id |
@@ -33,7 +33,7 b' from rhodecode.lib.auth import (' | |||||
33 | from rhodecode.lib.graphmod import _colored, _dagwalker |
|
33 | from rhodecode.lib.graphmod import _colored, _dagwalker | |
34 | from rhodecode.lib.helpers import RepoPage |
|
34 | from rhodecode.lib.helpers import RepoPage | |
35 | from rhodecode.lib.utils2 import str2bool |
|
35 | from rhodecode.lib.utils2 import str2bool | |
36 | from rhodecode.lib.str_utils import safe_int, safe_str |
|
36 | from rhodecode.lib.str_utils import safe_int, safe_str, safe_bytes | |
37 | from rhodecode.lib.vcs.exceptions import ( |
|
37 | from rhodecode.lib.vcs.exceptions import ( | |
38 | RepositoryError, CommitDoesNotExistError, |
|
38 | RepositoryError, CommitDoesNotExistError, | |
39 | CommitError, NodeDoesNotExistError, EmptyRepositoryError) |
|
39 | CommitError, NodeDoesNotExistError, EmptyRepositoryError) | |
@@ -204,10 +204,9 b' class RepoChangelogView(RepoAppView):' | |||||
204 | log.debug('generating changelog for path %s', f_path) |
|
204 | log.debug('generating changelog for path %s', f_path) | |
205 | # get the history for the file ! |
|
205 | # get the history for the file ! | |
206 | base_commit = self.rhodecode_vcs_repo.get_commit(commit_id) |
|
206 | base_commit = self.rhodecode_vcs_repo.get_commit(commit_id) | |
207 |
|
207 | bytes_path = safe_bytes(f_path) | ||
208 | try: |
|
208 | try: | |
209 | collection = base_commit.get_path_history( |
|
209 | collection = base_commit.get_path_history(bytes_path, limit=hist_limit, pre_load=pre_load) | |
210 | f_path, limit=hist_limit, pre_load=pre_load) |
|
|||
211 | if collection and partial_xhr: |
|
210 | if collection and partial_xhr: | |
212 | # for ajax call we remove first one since we're looking |
|
211 | # for ajax call we remove first one since we're looking | |
213 | # at it right now in the context of a file commit |
|
212 | # at it right now in the context of a file commit | |
@@ -216,7 +215,7 b' class RepoChangelogView(RepoAppView):' | |||||
216 | # this node is not present at tip! |
|
215 | # this node is not present at tip! | |
217 | try: |
|
216 | try: | |
218 | commit = self._get_commit_or_redirect(commit_id) |
|
217 | commit = self._get_commit_or_redirect(commit_id) | |
219 |
collection = commit.get_path_history( |
|
218 | collection = commit.get_path_history(bytes_path) | |
220 | except RepositoryError as e: |
|
219 | except RepositoryError as e: | |
221 | h.flash(safe_str(e), category='warning') |
|
220 | h.flash(safe_str(e), category='warning') | |
222 | redirect_url = h.route_path( |
|
221 | redirect_url = h.route_path( | |
@@ -310,9 +309,8 b' class RepoChangelogView(RepoAppView):' | |||||
310 | log.exception(safe_str(e)) |
|
309 | log.exception(safe_str(e)) | |
311 | raise HTTPFound( |
|
310 | raise HTTPFound( | |
312 | h.route_path('repo_commits', repo_name=self.db_repo_name)) |
|
311 | h.route_path('repo_commits', repo_name=self.db_repo_name)) | |
313 |
|
312 | bytes_path = safe_bytes(f_path) | ||
314 | collection = base_commit.get_path_history( |
|
313 | collection = base_commit.get_path_history(bytes_path, limit=hist_limit, pre_load=pre_load) | |
315 | f_path, limit=hist_limit, pre_load=pre_load) |
|
|||
316 | collection = list(reversed(collection)) |
|
314 | collection = list(reversed(collection)) | |
317 | else: |
|
315 | else: | |
318 | collection = self.rhodecode_vcs_repo.get_commits( |
|
316 | collection = self.rhodecode_vcs_repo.get_commits( |
@@ -89,8 +89,7 b' class RepoCommitsView(RepoAppView):' | |||||
89 | commit_range = commit_id_range.split('...')[:2] |
|
89 | commit_range = commit_id_range.split('...')[:2] | |
90 |
|
90 | |||
91 | try: |
|
91 | try: | |
92 |
pre_load = [ |
|
92 | pre_load = ['author', 'branch', 'date', 'message', 'parents'] | |
93 | 'message', 'parents'] |
|
|||
94 | if self.rhodecode_vcs_repo.alias == 'hg': |
|
93 | if self.rhodecode_vcs_repo.alias == 'hg': | |
95 | pre_load += ['hidden', 'obsolete', 'phase'] |
|
94 | pre_load += ['hidden', 'obsolete', 'phase'] | |
96 |
|
95 | |||
@@ -100,8 +99,7 b' class RepoCommitsView(RepoAppView):' | |||||
100 | pre_load=pre_load, translate_tags=False) |
|
99 | pre_load=pre_load, translate_tags=False) | |
101 | commits = list(commits) |
|
100 | commits = list(commits) | |
102 | else: |
|
101 | else: | |
103 | commits = [self.rhodecode_vcs_repo.get_commit( |
|
102 | commits = [self.rhodecode_vcs_repo.get_commit(commit_id=commit_id_range, pre_load=pre_load)] | |
104 | commit_id=commit_id_range, pre_load=pre_load)] |
|
|||
105 |
|
103 | |||
106 | c.commit_ranges = commits |
|
104 | c.commit_ranges = commits | |
107 | if not c.commit_ranges: |
|
105 | if not c.commit_ranges: |
@@ -187,12 +187,14 b' class RepoFilesView(RepoAppView):' | |||||
187 | default_commit_id = self.db_repo.landing_ref_name |
|
187 | default_commit_id = self.db_repo.landing_ref_name | |
188 | default_f_path = '/' |
|
188 | default_f_path = '/' | |
189 |
|
189 | |||
190 | commit_id = self.request.matchdict.get( |
|
190 | commit_id = self.request.matchdict.get('commit_id', default_commit_id) | |
191 | 'commit_id', default_commit_id) |
|
|||
192 | f_path = self._get_f_path(self.request.matchdict, default_f_path) |
|
191 | f_path = self._get_f_path(self.request.matchdict, default_f_path) | |
193 | return commit_id, f_path |
|
|||
194 |
|
192 | |||
195 | def _get_default_encoding(self, c): |
|
193 | bytes_path = safe_bytes(f_path) | |
|
194 | return commit_id, f_path, bytes_path | |||
|
195 | ||||
|
196 | @classmethod | |||
|
197 | def _get_default_encoding(cls, c): | |||
196 | enc_list = getattr(c, 'default_encodings', []) |
|
198 | enc_list = getattr(c, 'default_encodings', []) | |
197 | return enc_list[0] if enc_list else 'UTF-8' |
|
199 | return enc_list[0] if enc_list else 'UTF-8' | |
198 |
|
200 | |||
@@ -361,21 +363,21 b' class RepoFilesView(RepoAppView):' | |||||
361 | from rhodecode import CONFIG |
|
363 | from rhodecode import CONFIG | |
362 | _ = self.request.translate |
|
364 | _ = self.request.translate | |
363 | self.load_default_context() |
|
365 | self.load_default_context() | |
|
366 | ||||
|
367 | subrepos = self.request.GET.get('subrepos') == 'true' | |||
|
368 | with_hash = str2bool(self.request.GET.get('with_hash', '1')) | |||
|
369 | ||||
364 | default_at_path = '/' |
|
370 | default_at_path = '/' | |
365 | fname = self.request.matchdict['fname'] |
|
371 | fname = self.request.matchdict['fname'] | |
366 | subrepos = self.request.GET.get('subrepos') == 'true' |
|
|||
367 | with_hash = str2bool(self.request.GET.get('with_hash', '1')) |
|
|||
368 | at_path = self.request.GET.get('at_path') or default_at_path |
|
372 | at_path = self.request.GET.get('at_path') or default_at_path | |
369 |
|
373 | |||
370 | if not self.db_repo.enable_downloads: |
|
374 | if not self.db_repo.enable_downloads: | |
371 | return Response(_('Downloads disabled')) |
|
375 | return Response(_('Downloads disabled')) | |
372 |
|
376 | |||
373 | try: |
|
377 | try: | |
374 |
commit_id, ext, fileformat, content_type = |
|
378 | commit_id, ext, file_format, content_type = _get_archive_spec(fname) | |
375 | _get_archive_spec(fname) |
|
|||
376 | except ValueError: |
|
379 | except ValueError: | |
377 | return Response(_('Unknown archive type for: `{}`').format( |
|
380 | return Response(_('Unknown archive type for: `{}`').format(h.escape(fname))) | |
378 | h.escape(fname))) |
|
|||
379 |
|
381 | |||
380 | try: |
|
382 | try: | |
381 | commit = self.rhodecode_vcs_repo.get_commit(commit_id) |
|
383 | commit = self.rhodecode_vcs_repo.get_commit(commit_id) | |
@@ -391,7 +393,7 b' class RepoFilesView(RepoAppView):' | |||||
391 | raise HTTPFound(self.request.current_route_path(fname=fname)) |
|
393 | raise HTTPFound(self.request.current_route_path(fname=fname)) | |
392 |
|
394 | |||
393 | try: |
|
395 | try: | |
394 | at_path = commit.get_node(at_path).path or default_at_path |
|
396 | at_path = commit.get_node(safe_bytes(at_path)).path or default_at_path | |
395 | except Exception: |
|
397 | except Exception: | |
396 | return Response(_('No node at path {} for this repository').format(h.escape(at_path))) |
|
398 | return Response(_('No node at path {} for this repository').format(h.escape(at_path))) | |
397 |
|
399 | |||
@@ -440,7 +442,7 b' class RepoFilesView(RepoAppView):' | |||||
440 | with d_cache.get_lock(reentrant_lock_key): |
|
442 | with d_cache.get_lock(reentrant_lock_key): | |
441 | try: |
|
443 | try: | |
442 | commit.archive_repo(archive_name_key, archive_dir_name=archive_dir_name, |
|
444 | commit.archive_repo(archive_name_key, archive_dir_name=archive_dir_name, | |
443 | kind=fileformat, subrepos=subrepos, |
|
445 | kind=file_format, subrepos=subrepos, | |
444 | archive_at_path=at_path, cache_config=d_cache_conf) |
|
446 | archive_at_path=at_path, cache_config=d_cache_conf) | |
445 | except ImproperArchiveTypeError: |
|
447 | except ImproperArchiveTypeError: | |
446 | return _('Unknown archive type') |
|
448 | return _('Unknown archive type') | |
@@ -484,7 +486,7 b' class RepoFilesView(RepoAppView):' | |||||
484 | if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]: |
|
486 | if commit_id not in ['', None, 'None', '0' * 12, '0' * 40]: | |
485 | commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id) |
|
487 | commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id) | |
486 | try: |
|
488 | try: | |
487 | node = commit.get_node(f_path) |
|
489 | node = commit.get_node(safe_bytes(f_path)) | |
488 | if node.is_dir(): |
|
490 | if node.is_dir(): | |
489 | raise NodeError(f'{node} path is a {type(node)} not a file') |
|
491 | raise NodeError(f'{node} path is a {type(node)} not a file') | |
490 | except NodeDoesNotExistError: |
|
492 | except NodeDoesNotExistError: | |
@@ -648,7 +650,7 b' class RepoFilesView(RepoAppView):' | |||||
648 | # overwrite auto rendering by setting this GET flag |
|
650 | # overwrite auto rendering by setting this GET flag | |
649 | c.renderer = view_name == 'repo_files:rendered' or not self.request.GET.get('no-render', False) |
|
651 | c.renderer = view_name == 'repo_files:rendered' or not self.request.GET.get('no-render', False) | |
650 |
|
652 | |||
651 | commit_id, f_path = self._get_commit_and_path() |
|
653 | commit_id, f_path, bytes_path = self._get_commit_and_path() | |
652 |
|
654 | |||
653 | c.commit = self._get_commit_or_redirect(commit_id) |
|
655 | c.commit = self._get_commit_or_redirect(commit_id) | |
654 | c.branch = self.request.GET.get('branch', None) |
|
656 | c.branch = self.request.GET.get('branch', None) | |
@@ -657,7 +659,8 b' class RepoFilesView(RepoAppView):' | |||||
657 |
|
659 | |||
658 | # files or dirs |
|
660 | # files or dirs | |
659 | try: |
|
661 | try: | |
660 | c.file = c.commit.get_node(f_path, pre_load=['is_binary', 'size', 'data']) |
|
662 | ||
|
663 | c.file = c.commit.get_node(bytes_path, pre_load=['is_binary', 'size', 'data']) | |||
661 |
|
664 | |||
662 | c.file_author = True |
|
665 | c.file_author = True | |
663 | c.file_tree = '' |
|
666 | c.file_tree = '' | |
@@ -666,11 +669,9 b' class RepoFilesView(RepoAppView):' | |||||
666 | try: |
|
669 | try: | |
667 | prev_commit = c.commit.prev(c.branch) |
|
670 | prev_commit = c.commit.prev(c.branch) | |
668 | c.prev_commit = prev_commit |
|
671 | c.prev_commit = prev_commit | |
669 | c.url_prev = h.route_path( |
|
672 | c.url_prev = h.route_path('repo_files', repo_name=self.db_repo_name, commit_id=prev_commit.raw_id, f_path=f_path) | |
670 | 'repo_files', repo_name=self.db_repo_name, |
|
|||
671 | commit_id=prev_commit.raw_id, f_path=f_path) |
|
|||
672 | if c.branch: |
|
673 | if c.branch: | |
673 |
c.url_prev += '?branch= |
|
674 | c.url_prev += f'?branch={c.branch}' | |
674 | except (CommitDoesNotExistError, VCSError): |
|
675 | except (CommitDoesNotExistError, VCSError): | |
675 | c.url_prev = '#' |
|
676 | c.url_prev = '#' | |
676 | c.prev_commit = EmptyCommit() |
|
677 | c.prev_commit = EmptyCommit() | |
@@ -679,11 +680,9 b' class RepoFilesView(RepoAppView):' | |||||
679 | try: |
|
680 | try: | |
680 | next_commit = c.commit.next(c.branch) |
|
681 | next_commit = c.commit.next(c.branch) | |
681 | c.next_commit = next_commit |
|
682 | c.next_commit = next_commit | |
682 | c.url_next = h.route_path( |
|
683 | c.url_next = h.route_path('repo_files', repo_name=self.db_repo_name, commit_id=next_commit.raw_id, f_path=f_path) | |
683 | 'repo_files', repo_name=self.db_repo_name, |
|
|||
684 | commit_id=next_commit.raw_id, f_path=f_path) |
|
|||
685 | if c.branch: |
|
684 | if c.branch: | |
686 |
c.url_next += '?branch= |
|
685 | c.url_next += f'?branch={c.branch}' | |
687 | except (CommitDoesNotExistError, VCSError): |
|
686 | except (CommitDoesNotExistError, VCSError): | |
688 | c.url_next = '#' |
|
687 | c.url_next = '#' | |
689 | c.next_commit = EmptyCommit() |
|
688 | c.next_commit = EmptyCommit() | |
@@ -739,8 +738,7 b' class RepoFilesView(RepoAppView):' | |||||
739 | c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev) |
|
738 | c.file_tree = self._get_tree_at_commit(c, c.commit.raw_id, f_path, at_rev=at_rev) | |
740 |
|
739 | |||
741 | c.readme_data, c.readme_file = \ |
|
740 | c.readme_data, c.readme_file = \ | |
742 | self._get_readme_data(self.db_repo, c.visual.default_renderer, |
|
741 | self._get_readme_data(self.db_repo, c.visual.default_renderer, c.commit.raw_id, bytes_path) | |
743 | c.commit.raw_id, f_path) |
|
|||
744 |
|
742 | |||
745 | except RepositoryError as e: |
|
743 | except RepositoryError as e: | |
746 | h.flash(h.escape(safe_str(e)), category='error') |
|
744 | h.flash(h.escape(safe_str(e)), category='error') | |
@@ -759,24 +757,24 b' class RepoFilesView(RepoAppView):' | |||||
759 | def repo_files_annotated_previous(self): |
|
757 | def repo_files_annotated_previous(self): | |
760 | self.load_default_context() |
|
758 | self.load_default_context() | |
761 |
|
759 | |||
762 |
commit_id, |
|
760 | commit_id, bytes_path, bytes_path = self._get_commit_and_path() | |
763 | commit = self._get_commit_or_redirect(commit_id) |
|
761 | commit = self._get_commit_or_redirect(commit_id) | |
764 | prev_commit_id = commit.raw_id |
|
762 | prev_commit_id = commit.raw_id | |
765 | line_anchor = self.request.GET.get('line_anchor') |
|
763 | line_anchor = self.request.GET.get('line_anchor') | |
766 | is_file = False |
|
764 | is_file = False | |
767 | try: |
|
765 | try: | |
768 |
_file = commit.get_node( |
|
766 | _file = commit.get_node(bytes_path) | |
769 | is_file = _file.is_file() |
|
767 | is_file = _file.is_file() | |
770 | except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError): |
|
768 | except (NodeDoesNotExistError, CommitDoesNotExistError, VCSError): | |
771 | pass |
|
769 | pass | |
772 |
|
770 | |||
773 | if is_file: |
|
771 | if is_file: | |
774 |
history = commit.get_path_history( |
|
772 | history = commit.get_path_history(bytes_path) | |
775 | prev_commit_id = history[1].raw_id \ |
|
773 | prev_commit_id = history[1].raw_id \ | |
776 | if len(history) > 1 else prev_commit_id |
|
774 | if len(history) > 1 else prev_commit_id | |
777 | prev_url = h.route_path( |
|
775 | prev_url = h.route_path( | |
778 | 'repo_files:annotated', repo_name=self.db_repo_name, |
|
776 | 'repo_files:annotated', repo_name=self.db_repo_name, | |
779 |
commit_id=prev_commit_id, f_path= |
|
777 | commit_id=prev_commit_id, f_path=bytes_path, | |
780 | _anchor=f'L{line_anchor}') |
|
778 | _anchor=f'L{line_anchor}') | |
781 |
|
779 | |||
782 | raise HTTPFound(prev_url) |
|
780 | raise HTTPFound(prev_url) | |
@@ -792,10 +790,10 b' class RepoFilesView(RepoAppView):' | |||||
792 | """ |
|
790 | """ | |
793 | c = self.load_default_context() |
|
791 | c = self.load_default_context() | |
794 |
|
792 | |||
795 | commit_id, f_path = self._get_commit_and_path() |
|
793 | commit_id, f_path, bytes_path = self._get_commit_and_path() | |
796 | commit = self._get_commit_or_redirect(commit_id) |
|
794 | commit = self._get_commit_or_redirect(commit_id) | |
797 | try: |
|
795 | try: | |
798 |
dir_node = commit.get_node( |
|
796 | dir_node = commit.get_node(bytes_path) | |
799 | except RepositoryError as e: |
|
797 | except RepositoryError as e: | |
800 | return Response(f'error: {h.escape(safe_str(e))}') |
|
798 | return Response(f'error: {h.escape(safe_str(e))}') | |
801 |
|
799 | |||
@@ -816,9 +814,9 b' class RepoFilesView(RepoAppView):' | |||||
816 | safe_path = f_name.replace('"', '\\"') |
|
814 | safe_path = f_name.replace('"', '\\"') | |
817 | encoded_path = urllib.parse.quote(f_name) |
|
815 | encoded_path = urllib.parse.quote(f_name) | |
818 |
|
816 | |||
819 | headers = "attachment; " \ |
|
817 | headers = f"attachment; " \ | |
820 | "filename=\"{}\"; " \ |
|
818 | f"filename=\"{safe_path}\"; " \ | |
821 |
"filename*=UTF-8\'\'{}" |
|
819 | f"filename*=UTF-8\'\'{encoded_path}" | |
822 |
|
820 | |||
823 | return header_safe_str(headers) |
|
821 | return header_safe_str(headers) | |
824 |
|
822 | |||
@@ -832,9 +830,9 b' class RepoFilesView(RepoAppView):' | |||||
832 | """ |
|
830 | """ | |
833 | c = self.load_default_context() |
|
831 | c = self.load_default_context() | |
834 |
|
832 | |||
835 | commit_id, f_path = self._get_commit_and_path() |
|
833 | commit_id, f_path, bytes_path = self._get_commit_and_path() | |
836 | commit = self._get_commit_or_redirect(commit_id) |
|
834 | commit = self._get_commit_or_redirect(commit_id) | |
837 |
file_node = self._get_filenode_or_redirect(commit, |
|
835 | file_node = self._get_filenode_or_redirect(commit, bytes_path) | |
838 |
|
836 | |||
839 | raw_mimetype_mapping = { |
|
837 | raw_mimetype_mapping = { | |
840 | # map original mimetype to a mimetype used for "show as raw" |
|
838 | # map original mimetype to a mimetype used for "show as raw" | |
@@ -892,9 +890,9 b' class RepoFilesView(RepoAppView):' | |||||
892 | def repo_file_download(self): |
|
890 | def repo_file_download(self): | |
893 | c = self.load_default_context() |
|
891 | c = self.load_default_context() | |
894 |
|
892 | |||
895 | commit_id, f_path = self._get_commit_and_path() |
|
893 | commit_id, f_path, bytes_path = self._get_commit_and_path() | |
896 | commit = self._get_commit_or_redirect(commit_id) |
|
894 | commit = self._get_commit_or_redirect(commit_id) | |
897 |
file_node = self._get_filenode_or_redirect(commit, |
|
895 | file_node = self._get_filenode_or_redirect(commit, bytes_path) | |
898 |
|
896 | |||
899 | if self.request.GET.get('lf'): |
|
897 | if self.request.GET.get('lf'): | |
900 | # only if lf get flag is passed, we download this file |
|
898 | # only if lf get flag is passed, we download this file | |
@@ -920,8 +918,7 b' class RepoFilesView(RepoAppView):' | |||||
920 |
|
918 | |||
921 | def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path): |
|
919 | def _get_nodelist_at_commit(self, repo_name, repo_id, commit_id, f_path): | |
922 |
|
920 | |||
923 | cache_seconds = safe_int( |
|
921 | cache_seconds = rhodecode.ConfigGet().get_int('rc_cache.cache_repo.expiration_time') | |
924 | rhodecode.CONFIG.get('rc_cache.cache_repo.expiration_time')) |
|
|||
925 | cache_on = cache_seconds > 0 |
|
922 | cache_on = cache_seconds > 0 | |
926 | log.debug( |
|
923 | log.debug( | |
927 | 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`' |
|
924 | 'Computing FILE SEARCH for repo_id %s commit_id `%s` and path `%s`' | |
@@ -933,21 +930,17 b' class RepoFilesView(RepoAppView):' | |||||
933 |
|
930 | |||
934 | @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on) |
|
931 | @region.conditional_cache_on_arguments(namespace=cache_namespace_uid, condition=cache_on) | |
935 | def compute_file_search(_name_hash, _repo_id, _commit_id, _f_path): |
|
932 | def compute_file_search(_name_hash, _repo_id, _commit_id, _f_path): | |
936 | log.debug('Generating cached nodelist for repo_id:%s, %s, %s', |
|
933 | log.debug('Generating cached nodelist for repo_id:%s, %s, %s', _repo_id, commit_id, f_path) | |
937 | _repo_id, commit_id, f_path) |
|
|||
938 | try: |
|
934 | try: | |
939 | _d, _f = ScmModel().get_quick_filter_nodes(repo_name, _commit_id, _f_path) |
|
935 | _d, _f = ScmModel().get_quick_filter_nodes(repo_name, _commit_id, _f_path) | |
940 | except (RepositoryError, CommitDoesNotExistError, Exception) as e: |
|
936 | except (RepositoryError, CommitDoesNotExistError, Exception) as e: | |
941 | log.exception(safe_str(e)) |
|
937 | log.exception(safe_str(e)) | |
942 | h.flash(h.escape(safe_str(e)), category='error') |
|
938 | h.flash(h.escape(safe_str(e)), category='error') | |
943 | raise HTTPFound(h.route_path( |
|
939 | raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name, commit_id='tip', f_path='/')) | |
944 | 'repo_files', repo_name=self.db_repo_name, |
|
|||
945 | commit_id='tip', f_path='/')) |
|
|||
946 |
|
940 | |||
947 | return _d + _f |
|
941 | return _d + _f | |
948 |
|
942 | |||
949 | result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id, |
|
943 | result = compute_file_search(self.db_repo.repo_name_hash, self.db_repo.repo_id, commit_id, f_path) | |
950 | commit_id, f_path) |
|
|||
951 | return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result) |
|
944 | return filter(lambda n: self.path_filter.path_access_allowed(n['name']), result) | |
952 |
|
945 | |||
953 | @LoginRequired() |
|
946 | @LoginRequired() | |
@@ -956,7 +949,7 b' class RepoFilesView(RepoAppView):' | |||||
956 | def repo_nodelist(self): |
|
949 | def repo_nodelist(self): | |
957 | self.load_default_context() |
|
950 | self.load_default_context() | |
958 |
|
951 | |||
959 | commit_id, f_path = self._get_commit_and_path() |
|
952 | commit_id, f_path, bytes_path = self._get_commit_and_path() | |
960 | commit = self._get_commit_or_redirect(commit_id) |
|
953 | commit = self._get_commit_or_redirect(commit_id) | |
961 |
|
954 | |||
962 | metadata = self._get_nodelist_at_commit( |
|
955 | metadata = self._get_nodelist_at_commit( | |
@@ -996,10 +989,10 b' class RepoFilesView(RepoAppView):' | |||||
996 | if commits is None: |
|
989 | if commits is None: | |
997 | pre_load = ["author", "branch"] |
|
990 | pre_load = ["author", "branch"] | |
998 | try: |
|
991 | try: | |
999 | commits = tip.get_path_history(f_path, pre_load=pre_load) |
|
992 | commits = tip.get_path_history(safe_bytes(f_path), pre_load=pre_load) | |
1000 | except (NodeDoesNotExistError, CommitError): |
|
993 | except (NodeDoesNotExistError, CommitError): | |
1001 | # this node is not present at tip! |
|
994 | # this node is not present at tip! | |
1002 | commits = commit_obj.get_path_history(f_path, pre_load=pre_load) |
|
995 | commits = commit_obj.get_path_history(safe_bytes(f_path), pre_load=pre_load) | |
1003 |
|
996 | |||
1004 | history = [] |
|
997 | history = [] | |
1005 | commits_group = ([], _("Changesets")) |
|
998 | commits_group = ([], _("Changesets")) | |
@@ -1040,9 +1033,9 b' class RepoFilesView(RepoAppView):' | |||||
1040 | def repo_file_history(self): |
|
1033 | def repo_file_history(self): | |
1041 | self.load_default_context() |
|
1034 | self.load_default_context() | |
1042 |
|
1035 | |||
1043 | commit_id, f_path = self._get_commit_and_path() |
|
1036 | commit_id, f_path, bytes_path = self._get_commit_and_path() | |
1044 | commit = self._get_commit_or_redirect(commit_id) |
|
1037 | commit = self._get_commit_or_redirect(commit_id) | |
1045 |
file_node = self._get_filenode_or_redirect(commit, |
|
1038 | file_node = self._get_filenode_or_redirect(commit, bytes_path) | |
1046 |
|
1039 | |||
1047 | if file_node.is_file(): |
|
1040 | if file_node.is_file(): | |
1048 | file_history, _hist = self._get_node_history(commit, f_path) |
|
1041 | file_history, _hist = self._get_node_history(commit, f_path) | |
@@ -1083,9 +1076,9 b' class RepoFilesView(RepoAppView):' | |||||
1083 | def repo_file_authors(self): |
|
1076 | def repo_file_authors(self): | |
1084 | c = self.load_default_context() |
|
1077 | c = self.load_default_context() | |
1085 |
|
1078 | |||
1086 | commit_id, f_path = self._get_commit_and_path() |
|
1079 | commit_id, f_path, bytes_path = self._get_commit_and_path() | |
1087 | commit = self._get_commit_or_redirect(commit_id) |
|
1080 | commit = self._get_commit_or_redirect(commit_id) | |
1088 |
file_node = self._get_filenode_or_redirect(commit, |
|
1081 | file_node = self._get_filenode_or_redirect(commit, bytes_path) | |
1089 |
|
1082 | |||
1090 | if not file_node.is_file(): |
|
1083 | if not file_node.is_file(): | |
1091 | raise HTTPBadRequest() |
|
1084 | raise HTTPBadRequest() | |
@@ -1124,10 +1117,9 b' class RepoFilesView(RepoAppView):' | |||||
1124 | def repo_files_check_head(self): |
|
1117 | def repo_files_check_head(self): | |
1125 | self.load_default_context() |
|
1118 | self.load_default_context() | |
1126 |
|
1119 | |||
1127 | commit_id, f_path = self._get_commit_and_path() |
|
1120 | commit_id, f_path, bytes_path = self._get_commit_and_path() | |
1128 | _branch_name, _sha_commit_id, is_head = \ |
|
1121 | _branch_name, _sha_commit_id, is_head = \ | |
1129 | self._is_valid_head(commit_id, self.rhodecode_vcs_repo, |
|
1122 | self._is_valid_head(commit_id, self.rhodecode_vcs_repo, landing_ref=self.db_repo.landing_ref_name) | |
1130 | landing_ref=self.db_repo.landing_ref_name) |
|
|||
1131 |
|
1123 | |||
1132 | new_path = self.request.POST.get('path') |
|
1124 | new_path = self.request.POST.get('path') | |
1133 | operation = self.request.POST.get('operation') |
|
1125 | operation = self.request.POST.get('operation') | |
@@ -1138,12 +1130,10 b' class RepoFilesView(RepoAppView):' | |||||
1138 | try: |
|
1130 | try: | |
1139 | commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id) |
|
1131 | commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id) | |
1140 | # NOTE(dan): construct whole path without leading / |
|
1132 | # NOTE(dan): construct whole path without leading / | |
1141 | file_node = commit_obj.get_node(new_f_path) |
|
1133 | file_node = commit_obj.get_node(safe_bytes(new_f_path)) | |
1142 |
if file_node |
|
1134 | if file_node: | |
1143 | path_exist = new_f_path |
|
1135 | path_exist = new_f_path | |
1144 | except EmptyRepositoryError: |
|
1136 | except (EmptyRepositoryError, NodeDoesNotExistError): | |
1145 | pass |
|
|||
1146 | except Exception: |
|
|||
1147 | pass |
|
1137 | pass | |
1148 |
|
1138 | |||
1149 | return { |
|
1139 | return { | |
@@ -1158,7 +1148,7 b' class RepoFilesView(RepoAppView):' | |||||
1158 | def repo_files_remove_file(self): |
|
1148 | def repo_files_remove_file(self): | |
1159 | _ = self.request.translate |
|
1149 | _ = self.request.translate | |
1160 | c = self.load_default_context() |
|
1150 | c = self.load_default_context() | |
1161 | commit_id, f_path = self._get_commit_and_path() |
|
1151 | commit_id, f_path, bytes_path = self._get_commit_and_path() | |
1162 |
|
1152 | |||
1163 | self._ensure_not_locked() |
|
1153 | self._ensure_not_locked() | |
1164 | _branch_name, _sha_commit_id, is_head = \ |
|
1154 | _branch_name, _sha_commit_id, is_head = \ | |
@@ -1169,7 +1159,7 b' class RepoFilesView(RepoAppView):' | |||||
1169 | self.check_branch_permission(_branch_name) |
|
1159 | self.check_branch_permission(_branch_name) | |
1170 |
|
1160 | |||
1171 | c.commit = self._get_commit_or_redirect(commit_id) |
|
1161 | c.commit = self._get_commit_or_redirect(commit_id) | |
1172 |
c.file = self._get_filenode_or_redirect(c.commit, |
|
1162 | c.file = self._get_filenode_or_redirect(c.commit, bytes_path) | |
1173 |
|
1163 | |||
1174 | c.default_message = _( |
|
1164 | c.default_message = _( | |
1175 | 'Deleted file {} via RhodeCode Enterprise').format(f_path) |
|
1165 | 'Deleted file {} via RhodeCode Enterprise').format(f_path) | |
@@ -1184,7 +1174,7 b' class RepoFilesView(RepoAppView):' | |||||
1184 | _ = self.request.translate |
|
1174 | _ = self.request.translate | |
1185 |
|
1175 | |||
1186 | c = self.load_default_context() |
|
1176 | c = self.load_default_context() | |
1187 | commit_id, f_path = self._get_commit_and_path() |
|
1177 | commit_id, f_path, bytes_path = self._get_commit_and_path() | |
1188 |
|
1178 | |||
1189 | self._ensure_not_locked() |
|
1179 | self._ensure_not_locked() | |
1190 | _branch_name, _sha_commit_id, is_head = \ |
|
1180 | _branch_name, _sha_commit_id, is_head = \ | |
@@ -1195,10 +1185,9 b' class RepoFilesView(RepoAppView):' | |||||
1195 | self.check_branch_permission(_branch_name) |
|
1185 | self.check_branch_permission(_branch_name) | |
1196 |
|
1186 | |||
1197 | c.commit = self._get_commit_or_redirect(commit_id) |
|
1187 | c.commit = self._get_commit_or_redirect(commit_id) | |
1198 |
c.file = self._get_filenode_or_redirect(c.commit, |
|
1188 | c.file = self._get_filenode_or_redirect(c.commit, bytes_path) | |
1199 |
|
1189 | |||
1200 | c.default_message = _( |
|
1190 | c.default_message = _('Deleted file {} via RhodeCode Enterprise').format(f_path) | |
1201 | 'Deleted file {} via RhodeCode Enterprise').format(f_path) |
|
|||
1202 | c.f_path = f_path |
|
1191 | c.f_path = f_path | |
1203 | node_path = f_path |
|
1192 | node_path = f_path | |
1204 | author = self._rhodecode_db_user.full_contact |
|
1193 | author = self._rhodecode_db_user.full_contact | |
@@ -1232,7 +1221,7 b' class RepoFilesView(RepoAppView):' | |||||
1232 | def repo_files_edit_file(self): |
|
1221 | def repo_files_edit_file(self): | |
1233 | _ = self.request.translate |
|
1222 | _ = self.request.translate | |
1234 | c = self.load_default_context() |
|
1223 | c = self.load_default_context() | |
1235 | commit_id, f_path = self._get_commit_and_path() |
|
1224 | commit_id, f_path, bytes_path = self._get_commit_and_path() | |
1236 |
|
1225 | |||
1237 | self._ensure_not_locked() |
|
1226 | self._ensure_not_locked() | |
1238 | _branch_name, _sha_commit_id, is_head = \ |
|
1227 | _branch_name, _sha_commit_id, is_head = \ | |
@@ -1243,7 +1232,7 b' class RepoFilesView(RepoAppView):' | |||||
1243 | self.check_branch_permission(_branch_name, commit_id=commit_id) |
|
1232 | self.check_branch_permission(_branch_name, commit_id=commit_id) | |
1244 |
|
1233 | |||
1245 | c.commit = self._get_commit_or_redirect(commit_id) |
|
1234 | c.commit = self._get_commit_or_redirect(commit_id) | |
1246 |
c.file = self._get_filenode_or_redirect(c.commit, |
|
1235 | c.file = self._get_filenode_or_redirect(c.commit, bytes_path) | |
1247 |
|
1236 | |||
1248 | if c.file.is_binary: |
|
1237 | if c.file.is_binary: | |
1249 | files_url = h.route_path( |
|
1238 | files_url = h.route_path( | |
@@ -1263,12 +1252,12 b' class RepoFilesView(RepoAppView):' | |||||
1263 | def repo_files_update_file(self): |
|
1252 | def repo_files_update_file(self): | |
1264 | _ = self.request.translate |
|
1253 | _ = self.request.translate | |
1265 | c = self.load_default_context() |
|
1254 | c = self.load_default_context() | |
1266 | commit_id, f_path = self._get_commit_and_path() |
|
1255 | commit_id, f_path, bytes_path = self._get_commit_and_path() | |
1267 |
|
1256 | |||
1268 | self._ensure_not_locked() |
|
1257 | self._ensure_not_locked() | |
1269 |
|
1258 | |||
1270 | c.commit = self._get_commit_or_redirect(commit_id) |
|
1259 | c.commit = self._get_commit_or_redirect(commit_id) | |
1271 |
c.file = self._get_filenode_or_redirect(c.commit, |
|
1260 | c.file = self._get_filenode_or_redirect(c.commit, bytes_path) | |
1272 |
|
1261 | |||
1273 | if c.file.is_binary: |
|
1262 | if c.file.is_binary: | |
1274 | raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name, |
|
1263 | raise HTTPFound(h.route_path('repo_files', repo_name=self.db_repo_name, | |
@@ -1345,7 +1334,7 b' class RepoFilesView(RepoAppView):' | |||||
1345 | def repo_files_add_file(self): |
|
1334 | def repo_files_add_file(self): | |
1346 | _ = self.request.translate |
|
1335 | _ = self.request.translate | |
1347 | c = self.load_default_context() |
|
1336 | c = self.load_default_context() | |
1348 | commit_id, f_path = self._get_commit_and_path() |
|
1337 | commit_id, f_path, bytes_path = self._get_commit_and_path() | |
1349 |
|
1338 | |||
1350 | self._ensure_not_locked() |
|
1339 | self._ensure_not_locked() | |
1351 |
|
1340 | |||
@@ -1381,7 +1370,7 b' class RepoFilesView(RepoAppView):' | |||||
1381 | def repo_files_create_file(self): |
|
1370 | def repo_files_create_file(self): | |
1382 | _ = self.request.translate |
|
1371 | _ = self.request.translate | |
1383 | c = self.load_default_context() |
|
1372 | c = self.load_default_context() | |
1384 | commit_id, f_path = self._get_commit_and_path() |
|
1373 | commit_id, f_path, bytes_path = self._get_commit_and_path() | |
1385 |
|
1374 | |||
1386 | self._ensure_not_locked() |
|
1375 | self._ensure_not_locked() | |
1387 |
|
1376 | |||
@@ -1450,8 +1439,7 b' class RepoFilesView(RepoAppView):' | |||||
1450 | author=author, |
|
1439 | author=author, | |
1451 | ) |
|
1440 | ) | |
1452 |
|
1441 | |||
1453 | h.flash(_('Successfully committed new file `{}`').format( |
|
1442 | h.flash(_('Successfully committed new file `{}`').format(h.escape(node_path)), category='success') | |
1454 | h.escape(node_path)), category='success') |
|
|||
1455 |
|
1443 | |||
1456 | default_redirect_url = h.route_path( |
|
1444 | default_redirect_url = h.route_path( | |
1457 | 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id) |
|
1445 | 'repo_commit', repo_name=self.db_repo_name, commit_id=commit.raw_id) | |
@@ -1475,7 +1463,7 b' class RepoFilesView(RepoAppView):' | |||||
1475 | def repo_files_upload_file(self): |
|
1463 | def repo_files_upload_file(self): | |
1476 | _ = self.request.translate |
|
1464 | _ = self.request.translate | |
1477 | c = self.load_default_context() |
|
1465 | c = self.load_default_context() | |
1478 | commit_id, f_path = self._get_commit_and_path() |
|
1466 | commit_id, f_path, bytes_path = self._get_commit_and_path() | |
1479 |
|
1467 | |||
1480 | self._ensure_not_locked() |
|
1468 | self._ensure_not_locked() | |
1481 |
|
1469 | |||
@@ -1604,7 +1592,7 b' class RepoFilesView(RepoAppView):' | |||||
1604 | def repo_files_replace_file(self): |
|
1592 | def repo_files_replace_file(self): | |
1605 | _ = self.request.translate |
|
1593 | _ = self.request.translate | |
1606 | c = self.load_default_context() |
|
1594 | c = self.load_default_context() | |
1607 | commit_id, f_path = self._get_commit_and_path() |
|
1595 | commit_id, f_path, bytes_path = self._get_commit_and_path() | |
1608 |
|
1596 | |||
1609 | self._ensure_not_locked() |
|
1597 | self._ensure_not_locked() | |
1610 |
|
1598 |
@@ -39,7 +39,6 b' def configure_vcs(config):' | |||||
39 |
|
39 | |||
40 | conf.settings.HOOKS_PROTOCOL = config['vcs.hooks.protocol.v2'] |
|
40 | conf.settings.HOOKS_PROTOCOL = config['vcs.hooks.protocol.v2'] | |
41 | conf.settings.HOOKS_HOST = config['vcs.hooks.host'] |
|
41 | conf.settings.HOOKS_HOST = config['vcs.hooks.host'] | |
42 | conf.settings.DEFAULT_ENCODINGS = config['default_encoding'] |
|
|||
43 | conf.settings.ALIASES[:] = config['vcs.backends'] |
|
42 | conf.settings.ALIASES[:] = config['vcs.backends'] | |
44 | conf.settings.SVN_COMPATIBLE_VERSION = config['vcs.svn.compatible_version'] |
|
43 | conf.settings.SVN_COMPATIBLE_VERSION = config['vcs.svn.compatible_version'] | |
45 |
|
44 |
@@ -478,7 +478,9 b' class DiffSet(object):' | |||||
478 | log.debug('rendering diff for %r', patch['filename']) |
|
478 | log.debug('rendering diff for %r', patch['filename']) | |
479 |
|
479 | |||
480 | source_filename = patch['original_filename'] |
|
480 | source_filename = patch['original_filename'] | |
|
481 | source_filename_bytes = patch['original_filename_bytes'] | |||
481 | target_filename = patch['filename'] |
|
482 | target_filename = patch['filename'] | |
|
483 | target_filename_bytes = patch['filename_bytes'] | |||
482 |
|
484 | |||
483 | source_lexer = plain_text_lexer |
|
485 | source_lexer = plain_text_lexer | |
484 | target_lexer = plain_text_lexer |
|
486 | target_lexer = plain_text_lexer | |
@@ -491,12 +493,12 b' class DiffSet(object):' | |||||
491 | if (source_filename and patch['operation'] in ('D', 'M') |
|
493 | if (source_filename and patch['operation'] in ('D', 'M') | |
492 | and source_filename not in self.source_nodes): |
|
494 | and source_filename not in self.source_nodes): | |
493 | self.source_nodes[source_filename] = ( |
|
495 | self.source_nodes[source_filename] = ( | |
494 | self.source_node_getter(source_filename)) |
|
496 | self.source_node_getter(source_filename_bytes)) | |
495 |
|
497 | |||
496 | if (target_filename and patch['operation'] in ('A', 'M') |
|
498 | if (target_filename and patch['operation'] in ('A', 'M') | |
497 | and target_filename not in self.target_nodes): |
|
499 | and target_filename not in self.target_nodes): | |
498 | self.target_nodes[target_filename] = ( |
|
500 | self.target_nodes[target_filename] = ( | |
499 | self.target_node_getter(target_filename)) |
|
501 | self.target_node_getter(target_filename_bytes)) | |
500 |
|
502 | |||
501 | elif hl_mode == self.HL_FAST: |
|
503 | elif hl_mode == self.HL_FAST: | |
502 | source_lexer = self._get_lexer_for_filename(source_filename) |
|
504 | source_lexer = self._get_lexer_for_filename(source_filename) | |
@@ -558,6 +560,7 b' class DiffSet(object):' | |||||
558 | }) |
|
560 | }) | |
559 |
|
561 | |||
560 | file_chunks = patch['chunks'][1:] |
|
562 | file_chunks = patch['chunks'][1:] | |
|
563 | ||||
561 | for i, hunk in enumerate(file_chunks, 1): |
|
564 | for i, hunk in enumerate(file_chunks, 1): | |
562 | hunkbit = self.parse_hunk(hunk, source_file, target_file) |
|
565 | hunkbit = self.parse_hunk(hunk, source_file, target_file) | |
563 | hunkbit.source_file_path = source_file_path |
|
566 | hunkbit.source_file_path = source_file_path | |
@@ -593,12 +596,13 b' class DiffSet(object):' | |||||
593 | return filediff |
|
596 | return filediff | |
594 |
|
597 | |||
595 | def parse_hunk(self, hunk, source_file, target_file): |
|
598 | def parse_hunk(self, hunk, source_file, target_file): | |
|
599 | ||||
596 | result = AttributeDict(dict( |
|
600 | result = AttributeDict(dict( | |
597 | source_start=hunk['source_start'], |
|
601 | source_start=hunk['source_start'], | |
598 | source_length=hunk['source_length'], |
|
602 | source_length=hunk['source_length'], | |
599 | target_start=hunk['target_start'], |
|
603 | target_start=hunk['target_start'], | |
600 | target_length=hunk['target_length'], |
|
604 | target_length=hunk['target_length'], | |
601 | section_header=hunk['section_header'], |
|
605 | section_header=safe_str(hunk['section_header']), | |
602 | lines=[], |
|
606 | lines=[], | |
603 | )) |
|
607 | )) | |
604 | before, after = [], [] |
|
608 | before, after = [], [] |
@@ -455,7 +455,9 b' class DiffProcessor(object):' | |||||
455 | return arg |
|
455 | return arg | |
456 |
|
456 | |||
457 | for chunk in self._diff.chunks(): |
|
457 | for chunk in self._diff.chunks(): | |
|
458 | bytes_head = chunk.header | |||
458 | head = chunk.header_as_str |
|
459 | head = chunk.header_as_str | |
|
460 | ||||
459 | log.debug('parsing diff chunk %r', chunk) |
|
461 | log.debug('parsing diff chunk %r', chunk) | |
460 |
|
462 | |||
461 | raw_diff = chunk.raw |
|
463 | raw_diff = chunk.raw | |
@@ -598,10 +600,17 b' class DiffProcessor(object):' | |||||
598 |
|
600 | |||
599 | chunks.insert(0, frag) |
|
601 | chunks.insert(0, frag) | |
600 |
|
602 | |||
601 |
original_filename = |
|
603 | original_filename = head['a_path'] | |
|
604 | original_filename_bytes = bytes_head['a_path'] | |||
|
605 | ||||
|
606 | filename = head['b_path'] | |||
|
607 | filename_bytes = bytes_head['b_path'] | |||
|
608 | ||||
602 | _files.append({ |
|
609 | _files.append({ | |
603 | 'original_filename': original_filename, |
|
610 | 'original_filename': original_filename, | |
604 | 'filename': safe_str(head['b_path']), |
|
611 | 'original_filename_bytes': original_filename_bytes, | |
|
612 | 'filename': filename, | |||
|
613 | 'filename_bytes': filename_bytes, | |||
605 | 'old_revision': head['a_blob_id'], |
|
614 | 'old_revision': head['a_blob_id'], | |
606 | 'new_revision': head['b_blob_id'], |
|
615 | 'new_revision': head['b_blob_id'], | |
607 | 'chunks': chunks, |
|
616 | 'chunks': chunks, |
@@ -84,7 +84,6 b' def check_locked_repo(extras, check_same' | |||||
84 | user = User.get_by_username(extras.username) |
|
84 | user = User.get_by_username(extras.username) | |
85 | output = '' |
|
85 | output = '' | |
86 | if extras.locked_by[0] and (not check_same_user or user.user_id != extras.locked_by[0]): |
|
86 | if extras.locked_by[0] and (not check_same_user or user.user_id != extras.locked_by[0]): | |
87 |
|
||||
88 | locked_by = User.get(extras.locked_by[0]).username |
|
87 | locked_by = User.get(extras.locked_by[0]).username | |
89 | reason = extras.locked_by[2] |
|
88 | reason = extras.locked_by[2] | |
90 | # this exception is interpreted in git/hg middlewares and based |
|
89 | # this exception is interpreted in git/hg middlewares and based |
@@ -67,10 +67,7 b' def base64_to_str(text: str | bytes) -> ' | |||||
67 |
|
67 | |||
68 |
|
68 | |||
69 | def get_default_encodings() -> list[str]: |
|
69 | def get_default_encodings() -> list[str]: | |
70 |
return |
|
70 | return rhodecode.ConfigGet().get_list('default_encoding', missing='utf8') | |
71 |
|
||||
72 |
|
||||
73 | DEFAULT_ENCODINGS = get_default_encodings() |
|
|||
74 |
|
71 | |||
75 |
|
72 | |||
76 | def safe_str(str_, to_encoding=None) -> str: |
|
73 | def safe_str(str_, to_encoding=None) -> str: | |
@@ -87,7 +84,7 b' def safe_str(str_, to_encoding=None) -> ' | |||||
87 | if not isinstance(str_, bytes): |
|
84 | if not isinstance(str_, bytes): | |
88 | return str(str_) |
|
85 | return str(str_) | |
89 |
|
86 | |||
90 |
to_encoding = to_encoding or |
|
87 | to_encoding = to_encoding or get_default_encodings() | |
91 | if not isinstance(to_encoding, (list, tuple)): |
|
88 | if not isinstance(to_encoding, (list, tuple)): | |
92 | to_encoding = [to_encoding] |
|
89 | to_encoding = [to_encoding] | |
93 |
|
90 | |||
@@ -120,7 +117,7 b' def safe_bytes(str_, from_encoding=None)' | |||||
120 | for enc in from_encoding: |
|
117 | for enc in from_encoding: | |
121 | try: |
|
118 | try: | |
122 | return str_.encode(enc) |
|
119 | return str_.encode(enc) | |
123 | except UnicodeDecodeError: |
|
120 | except (UnicodeDecodeError, UnicodeEncodeError): | |
124 | pass |
|
121 | pass | |
125 |
|
122 | |||
126 | return str_.encode(from_encoding[0], 'replace') |
|
123 | return str_.encode(from_encoding[0], 'replace') |
@@ -139,7 +139,7 b' class CurlSession(object):' | |||||
139 |
|
139 | |||
140 | try: |
|
140 | try: | |
141 | curl.perform() |
|
141 | curl.perform() | |
142 |
except pycurl.error |
|
142 | except pycurl.error: | |
143 | log.error('Failed to call endpoint url: %s using pycurl', url) |
|
143 | log.error('Failed to call endpoint url: %s using pycurl', url) | |
144 | raise |
|
144 | raise | |
145 |
|
145 |
This diff has been collapsed as it changes many lines, (1633 lines changed) Show them Hide them | |||||
@@ -19,6 +19,7 b'' | |||||
19 | """ |
|
19 | """ | |
20 | Base module for all VCS systems |
|
20 | Base module for all VCS systems | |
21 | """ |
|
21 | """ | |
|
22 | ||||
22 | import os |
|
23 | import os | |
23 | import re |
|
24 | import re | |
24 | import time |
|
25 | import time | |
@@ -39,19 +40,25 b' from rhodecode.lib.utils2 import safe_st' | |||||
39 | from rhodecode.lib.vcs.utils import author_name, author_email |
|
40 | from rhodecode.lib.vcs.utils import author_name, author_email | |
40 | from rhodecode.lib.vcs.conf import settings |
|
41 | from rhodecode.lib.vcs.conf import settings | |
41 | from rhodecode.lib.vcs.exceptions import ( |
|
42 | from rhodecode.lib.vcs.exceptions import ( | |
42 | CommitError, EmptyRepositoryError, NodeAlreadyAddedError, |
|
43 | CommitError, | |
43 | NodeAlreadyChangedError, NodeAlreadyExistsError, NodeAlreadyRemovedError, |
|
44 | EmptyRepositoryError, | |
44 | NodeDoesNotExistError, NodeNotChangedError, VCSError, |
|
45 | NodeAlreadyAddedError, | |
45 | ImproperArchiveTypeError, BranchDoesNotExistError, CommitDoesNotExistError, |
|
46 | NodeAlreadyChangedError, | |
46 | RepositoryError) |
|
47 | NodeAlreadyExistsError, | |
|
48 | NodeAlreadyRemovedError, | |||
|
49 | NodeDoesNotExistError, | |||
|
50 | NodeNotChangedError, | |||
|
51 | VCSError, | |||
|
52 | ImproperArchiveTypeError, | |||
|
53 | BranchDoesNotExistError, | |||
|
54 | CommitDoesNotExistError, | |||
|
55 | RepositoryError, | |||
|
56 | ) | |||
47 |
|
57 | |||
48 |
|
58 | |||
49 | log = logging.getLogger(__name__) |
|
59 | log = logging.getLogger(__name__) | |
50 |
|
60 | |||
51 |
|
61 | EMPTY_COMMIT_ID = "0" * 40 | ||
52 | FILEMODE_DEFAULT = 0o100644 |
|
|||
53 | FILEMODE_EXECUTABLE = 0o100755 |
|
|||
54 | EMPTY_COMMIT_ID = '0' * 40 |
|
|||
55 |
|
62 | |||
56 |
|
63 | |||
57 | @dataclasses.dataclass |
|
64 | @dataclasses.dataclass | |
@@ -67,12 +74,12 b' class Reference:' | |||||
67 |
|
74 | |||
68 | @property |
|
75 | @property | |
69 | def branch(self): |
|
76 | def branch(self): | |
70 |
if self.type == |
|
77 | if self.type == "branch": | |
71 | return self.name |
|
78 | return self.name | |
72 |
|
79 | |||
73 | @property |
|
80 | @property | |
74 | def bookmark(self): |
|
81 | def bookmark(self): | |
75 |
if self.type == |
|
82 | if self.type == "book": | |
76 | return self.name |
|
83 | return self.name | |
77 |
|
84 | |||
78 | @property |
|
85 | @property | |
@@ -80,11 +87,7 b' class Reference:' | |||||
80 | return reference_to_unicode(self) |
|
87 | return reference_to_unicode(self) | |
81 |
|
88 | |||
82 | def asdict(self): |
|
89 | def asdict(self): | |
83 | return dict( |
|
90 | return dict(type=self.type, name=self.name, commit_id=self.commit_id) | |
84 | type=self.type, |
|
|||
85 | name=self.name, |
|
|||
86 | commit_id=self.commit_id |
|
|||
87 | ) |
|
|||
88 |
|
91 | |||
89 |
|
92 | |||
90 | def unicode_to_reference(raw: str): |
|
93 | def unicode_to_reference(raw: str): | |
@@ -93,7 +96,7 b' def unicode_to_reference(raw: str):' | |||||
93 | If unicode evaluates to False it returns None. |
|
96 | If unicode evaluates to False it returns None. | |
94 | """ |
|
97 | """ | |
95 | if raw: |
|
98 | if raw: | |
96 |
refs = raw.split( |
|
99 | refs = raw.split(":") | |
97 | return Reference(*refs) |
|
100 | return Reference(*refs) | |
98 | else: |
|
101 | else: | |
99 | return None |
|
102 | return None | |
@@ -105,7 +108,7 b' def reference_to_unicode(ref: Reference)' | |||||
105 | If reference is None it returns None. |
|
108 | If reference is None it returns None. | |
106 | """ |
|
109 | """ | |
107 | if ref: |
|
110 | if ref: | |
108 |
return |
|
111 | return ":".join(ref) | |
109 | else: |
|
112 | else: | |
110 | return None |
|
113 | return None | |
111 |
|
114 | |||
@@ -194,47 +197,44 b' class UpdateFailureReason(object):' | |||||
194 |
|
197 | |||
195 |
|
198 | |||
196 | class MergeResponse(object): |
|
199 | class MergeResponse(object): | |
197 |
|
||||
198 | # uses .format(**metadata) for variables |
|
200 | # uses .format(**metadata) for variables | |
199 | MERGE_STATUS_MESSAGES = { |
|
201 | MERGE_STATUS_MESSAGES = { | |
200 | MergeFailureReason.NONE: lazy_ugettext( |
|
202 | MergeFailureReason.NONE: lazy_ugettext("This pull request can be automatically merged."), | |
201 | 'This pull request can be automatically merged.'), |
|
|||
202 | MergeFailureReason.UNKNOWN: lazy_ugettext( |
|
203 | MergeFailureReason.UNKNOWN: lazy_ugettext( | |
203 |
|
|
204 | "This pull request cannot be merged because of an unhandled exception. " "{exception}" | |
204 | '{exception}'), |
|
205 | ), | |
205 | MergeFailureReason.MERGE_FAILED: lazy_ugettext( |
|
206 | MergeFailureReason.MERGE_FAILED: lazy_ugettext( | |
206 |
|
|
207 | "This pull request cannot be merged because of merge conflicts. {unresolved_files}" | |
|
208 | ), | |||
207 | MergeFailureReason.PUSH_FAILED: lazy_ugettext( |
|
209 | MergeFailureReason.PUSH_FAILED: lazy_ugettext( | |
208 |
|
|
210 | "This pull request could not be merged because push to " "target:`{target}@{merge_commit}` failed." | |
209 | 'target:`{target}@{merge_commit}` failed.'), |
|
211 | ), | |
210 | MergeFailureReason.TARGET_IS_NOT_HEAD: lazy_ugettext( |
|
212 | MergeFailureReason.TARGET_IS_NOT_HEAD: lazy_ugettext( | |
211 |
|
|
213 | "This pull request cannot be merged because the target " "`{target_ref.name}` is not a head." | |
212 | '`{target_ref.name}` is not a head.'), |
|
214 | ), | |
213 | MergeFailureReason.HG_SOURCE_HAS_MORE_BRANCHES: lazy_ugettext( |
|
215 | MergeFailureReason.HG_SOURCE_HAS_MORE_BRANCHES: lazy_ugettext( | |
214 |
|
|
216 | "This pull request cannot be merged because the source contains " "more branches than the target." | |
215 | 'more branches than the target.'), |
|
217 | ), | |
216 | MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS: lazy_ugettext( |
|
218 | MergeFailureReason.HG_TARGET_HAS_MULTIPLE_HEADS: lazy_ugettext( | |
217 |
|
|
219 | "This pull request cannot be merged because the target `{target_ref.name}` " | |
218 |
|
|
220 | "has multiple heads: `{heads}`." | |
|
221 | ), | |||
219 | MergeFailureReason.TARGET_IS_LOCKED: lazy_ugettext( |
|
222 | MergeFailureReason.TARGET_IS_LOCKED: lazy_ugettext( | |
220 |
|
|
223 | "This pull request cannot be merged because the target repository is " "locked by {locked_by}." | |
221 | 'locked by {locked_by}.'), |
|
224 | ), | |
222 |
|
||||
223 | MergeFailureReason.MISSING_TARGET_REF: lazy_ugettext( |
|
225 | MergeFailureReason.MISSING_TARGET_REF: lazy_ugettext( | |
224 |
|
|
226 | "This pull request cannot be merged because the target " "reference `{target_ref.name}` is missing." | |
225 | 'reference `{target_ref.name}` is missing.'), |
|
227 | ), | |
226 | MergeFailureReason.MISSING_SOURCE_REF: lazy_ugettext( |
|
228 | MergeFailureReason.MISSING_SOURCE_REF: lazy_ugettext( | |
227 |
|
|
229 | "This pull request cannot be merged because the source " "reference `{source_ref.name}` is missing." | |
228 | 'reference `{source_ref.name}` is missing.'), |
|
230 | ), | |
229 | MergeFailureReason.SUBREPO_MERGE_FAILED: lazy_ugettext( |
|
231 | MergeFailureReason.SUBREPO_MERGE_FAILED: lazy_ugettext( | |
230 |
|
|
232 | "This pull request cannot be merged because of conflicts related " "to sub repositories." | |
231 | 'to sub repositories.'), |
|
233 | ), | |
232 |
|
||||
233 | # Deprecations |
|
234 | # Deprecations | |
234 | MergeFailureReason._DEPRECATED_MISSING_COMMIT: lazy_ugettext( |
|
235 | MergeFailureReason._DEPRECATED_MISSING_COMMIT: lazy_ugettext( | |
235 |
|
|
236 | "This pull request cannot be merged because the target or the " "source reference is missing." | |
236 | 'source reference is missing.'), |
|
237 | ), | |
237 |
|
||||
238 | } |
|
238 | } | |
239 |
|
239 | |||
240 | def __init__(self, possible, executed, merge_ref: Reference, failure_reason, metadata=None): |
|
240 | def __init__(self, possible, executed, merge_ref: Reference, failure_reason, metadata=None): | |
@@ -245,19 +245,20 b' class MergeResponse(object):' | |||||
245 | self.metadata = metadata or {} |
|
245 | self.metadata = metadata or {} | |
246 |
|
246 | |||
247 | def __repr__(self): |
|
247 | def __repr__(self): | |
248 |
return f |
|
248 | return f"<MergeResponse:{self.label} {self.failure_reason}>" | |
249 |
|
249 | |||
250 | def __eq__(self, other): |
|
250 | def __eq__(self, other): | |
251 | same_instance = isinstance(other, self.__class__) |
|
251 | same_instance = isinstance(other, self.__class__) | |
252 |
return |
|
252 | return ( | |
253 | and self.possible == other.possible \ |
|
253 | same_instance | |
254 |
|
|
254 | and self.possible == other.possible | |
255 |
|
|
255 | and self.executed == other.executed | |
|
256 | and self.failure_reason == other.failure_reason | |||
|
257 | ) | |||
256 |
|
258 | |||
257 | @property |
|
259 | @property | |
258 | def label(self): |
|
260 | def label(self): | |
259 | label_dict = dict((v, k) for k, v in MergeFailureReason.__dict__.items() if |
|
261 | label_dict = dict((v, k) for k, v in MergeFailureReason.__dict__.items() if not k.startswith("_")) | |
260 | not k.startswith('_')) |
|
|||
261 | return label_dict.get(self.failure_reason) |
|
262 | return label_dict.get(self.failure_reason) | |
262 |
|
263 | |||
263 | @property |
|
264 | @property | |
@@ -270,13 +271,12 b' class MergeResponse(object):' | |||||
270 | try: |
|
271 | try: | |
271 | return msg.format(**self.metadata) |
|
272 | return msg.format(**self.metadata) | |
272 | except Exception: |
|
273 | except Exception: | |
273 |
log.exception( |
|
274 | log.exception("Failed to format %s message", self) | |
274 | return msg |
|
275 | return msg | |
275 |
|
276 | |||
276 | def asdict(self): |
|
277 | def asdict(self): | |
277 | data = {} |
|
278 | data = {} | |
278 |
for k in [ |
|
279 | for k in ["possible", "executed", "merge_ref", "failure_reason", "merge_status_message"]: | |
279 | 'merge_status_message']: |
|
|||
280 | data[k] = getattr(self, k) |
|
280 | data[k] = getattr(self, k) | |
281 | return data |
|
281 | return data | |
282 |
|
282 | |||
@@ -289,607 +289,7 b' class SourceRefMissing(ValueError):' | |||||
289 | pass |
|
289 | pass | |
290 |
|
290 | |||
291 |
|
291 | |||
292 |
class Base |
|
292 | class BaseCommit: | |
293 | """ |
|
|||
294 | Base Repository for final backends |
|
|||
295 |
|
||||
296 | .. attribute:: DEFAULT_BRANCH_NAME |
|
|||
297 |
|
||||
298 | name of default branch (i.e. "trunk" for svn, "master" for git etc. |
|
|||
299 |
|
||||
300 | .. attribute:: commit_ids |
|
|||
301 |
|
||||
302 | list of all available commit ids, in ascending order |
|
|||
303 |
|
||||
304 | .. attribute:: path |
|
|||
305 |
|
||||
306 | absolute path to the repository |
|
|||
307 |
|
||||
308 | .. attribute:: bookmarks |
|
|||
309 |
|
||||
310 | Mapping from name to :term:`Commit ID` of the bookmark. Empty in case |
|
|||
311 | there are no bookmarks or the backend implementation does not support |
|
|||
312 | bookmarks. |
|
|||
313 |
|
||||
314 | .. attribute:: tags |
|
|||
315 |
|
||||
316 | Mapping from name to :term:`Commit ID` of the tag. |
|
|||
317 |
|
||||
318 | """ |
|
|||
319 |
|
||||
320 | DEFAULT_BRANCH_NAME = None |
|
|||
321 | DEFAULT_CONTACT = "Unknown" |
|
|||
322 | DEFAULT_DESCRIPTION = "unknown" |
|
|||
323 | EMPTY_COMMIT_ID = '0' * 40 |
|
|||
324 | COMMIT_ID_PAT = re.compile(r'[0-9a-fA-F]{40}') |
|
|||
325 |
|
||||
326 | path = None |
|
|||
327 |
|
||||
328 | _is_empty = None |
|
|||
329 | _commit_ids = {} |
|
|||
330 |
|
||||
331 | def __init__(self, repo_path, config=None, create=False, **kwargs): |
|
|||
332 | """ |
|
|||
333 | Initializes repository. Raises RepositoryError if repository could |
|
|||
334 | not be find at the given ``repo_path`` or directory at ``repo_path`` |
|
|||
335 | exists and ``create`` is set to True. |
|
|||
336 |
|
||||
337 | :param repo_path: local path of the repository |
|
|||
338 | :param config: repository configuration |
|
|||
339 | :param create=False: if set to True, would try to create repository. |
|
|||
340 | :param src_url=None: if set, should be proper url from which repository |
|
|||
341 | would be cloned; requires ``create`` parameter to be set to True - |
|
|||
342 | raises RepositoryError if src_url is set and create evaluates to |
|
|||
343 | False |
|
|||
344 | """ |
|
|||
345 | raise NotImplementedError |
|
|||
346 |
|
||||
347 | def __repr__(self): |
|
|||
348 | return f'<{self.__class__.__name__} at {self.path}>' |
|
|||
349 |
|
||||
350 | def __len__(self): |
|
|||
351 | return self.count() |
|
|||
352 |
|
||||
353 | def __eq__(self, other): |
|
|||
354 | same_instance = isinstance(other, self.__class__) |
|
|||
355 | return same_instance and other.path == self.path |
|
|||
356 |
|
||||
357 | def __ne__(self, other): |
|
|||
358 | return not self.__eq__(other) |
|
|||
359 |
|
||||
360 | def get_create_shadow_cache_pr_path(self, db_repo): |
|
|||
361 | path = db_repo.cached_diffs_dir |
|
|||
362 | if not os.path.exists(path): |
|
|||
363 | os.makedirs(path, 0o755) |
|
|||
364 | return path |
|
|||
365 |
|
||||
366 | @classmethod |
|
|||
367 | def get_default_config(cls, default=None): |
|
|||
368 | config = Config() |
|
|||
369 | if default and isinstance(default, list): |
|
|||
370 | for section, key, val in default: |
|
|||
371 | config.set(section, key, val) |
|
|||
372 | return config |
|
|||
373 |
|
||||
374 | @LazyProperty |
|
|||
375 | def _remote(self): |
|
|||
376 | raise NotImplementedError |
|
|||
377 |
|
||||
378 | def _heads(self, branch=None): |
|
|||
379 | return [] |
|
|||
380 |
|
||||
381 | @LazyProperty |
|
|||
382 | def EMPTY_COMMIT(self): |
|
|||
383 | return EmptyCommit(self.EMPTY_COMMIT_ID) |
|
|||
384 |
|
||||
385 | @LazyProperty |
|
|||
386 | def alias(self): |
|
|||
387 | for k, v in settings.BACKENDS.items(): |
|
|||
388 | if v.split('.')[-1] == str(self.__class__.__name__): |
|
|||
389 | return k |
|
|||
390 |
|
||||
391 | @LazyProperty |
|
|||
392 | def name(self): |
|
|||
393 | return safe_str(os.path.basename(self.path)) |
|
|||
394 |
|
||||
395 | @LazyProperty |
|
|||
396 | def description(self): |
|
|||
397 | raise NotImplementedError |
|
|||
398 |
|
||||
399 | def refs(self): |
|
|||
400 | """ |
|
|||
401 | returns a `dict` with branches, bookmarks, tags, and closed_branches |
|
|||
402 | for this repository |
|
|||
403 | """ |
|
|||
404 | return dict( |
|
|||
405 | branches=self.branches, |
|
|||
406 | branches_closed=self.branches_closed, |
|
|||
407 | tags=self.tags, |
|
|||
408 | bookmarks=self.bookmarks |
|
|||
409 | ) |
|
|||
410 |
|
||||
411 | @LazyProperty |
|
|||
412 | def branches(self): |
|
|||
413 | """ |
|
|||
414 | A `dict` which maps branch names to commit ids. |
|
|||
415 | """ |
|
|||
416 | raise NotImplementedError |
|
|||
417 |
|
||||
418 | @LazyProperty |
|
|||
419 | def branches_closed(self): |
|
|||
420 | """ |
|
|||
421 | A `dict` which maps tags names to commit ids. |
|
|||
422 | """ |
|
|||
423 | raise NotImplementedError |
|
|||
424 |
|
||||
425 | @LazyProperty |
|
|||
426 | def bookmarks(self): |
|
|||
427 | """ |
|
|||
428 | A `dict` which maps tags names to commit ids. |
|
|||
429 | """ |
|
|||
430 | raise NotImplementedError |
|
|||
431 |
|
||||
432 | @LazyProperty |
|
|||
433 | def tags(self): |
|
|||
434 | """ |
|
|||
435 | A `dict` which maps tags names to commit ids. |
|
|||
436 | """ |
|
|||
437 | raise NotImplementedError |
|
|||
438 |
|
||||
439 | @LazyProperty |
|
|||
440 | def size(self): |
|
|||
441 | """ |
|
|||
442 | Returns combined size in bytes for all repository files |
|
|||
443 | """ |
|
|||
444 | tip = self.get_commit() |
|
|||
445 | return tip.size |
|
|||
446 |
|
||||
447 | def size_at_commit(self, commit_id): |
|
|||
448 | commit = self.get_commit(commit_id) |
|
|||
449 | return commit.size |
|
|||
450 |
|
||||
451 | def _check_for_empty(self): |
|
|||
452 | no_commits = len(self._commit_ids) == 0 |
|
|||
453 | if no_commits: |
|
|||
454 | # check on remote to be sure |
|
|||
455 | return self._remote.is_empty() |
|
|||
456 | else: |
|
|||
457 | return False |
|
|||
458 |
|
||||
459 | def is_empty(self): |
|
|||
460 | if rhodecode.is_test: |
|
|||
461 | return self._check_for_empty() |
|
|||
462 |
|
||||
463 | if self._is_empty is None: |
|
|||
464 | # cache empty for production, but not tests |
|
|||
465 | self._is_empty = self._check_for_empty() |
|
|||
466 |
|
||||
467 | return self._is_empty |
|
|||
468 |
|
||||
469 | @staticmethod |
|
|||
470 | def check_url(url, config): |
|
|||
471 | """ |
|
|||
472 | Function will check given url and try to verify if it's a valid |
|
|||
473 | link. |
|
|||
474 | """ |
|
|||
475 | raise NotImplementedError |
|
|||
476 |
|
||||
477 | @staticmethod |
|
|||
478 | def is_valid_repository(path): |
|
|||
479 | """ |
|
|||
480 | Check if given `path` contains a valid repository of this backend |
|
|||
481 | """ |
|
|||
482 | raise NotImplementedError |
|
|||
483 |
|
||||
484 | # ========================================================================== |
|
|||
485 | # COMMITS |
|
|||
486 | # ========================================================================== |
|
|||
487 |
|
||||
488 | @CachedProperty |
|
|||
489 | def commit_ids(self): |
|
|||
490 | raise NotImplementedError |
|
|||
491 |
|
||||
492 | def append_commit_id(self, commit_id): |
|
|||
493 | if commit_id not in self.commit_ids: |
|
|||
494 | self._rebuild_cache(self.commit_ids + [commit_id]) |
|
|||
495 |
|
||||
496 | # clear cache |
|
|||
497 | self._invalidate_prop_cache('commit_ids') |
|
|||
498 | self._is_empty = False |
|
|||
499 |
|
||||
500 | def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, |
|
|||
501 | translate_tag=None, maybe_unreachable=False, reference_obj=None): |
|
|||
502 | """ |
|
|||
503 | Returns instance of `BaseCommit` class. If `commit_id` and `commit_idx` |
|
|||
504 | are both None, most recent commit is returned. |
|
|||
505 |
|
||||
506 | :param pre_load: Optional. List of commit attributes to load. |
|
|||
507 |
|
||||
508 | :raises ``EmptyRepositoryError``: if there are no commits |
|
|||
509 | """ |
|
|||
510 | raise NotImplementedError |
|
|||
511 |
|
||||
512 | def __iter__(self): |
|
|||
513 | for commit_id in self.commit_ids: |
|
|||
514 | yield self.get_commit(commit_id=commit_id) |
|
|||
515 |
|
||||
516 | def get_commits( |
|
|||
517 | self, start_id=None, end_id=None, start_date=None, end_date=None, |
|
|||
518 | branch_name=None, show_hidden=False, pre_load=None, translate_tags=None): |
|
|||
519 | """ |
|
|||
520 | Returns iterator of `BaseCommit` objects from start to end |
|
|||
521 | not inclusive. This should behave just like a list, ie. end is not |
|
|||
522 | inclusive. |
|
|||
523 |
|
||||
524 | :param start_id: None or str, must be a valid commit id |
|
|||
525 | :param end_id: None or str, must be a valid commit id |
|
|||
526 | :param start_date: |
|
|||
527 | :param end_date: |
|
|||
528 | :param branch_name: |
|
|||
529 | :param show_hidden: |
|
|||
530 | :param pre_load: |
|
|||
531 | :param translate_tags: |
|
|||
532 | """ |
|
|||
533 | raise NotImplementedError |
|
|||
534 |
|
||||
535 | def __getitem__(self, key): |
|
|||
536 | """ |
|
|||
537 | Allows index based access to the commit objects of this repository. |
|
|||
538 | """ |
|
|||
539 | pre_load = ["author", "branch", "date", "message", "parents"] |
|
|||
540 | if isinstance(key, slice): |
|
|||
541 | return self._get_range(key, pre_load) |
|
|||
542 | return self.get_commit(commit_idx=key, pre_load=pre_load) |
|
|||
543 |
|
||||
544 | def _get_range(self, slice_obj, pre_load): |
|
|||
545 | for commit_id in self.commit_ids.__getitem__(slice_obj): |
|
|||
546 | yield self.get_commit(commit_id=commit_id, pre_load=pre_load) |
|
|||
547 |
|
||||
548 | def count(self): |
|
|||
549 | return len(self.commit_ids) |
|
|||
550 |
|
||||
551 | def tag(self, name, user, commit_id=None, message=None, date=None, **opts): |
|
|||
552 | """ |
|
|||
553 | Creates and returns a tag for the given ``commit_id``. |
|
|||
554 |
|
||||
555 | :param name: name for new tag |
|
|||
556 | :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>" |
|
|||
557 | :param commit_id: commit id for which new tag would be created |
|
|||
558 | :param message: message of the tag's commit |
|
|||
559 | :param date: date of tag's commit |
|
|||
560 |
|
||||
561 | :raises TagAlreadyExistError: if tag with same name already exists |
|
|||
562 | """ |
|
|||
563 | raise NotImplementedError |
|
|||
564 |
|
||||
565 | def remove_tag(self, name, user, message=None, date=None): |
|
|||
566 | """ |
|
|||
567 | Removes tag with the given ``name``. |
|
|||
568 |
|
||||
569 | :param name: name of the tag to be removed |
|
|||
570 | :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>" |
|
|||
571 | :param message: message of the tag's removal commit |
|
|||
572 | :param date: date of tag's removal commit |
|
|||
573 |
|
||||
574 | :raises TagDoesNotExistError: if tag with given name does not exists |
|
|||
575 | """ |
|
|||
576 | raise NotImplementedError |
|
|||
577 |
|
||||
578 | def get_diff( |
|
|||
579 | self, commit1, commit2, path=None, ignore_whitespace=False, |
|
|||
580 | context=3, path1=None): |
|
|||
581 | """ |
|
|||
582 | Returns (git like) *diff*, as plain text. Shows changes introduced by |
|
|||
583 | `commit2` since `commit1`. |
|
|||
584 |
|
||||
585 | :param commit1: Entry point from which diff is shown. Can be |
|
|||
586 | ``self.EMPTY_COMMIT`` - in this case, patch showing all |
|
|||
587 | the changes since empty state of the repository until `commit2` |
|
|||
588 | :param commit2: Until which commit changes should be shown. |
|
|||
589 | :param path: Can be set to a path of a file to create a diff of that |
|
|||
590 | file. If `path1` is also set, this value is only associated to |
|
|||
591 | `commit2`. |
|
|||
592 | :param ignore_whitespace: If set to ``True``, would not show whitespace |
|
|||
593 | changes. Defaults to ``False``. |
|
|||
594 | :param context: How many lines before/after changed lines should be |
|
|||
595 | shown. Defaults to ``3``. |
|
|||
596 | :param path1: Can be set to a path to associate with `commit1`. This |
|
|||
597 | parameter works only for backends which support diff generation for |
|
|||
598 | different paths. Other backends will raise a `ValueError` if `path1` |
|
|||
599 | is set and has a different value than `path`. |
|
|||
600 | :param file_path: filter this diff by given path pattern |
|
|||
601 | """ |
|
|||
602 | raise NotImplementedError |
|
|||
603 |
|
||||
604 | def strip(self, commit_id, branch=None): |
|
|||
605 | """ |
|
|||
606 | Strip given commit_id from the repository |
|
|||
607 | """ |
|
|||
608 | raise NotImplementedError |
|
|||
609 |
|
||||
610 | def get_common_ancestor(self, commit_id1, commit_id2, repo2): |
|
|||
611 | """ |
|
|||
612 | Return a latest common ancestor commit if one exists for this repo |
|
|||
613 | `commit_id1` vs `commit_id2` from `repo2`. |
|
|||
614 |
|
||||
615 | :param commit_id1: Commit it from this repository to use as a |
|
|||
616 | target for the comparison. |
|
|||
617 | :param commit_id2: Source commit id to use for comparison. |
|
|||
618 | :param repo2: Source repository to use for comparison. |
|
|||
619 | """ |
|
|||
620 | raise NotImplementedError |
|
|||
621 |
|
||||
622 | def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None): |
|
|||
623 | """ |
|
|||
624 | Compare this repository's revision `commit_id1` with `commit_id2`. |
|
|||
625 |
|
||||
626 | Returns a tuple(commits, ancestor) that would be merged from |
|
|||
627 | `commit_id2`. Doing a normal compare (``merge=False``), ``None`` |
|
|||
628 | will be returned as ancestor. |
|
|||
629 |
|
||||
630 | :param commit_id1: Commit it from this repository to use as a |
|
|||
631 | target for the comparison. |
|
|||
632 | :param commit_id2: Source commit id to use for comparison. |
|
|||
633 | :param repo2: Source repository to use for comparison. |
|
|||
634 | :param merge: If set to ``True`` will do a merge compare which also |
|
|||
635 | returns the common ancestor. |
|
|||
636 | :param pre_load: Optional. List of commit attributes to load. |
|
|||
637 | """ |
|
|||
638 | raise NotImplementedError |
|
|||
639 |
|
||||
640 | def merge(self, repo_id, workspace_id, target_ref, source_repo, source_ref, |
|
|||
641 | user_name='', user_email='', message='', dry_run=False, |
|
|||
642 | use_rebase=False, close_branch=False): |
|
|||
643 | """ |
|
|||
644 | Merge the revisions specified in `source_ref` from `source_repo` |
|
|||
645 | onto the `target_ref` of this repository. |
|
|||
646 |
|
||||
647 | `source_ref` and `target_ref` are named tupls with the following |
|
|||
648 | fields `type`, `name` and `commit_id`. |
|
|||
649 |
|
||||
650 | Returns a MergeResponse named tuple with the following fields |
|
|||
651 | 'possible', 'executed', 'source_commit', 'target_commit', |
|
|||
652 | 'merge_commit'. |
|
|||
653 |
|
||||
654 | :param repo_id: `repo_id` target repo id. |
|
|||
655 | :param workspace_id: `workspace_id` unique identifier. |
|
|||
656 | :param target_ref: `target_ref` points to the commit on top of which |
|
|||
657 | the `source_ref` should be merged. |
|
|||
658 | :param source_repo: The repository that contains the commits to be |
|
|||
659 | merged. |
|
|||
660 | :param source_ref: `source_ref` points to the topmost commit from |
|
|||
661 | the `source_repo` which should be merged. |
|
|||
662 | :param user_name: Merge commit `user_name`. |
|
|||
663 | :param user_email: Merge commit `user_email`. |
|
|||
664 | :param message: Merge commit `message`. |
|
|||
665 | :param dry_run: If `True` the merge will not take place. |
|
|||
666 | :param use_rebase: If `True` commits from the source will be rebased |
|
|||
667 | on top of the target instead of being merged. |
|
|||
668 | :param close_branch: If `True` branch will be close before merging it |
|
|||
669 | """ |
|
|||
670 | if dry_run: |
|
|||
671 | message = message or settings.MERGE_DRY_RUN_MESSAGE |
|
|||
672 | user_email = user_email or settings.MERGE_DRY_RUN_EMAIL |
|
|||
673 | user_name = user_name or settings.MERGE_DRY_RUN_USER |
|
|||
674 | else: |
|
|||
675 | if not user_name: |
|
|||
676 | raise ValueError('user_name cannot be empty') |
|
|||
677 | if not user_email: |
|
|||
678 | raise ValueError('user_email cannot be empty') |
|
|||
679 | if not message: |
|
|||
680 | raise ValueError('message cannot be empty') |
|
|||
681 |
|
||||
682 | try: |
|
|||
683 | return self._merge_repo( |
|
|||
684 | repo_id, workspace_id, target_ref, source_repo, |
|
|||
685 | source_ref, message, user_name, user_email, dry_run=dry_run, |
|
|||
686 | use_rebase=use_rebase, close_branch=close_branch) |
|
|||
687 | except RepositoryError as exc: |
|
|||
688 | log.exception('Unexpected failure when running merge, dry-run=%s', dry_run) |
|
|||
689 | return MergeResponse( |
|
|||
690 | False, False, None, MergeFailureReason.UNKNOWN, |
|
|||
691 | metadata={'exception': str(exc)}) |
|
|||
692 |
|
||||
693 | def _merge_repo(self, repo_id, workspace_id, target_ref, |
|
|||
694 | source_repo, source_ref, merge_message, |
|
|||
695 | merger_name, merger_email, dry_run=False, |
|
|||
696 | use_rebase=False, close_branch=False): |
|
|||
697 | """Internal implementation of merge.""" |
|
|||
698 | raise NotImplementedError |
|
|||
699 |
|
||||
700 | def _maybe_prepare_merge_workspace( |
|
|||
701 | self, repo_id, workspace_id, target_ref, source_ref): |
|
|||
702 | """ |
|
|||
703 | Create the merge workspace. |
|
|||
704 |
|
||||
705 | :param workspace_id: `workspace_id` unique identifier. |
|
|||
706 | """ |
|
|||
707 | raise NotImplementedError |
|
|||
708 |
|
||||
709 | @classmethod |
|
|||
710 | def _get_legacy_shadow_repository_path(cls, repo_path, workspace_id): |
|
|||
711 | """ |
|
|||
712 | Legacy version that was used before. We still need it for |
|
|||
713 | backward compat |
|
|||
714 | """ |
|
|||
715 | return os.path.join( |
|
|||
716 | os.path.dirname(repo_path), |
|
|||
717 | f'.__shadow_{os.path.basename(repo_path)}_{workspace_id}') |
|
|||
718 |
|
||||
719 | @classmethod |
|
|||
720 | def _get_shadow_repository_path(cls, repo_path, repo_id, workspace_id): |
|
|||
721 | # The name of the shadow repository must start with '.', so it is |
|
|||
722 | # skipped by 'rhodecode.lib.utils.get_filesystem_repos'. |
|
|||
723 | legacy_repository_path = cls._get_legacy_shadow_repository_path(repo_path, workspace_id) |
|
|||
724 | if os.path.exists(legacy_repository_path): |
|
|||
725 | return legacy_repository_path |
|
|||
726 | else: |
|
|||
727 | return os.path.join( |
|
|||
728 | os.path.dirname(repo_path), |
|
|||
729 | f'.__shadow_repo_{repo_id}_{workspace_id}') |
|
|||
730 |
|
||||
731 | def cleanup_merge_workspace(self, repo_id, workspace_id): |
|
|||
732 | """ |
|
|||
733 | Remove merge workspace. |
|
|||
734 |
|
||||
735 | This function MUST not fail in case there is no workspace associated to |
|
|||
736 | the given `workspace_id`. |
|
|||
737 |
|
||||
738 | :param workspace_id: `workspace_id` unique identifier. |
|
|||
739 | """ |
|
|||
740 | shadow_repository_path = self._get_shadow_repository_path( |
|
|||
741 | self.path, repo_id, workspace_id) |
|
|||
742 | shadow_repository_path_del = '{}.{}.delete'.format( |
|
|||
743 | shadow_repository_path, time.time()) |
|
|||
744 |
|
||||
745 | # move the shadow repo, so it never conflicts with the one used. |
|
|||
746 | # we use this method because shutil.rmtree had some edge case problems |
|
|||
747 | # removing symlinked repositories |
|
|||
748 | if not os.path.isdir(shadow_repository_path): |
|
|||
749 | return |
|
|||
750 |
|
||||
751 | shutil.move(shadow_repository_path, shadow_repository_path_del) |
|
|||
752 | try: |
|
|||
753 | shutil.rmtree(shadow_repository_path_del, ignore_errors=False) |
|
|||
754 | except Exception: |
|
|||
755 | log.exception('Failed to gracefully remove shadow repo under %s', |
|
|||
756 | shadow_repository_path_del) |
|
|||
757 | shutil.rmtree(shadow_repository_path_del, ignore_errors=True) |
|
|||
758 |
|
||||
759 | # ========== # |
|
|||
760 | # COMMIT API # |
|
|||
761 | # ========== # |
|
|||
762 |
|
||||
763 | @LazyProperty |
|
|||
764 | def in_memory_commit(self): |
|
|||
765 | """ |
|
|||
766 | Returns :class:`InMemoryCommit` object for this repository. |
|
|||
767 | """ |
|
|||
768 | raise NotImplementedError |
|
|||
769 |
|
||||
770 | # ======================== # |
|
|||
771 | # UTILITIES FOR SUBCLASSES # |
|
|||
772 | # ======================== # |
|
|||
773 |
|
||||
774 | def _validate_diff_commits(self, commit1, commit2): |
|
|||
775 | """ |
|
|||
776 | Validates that the given commits are related to this repository. |
|
|||
777 |
|
||||
778 | Intended as a utility for sub classes to have a consistent validation |
|
|||
779 | of input parameters in methods like :meth:`get_diff`. |
|
|||
780 | """ |
|
|||
781 | self._validate_commit(commit1) |
|
|||
782 | self._validate_commit(commit2) |
|
|||
783 | if (isinstance(commit1, EmptyCommit) and |
|
|||
784 | isinstance(commit2, EmptyCommit)): |
|
|||
785 | raise ValueError("Cannot compare two empty commits") |
|
|||
786 |
|
||||
787 | def _validate_commit(self, commit): |
|
|||
788 | if not isinstance(commit, BaseCommit): |
|
|||
789 | raise TypeError( |
|
|||
790 | "%s is not of type BaseCommit" % repr(commit)) |
|
|||
791 | if commit.repository != self and not isinstance(commit, EmptyCommit): |
|
|||
792 | raise ValueError( |
|
|||
793 | "Commit %s must be a valid commit from this repository %s, " |
|
|||
794 | "related to this repository instead %s." % |
|
|||
795 | (commit, self, commit.repository)) |
|
|||
796 |
|
||||
797 | def _validate_commit_id(self, commit_id): |
|
|||
798 | if not isinstance(commit_id, str): |
|
|||
799 | raise TypeError(f"commit_id must be a string value got {type(commit_id)} instead") |
|
|||
800 |
|
||||
801 | def _validate_commit_idx(self, commit_idx): |
|
|||
802 | if not isinstance(commit_idx, int): |
|
|||
803 | raise TypeError(f"commit_idx must be a numeric value, got {type(commit_idx)}") |
|
|||
804 |
|
||||
805 | def _validate_branch_name(self, branch_name): |
|
|||
806 | if branch_name and branch_name not in self.branches_all: |
|
|||
807 | msg = (f"Branch {branch_name} not found in {self}") |
|
|||
808 | raise BranchDoesNotExistError(msg) |
|
|||
809 |
|
||||
810 | # |
|
|||
811 | # Supporting deprecated API parts |
|
|||
812 | # TODO: johbo: consider to move this into a mixin |
|
|||
813 | # |
|
|||
814 |
|
||||
815 | @property |
|
|||
816 | def EMPTY_CHANGESET(self): |
|
|||
817 | warnings.warn( |
|
|||
818 | "Use EMPTY_COMMIT or EMPTY_COMMIT_ID instead", DeprecationWarning) |
|
|||
819 | return self.EMPTY_COMMIT_ID |
|
|||
820 |
|
||||
821 | @property |
|
|||
822 | def revisions(self): |
|
|||
823 | warnings.warn("Use commits attribute instead", DeprecationWarning) |
|
|||
824 | return self.commit_ids |
|
|||
825 |
|
||||
826 | @revisions.setter |
|
|||
827 | def revisions(self, value): |
|
|||
828 | warnings.warn("Use commits attribute instead", DeprecationWarning) |
|
|||
829 | self.commit_ids = value |
|
|||
830 |
|
||||
831 | def get_changeset(self, revision=None, pre_load=None): |
|
|||
832 | warnings.warn("Use get_commit instead", DeprecationWarning) |
|
|||
833 | commit_id = None |
|
|||
834 | commit_idx = None |
|
|||
835 | if isinstance(revision, str): |
|
|||
836 | commit_id = revision |
|
|||
837 | else: |
|
|||
838 | commit_idx = revision |
|
|||
839 | return self.get_commit( |
|
|||
840 | commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load) |
|
|||
841 |
|
||||
842 | def get_changesets( |
|
|||
843 | self, start=None, end=None, start_date=None, end_date=None, |
|
|||
844 | branch_name=None, pre_load=None): |
|
|||
845 | warnings.warn("Use get_commits instead", DeprecationWarning) |
|
|||
846 | start_id = self._revision_to_commit(start) |
|
|||
847 | end_id = self._revision_to_commit(end) |
|
|||
848 | return self.get_commits( |
|
|||
849 | start_id=start_id, end_id=end_id, start_date=start_date, |
|
|||
850 | end_date=end_date, branch_name=branch_name, pre_load=pre_load) |
|
|||
851 |
|
||||
852 | def _revision_to_commit(self, revision): |
|
|||
853 | """ |
|
|||
854 | Translates a revision to a commit_id |
|
|||
855 |
|
||||
856 | Helps to support the old changeset based API which allows to use |
|
|||
857 | commit ids and commit indices interchangeable. |
|
|||
858 | """ |
|
|||
859 | if revision is None: |
|
|||
860 | return revision |
|
|||
861 |
|
||||
862 | if isinstance(revision, str): |
|
|||
863 | commit_id = revision |
|
|||
864 | else: |
|
|||
865 | commit_id = self.commit_ids[revision] |
|
|||
866 | return commit_id |
|
|||
867 |
|
||||
868 | @property |
|
|||
869 | def in_memory_changeset(self): |
|
|||
870 | warnings.warn("Use in_memory_commit instead", DeprecationWarning) |
|
|||
871 | return self.in_memory_commit |
|
|||
872 |
|
||||
873 | def get_path_permissions(self, username): |
|
|||
874 | """ |
|
|||
875 | Returns a path permission checker or None if not supported |
|
|||
876 |
|
||||
877 | :param username: session user name |
|
|||
878 | :return: an instance of BasePathPermissionChecker or None |
|
|||
879 | """ |
|
|||
880 | return None |
|
|||
881 |
|
||||
882 | def install_hooks(self, force=False): |
|
|||
883 | return self._remote.install_hooks(force) |
|
|||
884 |
|
||||
885 | def get_hooks_info(self): |
|
|||
886 | return self._remote.get_hooks_info() |
|
|||
887 |
|
||||
888 | def vcsserver_invalidate_cache(self, delete=False): |
|
|||
889 | return self._remote.vcsserver_invalidate_cache(delete) |
|
|||
890 |
|
||||
891 |
|
||||
892 | class BaseCommit(object): |
|
|||
893 | """ |
|
293 | """ | |
894 | Each backend should implement it's commit representation. |
|
294 | Each backend should implement it's commit representation. | |
895 |
|
295 | |||
@@ -933,6 +333,7 b' class BaseCommit(object):' | |||||
933 | list of parent commits |
|
333 | list of parent commits | |
934 |
|
334 | |||
935 | """ |
|
335 | """ | |
|
336 | ||||
936 | repository = None |
|
337 | repository = None | |
937 | branch = None |
|
338 | branch = None | |
938 |
|
339 | |||
@@ -942,7 +343,7 b' class BaseCommit(object):' | |||||
942 | value as ``None``. |
|
343 | value as ``None``. | |
943 | """ |
|
344 | """ | |
944 |
|
345 | |||
945 |
_ARCHIVE_PREFIX_TEMPLATE = |
|
346 | _ARCHIVE_PREFIX_TEMPLATE = "{repo_name}-{short_id}" | |
946 | """ |
|
347 | """ | |
947 | This template is used to generate a default prefix for repository archives |
|
348 | This template is used to generate a default prefix for repository archives | |
948 | if no prefix has been specified. |
|
349 | if no prefix has been specified. | |
@@ -952,7 +353,7 b' class BaseCommit(object):' | |||||
952 | return self.__str__() |
|
353 | return self.__str__() | |
953 |
|
354 | |||
954 | def __str__(self): |
|
355 | def __str__(self): | |
955 |
return f |
|
356 | return f"<{self.__class__.__name__} at {self.idx}:{self.short_id}>" | |
956 |
|
357 | |||
957 | def __eq__(self, other): |
|
358 | def __eq__(self, other): | |
958 | same_instance = isinstance(other, self.__class__) |
|
359 | same_instance = isinstance(other, self.__class__) | |
@@ -962,26 +363,26 b' class BaseCommit(object):' | |||||
962 | parents = [] |
|
363 | parents = [] | |
963 | try: |
|
364 | try: | |
964 | for parent in self.parents: |
|
365 | for parent in self.parents: | |
965 |
parents.append({ |
|
366 | parents.append({"raw_id": parent.raw_id}) | |
966 | except NotImplementedError: |
|
367 | except NotImplementedError: | |
967 | # empty commit doesn't have parents implemented |
|
368 | # empty commit doesn't have parents implemented | |
968 | pass |
|
369 | pass | |
969 |
|
370 | |||
970 | return { |
|
371 | return { | |
971 |
|
|
372 | "short_id": self.short_id, | |
972 |
|
|
373 | "raw_id": self.raw_id, | |
973 |
|
|
374 | "revision": self.idx, | |
974 |
|
|
375 | "message": self.message, | |
975 |
|
|
376 | "date": self.date, | |
976 |
|
|
377 | "author": self.author, | |
977 |
|
|
378 | "parents": parents, | |
978 |
|
|
379 | "branch": self.branch, | |
979 | } |
|
380 | } | |
980 |
|
381 | |||
981 | def __getstate__(self): |
|
382 | def __getstate__(self): | |
982 | d = self.__dict__.copy() |
|
383 | d = self.__dict__.copy() | |
983 |
d.pop( |
|
384 | d.pop("_remote", None) | |
984 |
d.pop( |
|
385 | d.pop("repository", None) | |
985 | return d |
|
386 | return d | |
986 |
|
387 | |||
987 | def get_remote(self): |
|
388 | def get_remote(self): | |
@@ -992,9 +393,9 b' class BaseCommit(object):' | |||||
992 |
|
393 | |||
993 | def _get_refs(self): |
|
394 | def _get_refs(self): | |
994 | return { |
|
395 | return { | |
995 |
|
|
396 | "branches": [self.branch] if self.branch else [], | |
996 |
|
|
397 | "bookmarks": getattr(self, "bookmarks", []), | |
997 |
|
|
398 | "tags": self.tags, | |
998 | } |
|
399 | } | |
999 |
|
400 | |||
1000 | @LazyProperty |
|
401 | @LazyProperty | |
@@ -1118,7 +519,7 b' class BaseCommit(object):' | |||||
1118 | """ |
|
519 | """ | |
1119 | raise NotImplementedError |
|
520 | raise NotImplementedError | |
1120 |
|
521 | |||
1121 | def is_link(self, path): |
|
522 | def is_link(self, path: bytes): | |
1122 | """ |
|
523 | """ | |
1123 | Returns ``True`` if given `path` is a symlink |
|
524 | Returns ``True`` if given `path` is a symlink | |
1124 | """ |
|
525 | """ | |
@@ -1154,25 +555,24 b' class BaseCommit(object):' | |||||
1154 | """ |
|
555 | """ | |
1155 | raise NotImplementedError |
|
556 | raise NotImplementedError | |
1156 |
|
557 | |||
1157 | def get_path_commit(self, path, pre_load=None): |
|
558 | def get_path_commit(self, path: bytes, pre_load=None): | |
1158 | """ |
|
559 | """ | |
1159 | Returns last commit of the file at the given `path`. |
|
560 | Returns last commit of the file at the given `path`. | |
1160 |
|
561 | |||
1161 | :param pre_load: Optional. List of commit attributes to load. |
|
|||
1162 | """ |
|
562 | """ | |
1163 | commits = self.get_path_history(path, limit=1, pre_load=pre_load) |
|
563 | commits = self.get_path_history(path, limit=1, pre_load=pre_load) | |
1164 | if not commits: |
|
564 | if not commits: | |
1165 | raise RepositoryError( |
|
565 | raise RepositoryError( | |
1166 |
|
|
566 | f"Failed to fetch history for path {path}. Please check if such path exists in your repository" | |
1167 | 'Please check if such path exists in your repository'.format( |
|
567 | ) | |
1168 | path)) |
|
|||
1169 | return commits[0] |
|
568 | return commits[0] | |
1170 |
|
569 | |||
1171 | def get_path_history(self, path, limit=None, pre_load=None): |
|
570 | def get_path_history(self, path: bytes, limit=None, pre_load=None): | |
1172 | """ |
|
571 | """ | |
1173 | Returns history of file as reversed list of :class:`BaseCommit` |
|
572 | Returns history of file as reversed list of :class:`BaseCommit` | |
1174 | objects for which file at given `path` has been modified. |
|
573 | objects for which file at given `path` has been modified. | |
1175 |
|
574 | |||
|
575 | :param path: file path or dir path | |||
1176 | :param limit: Optional. Allows to limit the size of the returned |
|
576 | :param limit: Optional. Allows to limit the size of the returned | |
1177 | history. This is intended as a hint to the underlying backend, so |
|
577 | history. This is intended as a hint to the underlying backend, so | |
1178 | that it can apply optimizations depending on the limit. |
|
578 | that it can apply optimizations depending on the limit. | |
@@ -1180,16 +580,17 b' class BaseCommit(object):' | |||||
1180 | """ |
|
580 | """ | |
1181 | raise NotImplementedError |
|
581 | raise NotImplementedError | |
1182 |
|
582 | |||
1183 | def get_file_annotate(self, path, pre_load=None): |
|
583 | def get_file_annotate(self, path: bytes, pre_load=None): | |
1184 | """ |
|
584 | """ | |
1185 | Returns a generator of four element tuples with |
|
585 | Returns a generator of four element tuples with | |
1186 | lineno, sha, commit lazy loader and line |
|
586 | lineno, sha, commit lazy loader and line | |
1187 |
|
587 | |||
|
588 | :param path: file path | |||
1188 | :param pre_load: Optional. List of commit attributes to load. |
|
589 | :param pre_load: Optional. List of commit attributes to load. | |
1189 | """ |
|
590 | """ | |
1190 | raise NotImplementedError |
|
591 | raise NotImplementedError | |
1191 |
|
592 | |||
1192 | def get_nodes(self, path, pre_load=None): |
|
593 | def get_nodes(self, path: bytes, pre_load=None): | |
1193 | """ |
|
594 | """ | |
1194 | Returns combined ``DirNode`` and ``FileNode`` objects list representing |
|
595 | Returns combined ``DirNode`` and ``FileNode`` objects list representing | |
1195 | state of commit at the given ``path``. |
|
596 | state of commit at the given ``path``. | |
@@ -1199,7 +600,7 b' class BaseCommit(object):' | |||||
1199 | """ |
|
600 | """ | |
1200 | raise NotImplementedError |
|
601 | raise NotImplementedError | |
1201 |
|
602 | |||
1202 | def get_node(self, path): |
|
603 | def get_node(self, path: bytes, pre_load=None): | |
1203 | """ |
|
604 | """ | |
1204 | Returns ``Node`` object from the given ``path``. |
|
605 | Returns ``Node`` object from the given ``path``. | |
1205 |
|
606 | |||
@@ -1208,16 +609,24 b' class BaseCommit(object):' | |||||
1208 | """ |
|
609 | """ | |
1209 | raise NotImplementedError |
|
610 | raise NotImplementedError | |
1210 |
|
611 | |||
1211 | def get_largefile_node(self, path): |
|
612 | def get_largefile_node(self, path: bytes): | |
1212 | """ |
|
613 | """ | |
1213 | Returns the path to largefile from Mercurial/Git-lfs storage. |
|
614 | Returns the path to largefile from Mercurial/Git-lfs storage. | |
1214 | or None if it's not a largefile node |
|
615 | or None if it's not a largefile node | |
1215 | """ |
|
616 | """ | |
1216 | return None |
|
617 | return None | |
1217 |
|
618 | |||
1218 | def archive_repo(self, archive_name_key, kind='tgz', subrepos=None, |
|
619 | def archive_repo( | |
1219 | archive_dir_name=None, write_metadata=False, mtime=None, |
|
620 | self, | |
1220 | archive_at_path='/', cache_config=None): |
|
621 | archive_name_key, | |
|
622 | kind="tgz", | |||
|
623 | subrepos=None, | |||
|
624 | archive_dir_name=None, | |||
|
625 | write_metadata=False, | |||
|
626 | mtime=None, | |||
|
627 | archive_at_path="/", | |||
|
628 | cache_config=None, | |||
|
629 | ): | |||
1221 | """ |
|
630 | """ | |
1222 | Creates an archive containing the contents of the repository. |
|
631 | Creates an archive containing the contents of the repository. | |
1223 |
|
632 | |||
@@ -1237,27 +646,26 b' class BaseCommit(object):' | |||||
1237 | cache_config = cache_config or {} |
|
646 | cache_config = cache_config or {} | |
1238 | allowed_kinds = [x[0] for x in settings.ARCHIVE_SPECS] |
|
647 | allowed_kinds = [x[0] for x in settings.ARCHIVE_SPECS] | |
1239 | if kind not in allowed_kinds: |
|
648 | if kind not in allowed_kinds: | |
1240 | raise ImproperArchiveTypeError( |
|
649 | raise ImproperArchiveTypeError(f"Archive kind ({kind}) not supported use one of {allowed_kinds}") | |
1241 | f'Archive kind ({kind}) not supported use one of {allowed_kinds}') |
|
|||
1242 |
|
650 | |||
1243 | archive_dir_name = self._validate_archive_prefix(archive_dir_name) |
|
651 | archive_dir_name = self._validate_archive_prefix(archive_dir_name) | |
1244 | mtime = mtime is not None or time.mktime(self.date.timetuple()) |
|
652 | mtime = mtime is not None or time.mktime(self.date.timetuple()) | |
1245 | commit_id = self.raw_id |
|
653 | commit_id = self.raw_id | |
1246 |
|
654 | |||
1247 | return self.repository._remote.archive_repo( |
|
655 | return self.repository._remote.archive_repo( | |
1248 | archive_name_key, kind, mtime, archive_at_path, |
|
656 | archive_name_key, kind, mtime, archive_at_path, archive_dir_name, commit_id, cache_config | |
1249 | archive_dir_name, commit_id, cache_config) |
|
657 | ) | |
1250 |
|
658 | |||
1251 | def _validate_archive_prefix(self, archive_dir_name): |
|
659 | def _validate_archive_prefix(self, archive_dir_name): | |
1252 | if archive_dir_name is None: |
|
660 | if archive_dir_name is None: | |
1253 | archive_dir_name = self._ARCHIVE_PREFIX_TEMPLATE.format( |
|
661 | archive_dir_name = self._ARCHIVE_PREFIX_TEMPLATE.format( | |
1254 | repo_name=safe_str(self.repository.name), |
|
662 | repo_name=safe_str(self.repository.name), short_id=self.short_id | |
1255 | short_id=self.short_id) |
|
663 | ) | |
1256 | elif not isinstance(archive_dir_name, str): |
|
664 | elif not isinstance(archive_dir_name, str): | |
1257 | raise ValueError(f"archive_dir_name is not str object but: {type(archive_dir_name)}") |
|
665 | raise ValueError(f"archive_dir_name is not str object but: {type(archive_dir_name)}") | |
1258 |
elif archive_dir_name.startswith( |
|
666 | elif archive_dir_name.startswith("/"): | |
1259 | raise VCSError("Prefix cannot start with leading slash") |
|
667 | raise VCSError("Prefix cannot start with leading slash") | |
1260 |
elif archive_dir_name.strip() == |
|
668 | elif archive_dir_name.strip() == "": | |
1261 | raise VCSError("Prefix cannot be empty") |
|
669 | raise VCSError("Prefix cannot be empty") | |
1262 | elif not archive_dir_name.isascii(): |
|
670 | elif not archive_dir_name.isascii(): | |
1263 | raise VCSError("Prefix cannot contain non ascii characters") |
|
671 | raise VCSError("Prefix cannot contain non ascii characters") | |
@@ -1268,7 +676,7 b' class BaseCommit(object):' | |||||
1268 | """ |
|
676 | """ | |
1269 | Returns ``RootNode`` object for this commit. |
|
677 | Returns ``RootNode`` object for this commit. | |
1270 | """ |
|
678 | """ | |
1271 |
return self.get_node( |
|
679 | return self.get_node(b"") | |
1272 |
|
680 | |||
1273 | def next(self, branch=None): |
|
681 | def next(self, branch=None): | |
1274 | """ |
|
682 | """ | |
@@ -1292,8 +700,7 b' class BaseCommit(object):' | |||||
1292 |
|
700 | |||
1293 | def _find_next(self, indexes, branch=None): |
|
701 | def _find_next(self, indexes, branch=None): | |
1294 | if branch and self.branch != branch: |
|
702 | if branch and self.branch != branch: | |
1295 |
raise VCSError( |
|
703 | raise VCSError("Branch option used on commit not belonging " "to that branch") | |
1296 | 'to that branch') |
|
|||
1297 |
|
704 | |||
1298 | for next_idx in indexes: |
|
705 | for next_idx in indexes: | |
1299 | commit = self.repository.get_commit(commit_idx=next_idx) |
|
706 | commit = self.repository.get_commit(commit_idx=next_idx) | |
@@ -1307,41 +714,17 b' class BaseCommit(object):' | |||||
1307 | Returns a `Diff` object representing the change made by this commit. |
|
714 | Returns a `Diff` object representing the change made by this commit. | |
1308 | """ |
|
715 | """ | |
1309 | parent = self.first_parent |
|
716 | parent = self.first_parent | |
1310 | diff = self.repository.get_diff( |
|
717 | diff = self.repository.get_diff(parent, self, ignore_whitespace=ignore_whitespace, context=context) | |
1311 | parent, self, |
|
|||
1312 | ignore_whitespace=ignore_whitespace, |
|
|||
1313 | context=context) |
|
|||
1314 | return diff |
|
718 | return diff | |
1315 |
|
719 | |||
1316 | @LazyProperty |
|
720 | @LazyProperty | |
1317 | def added(self): |
|
|||
1318 | """ |
|
|||
1319 | Returns list of added ``FileNode`` objects. |
|
|||
1320 | """ |
|
|||
1321 | raise NotImplementedError |
|
|||
1322 |
|
||||
1323 | @LazyProperty |
|
|||
1324 | def changed(self): |
|
|||
1325 | """ |
|
|||
1326 | Returns list of modified ``FileNode`` objects. |
|
|||
1327 | """ |
|
|||
1328 | raise NotImplementedError |
|
|||
1329 |
|
||||
1330 | @LazyProperty |
|
|||
1331 | def removed(self): |
|
|||
1332 | """ |
|
|||
1333 | Returns list of removed ``FileNode`` objects. |
|
|||
1334 | """ |
|
|||
1335 | raise NotImplementedError |
|
|||
1336 |
|
||||
1337 | @LazyProperty |
|
|||
1338 | def size(self): |
|
721 | def size(self): | |
1339 | """ |
|
722 | """ | |
1340 | Returns total number of bytes from contents of all filenodes. |
|
723 | Returns total number of bytes from contents of all filenodes. | |
1341 | """ |
|
724 | """ | |
1342 | return sum(node.size for node in self.get_filenodes_generator()) |
|
725 | return sum(node.size for node in self.get_filenodes_generator()) | |
1343 |
|
726 | |||
1344 |
def walk(self, topurl= |
|
727 | def walk(self, top_url=b""): | |
1345 | """ |
|
728 | """ | |
1346 | Similar to os.walk method. Insted of filesystem it walks through |
|
729 | Similar to os.walk method. Insted of filesystem it walks through | |
1347 | commit starting at given ``topurl``. Returns generator of tuples |
|
730 | commit starting at given ``topurl``. Returns generator of tuples | |
@@ -1349,10 +732,10 b' class BaseCommit(object):' | |||||
1349 | """ |
|
732 | """ | |
1350 | from rhodecode.lib.vcs.nodes import DirNode |
|
733 | from rhodecode.lib.vcs.nodes import DirNode | |
1351 |
|
734 | |||
1352 | if isinstance(topurl, DirNode): |
|
735 | if isinstance(top_url, DirNode): | |
1353 | top_node = topurl |
|
736 | top_node = top_url | |
1354 | else: |
|
737 | else: | |
1355 | top_node = self.get_node(topurl) |
|
738 | top_node = self.get_node(top_url) | |
1356 |
|
739 | |||
1357 | has_default_pre_load = False |
|
740 | has_default_pre_load = False | |
1358 | if isinstance(top_node, DirNode): |
|
741 | if isinstance(top_node, DirNode): | |
@@ -1381,15 +764,20 b' class BaseCommit(object):' | |||||
1381 |
|
764 | |||
1382 | def no_node_at_path(self, path): |
|
765 | def no_node_at_path(self, path): | |
1383 | return NodeDoesNotExistError( |
|
766 | return NodeDoesNotExistError( | |
1384 | f"There is no file nor directory at the given path: " |
|
767 | f"There is no file nor directory at the given path: " f"`{safe_str(path)}` at commit {self.short_id}" | |
1385 | f"`{safe_str(path)}` at commit {self.short_id}") |
|
768 | ) | |
1386 |
|
769 | |||
1387 | def _fix_path(self, path: str) -> str: |
|
770 | @classmethod | |
|
771 | def _fix_path(cls, path: bytes) -> bytes: | |||
1388 | """ |
|
772 | """ | |
1389 | Paths are stored without trailing slash so we need to get rid off it if |
|
773 | Paths are stored without trailing slash so we need to get rid off it if needed. | |
1390 | needed. |
|
774 | It also validates that path is a bytestring for support of mixed encodings... | |
1391 | """ |
|
775 | """ | |
1392 | return safe_str(path).rstrip('/') |
|
776 | ||
|
777 | if not isinstance(path, bytes): | |||
|
778 | raise ValueError(f'path=`{safe_str(path)}` must be bytes') | |||
|
779 | ||||
|
780 | return path.rstrip(b"/") | |||
1393 |
|
781 | |||
1394 | # |
|
782 | # | |
1395 | # Deprecated API based on changesets |
|
783 | # Deprecated API based on changesets | |
@@ -1410,17 +798,644 b' class BaseCommit(object):' | |||||
1410 | return self.get_path_commit(path) |
|
798 | return self.get_path_commit(path) | |
1411 |
|
799 | |||
1412 |
|
800 | |||
|
801 | class BaseRepository(object): | |||
|
802 | """ | |||
|
803 | Base Repository for final backends | |||
|
804 | ||||
|
805 | .. attribute:: DEFAULT_BRANCH_NAME | |||
|
806 | ||||
|
807 | name of default branch (i.e. "trunk" for svn, "master" for git etc. | |||
|
808 | ||||
|
809 | .. attribute:: commit_ids | |||
|
810 | ||||
|
811 | list of all available commit ids, in ascending order | |||
|
812 | ||||
|
813 | .. attribute:: path | |||
|
814 | ||||
|
815 | absolute path to the repository | |||
|
816 | ||||
|
817 | .. attribute:: bookmarks | |||
|
818 | ||||
|
819 | Mapping from name to :term:`Commit ID` of the bookmark. Empty in case | |||
|
820 | there are no bookmarks or the backend implementation does not support | |||
|
821 | bookmarks. | |||
|
822 | ||||
|
823 | .. attribute:: tags | |||
|
824 | ||||
|
825 | Mapping from name to :term:`Commit ID` of the tag. | |||
|
826 | ||||
|
827 | """ | |||
|
828 | ||||
|
829 | DEFAULT_BRANCH_NAME = None | |||
|
830 | DEFAULT_CONTACT = "Unknown" | |||
|
831 | DEFAULT_DESCRIPTION = "unknown" | |||
|
832 | EMPTY_COMMIT_ID = "0" * 40 | |||
|
833 | COMMIT_ID_PAT = re.compile(r"[0-9a-fA-F]{40}") | |||
|
834 | ||||
|
835 | path = None | |||
|
836 | ||||
|
837 | _is_empty = None | |||
|
838 | _commit_ids = {} | |||
|
839 | ||||
|
840 | def __init__(self, repo_path, config=None, create=False, **kwargs): | |||
|
841 | """ | |||
|
842 | Initializes repository. Raises RepositoryError if repository could | |||
|
843 | not be find at the given ``repo_path`` or directory at ``repo_path`` | |||
|
844 | exists and ``create`` is set to True. | |||
|
845 | ||||
|
846 | :param repo_path: local path of the repository | |||
|
847 | :param config: repository configuration | |||
|
848 | :param create=False: if set to True, would try to create repository. | |||
|
849 | :param src_url=None: if set, should be proper url from which repository | |||
|
850 | would be cloned; requires ``create`` parameter to be set to True - | |||
|
851 | raises RepositoryError if src_url is set and create evaluates to | |||
|
852 | False | |||
|
853 | """ | |||
|
854 | raise NotImplementedError | |||
|
855 | ||||
|
856 | def __repr__(self): | |||
|
857 | return f"<{self.__class__.__name__} at {self.path}>" | |||
|
858 | ||||
|
859 | def __len__(self): | |||
|
860 | return self.count() | |||
|
861 | ||||
|
862 | def __eq__(self, other): | |||
|
863 | same_instance = isinstance(other, self.__class__) | |||
|
864 | return same_instance and other.path == self.path | |||
|
865 | ||||
|
866 | def __ne__(self, other): | |||
|
867 | return not self.__eq__(other) | |||
|
868 | ||||
|
869 | @classmethod | |||
|
870 | def get_create_shadow_cache_pr_path(cls, db_repo): | |||
|
871 | path = db_repo.cached_diffs_dir | |||
|
872 | if not os.path.exists(path): | |||
|
873 | os.makedirs(path, 0o755) | |||
|
874 | return path | |||
|
875 | ||||
|
876 | @classmethod | |||
|
877 | def get_default_config(cls, default=None): | |||
|
878 | config = Config() | |||
|
879 | if default and isinstance(default, list): | |||
|
880 | for section, key, val in default: | |||
|
881 | config.set(section, key, val) | |||
|
882 | return config | |||
|
883 | ||||
|
884 | @LazyProperty | |||
|
885 | def _remote(self): | |||
|
886 | raise NotImplementedError | |||
|
887 | ||||
|
888 | def _heads(self, branch=None): | |||
|
889 | return [] | |||
|
890 | ||||
|
891 | @LazyProperty | |||
|
892 | def EMPTY_COMMIT(self): | |||
|
893 | return EmptyCommit(self.EMPTY_COMMIT_ID) | |||
|
894 | ||||
|
895 | @LazyProperty | |||
|
896 | def alias(self): | |||
|
897 | for k, v in settings.BACKENDS.items(): | |||
|
898 | if v.split(".")[-1] == str(self.__class__.__name__): | |||
|
899 | return k | |||
|
900 | ||||
|
901 | @LazyProperty | |||
|
902 | def name(self): | |||
|
903 | return safe_str(os.path.basename(self.path)) | |||
|
904 | ||||
|
905 | @LazyProperty | |||
|
906 | def description(self): | |||
|
907 | raise NotImplementedError | |||
|
908 | ||||
|
909 | def refs(self): | |||
|
910 | """ | |||
|
911 | returns a `dict` with branches, bookmarks, tags, and closed_branches | |||
|
912 | for this repository | |||
|
913 | """ | |||
|
914 | return dict( | |||
|
915 | branches=self.branches, branches_closed=self.branches_closed, tags=self.tags, bookmarks=self.bookmarks | |||
|
916 | ) | |||
|
917 | ||||
|
918 | @LazyProperty | |||
|
919 | def branches(self): | |||
|
920 | """ | |||
|
921 | A `dict` which maps branch names to commit ids. | |||
|
922 | """ | |||
|
923 | raise NotImplementedError | |||
|
924 | ||||
|
925 | @LazyProperty | |||
|
926 | def branches_closed(self): | |||
|
927 | """ | |||
|
928 | A `dict` which maps tags names to commit ids. | |||
|
929 | """ | |||
|
930 | raise NotImplementedError | |||
|
931 | ||||
|
932 | @LazyProperty | |||
|
933 | def bookmarks(self): | |||
|
934 | """ | |||
|
935 | A `dict` which maps tags names to commit ids. | |||
|
936 | """ | |||
|
937 | raise NotImplementedError | |||
|
938 | ||||
|
939 | @LazyProperty | |||
|
940 | def tags(self): | |||
|
941 | """ | |||
|
942 | A `dict` which maps tags names to commit ids. | |||
|
943 | """ | |||
|
944 | raise NotImplementedError | |||
|
945 | ||||
|
946 | @LazyProperty | |||
|
947 | def size(self): | |||
|
948 | """ | |||
|
949 | Returns combined size in bytes for all repository files | |||
|
950 | """ | |||
|
951 | tip = self.get_commit() | |||
|
952 | return tip.size | |||
|
953 | ||||
|
954 | def size_at_commit(self, commit_id): | |||
|
955 | commit = self.get_commit(commit_id) | |||
|
956 | return commit.size | |||
|
957 | ||||
|
958 | def _check_for_empty(self): | |||
|
959 | no_commits = len(self._commit_ids) == 0 | |||
|
960 | if no_commits: | |||
|
961 | # check on remote to be sure | |||
|
962 | return self._remote.is_empty() | |||
|
963 | else: | |||
|
964 | return False | |||
|
965 | ||||
|
966 | def is_empty(self): | |||
|
967 | if rhodecode.is_test: | |||
|
968 | return self._check_for_empty() | |||
|
969 | ||||
|
970 | if self._is_empty is None: | |||
|
971 | # cache empty for production, but not tests | |||
|
972 | self._is_empty = self._check_for_empty() | |||
|
973 | ||||
|
974 | return self._is_empty | |||
|
975 | ||||
|
976 | @staticmethod | |||
|
977 | def check_url(url, config): | |||
|
978 | """ | |||
|
979 | Function will check given url and try to verify if it's a valid | |||
|
980 | link. | |||
|
981 | """ | |||
|
982 | raise NotImplementedError | |||
|
983 | ||||
|
984 | @staticmethod | |||
|
985 | def is_valid_repository(path): | |||
|
986 | """ | |||
|
987 | Check if given `path` contains a valid repository of this backend | |||
|
988 | """ | |||
|
989 | raise NotImplementedError | |||
|
990 | ||||
|
991 | # ========================================================================== | |||
|
992 | # COMMITS | |||
|
993 | # ========================================================================== | |||
|
994 | ||||
|
995 | @CachedProperty | |||
|
996 | def commit_ids(self): | |||
|
997 | raise NotImplementedError | |||
|
998 | ||||
|
999 | def append_commit_id(self, commit_id): | |||
|
1000 | if commit_id not in self.commit_ids: | |||
|
1001 | self._rebuild_cache(self.commit_ids + [commit_id]) | |||
|
1002 | ||||
|
1003 | # clear cache | |||
|
1004 | self._invalidate_prop_cache("commit_ids") | |||
|
1005 | self._is_empty = False | |||
|
1006 | ||||
|
1007 | def get_commit( | |||
|
1008 | self, | |||
|
1009 | commit_id=None, | |||
|
1010 | commit_idx=None, | |||
|
1011 | pre_load=None, | |||
|
1012 | translate_tag=None, | |||
|
1013 | maybe_unreachable=False, | |||
|
1014 | reference_obj=None, | |||
|
1015 | ) -> BaseCommit: | |||
|
1016 | """ | |||
|
1017 | Returns instance of `BaseCommit` class. If `commit_id` and `commit_idx` | |||
|
1018 | are both None, most recent commit is returned. | |||
|
1019 | ||||
|
1020 | :param pre_load: Optional. List of commit attributes to load. | |||
|
1021 | ||||
|
1022 | :raises ``EmptyRepositoryError``: if there are no commits | |||
|
1023 | """ | |||
|
1024 | raise NotImplementedError | |||
|
1025 | ||||
|
1026 | def __iter__(self): | |||
|
1027 | for commit_id in self.commit_ids: | |||
|
1028 | yield self.get_commit(commit_id=commit_id) | |||
|
1029 | ||||
|
1030 | def get_commits( | |||
|
1031 | self, | |||
|
1032 | start_id=None, | |||
|
1033 | end_id=None, | |||
|
1034 | start_date=None, | |||
|
1035 | end_date=None, | |||
|
1036 | branch_name=None, | |||
|
1037 | show_hidden=False, | |||
|
1038 | pre_load=None, | |||
|
1039 | translate_tags=None, | |||
|
1040 | ): | |||
|
1041 | """ | |||
|
1042 | Returns iterator of `BaseCommit` objects from start to end | |||
|
1043 | not inclusive. This should behave just like a list, ie. end is not | |||
|
1044 | inclusive. | |||
|
1045 | ||||
|
1046 | :param start_id: None or str, must be a valid commit id | |||
|
1047 | :param end_id: None or str, must be a valid commit id | |||
|
1048 | :param start_date: | |||
|
1049 | :param end_date: | |||
|
1050 | :param branch_name: | |||
|
1051 | :param show_hidden: | |||
|
1052 | :param pre_load: | |||
|
1053 | :param translate_tags: | |||
|
1054 | """ | |||
|
1055 | raise NotImplementedError | |||
|
1056 | ||||
|
1057 | def __getitem__(self, key): | |||
|
1058 | """ | |||
|
1059 | Allows index based access to the commit objects of this repository. | |||
|
1060 | """ | |||
|
1061 | pre_load = ["author", "branch", "date", "message", "parents"] | |||
|
1062 | if isinstance(key, slice): | |||
|
1063 | return self._get_range(key, pre_load) | |||
|
1064 | return self.get_commit(commit_idx=key, pre_load=pre_load) | |||
|
1065 | ||||
|
1066 | def _get_range(self, slice_obj, pre_load): | |||
|
1067 | for commit_id in self.commit_ids.__getitem__(slice_obj): | |||
|
1068 | yield self.get_commit(commit_id=commit_id, pre_load=pre_load) | |||
|
1069 | ||||
|
1070 | def count(self): | |||
|
1071 | return len(self.commit_ids) | |||
|
1072 | ||||
|
1073 | def tag(self, name, user, commit_id=None, message=None, date=None, **opts): | |||
|
1074 | """ | |||
|
1075 | Creates and returns a tag for the given ``commit_id``. | |||
|
1076 | ||||
|
1077 | :param name: name for new tag | |||
|
1078 | :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>" | |||
|
1079 | :param commit_id: commit id for which new tag would be created | |||
|
1080 | :param message: message of the tag's commit | |||
|
1081 | :param date: date of tag's commit | |||
|
1082 | ||||
|
1083 | :raises TagAlreadyExistError: if tag with same name already exists | |||
|
1084 | """ | |||
|
1085 | raise NotImplementedError | |||
|
1086 | ||||
|
1087 | def remove_tag(self, name, user, message=None, date=None): | |||
|
1088 | """ | |||
|
1089 | Removes tag with the given ``name``. | |||
|
1090 | ||||
|
1091 | :param name: name of the tag to be removed | |||
|
1092 | :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>" | |||
|
1093 | :param message: message of the tag's removal commit | |||
|
1094 | :param date: date of tag's removal commit | |||
|
1095 | ||||
|
1096 | :raises TagDoesNotExistError: if tag with given name does not exists | |||
|
1097 | """ | |||
|
1098 | raise NotImplementedError | |||
|
1099 | ||||
|
1100 | def get_diff(self, commit1, commit2, path=None, ignore_whitespace=False, context=3, path1=None): | |||
|
1101 | """ | |||
|
1102 | Returns (git like) *diff*, as plain text. Shows changes introduced by | |||
|
1103 | `commit2` since `commit1`. | |||
|
1104 | ||||
|
1105 | :param commit1: Entry point from which diff is shown. Can be | |||
|
1106 | ``self.EMPTY_COMMIT`` - in this case, patch showing all | |||
|
1107 | the changes since empty state of the repository until `commit2` | |||
|
1108 | :param commit2: Until which commit changes should be shown. | |||
|
1109 | :param path: Can be set to a path of a file to create a diff of that | |||
|
1110 | file. If `path1` is also set, this value is only associated to | |||
|
1111 | `commit2`. | |||
|
1112 | :param ignore_whitespace: If set to ``True``, would not show whitespace | |||
|
1113 | changes. Defaults to ``False``. | |||
|
1114 | :param context: How many lines before/after changed lines should be | |||
|
1115 | shown. Defaults to ``3``. | |||
|
1116 | :param path1: Can be set to a path to associate with `commit1`. This | |||
|
1117 | parameter works only for backends which support diff generation for | |||
|
1118 | different paths. Other backends will raise a `ValueError` if `path1` | |||
|
1119 | is set and has a different value than `path`. | |||
|
1120 | :param file_path: filter this diff by given path pattern | |||
|
1121 | """ | |||
|
1122 | raise NotImplementedError | |||
|
1123 | ||||
|
1124 | def strip(self, commit_id, branch=None): | |||
|
1125 | """ | |||
|
1126 | Strip given commit_id from the repository | |||
|
1127 | """ | |||
|
1128 | raise NotImplementedError | |||
|
1129 | ||||
|
1130 | def get_common_ancestor(self, commit_id1, commit_id2, repo2): | |||
|
1131 | """ | |||
|
1132 | Return a latest common ancestor commit if one exists for this repo | |||
|
1133 | `commit_id1` vs `commit_id2` from `repo2`. | |||
|
1134 | ||||
|
1135 | :param commit_id1: Commit it from this repository to use as a | |||
|
1136 | target for the comparison. | |||
|
1137 | :param commit_id2: Source commit id to use for comparison. | |||
|
1138 | :param repo2: Source repository to use for comparison. | |||
|
1139 | """ | |||
|
1140 | raise NotImplementedError | |||
|
1141 | ||||
|
1142 | def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None): | |||
|
1143 | """ | |||
|
1144 | Compare this repository's revision `commit_id1` with `commit_id2`. | |||
|
1145 | ||||
|
1146 | Returns a tuple(commits, ancestor) that would be merged from | |||
|
1147 | `commit_id2`. Doing a normal compare (``merge=False``), ``None`` | |||
|
1148 | will be returned as ancestor. | |||
|
1149 | ||||
|
1150 | :param commit_id1: Commit it from this repository to use as a | |||
|
1151 | target for the comparison. | |||
|
1152 | :param commit_id2: Source commit id to use for comparison. | |||
|
1153 | :param repo2: Source repository to use for comparison. | |||
|
1154 | :param merge: If set to ``True`` will do a merge compare which also | |||
|
1155 | returns the common ancestor. | |||
|
1156 | :param pre_load: Optional. List of commit attributes to load. | |||
|
1157 | """ | |||
|
1158 | raise NotImplementedError | |||
|
1159 | ||||
|
1160 | def merge( | |||
|
1161 | self, | |||
|
1162 | repo_id, | |||
|
1163 | workspace_id, | |||
|
1164 | target_ref, | |||
|
1165 | source_repo, | |||
|
1166 | source_ref, | |||
|
1167 | user_name="", | |||
|
1168 | user_email="", | |||
|
1169 | message="", | |||
|
1170 | dry_run=False, | |||
|
1171 | use_rebase=False, | |||
|
1172 | close_branch=False, | |||
|
1173 | ): | |||
|
1174 | """ | |||
|
1175 | Merge the revisions specified in `source_ref` from `source_repo` | |||
|
1176 | onto the `target_ref` of this repository. | |||
|
1177 | ||||
|
1178 | `source_ref` and `target_ref` are named tupls with the following | |||
|
1179 | fields `type`, `name` and `commit_id`. | |||
|
1180 | ||||
|
1181 | Returns a MergeResponse named tuple with the following fields | |||
|
1182 | 'possible', 'executed', 'source_commit', 'target_commit', | |||
|
1183 | 'merge_commit'. | |||
|
1184 | ||||
|
1185 | :param repo_id: `repo_id` target repo id. | |||
|
1186 | :param workspace_id: `workspace_id` unique identifier. | |||
|
1187 | :param target_ref: `target_ref` points to the commit on top of which | |||
|
1188 | the `source_ref` should be merged. | |||
|
1189 | :param source_repo: The repository that contains the commits to be | |||
|
1190 | merged. | |||
|
1191 | :param source_ref: `source_ref` points to the topmost commit from | |||
|
1192 | the `source_repo` which should be merged. | |||
|
1193 | :param user_name: Merge commit `user_name`. | |||
|
1194 | :param user_email: Merge commit `user_email`. | |||
|
1195 | :param message: Merge commit `message`. | |||
|
1196 | :param dry_run: If `True` the merge will not take place. | |||
|
1197 | :param use_rebase: If `True` commits from the source will be rebased | |||
|
1198 | on top of the target instead of being merged. | |||
|
1199 | :param close_branch: If `True` branch will be close before merging it | |||
|
1200 | """ | |||
|
1201 | if dry_run: | |||
|
1202 | message = message or settings.MERGE_DRY_RUN_MESSAGE | |||
|
1203 | user_email = user_email or settings.MERGE_DRY_RUN_EMAIL | |||
|
1204 | user_name = user_name or settings.MERGE_DRY_RUN_USER | |||
|
1205 | else: | |||
|
1206 | if not user_name: | |||
|
1207 | raise ValueError("user_name cannot be empty") | |||
|
1208 | if not user_email: | |||
|
1209 | raise ValueError("user_email cannot be empty") | |||
|
1210 | if not message: | |||
|
1211 | raise ValueError("message cannot be empty") | |||
|
1212 | ||||
|
1213 | try: | |||
|
1214 | return self._merge_repo( | |||
|
1215 | repo_id, | |||
|
1216 | workspace_id, | |||
|
1217 | target_ref, | |||
|
1218 | source_repo, | |||
|
1219 | source_ref, | |||
|
1220 | message, | |||
|
1221 | user_name, | |||
|
1222 | user_email, | |||
|
1223 | dry_run=dry_run, | |||
|
1224 | use_rebase=use_rebase, | |||
|
1225 | close_branch=close_branch, | |||
|
1226 | ) | |||
|
1227 | except RepositoryError as exc: | |||
|
1228 | log.exception("Unexpected failure when running merge, dry-run=%s", dry_run) | |||
|
1229 | return MergeResponse(False, False, None, MergeFailureReason.UNKNOWN, metadata={"exception": str(exc)}) | |||
|
1230 | ||||
|
1231 | def _merge_repo( | |||
|
1232 | self, | |||
|
1233 | repo_id, | |||
|
1234 | workspace_id, | |||
|
1235 | target_ref, | |||
|
1236 | source_repo, | |||
|
1237 | source_ref, | |||
|
1238 | merge_message, | |||
|
1239 | merger_name, | |||
|
1240 | merger_email, | |||
|
1241 | dry_run=False, | |||
|
1242 | use_rebase=False, | |||
|
1243 | close_branch=False, | |||
|
1244 | ): | |||
|
1245 | """Internal implementation of merge.""" | |||
|
1246 | raise NotImplementedError | |||
|
1247 | ||||
|
1248 | def _maybe_prepare_merge_workspace(self, repo_id, workspace_id, target_ref, source_ref): | |||
|
1249 | """ | |||
|
1250 | Create the merge workspace. | |||
|
1251 | ||||
|
1252 | :param workspace_id: `workspace_id` unique identifier. | |||
|
1253 | """ | |||
|
1254 | raise NotImplementedError | |||
|
1255 | ||||
|
1256 | @classmethod | |||
|
1257 | def _get_legacy_shadow_repository_path(cls, repo_path, workspace_id): | |||
|
1258 | """ | |||
|
1259 | Legacy version that was used before. We still need it for | |||
|
1260 | backward compat | |||
|
1261 | """ | |||
|
1262 | return os.path.join(os.path.dirname(repo_path), f".__shadow_{os.path.basename(repo_path)}_{workspace_id}") | |||
|
1263 | ||||
|
1264 | @classmethod | |||
|
1265 | def _get_shadow_repository_path(cls, repo_path, repo_id, workspace_id): | |||
|
1266 | # The name of the shadow repository must start with '.', so it is | |||
|
1267 | # skipped by 'rhodecode.lib.utils.get_filesystem_repos'. | |||
|
1268 | legacy_repository_path = cls._get_legacy_shadow_repository_path(repo_path, workspace_id) | |||
|
1269 | if os.path.exists(legacy_repository_path): | |||
|
1270 | return legacy_repository_path | |||
|
1271 | else: | |||
|
1272 | return os.path.join(os.path.dirname(repo_path), f".__shadow_repo_{repo_id}_{workspace_id}") | |||
|
1273 | ||||
|
1274 | def cleanup_merge_workspace(self, repo_id, workspace_id): | |||
|
1275 | """ | |||
|
1276 | Remove merge workspace. | |||
|
1277 | ||||
|
1278 | This function MUST not fail in case there is no workspace associated to | |||
|
1279 | the given `workspace_id`. | |||
|
1280 | ||||
|
1281 | :param workspace_id: `workspace_id` unique identifier. | |||
|
1282 | """ | |||
|
1283 | shadow_repository_path = self._get_shadow_repository_path(self.path, repo_id, workspace_id) | |||
|
1284 | shadow_repository_path_del = "{}.{}.delete".format(shadow_repository_path, time.time()) | |||
|
1285 | ||||
|
1286 | # move the shadow repo, so it never conflicts with the one used. | |||
|
1287 | # we use this method because shutil.rmtree had some edge case problems | |||
|
1288 | # removing symlinked repositories | |||
|
1289 | if not os.path.isdir(shadow_repository_path): | |||
|
1290 | return | |||
|
1291 | ||||
|
1292 | shutil.move(shadow_repository_path, shadow_repository_path_del) | |||
|
1293 | try: | |||
|
1294 | shutil.rmtree(shadow_repository_path_del, ignore_errors=False) | |||
|
1295 | except Exception: | |||
|
1296 | log.exception("Failed to gracefully remove shadow repo under %s", shadow_repository_path_del) | |||
|
1297 | shutil.rmtree(shadow_repository_path_del, ignore_errors=True) | |||
|
1298 | ||||
|
1299 | # ========== # | |||
|
1300 | # COMMIT API # | |||
|
1301 | # ========== # | |||
|
1302 | ||||
|
1303 | @LazyProperty | |||
|
1304 | def in_memory_commit(self): | |||
|
1305 | """ | |||
|
1306 | Returns :class:`InMemoryCommit` object for this repository. | |||
|
1307 | """ | |||
|
1308 | raise NotImplementedError | |||
|
1309 | ||||
|
1310 | # ======================== # | |||
|
1311 | # UTILITIES FOR SUBCLASSES # | |||
|
1312 | # ======================== # | |||
|
1313 | ||||
|
1314 | def _validate_diff_commits(self, commit1, commit2): | |||
|
1315 | """ | |||
|
1316 | Validates that the given commits are related to this repository. | |||
|
1317 | ||||
|
1318 | Intended as a utility for sub classes to have a consistent validation | |||
|
1319 | of input parameters in methods like :meth:`get_diff`. | |||
|
1320 | """ | |||
|
1321 | self._validate_commit(commit1) | |||
|
1322 | self._validate_commit(commit2) | |||
|
1323 | if isinstance(commit1, EmptyCommit) and isinstance(commit2, EmptyCommit): | |||
|
1324 | raise ValueError("Cannot compare two empty commits") | |||
|
1325 | ||||
|
1326 | def _validate_commit(self, commit): | |||
|
1327 | if not isinstance(commit, BaseCommit): | |||
|
1328 | raise TypeError("%s is not of type BaseCommit" % repr(commit)) | |||
|
1329 | if commit.repository != self and not isinstance(commit, EmptyCommit): | |||
|
1330 | raise ValueError( | |||
|
1331 | "Commit %s must be a valid commit from this repository %s, " | |||
|
1332 | "related to this repository instead %s." % (commit, self, commit.repository) | |||
|
1333 | ) | |||
|
1334 | ||||
|
1335 | def _validate_commit_id(self, commit_id): | |||
|
1336 | if not isinstance(commit_id, str): | |||
|
1337 | raise TypeError(f"commit_id must be a string value got {type(commit_id)} instead") | |||
|
1338 | ||||
|
1339 | def _validate_commit_idx(self, commit_idx): | |||
|
1340 | if not isinstance(commit_idx, int): | |||
|
1341 | raise TypeError(f"commit_idx must be a numeric value, got {type(commit_idx)}") | |||
|
1342 | ||||
|
1343 | def _validate_branch_name(self, branch_name): | |||
|
1344 | if branch_name and branch_name not in self.branches_all: | |||
|
1345 | msg = f"Branch {branch_name} not found in {self}" | |||
|
1346 | raise BranchDoesNotExistError(msg) | |||
|
1347 | ||||
|
1348 | # | |||
|
1349 | # Supporting deprecated API parts | |||
|
1350 | # TODO: johbo: consider to move this into a mixin | |||
|
1351 | # | |||
|
1352 | ||||
|
1353 | @property | |||
|
1354 | def EMPTY_CHANGESET(self): | |||
|
1355 | warnings.warn("Use EMPTY_COMMIT or EMPTY_COMMIT_ID instead", DeprecationWarning) | |||
|
1356 | return self.EMPTY_COMMIT_ID | |||
|
1357 | ||||
|
1358 | @property | |||
|
1359 | def revisions(self): | |||
|
1360 | warnings.warn("Use commits attribute instead", DeprecationWarning) | |||
|
1361 | return self.commit_ids | |||
|
1362 | ||||
|
1363 | @revisions.setter | |||
|
1364 | def revisions(self, value): | |||
|
1365 | warnings.warn("Use commits attribute instead", DeprecationWarning) | |||
|
1366 | self.commit_ids = value | |||
|
1367 | ||||
|
1368 | def get_changeset(self, revision=None, pre_load=None): | |||
|
1369 | warnings.warn("Use get_commit instead", DeprecationWarning) | |||
|
1370 | commit_id = None | |||
|
1371 | commit_idx = None | |||
|
1372 | if isinstance(revision, str): | |||
|
1373 | commit_id = revision | |||
|
1374 | else: | |||
|
1375 | commit_idx = revision | |||
|
1376 | return self.get_commit(commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load) | |||
|
1377 | ||||
|
1378 | def get_changesets(self, start=None, end=None, start_date=None, end_date=None, branch_name=None, pre_load=None): | |||
|
1379 | warnings.warn("Use get_commits instead", DeprecationWarning) | |||
|
1380 | start_id = self._revision_to_commit(start) | |||
|
1381 | end_id = self._revision_to_commit(end) | |||
|
1382 | return self.get_commits( | |||
|
1383 | start_id=start_id, | |||
|
1384 | end_id=end_id, | |||
|
1385 | start_date=start_date, | |||
|
1386 | end_date=end_date, | |||
|
1387 | branch_name=branch_name, | |||
|
1388 | pre_load=pre_load, | |||
|
1389 | ) | |||
|
1390 | ||||
|
1391 | def _revision_to_commit(self, revision): | |||
|
1392 | """ | |||
|
1393 | Translates a revision to a commit_id | |||
|
1394 | ||||
|
1395 | Helps to support the old changeset based API which allows to use | |||
|
1396 | commit ids and commit indices interchangeable. | |||
|
1397 | """ | |||
|
1398 | if revision is None: | |||
|
1399 | return revision | |||
|
1400 | ||||
|
1401 | if isinstance(revision, str): | |||
|
1402 | commit_id = revision | |||
|
1403 | else: | |||
|
1404 | commit_id = self.commit_ids[revision] | |||
|
1405 | return commit_id | |||
|
1406 | ||||
|
1407 | @property | |||
|
1408 | def in_memory_changeset(self): | |||
|
1409 | warnings.warn("Use in_memory_commit instead", DeprecationWarning) | |||
|
1410 | return self.in_memory_commit | |||
|
1411 | ||||
|
1412 | def get_path_permissions(self, username): | |||
|
1413 | """ | |||
|
1414 | Returns a path permission checker or None if not supported | |||
|
1415 | ||||
|
1416 | :param username: session user name | |||
|
1417 | :return: an instance of BasePathPermissionChecker or None | |||
|
1418 | """ | |||
|
1419 | return None | |||
|
1420 | ||||
|
1421 | def install_hooks(self, force=False): | |||
|
1422 | return self._remote.install_hooks(force) | |||
|
1423 | ||||
|
1424 | def get_hooks_info(self): | |||
|
1425 | return self._remote.get_hooks_info() | |||
|
1426 | ||||
|
1427 | def vcsserver_invalidate_cache(self, delete=False): | |||
|
1428 | return self._remote.vcsserver_invalidate_cache(delete) | |||
|
1429 | ||||
|
1430 | ||||
1413 | class BaseChangesetClass(type): |
|
1431 | class BaseChangesetClass(type): | |
1414 |
|
||||
1415 | def __instancecheck__(self, instance): |
|
1432 | def __instancecheck__(self, instance): | |
1416 | return isinstance(instance, BaseCommit) |
|
1433 | return isinstance(instance, BaseCommit) | |
1417 |
|
1434 | |||
1418 |
|
1435 | |||
1419 | class BaseChangeset(BaseCommit, metaclass=BaseChangesetClass): |
|
1436 | class BaseChangeset(BaseCommit, metaclass=BaseChangesetClass): | |
1420 |
|
||||
1421 | def __new__(cls, *args, **kwargs): |
|
1437 | def __new__(cls, *args, **kwargs): | |
1422 | warnings.warn( |
|
1438 | warnings.warn("Use BaseCommit instead of BaseChangeset", DeprecationWarning) | |
1423 | "Use BaseCommit instead of BaseChangeset", DeprecationWarning) |
|
|||
1424 | return super().__new__(cls, *args, **kwargs) |
|
1439 | return super().__new__(cls, *args, **kwargs) | |
1425 |
|
1440 | |||
1426 |
|
1441 | |||
@@ -1441,8 +1456,7 b' class BaseInMemoryCommit(object):' | |||||
1441 | list of ``FileNode`` objects marked as *changed* |
|
1456 | list of ``FileNode`` objects marked as *changed* | |
1442 |
|
1457 | |||
1443 | ``removed`` |
|
1458 | ``removed`` | |
1444 |
list of ``FileNode`` o |
|
1459 | list of ``FileNode`` objects marked to be *removed* | |
1445 | *removed* |
|
|||
1446 |
|
1460 | |||
1447 | ``parents`` |
|
1461 | ``parents`` | |
1448 | list of :class:`BaseCommit` instances representing parents of |
|
1462 | list of :class:`BaseCommit` instances representing parents of | |
@@ -1469,9 +1483,7 b' class BaseInMemoryCommit(object):' | |||||
1469 | # Check if not already marked as *added* first |
|
1483 | # Check if not already marked as *added* first | |
1470 | for node in filenodes: |
|
1484 | for node in filenodes: | |
1471 | if node.path in (n.path for n in self.added): |
|
1485 | if node.path in (n.path for n in self.added): | |
1472 | raise NodeAlreadyAddedError( |
|
1486 | raise NodeAlreadyAddedError(f"Such FileNode {node.path} is already marked for addition") | |
1473 | "Such FileNode %s is already marked for addition" |
|
|||
1474 | % node.path) |
|
|||
1475 | for node in filenodes: |
|
1487 | for node in filenodes: | |
1476 | self.added.append(node) |
|
1488 | self.added.append(node) | |
1477 |
|
1489 | |||
@@ -1490,24 +1502,19 b' class BaseInMemoryCommit(object):' | |||||
1490 | """ |
|
1502 | """ | |
1491 | for node in filenodes: |
|
1503 | for node in filenodes: | |
1492 | if node.path in (n.path for n in self.removed): |
|
1504 | if node.path in (n.path for n in self.removed): | |
1493 | raise NodeAlreadyRemovedError( |
|
1505 | raise NodeAlreadyRemovedError("Node at %s is already marked as removed" % node.path) | |
1494 | "Node at %s is already marked as removed" % node.path) |
|
|||
1495 | try: |
|
1506 | try: | |
1496 | self.repository.get_commit() |
|
1507 | self.repository.get_commit() | |
1497 | except EmptyRepositoryError: |
|
1508 | except EmptyRepositoryError: | |
1498 | raise EmptyRepositoryError( |
|
1509 | raise EmptyRepositoryError("Nothing to change - try to *add* new nodes rather than " "changing them") | |
1499 | "Nothing to change - try to *add* new nodes rather than " |
|
|||
1500 | "changing them") |
|
|||
1501 | for node in filenodes: |
|
1510 | for node in filenodes: | |
1502 | if node.path in (n.path for n in self.changed): |
|
1511 | if node.path in (n.path for n in self.changed): | |
1503 | raise NodeAlreadyChangedError( |
|
1512 | raise NodeAlreadyChangedError("Node at '%s' is already marked as changed" % node.path) | |
1504 | "Node at '%s' is already marked as changed" % node.path) |
|
|||
1505 | self.changed.append(node) |
|
1513 | self.changed.append(node) | |
1506 |
|
1514 | |||
1507 | def remove(self, *filenodes): |
|
1515 | def remove(self, *filenodes): | |
1508 | """ |
|
1516 | """ | |
1509 |
Marks given ``FileNode`` |
|
1517 | Marks given ``FileNode`` objects to be *removed* in next commit. | |
1510 | *removed* in next commit. |
|
|||
1511 |
|
1518 | |||
1512 | :raises ``NodeAlreadyRemovedError``: if node has been already marked to |
|
1519 | :raises ``NodeAlreadyRemovedError``: if node has been already marked to | |
1513 | be *removed* |
|
1520 | be *removed* | |
@@ -1516,11 +1523,9 b' class BaseInMemoryCommit(object):' | |||||
1516 | """ |
|
1523 | """ | |
1517 | for node in filenodes: |
|
1524 | for node in filenodes: | |
1518 | if node.path in (n.path for n in self.removed): |
|
1525 | if node.path in (n.path for n in self.removed): | |
1519 | raise NodeAlreadyRemovedError( |
|
1526 | raise NodeAlreadyRemovedError("Node is already marked to for removal at %s" % node.path) | |
1520 | "Node is already marked to for removal at %s" % node.path) |
|
|||
1521 | if node.path in (n.path for n in self.changed): |
|
1527 | if node.path in (n.path for n in self.changed): | |
1522 | raise NodeAlreadyChangedError( |
|
1528 | raise NodeAlreadyChangedError("Node is already marked to be changed at %s" % node.path) | |
1523 | "Node is already marked to be changed at %s" % node.path) |
|
|||
1524 | # We only mark node as *removed* - real removal is done by |
|
1529 | # We only mark node as *removed* - real removal is done by | |
1525 | # commit method |
|
1530 | # commit method | |
1526 | self.removed.append(node) |
|
1531 | self.removed.append(node) | |
@@ -1575,12 +1580,11 b' class BaseInMemoryCommit(object):' | |||||
1575 | for p in parents: |
|
1580 | for p in parents: | |
1576 | for node in self.added: |
|
1581 | for node in self.added: | |
1577 | try: |
|
1582 | try: | |
1578 | p.get_node(node.path) |
|
1583 | p.get_node(node.bytes_path) | |
1579 | except NodeDoesNotExistError: |
|
1584 | except NodeDoesNotExistError: | |
1580 | pass |
|
1585 | pass | |
1581 | else: |
|
1586 | else: | |
1582 | raise NodeAlreadyExistsError( |
|
1587 | raise NodeAlreadyExistsError(f"Node `{node.path}` already exists at {p}") | |
1583 | f"Node `{node.path}` already exists at {p}") |
|
|||
1584 |
|
1588 | |||
1585 | # Check nodes marked as changed |
|
1589 | # Check nodes marked as changed | |
1586 | missing = set(self.changed) |
|
1590 | missing = set(self.changed) | |
@@ -1590,7 +1594,7 b' class BaseInMemoryCommit(object):' | |||||
1590 | for p in parents: |
|
1594 | for p in parents: | |
1591 | for node in self.changed: |
|
1595 | for node in self.changed: | |
1592 | try: |
|
1596 | try: | |
1593 | old = p.get_node(node.path) |
|
1597 | old = p.get_node(node.bytes_path) | |
1594 | missing.remove(node) |
|
1598 | missing.remove(node) | |
1595 | # if content actually changed, remove node from not_changed |
|
1599 | # if content actually changed, remove node from not_changed | |
1596 | if old.content != node.content: |
|
1600 | if old.content != node.content: | |
@@ -1598,24 +1602,23 b' class BaseInMemoryCommit(object):' | |||||
1598 | except NodeDoesNotExistError: |
|
1602 | except NodeDoesNotExistError: | |
1599 | pass |
|
1603 | pass | |
1600 | if self.changed and missing: |
|
1604 | if self.changed and missing: | |
1601 | raise NodeDoesNotExistError( |
|
1605 | raise NodeDoesNotExistError(f"Node `{node.path}` marked as modified but missing in parents: {parents}") | |
1602 | f"Node `{node.path}` marked as modified but missing in parents: {parents}") |
|
|||
1603 |
|
1606 | |||
1604 | if self.changed and not_changed: |
|
1607 | if self.changed and not_changed: | |
1605 | raise NodeNotChangedError( |
|
1608 | raise NodeNotChangedError( | |
1606 | "Node `%s` wasn't actually changed (parents: %s)" |
|
1609 | "Node `%s` wasn't actually changed (parents: %s)" % (not_changed.pop().path, parents) | |
1607 | % (not_changed.pop().path, parents)) |
|
1610 | ) | |
1608 |
|
1611 | |||
1609 | # Check nodes marked as removed |
|
1612 | # Check nodes marked as removed | |
1610 | if self.removed and not parents: |
|
1613 | if self.removed and not parents: | |
1611 | raise NodeDoesNotExistError( |
|
1614 | raise NodeDoesNotExistError( | |
1612 | "Cannot remove node at %s as there " |
|
1615 | "Cannot remove node at %s as there " "were no parents specified" % self.removed[0].path | |
1613 | "were no parents specified" % self.removed[0].path) |
|
1616 | ) | |
1614 | really_removed = set() |
|
1617 | really_removed = set() | |
1615 | for p in parents: |
|
1618 | for p in parents: | |
1616 | for node in self.removed: |
|
1619 | for node in self.removed: | |
1617 | try: |
|
1620 | try: | |
1618 | p.get_node(node.path) |
|
1621 | p.get_node(node.bytes_path) | |
1619 | really_removed.add(node) |
|
1622 | really_removed.add(node) | |
1620 | except CommitError: |
|
1623 | except CommitError: | |
1621 | pass |
|
1624 | pass | |
@@ -1623,8 +1626,8 b' class BaseInMemoryCommit(object):' | |||||
1623 | if not_removed: |
|
1626 | if not_removed: | |
1624 | # TODO: johbo: This code branch does not seem to be covered |
|
1627 | # TODO: johbo: This code branch does not seem to be covered | |
1625 | raise NodeDoesNotExistError( |
|
1628 | raise NodeDoesNotExistError( | |
1626 | "Cannot remove node at %s from " |
|
1629 | "Cannot remove node at %s from " "following parents: %s" % (not_removed, parents) | |
1627 | "following parents: %s" % (not_removed, parents)) |
|
1630 | ) | |
1628 |
|
1631 | |||
1629 | def commit(self, message, author, parents=None, branch=None, date=None, **kwargs): |
|
1632 | def commit(self, message, author, parents=None, branch=None, date=None, **kwargs): | |
1630 | """ |
|
1633 | """ | |
@@ -1652,16 +1655,13 b' class BaseInMemoryCommit(object):' | |||||
1652 |
|
1655 | |||
1653 |
|
1656 | |||
1654 | class BaseInMemoryChangesetClass(type): |
|
1657 | class BaseInMemoryChangesetClass(type): | |
1655 |
|
||||
1656 | def __instancecheck__(self, instance): |
|
1658 | def __instancecheck__(self, instance): | |
1657 | return isinstance(instance, BaseInMemoryCommit) |
|
1659 | return isinstance(instance, BaseInMemoryCommit) | |
1658 |
|
1660 | |||
1659 |
|
1661 | |||
1660 | class BaseInMemoryChangeset(BaseInMemoryCommit, metaclass=BaseInMemoryChangesetClass): |
|
1662 | class BaseInMemoryChangeset(BaseInMemoryCommit, metaclass=BaseInMemoryChangesetClass): | |
1661 |
|
||||
1662 | def __new__(cls, *args, **kwargs): |
|
1663 | def __new__(cls, *args, **kwargs): | |
1663 | warnings.warn( |
|
1664 | warnings.warn("Use BaseCommit instead of BaseInMemoryCommit", DeprecationWarning) | |
1664 | "Use BaseCommit instead of BaseInMemoryCommit", DeprecationWarning) |
|
|||
1665 | return super().__new__(cls, *args, **kwargs) |
|
1665 | return super().__new__(cls, *args, **kwargs) | |
1666 |
|
1666 | |||
1667 |
|
1667 | |||
@@ -1671,9 +1671,7 b' class EmptyCommit(BaseCommit):' | |||||
1671 | an EmptyCommit |
|
1671 | an EmptyCommit | |
1672 | """ |
|
1672 | """ | |
1673 |
|
1673 | |||
1674 | def __init__( |
|
1674 | def __init__(self, commit_id=EMPTY_COMMIT_ID, repo=None, alias=None, idx=-1, message="", author="", date=None): | |
1675 | self, commit_id=EMPTY_COMMIT_ID, repo=None, alias=None, idx=-1, |
|
|||
1676 | message='', author='', date=None): |
|
|||
1677 | self._empty_commit_id = commit_id |
|
1675 | self._empty_commit_id = commit_id | |
1678 | # TODO: johbo: Solve idx parameter, default value does not make |
|
1676 | # TODO: johbo: Solve idx parameter, default value does not make | |
1679 | # too much sense |
|
1677 | # too much sense | |
@@ -1697,6 +1695,7 b' class EmptyCommit(BaseCommit):' | |||||
1697 | def branch(self): |
|
1695 | def branch(self): | |
1698 | if self.alias: |
|
1696 | if self.alias: | |
1699 | from rhodecode.lib.vcs.backends import get_backend |
|
1697 | from rhodecode.lib.vcs.backends import get_backend | |
|
1698 | ||||
1700 | return get_backend(self.alias).DEFAULT_BRANCH_NAME |
|
1699 | return get_backend(self.alias).DEFAULT_BRANCH_NAME | |
1701 |
|
1700 | |||
1702 | @LazyProperty |
|
1701 | @LazyProperty | |
@@ -1711,7 +1710,7 b' class EmptyCommit(BaseCommit):' | |||||
1711 | return self |
|
1710 | return self | |
1712 |
|
1711 | |||
1713 | def get_file_content(self, path) -> bytes: |
|
1712 | def get_file_content(self, path) -> bytes: | |
1714 |
return b |
|
1713 | return b"" | |
1715 |
|
1714 | |||
1716 | def get_file_content_streamed(self, path): |
|
1715 | def get_file_content_streamed(self, path): | |
1717 | yield self.get_file_content(path) |
|
1716 | yield self.get_file_content(path) | |
@@ -1721,27 +1720,29 b' class EmptyCommit(BaseCommit):' | |||||
1721 |
|
1720 | |||
1722 |
|
1721 | |||
1723 | class EmptyChangesetClass(type): |
|
1722 | class EmptyChangesetClass(type): | |
1724 |
|
||||
1725 | def __instancecheck__(self, instance): |
|
1723 | def __instancecheck__(self, instance): | |
1726 | return isinstance(instance, EmptyCommit) |
|
1724 | return isinstance(instance, EmptyCommit) | |
1727 |
|
1725 | |||
1728 |
|
1726 | |||
1729 | class EmptyChangeset(EmptyCommit, metaclass=EmptyChangesetClass): |
|
1727 | class EmptyChangeset(EmptyCommit, metaclass=EmptyChangesetClass): | |
1730 |
|
||||
1731 | def __new__(cls, *args, **kwargs): |
|
1728 | def __new__(cls, *args, **kwargs): | |
1732 | warnings.warn( |
|
1729 | warnings.warn("Use EmptyCommit instead of EmptyChangeset", DeprecationWarning) | |
1733 | "Use EmptyCommit instead of EmptyChangeset", DeprecationWarning) |
|
|||
1734 | return super(EmptyCommit, cls).__new__(cls, *args, **kwargs) |
|
1730 | return super(EmptyCommit, cls).__new__(cls, *args, **kwargs) | |
1735 |
|
1731 | |||
1736 | def __init__(self, cs=EMPTY_COMMIT_ID, repo=None, requested_revision=None, |
|
1732 | def __init__( | |
1737 | alias=None, revision=-1, message='', author='', date=None): |
|
1733 | self, | |
|
1734 | cs=EMPTY_COMMIT_ID, | |||
|
1735 | repo=None, | |||
|
1736 | requested_revision=None, | |||
|
1737 | alias=None, | |||
|
1738 | revision=-1, | |||
|
1739 | message="", | |||
|
1740 | author="", | |||
|
1741 | date=None, | |||
|
1742 | ): | |||
1738 | if requested_revision is not None: |
|
1743 | if requested_revision is not None: | |
1739 | warnings.warn( |
|
1744 | warnings.warn("Parameter requested_revision not supported anymore", DeprecationWarning) | |
1740 | "Parameter requested_revision not supported anymore", |
|
1745 | super().__init__(commit_id=cs, repo=repo, alias=alias, idx=revision, message=message, author=author, date=date) | |
1741 | DeprecationWarning) |
|
|||
1742 | super().__init__( |
|
|||
1743 | commit_id=cs, repo=repo, alias=alias, idx=revision, |
|
|||
1744 | message=message, author=author, date=date) |
|
|||
1745 |
|
1746 | |||
1746 | @property |
|
1747 | @property | |
1747 | def revision(self): |
|
1748 | def revision(self): | |
@@ -1760,11 +1761,11 b' class EmptyRepository(BaseRepository):' | |||||
1760 |
|
1761 | |||
1761 | def get_diff(self, *args, **kwargs): |
|
1762 | def get_diff(self, *args, **kwargs): | |
1762 | from rhodecode.lib.vcs.backends.git.diff import GitDiff |
|
1763 | from rhodecode.lib.vcs.backends.git.diff import GitDiff | |
1763 | return GitDiff(b'') |
|
1764 | ||
|
1765 | return GitDiff(b"") | |||
1764 |
|
1766 | |||
1765 |
|
1767 | |||
1766 | class CollectionGenerator(object): |
|
1768 | class CollectionGenerator(object): | |
1767 |
|
||||
1768 | def __init__(self, repo, commit_ids, collection_size=None, pre_load=None, translate_tag=None): |
|
1769 | def __init__(self, repo, commit_ids, collection_size=None, pre_load=None, translate_tag=None): | |
1769 | self.repo = repo |
|
1770 | self.repo = repo | |
1770 | self.commit_ids = commit_ids |
|
1771 | self.commit_ids = commit_ids | |
@@ -1786,26 +1787,22 b' class CollectionGenerator(object):' | |||||
1786 | """ |
|
1787 | """ | |
1787 | Allows backends to override the way commits are generated. |
|
1788 | Allows backends to override the way commits are generated. | |
1788 | """ |
|
1789 | """ | |
1789 | return self.repo.get_commit( |
|
1790 | return self.repo.get_commit(commit_id=commit_id, pre_load=self.pre_load, translate_tag=self.translate_tag) | |
1790 | commit_id=commit_id, pre_load=self.pre_load, |
|
|||
1791 | translate_tag=self.translate_tag) |
|
|||
1792 |
|
1791 | |||
1793 | def __getitem__(self, key): |
|
1792 | def __getitem__(self, key): | |
1794 | """Return either a single element by index, or a sliced collection.""" |
|
1793 | """Return either a single element by index, or a sliced collection.""" | |
1795 |
|
1794 | |||
1796 | if isinstance(key, slice): |
|
1795 | if isinstance(key, slice): | |
1797 | commit_ids = self.commit_ids[key.start:key.stop] |
|
1796 | commit_ids = self.commit_ids[key.start : key.stop] | |
1798 |
|
1797 | |||
1799 | else: |
|
1798 | else: | |
1800 | # single item |
|
1799 | # single item | |
1801 | commit_ids = self.commit_ids[key] |
|
1800 | commit_ids = self.commit_ids[key] | |
1802 |
|
1801 | |||
1803 | return self.__class__( |
|
1802 | return self.__class__(self.repo, commit_ids, pre_load=self.pre_load, translate_tag=self.translate_tag) | |
1804 | self.repo, commit_ids, pre_load=self.pre_load, |
|
|||
1805 | translate_tag=self.translate_tag) |
|
|||
1806 |
|
1803 | |||
1807 | def __repr__(self): |
|
1804 | def __repr__(self): | |
1808 |
return |
|
1805 | return "<CollectionGenerator[len:%s]>" % (self.__len__()) | |
1809 |
|
1806 | |||
1810 |
|
1807 | |||
1811 | class Config(object): |
|
1808 | class Config(object): | |
@@ -1826,8 +1823,7 b' class Config(object):' | |||||
1826 | return clone |
|
1823 | return clone | |
1827 |
|
1824 | |||
1828 | def __repr__(self): |
|
1825 | def __repr__(self): | |
1829 |
return |
|
1826 | return "<Config({} sections) at {}>".format(len(self._values), hex(id(self))) | |
1830 | len(self._values), hex(id(self))) |
|
|||
1831 |
|
1827 | |||
1832 | def items(self, section): |
|
1828 | def items(self, section): | |
1833 | return self._values.get(section, {}).items() |
|
1829 | return self._values.get(section, {}).items() | |
@@ -1844,7 +1840,7 b' class Config(object):' | |||||
1844 |
|
1840 | |||
1845 | def drop_option(self, section, option): |
|
1841 | def drop_option(self, section, option): | |
1846 | if section not in self._values: |
|
1842 | if section not in self._values: | |
1847 |
raise ValueError(f |
|
1843 | raise ValueError(f"Section {section} does not exist") | |
1848 | del self._values[section][option] |
|
1844 | del self._values[section][option] | |
1849 |
|
1845 | |||
1850 | def serialize(self): |
|
1846 | def serialize(self): | |
@@ -1855,8 +1851,7 b' class Config(object):' | |||||
1855 | items = [] |
|
1851 | items = [] | |
1856 | for section in self._values: |
|
1852 | for section in self._values: | |
1857 | for option, value in self._values[section].items(): |
|
1853 | for option, value in self._values[section].items(): | |
1858 | items.append( |
|
1854 | items.append((safe_str(section), safe_str(option), safe_str(value))) | |
1859 | (safe_str(section), safe_str(option), safe_str(value))) |
|
|||
1860 | return items |
|
1855 | return items | |
1861 |
|
1856 | |||
1862 |
|
1857 | |||
@@ -1867,12 +1862,13 b' class Diff(object):' | |||||
1867 | Subclasses have to provide a backend specific value for |
|
1862 | Subclasses have to provide a backend specific value for | |
1868 | :attr:`_header_re` and :attr:`_meta_re`. |
|
1863 | :attr:`_header_re` and :attr:`_meta_re`. | |
1869 | """ |
|
1864 | """ | |
|
1865 | ||||
1870 | _meta_re = None |
|
1866 | _meta_re = None | |
1871 |
_header_re: bytes = re.compile( |
|
1867 | _header_re: bytes = re.compile(rb"") | |
1872 |
|
1868 | |||
1873 | def __init__(self, raw_diff: bytes): |
|
1869 | def __init__(self, raw_diff: bytes): | |
1874 | if not isinstance(raw_diff, bytes): |
|
1870 | if not isinstance(raw_diff, bytes): | |
1875 |
raise Exception(f |
|
1871 | raise Exception(f"raw_diff must be bytes - got {type(raw_diff)}") | |
1876 |
|
1872 | |||
1877 | self.raw = memoryview(raw_diff) |
|
1873 | self.raw = memoryview(raw_diff) | |
1878 |
|
1874 | |||
@@ -1886,7 +1882,7 b' class Diff(object):' | |||||
1886 | we can detect last chunk as this was also has special rule |
|
1882 | we can detect last chunk as this was also has special rule | |
1887 | """ |
|
1883 | """ | |
1888 |
|
1884 | |||
1889 |
diff_parts = (b |
|
1885 | diff_parts = (b"\n" + bytes(self.raw)).split(b"\ndiff --git") | |
1890 |
|
1886 | |||
1891 | chunks = diff_parts[1:] |
|
1887 | chunks = diff_parts[1:] | |
1892 | total_chunks = len(chunks) |
|
1888 | total_chunks = len(chunks) | |
@@ -1894,44 +1890,46 b' class Diff(object):' | |||||
1894 | def diff_iter(_chunks): |
|
1890 | def diff_iter(_chunks): | |
1895 | for cur_chunk, chunk in enumerate(_chunks, start=1): |
|
1891 | for cur_chunk, chunk in enumerate(_chunks, start=1): | |
1896 | yield DiffChunk(chunk, self, cur_chunk == total_chunks) |
|
1892 | yield DiffChunk(chunk, self, cur_chunk == total_chunks) | |
|
1893 | ||||
1897 | return diff_iter(chunks) |
|
1894 | return diff_iter(chunks) | |
1898 |
|
1895 | |||
1899 |
|
1896 | |||
1900 | class DiffChunk(object): |
|
1897 | class DiffChunk(object): | |
1901 |
|
||||
1902 | def __init__(self, chunk: bytes, diff_obj: Diff, is_last_chunk: bool): |
|
1898 | def __init__(self, chunk: bytes, diff_obj: Diff, is_last_chunk: bool): | |
1903 | self.diff_obj = diff_obj |
|
1899 | self.diff_obj = diff_obj | |
1904 |
|
1900 | |||
1905 | # since we split by \ndiff --git that part is lost from original diff |
|
1901 | # since we split by \ndiff --git that part is lost from original diff | |
1906 | # we need to re-apply it at the end, EXCEPT ! if it's last chunk |
|
1902 | # we need to re-apply it at the end, EXCEPT ! if it's last chunk | |
1907 | if not is_last_chunk: |
|
1903 | if not is_last_chunk: | |
1908 |
chunk += b |
|
1904 | chunk += b"\n" | |
1909 | header_re = self.diff_obj.get_header_re() |
|
1905 | header_re = self.diff_obj.get_header_re() | |
|
1906 | ||||
1910 | match = header_re.match(chunk) |
|
1907 | match = header_re.match(chunk) | |
1911 | self.header = match.groupdict() |
|
1908 | self.header = match.groupdict() | |
1912 | self.diff = chunk[match.end():] |
|
1909 | self.diff = chunk[match.end() :] | |
1913 | self.raw = chunk |
|
1910 | self.raw = chunk | |
1914 |
|
1911 | |||
1915 | @property |
|
1912 | @property | |
1916 | def header_as_str(self): |
|
1913 | def header_as_str(self): | |
1917 | if self.header: |
|
1914 | if self.header: | |
|
1915 | ||||
1918 | def safe_str_on_bytes(val): |
|
1916 | def safe_str_on_bytes(val): | |
1919 | if isinstance(val, bytes): |
|
1917 | if isinstance(val, bytes): | |
1920 | return safe_str(val) |
|
1918 | return safe_str(val) | |
1921 | return val |
|
1919 | return val | |
|
1920 | ||||
1922 | return {safe_str(k): safe_str_on_bytes(v) for k, v in self.header.items()} |
|
1921 | return {safe_str(k): safe_str_on_bytes(v) for k, v in self.header.items()} | |
1923 |
|
1922 | |||
1924 | def __repr__(self): |
|
1923 | def __repr__(self): | |
1925 |
return f |
|
1924 | return f"DiffChunk({self.header_as_str})" | |
1926 |
|
1925 | |||
1927 |
|
1926 | |||
1928 | class BasePathPermissionChecker(object): |
|
1927 | class BasePathPermissionChecker(object): | |
1929 |
|
||||
1930 | @staticmethod |
|
1928 | @staticmethod | |
1931 | def create_from_patterns(includes, excludes): |
|
1929 | def create_from_patterns(includes, excludes): | |
1932 |
if includes and |
|
1930 | if includes and "*" in includes and not excludes: | |
1933 | return AllPathPermissionChecker() |
|
1931 | return AllPathPermissionChecker() | |
1934 |
elif excludes and |
|
1932 | elif excludes and "*" in excludes: | |
1935 | return NonePathPermissionChecker() |
|
1933 | return NonePathPermissionChecker() | |
1936 | else: |
|
1934 | else: | |
1937 | return PatternPathPermissionChecker(includes, excludes) |
|
1935 | return PatternPathPermissionChecker(includes, excludes) | |
@@ -1945,7 +1943,6 b' class BasePathPermissionChecker(object):' | |||||
1945 |
|
1943 | |||
1946 |
|
1944 | |||
1947 | class AllPathPermissionChecker(BasePathPermissionChecker): |
|
1945 | class AllPathPermissionChecker(BasePathPermissionChecker): | |
1948 |
|
||||
1949 | @property |
|
1946 | @property | |
1950 | def has_full_access(self): |
|
1947 | def has_full_access(self): | |
1951 | return True |
|
1948 | return True | |
@@ -1955,7 +1952,6 b' class AllPathPermissionChecker(BasePathP' | |||||
1955 |
|
1952 | |||
1956 |
|
1953 | |||
1957 | class NonePathPermissionChecker(BasePathPermissionChecker): |
|
1954 | class NonePathPermissionChecker(BasePathPermissionChecker): | |
1958 |
|
||||
1959 | @property |
|
1955 | @property | |
1960 | def has_full_access(self): |
|
1956 | def has_full_access(self): | |
1961 | return False |
|
1957 | return False | |
@@ -1965,18 +1961,15 b' class NonePathPermissionChecker(BasePath' | |||||
1965 |
|
1961 | |||
1966 |
|
1962 | |||
1967 | class PatternPathPermissionChecker(BasePathPermissionChecker): |
|
1963 | class PatternPathPermissionChecker(BasePathPermissionChecker): | |
1968 |
|
||||
1969 | def __init__(self, includes, excludes): |
|
1964 | def __init__(self, includes, excludes): | |
1970 | self.includes = includes |
|
1965 | self.includes = includes | |
1971 | self.excludes = excludes |
|
1966 | self.excludes = excludes | |
1972 | self.includes_re = [] if not includes else [ |
|
1967 | self.includes_re = [] if not includes else [re.compile(fnmatch.translate(pattern)) for pattern in includes] | |
1973 |
re.compile(fnmatch.translate(pattern)) for pattern in |
|
1968 | self.excludes_re = [] if not excludes else [re.compile(fnmatch.translate(pattern)) for pattern in excludes] | |
1974 | self.excludes_re = [] if not excludes else [ |
|
|||
1975 | re.compile(fnmatch.translate(pattern)) for pattern in excludes] |
|
|||
1976 |
|
1969 | |||
1977 | @property |
|
1970 | @property | |
1978 | def has_full_access(self): |
|
1971 | def has_full_access(self): | |
1979 |
return |
|
1972 | return "*" in self.includes and not self.excludes | |
1980 |
|
1973 | |||
1981 | def has_access(self, path): |
|
1974 | def has_access(self, path): | |
1982 | for regex in self.excludes_re: |
|
1975 | for regex in self.excludes_re: |
@@ -21,8 +21,8 b' GIT commit module' | |||||
21 | """ |
|
21 | """ | |
22 |
|
22 | |||
23 | import io |
|
23 | import io | |
24 | import stat |
|
|||
25 | import configparser |
|
24 | import configparser | |
|
25 | import logging | |||
26 | from itertools import chain |
|
26 | from itertools import chain | |
27 |
|
27 | |||
28 | from zope.cachedescriptors.property import Lazy as LazyProperty |
|
28 | from zope.cachedescriptors.property import Lazy as LazyProperty | |
@@ -32,9 +32,16 b' from rhodecode.lib.str_utils import safe' | |||||
32 | from rhodecode.lib.vcs.backends import base |
|
32 | from rhodecode.lib.vcs.backends import base | |
33 | from rhodecode.lib.vcs.exceptions import CommitError, NodeDoesNotExistError |
|
33 | from rhodecode.lib.vcs.exceptions import CommitError, NodeDoesNotExistError | |
34 | from rhodecode.lib.vcs.nodes import ( |
|
34 | from rhodecode.lib.vcs.nodes import ( | |
35 | FileNode, DirNode, NodeKind, RootNode, SubModuleNode, |
|
35 | FileNode, | |
36 | ChangedFileNodesGenerator, AddedFileNodesGenerator, |
|
36 | DirNode, | |
37 | RemovedFileNodesGenerator, LargeFileNode) |
|
37 | NodeKind, | |
|
38 | RootNode, | |||
|
39 | SubModuleNode, | |||
|
40 | LargeFileNode, | |||
|
41 | ) | |||
|
42 | from rhodecode.lib.vcs_common import FILEMODE_LINK | |||
|
43 | ||||
|
44 | log = logging.getLogger(__name__) | |||
38 |
|
45 | |||
39 |
|
46 | |||
40 | class GitCommit(base.BaseCommit): |
|
47 | class GitCommit(base.BaseCommit): | |
@@ -50,13 +57,11 b' class GitCommit(base.BaseCommit):' | |||||
50 | # done through a more complex tree walk on parents |
|
57 | # done through a more complex tree walk on parents | |
51 | "status", |
|
58 | "status", | |
52 | # mercurial specific property not supported here |
|
59 | # mercurial specific property not supported here | |
53 |
" |
|
60 | "obsolete", | |
54 | # mercurial specific property not supported here |
|
|||
55 | 'obsolete', |
|
|||
56 | # mercurial specific property not supported here |
|
61 | # mercurial specific property not supported here | |
57 |
|
|
62 | "phase", | |
58 | # mercurial specific property not supported here |
|
63 | # mercurial specific property not supported here | |
59 |
|
|
64 | "hidden", | |
60 | ] |
|
65 | ] | |
61 |
|
66 | |||
62 | def __init__(self, repository, raw_id, idx, pre_load=None): |
|
67 | def __init__(self, repository, raw_id, idx, pre_load=None): | |
@@ -69,17 +74,16 b' class GitCommit(base.BaseCommit):' | |||||
69 | self._set_bulk_properties(pre_load) |
|
74 | self._set_bulk_properties(pre_load) | |
70 |
|
75 | |||
71 | # caches |
|
76 | # caches | |
72 | self._stat_modes = {} # stat info for paths |
|
|||
73 | self._paths = {} # path processed with parse_tree |
|
|||
74 | self.nodes = {} |
|
77 | self.nodes = {} | |
|
78 | self._path_mode_cache = {} # path stats cache, e.g filemode etc | |||
|
79 | self._path_type_cache = {} # path type dir/file/link etc cache | |||
|
80 | ||||
75 | self._submodules = None |
|
81 | self._submodules = None | |
76 |
|
82 | |||
77 | def _set_bulk_properties(self, pre_load): |
|
83 | def _set_bulk_properties(self, pre_load): | |
78 |
|
||||
79 | if not pre_load: |
|
84 | if not pre_load: | |
80 | return |
|
85 | return | |
81 | pre_load = [entry for entry in pre_load |
|
86 | pre_load = [entry for entry in pre_load if entry not in self._filter_pre_load] | |
82 | if entry not in self._filter_pre_load] |
|
|||
83 | if not pre_load: |
|
87 | if not pre_load: | |
84 | return |
|
88 | return | |
85 |
|
89 | |||
@@ -102,7 +106,7 b' class GitCommit(base.BaseCommit):' | |||||
102 |
|
106 | |||
103 | @LazyProperty |
|
107 | @LazyProperty | |
104 | def _tree_id(self): |
|
108 | def _tree_id(self): | |
105 |
return self._remote[self._commit[ |
|
109 | return self._remote[self._commit["tree"]]["id"] | |
106 |
|
110 | |||
107 | @LazyProperty |
|
111 | @LazyProperty | |
108 | def id(self): |
|
112 | def id(self): | |
@@ -134,13 +138,12 b' class GitCommit(base.BaseCommit):' | |||||
134 | """ |
|
138 | """ | |
135 | Returns modified, added, removed, deleted files for current commit |
|
139 | Returns modified, added, removed, deleted files for current commit | |
136 | """ |
|
140 | """ | |
137 | return self.changed, self.added, self.removed |
|
141 | added, modified, deleted = self._changes_cache | |
|
142 | return list(modified), list(modified), list(deleted) | |||
138 |
|
143 | |||
139 | @LazyProperty |
|
144 | @LazyProperty | |
140 | def tags(self): |
|
145 | def tags(self): | |
141 | tags = [safe_str(name) for name, |
|
146 | tags = [safe_str(name) for name, commit_id in self.repository.tags.items() if commit_id == self.raw_id] | |
142 | commit_id in self.repository.tags.items() |
|
|||
143 | if commit_id == self.raw_id] |
|
|||
144 | return tags |
|
147 | return tags | |
145 |
|
148 | |||
146 | @LazyProperty |
|
149 | @LazyProperty | |
@@ -161,47 +164,33 b' class GitCommit(base.BaseCommit):' | |||||
161 | branches = self._remote.branch(self.raw_id) |
|
164 | branches = self._remote.branch(self.raw_id) | |
162 | return self._set_branch(branches) |
|
165 | return self._set_branch(branches) | |
163 |
|
166 | |||
164 |
def _get_tree_id_ |
|
167 | def _get_path_tree_id_and_type(self, path: bytes): | |
165 |
|
168 | |||
166 |
path |
|
169 | if path in self._path_type_cache: | |
167 | if path in self._paths: |
|
170 | return self._path_type_cache[path] | |
168 | return self._paths[path] |
|
|||
169 |
|
||||
170 | tree_id = self._tree_id |
|
|||
171 |
|
171 | |||
172 | path = path.strip('/') |
|
172 | if path == b"": | |
173 | if path == '': |
|
173 | self._path_type_cache[b""] = [self._tree_id, NodeKind.DIR] | |
174 | data = [tree_id, "tree"] |
|
174 | return self._path_type_cache[path] | |
175 | self._paths[''] = data |
|
|||
176 | return data |
|
|||
177 |
|
175 | |||
178 | tree_id, tree_type, tree_mode = \ |
|
176 | tree_id, tree_type, tree_mode = self._remote.tree_and_type_for_path(self.raw_id, path) | |
179 | self._remote.tree_and_type_for_path(self.raw_id, path) |
|
|||
180 | if tree_id is None: |
|
177 | if tree_id is None: | |
181 | raise self.no_node_at_path(path) |
|
178 | raise self.no_node_at_path(path) | |
182 |
|
179 | |||
183 |
self._path |
|
180 | self._path_type_cache[path] = [tree_id, tree_type] | |
184 |
self._ |
|
181 | self._path_mode_cache[path] = tree_mode | |
185 |
|
182 | |||
186 | if path not in self._paths: |
|
183 | return self._path_type_cache[path] | |
187 | raise self.no_node_at_path(path) |
|
|||
188 |
|
||||
189 | return self._paths[path] |
|
|||
190 |
|
184 | |||
191 | def _get_kind(self, path): |
|
185 | def _get_kind(self, path): | |
192 |
|
|
186 | path = self._fix_path(path) | |
193 | if type_ == 'blob': |
|
187 | _, path_type = self._get_path_tree_id_and_type(path) | |
194 | return NodeKind.FILE |
|
188 | return path_type | |
195 | elif type_ == 'tree': |
|
|||
196 | return NodeKind.DIR |
|
|||
197 | elif type_ == 'link': |
|
|||
198 | return NodeKind.SUBMODULE |
|
|||
199 | return None |
|
|||
200 |
|
189 | |||
201 | def _assert_is_path(self, path): |
|
190 | def _assert_is_path(self, path): | |
202 | path = self._fix_path(path) |
|
191 | path = self._fix_path(path) | |
203 | if self._get_kind(path) != NodeKind.FILE: |
|
192 | if self._get_kind(path) != NodeKind.FILE: | |
204 |
raise CommitError(f"File does not exist for commit {self.raw_id} |
|
193 | raise CommitError(f"File at path={path} does not exist for commit {self.raw_id}") | |
205 | return path |
|
194 | return path | |
206 |
|
195 | |||
207 | def _get_file_nodes(self): |
|
196 | def _get_file_nodes(self): | |
@@ -237,15 +226,19 b' class GitCommit(base.BaseCommit):' | |||||
237 | path = self._assert_is_path(path) |
|
226 | path = self._assert_is_path(path) | |
238 |
|
227 | |||
239 | # ensure path is traversed |
|
228 | # ensure path is traversed | |
240 |
self._get_tree_id_ |
|
229 | self._get_path_tree_id_and_type(path) | |
|
230 | ||||
|
231 | return self._path_mode_cache[path] | |||
241 |
|
232 | |||
242 | return self._stat_modes[path] |
|
233 | def is_link(self, path: bytes): | |
|
234 | path = self._assert_is_path(path) | |||
|
235 | if path not in self._path_mode_cache: | |||
|
236 | self._path_mode_cache[path] = self._remote.fctx_flags(self.raw_id, path) | |||
243 |
|
237 | |||
244 | def is_link(self, path): |
|
238 | return self._path_mode_cache[path] == FILEMODE_LINK | |
245 | return stat.S_ISLNK(self.get_file_mode(path)) |
|
|||
246 |
|
239 | |||
247 | def is_node_binary(self, path): |
|
240 | def is_node_binary(self, path): | |
248 |
tree_id, _ = self._get_tree_id_ |
|
241 | tree_id, _ = self._get_path_tree_id_and_type(path) | |
249 | return self._remote.is_binary(tree_id) |
|
242 | return self._remote.is_binary(tree_id) | |
250 |
|
243 | |||
251 | def node_md5_hash(self, path): |
|
244 | def node_md5_hash(self, path): | |
@@ -256,19 +249,19 b' class GitCommit(base.BaseCommit):' | |||||
256 | """ |
|
249 | """ | |
257 | Returns content of the file at given `path`. |
|
250 | Returns content of the file at given `path`. | |
258 | """ |
|
251 | """ | |
259 |
tree_id, _ = self._get_tree_id_ |
|
252 | tree_id, _ = self._get_path_tree_id_and_type(path) | |
260 | return self._remote.blob_as_pretty_string(tree_id) |
|
253 | return self._remote.blob_as_pretty_string(tree_id) | |
261 |
|
254 | |||
262 | def get_file_content_streamed(self, path): |
|
255 | def get_file_content_streamed(self, path): | |
263 |
tree_id, _ = self._get_tree_id_ |
|
256 | tree_id, _ = self._get_path_tree_id_and_type(path) | |
264 |
stream_method = getattr(self._remote, |
|
257 | stream_method = getattr(self._remote, "stream:blob_as_pretty_string") | |
265 | return stream_method(tree_id) |
|
258 | return stream_method(tree_id) | |
266 |
|
259 | |||
267 | def get_file_size(self, path): |
|
260 | def get_file_size(self, path): | |
268 | """ |
|
261 | """ | |
269 | Returns size of the file at given `path`. |
|
262 | Returns size of the file at given `path`. | |
270 | """ |
|
263 | """ | |
271 |
tree_id, _ = self._get_tree_id_ |
|
264 | tree_id, _ = self._get_path_tree_id_and_type(path) | |
272 | return self._remote.blob_raw_length(tree_id) |
|
265 | return self._remote.blob_raw_length(tree_id) | |
273 |
|
266 | |||
274 | def get_path_history(self, path, limit=None, pre_load=None): |
|
267 | def get_path_history(self, path, limit=None, pre_load=None): | |
@@ -276,12 +269,9 b' class GitCommit(base.BaseCommit):' | |||||
276 | Returns history of file as reversed list of `GitCommit` objects for |
|
269 | Returns history of file as reversed list of `GitCommit` objects for | |
277 | which file at given `path` has been modified. |
|
270 | which file at given `path` has been modified. | |
278 | """ |
|
271 | """ | |
279 |
|
||||
280 | path = self._assert_is_path(path) |
|
272 | path = self._assert_is_path(path) | |
281 | hist = self._remote.node_history(self.raw_id, path, limit) |
|
273 | history = self._remote.node_history(self.raw_id, path, limit) | |
282 | return [ |
|
274 | return [self.repository.get_commit(commit_id=commit_id, pre_load=pre_load) for commit_id in history] | |
283 | self.repository.get_commit(commit_id=commit_id, pre_load=pre_load) |
|
|||
284 | for commit_id in hist] |
|
|||
285 |
|
275 | |||
286 | def get_file_annotate(self, path, pre_load=None): |
|
276 | def get_file_annotate(self, path, pre_load=None): | |
287 | """ |
|
277 | """ | |
@@ -293,95 +283,105 b' class GitCommit(base.BaseCommit):' | |||||
293 |
|
283 | |||
294 | for ln_no, commit_id, content in result: |
|
284 | for ln_no, commit_id, content in result: | |
295 | yield ( |
|
285 | yield ( | |
296 |
ln_no, |
|
286 | ln_no, | |
|
287 | commit_id, | |||
297 | lambda: self.repository.get_commit(commit_id=commit_id, pre_load=pre_load), |
|
288 | lambda: self.repository.get_commit(commit_id=commit_id, pre_load=pre_load), | |
298 |
content |
|
289 | content, | |
|
290 | ) | |||
299 |
|
291 | |||
300 | def get_nodes(self, path, pre_load=None): |
|
292 | def get_nodes(self, path: bytes, pre_load=None): | |
301 |
|
293 | |||
302 | if self._get_kind(path) != NodeKind.DIR: |
|
294 | if self._get_kind(path) != NodeKind.DIR: | |
303 | raise CommitError( |
|
295 | raise CommitError(f"Directory does not exist for commit {self.raw_id} at '{path}'") | |
304 | f"Directory does not exist for commit {self.raw_id} at '{path}'") |
|
|||
305 | path = self._fix_path(path) |
|
296 | path = self._fix_path(path) | |
306 |
|
297 | |||
307 | tree_id, _ = self._get_tree_id_for_path(path) |
|
298 | # call and check tree_id for this path | |
308 |
|
299 | tree_id, _ = self._get_path_tree_id_and_type(path) | ||
309 | dirnodes = [] |
|
|||
310 | filenodes = [] |
|
|||
311 |
|
300 | |||
312 | # extracted tree ID gives us our files... |
|
301 | path_nodes = [] | |
313 | str_path = safe_str(path) # libgit operates on bytes |
|
302 | ||
314 |
for name, stat_, id |
|
303 | for bytes_name, stat_, tree_item_id, node_kind in self._remote.tree_items(tree_id): | |
315 |
if |
|
304 | if node_kind is None: | |
316 | url = self._get_submodule_url('/'.join((str_path, name))) |
|
305 | raise CommitError(f"Requested object type={node_kind} cannot be determined") | |
317 | dirnodes.append(SubModuleNode( |
|
|||
318 | name, url=url, commit=id_, alias=self.repository.alias)) |
|
|||
319 | continue |
|
|||
320 |
|
306 | |||
321 |
if |
|
307 | if path != b"": | |
322 |
obj_path = |
|
308 | obj_path = b"/".join((path, bytes_name)) | |
323 | else: |
|
309 | else: | |
324 | obj_path = name |
|
310 | obj_path = bytes_name | |
325 | if obj_path not in self._stat_modes: |
|
|||
326 | self._stat_modes[obj_path] = stat_ |
|
|||
327 |
|
311 | |||
328 | if type_ == 'tree': |
|
312 | # cache file mode for git, since we have it already | |
329 | dirnodes.append(DirNode(safe_bytes(obj_path), commit=self)) |
|
313 | if obj_path not in self._path_mode_cache: | |
330 | elif type_ == 'blob': |
|
314 | self._path_mode_cache[obj_path] = stat_ | |
331 | filenodes.append(FileNode(safe_bytes(obj_path), commit=self, mode=stat_, pre_load=pre_load)) |
|
315 | ||
332 |
|
|
316 | # cache type | |
333 | raise CommitError(f"Requested object should be Tree or Blob, is {type_}") |
|
317 | if node_kind not in self._path_type_cache: | |
|
318 | self._path_type_cache[obj_path] = [tree_item_id, node_kind] | |||
334 |
|
319 | |||
335 | nodes = dirnodes + filenodes |
|
320 | entry = None | |
336 |
f |
|
321 | if obj_path in self.nodes: | |
337 |
|
|
322 | entry = self.nodes[obj_path] | |
338 | self.nodes[node.path] = node |
|
323 | else: | |
339 | nodes.sort() |
|
324 | if node_kind == NodeKind.SUBMODULE: | |
340 | return nodes |
|
325 | url = self._get_submodule_url(b"/".join((path, bytes_name))) | |
|
326 | entry= SubModuleNode(bytes_name, url=url, commit=tree_item_id, alias=self.repository.alias) | |||
|
327 | elif node_kind == NodeKind.DIR: | |||
|
328 | entry = DirNode(safe_bytes(obj_path), commit=self) | |||
|
329 | elif node_kind == NodeKind.FILE: | |||
|
330 | entry = FileNode(safe_bytes(obj_path), commit=self, mode=stat_, pre_load=pre_load) | |||
341 |
|
331 | |||
342 | def get_node(self, path, pre_load=None): |
|
332 | if entry: | |
|
333 | self.nodes[obj_path] = entry | |||
|
334 | path_nodes.append(entry) | |||
|
335 | ||||
|
336 | path_nodes.sort() | |||
|
337 | return path_nodes | |||
|
338 | ||||
|
339 | def get_node(self, path: bytes, pre_load=None): | |||
343 | path = self._fix_path(path) |
|
340 | path = self._fix_path(path) | |
344 | if path not in self.nodes: |
|
341 | ||
345 | try: |
|
342 | # use cached, if we have one | |
346 | tree_id, type_ = self._get_tree_id_for_path(path) |
|
343 | if path in self.nodes: | |
347 | except CommitError: |
|
344 | return self.nodes[path] | |
348 | raise NodeDoesNotExistError( |
|
|||
349 | f"Cannot find one of parents' directories for a given " |
|
|||
350 | f"path: {path}") |
|
|||
351 |
|
345 | |||
352 | if type_ in ['link', 'commit']: |
|
346 | try: | |
|
347 | tree_id, path_type = self._get_path_tree_id_and_type(path) | |||
|
348 | except CommitError: | |||
|
349 | raise NodeDoesNotExistError(f"Cannot find one of parents' directories for a given path: {path}") | |||
|
350 | ||||
|
351 | if path == b"": | |||
|
352 | node = RootNode(commit=self) | |||
|
353 | else: | |||
|
354 | if path_type == NodeKind.SUBMODULE: | |||
353 | url = self._get_submodule_url(path) |
|
355 | url = self._get_submodule_url(path) | |
354 | node = SubModuleNode(path, url=url, commit=tree_id, |
|
356 | node = SubModuleNode(path, url=url, commit=tree_id, alias=self.repository.alias) | |
355 | alias=self.repository.alias) |
|
357 | elif path_type == NodeKind.DIR: | |
356 | elif type_ == 'tree': |
|
358 | node = DirNode(safe_bytes(path), commit=self) | |
357 |
|
|
359 | elif path_type == NodeKind.FILE: | |
358 | node = RootNode(commit=self) |
|
|||
359 | else: |
|
|||
360 | node = DirNode(safe_bytes(path), commit=self) |
|
|||
361 | elif type_ == 'blob': |
|
|||
362 | node = FileNode(safe_bytes(path), commit=self, pre_load=pre_load) |
|
360 | node = FileNode(safe_bytes(path), commit=self, pre_load=pre_load) | |
363 |
self._ |
|
361 | self._path_mode_cache[path] = node.mode | |
364 | else: |
|
362 | else: | |
365 | raise self.no_node_at_path(path) |
|
363 | raise self.no_node_at_path(path) | |
366 |
|
364 | |||
367 |
|
|
365 | # cache node | |
368 |
|
|
366 | self.nodes[path] = node | |
369 |
|
||||
370 | return self.nodes[path] |
|
367 | return self.nodes[path] | |
371 |
|
368 | |||
372 | def get_largefile_node(self, path): |
|
369 | def get_largefile_node(self, path: bytes): | |
373 |
tree_id, _ = self._get_tree_id_ |
|
370 | tree_id, _ = self._get_path_tree_id_and_type(path) | |
374 | pointer_spec = self._remote.is_large_file(tree_id) |
|
371 | pointer_spec = self._remote.is_large_file(tree_id) | |
375 |
|
372 | |||
376 | if pointer_spec: |
|
373 | if pointer_spec: | |
377 | # content of that file regular FileNode is the hash of largefile |
|
374 | # content of that file regular FileNode is the hash of largefile | |
378 |
file_id = pointer_spec.get( |
|
375 | file_id = pointer_spec.get("oid_hash") | |
379 | if self._remote.in_largefiles_store(file_id): |
|
376 | if not self._remote.in_largefiles_store(file_id): | |
380 | lf_path = self._remote.store_path(file_id) |
|
377 | log.warning(f'Largefile oid={file_id} not found in store') | |
381 | return LargeFileNode(safe_bytes(lf_path), commit=self, org_path=path) |
|
378 | return None | |
|
379 | ||||
|
380 | lf_path = self._remote.store_path(file_id) | |||
|
381 | return LargeFileNode(safe_bytes(lf_path), commit=self, org_path=path) | |||
382 |
|
382 | |||
383 | @LazyProperty |
|
383 | @LazyProperty | |
384 | def affected_files(self): |
|
384 | def affected_files(self) -> list[bytes]: | |
385 | """ |
|
385 | """ | |
386 | Gets a fast accessible file changes for given commit |
|
386 | Gets a fast accessible file changes for given commit | |
387 | """ |
|
387 | """ | |
@@ -389,7 +389,7 b' class GitCommit(base.BaseCommit):' | |||||
389 | return list(added.union(modified).union(deleted)) |
|
389 | return list(added.union(modified).union(deleted)) | |
390 |
|
390 | |||
391 | @LazyProperty |
|
391 | @LazyProperty | |
392 | def _changes_cache(self): |
|
392 | def _changes_cache(self) -> tuple[set, set, set]: | |
393 | added = set() |
|
393 | added = set() | |
394 | modified = set() |
|
394 | modified = set() | |
395 | deleted = set() |
|
395 | deleted = set() | |
@@ -416,53 +416,22 b' class GitCommit(base.BaseCommit):' | |||||
416 | :param status: one of: *added*, *modified* or *deleted* |
|
416 | :param status: one of: *added*, *modified* or *deleted* | |
417 | """ |
|
417 | """ | |
418 | added, modified, deleted = self._changes_cache |
|
418 | added, modified, deleted = self._changes_cache | |
419 | return sorted({ |
|
419 | return sorted({"added": list(added), "modified": list(modified), "deleted": list(deleted)}[status]) | |
420 | 'added': list(added), |
|
|||
421 | 'modified': list(modified), |
|
|||
422 | 'deleted': list(deleted)}[status] |
|
|||
423 | ) |
|
|||
424 |
|
||||
425 | @LazyProperty |
|
|||
426 | def added(self): |
|
|||
427 | """ |
|
|||
428 | Returns list of added ``FileNode`` objects. |
|
|||
429 | """ |
|
|||
430 | if not self.parents: |
|
|||
431 | return list(self._get_file_nodes()) |
|
|||
432 | return AddedFileNodesGenerator(self.added_paths, self) |
|
|||
433 |
|
420 | |||
434 | @LazyProperty |
|
421 | @LazyProperty | |
435 | def added_paths(self): |
|
422 | def added_paths(self): | |
436 |
return [n for n in self._get_paths_for_status( |
|
423 | return [n for n in self._get_paths_for_status("added")] | |
437 |
|
||||
438 | @LazyProperty |
|
|||
439 | def changed(self): |
|
|||
440 | """ |
|
|||
441 | Returns list of modified ``FileNode`` objects. |
|
|||
442 | """ |
|
|||
443 | if not self.parents: |
|
|||
444 | return [] |
|
|||
445 | return ChangedFileNodesGenerator(self.changed_paths, self) |
|
|||
446 |
|
424 | |||
447 | @LazyProperty |
|
425 | @LazyProperty | |
448 | def changed_paths(self): |
|
426 | def changed_paths(self): | |
449 |
return [n for n in self._get_paths_for_status( |
|
427 | return [n for n in self._get_paths_for_status("modified")] | |
450 |
|
||||
451 | @LazyProperty |
|
|||
452 | def removed(self): |
|
|||
453 | """ |
|
|||
454 | Returns list of removed ``FileNode`` objects. |
|
|||
455 | """ |
|
|||
456 | if not self.parents: |
|
|||
457 | return [] |
|
|||
458 | return RemovedFileNodesGenerator(self.removed_paths, self) |
|
|||
459 |
|
428 | |||
460 | @LazyProperty |
|
429 | @LazyProperty | |
461 | def removed_paths(self): |
|
430 | def removed_paths(self): | |
462 |
return [n for n in self._get_paths_for_status( |
|
431 | return [n for n in self._get_paths_for_status("deleted")] | |
463 |
|
432 | |||
464 | def _get_submodule_url(self, submodule_path): |
|
433 | def _get_submodule_url(self, submodule_path: bytes): | |
465 |
git_modules_path = |
|
434 | git_modules_path = b".gitmodules" | |
466 |
|
435 | |||
467 | if self._submodules is None: |
|
436 | if self._submodules is None: | |
468 | self._submodules = {} |
|
437 | self._submodules = {} | |
@@ -476,9 +445,9 b' class GitCommit(base.BaseCommit):' | |||||
476 | parser.read_file(io.StringIO(submodules_node.str_content)) |
|
445 | parser.read_file(io.StringIO(submodules_node.str_content)) | |
477 |
|
446 | |||
478 | for section in parser.sections(): |
|
447 | for section in parser.sections(): | |
479 |
path = parser.get(section, |
|
448 | path = parser.get(section, "path") | |
480 |
url = parser.get(section, |
|
449 | url = parser.get(section, "url") | |
481 | if path and url: |
|
450 | if path and url: | |
482 |
self._submodules[path.strip( |
|
451 | self._submodules[safe_bytes(path).strip(b"/")] = url | |
483 |
|
452 | |||
484 |
return self._submodules.get(submodule_path.strip( |
|
453 | return self._submodules.get(submodule_path.strip(b"/")) |
@@ -425,7 +425,7 b' class GitRepository(BaseRepository):' | |||||
425 | return |
|
425 | return | |
426 |
|
426 | |||
427 | def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, |
|
427 | def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, | |
428 | translate_tag=True, maybe_unreachable=False, reference_obj=None): |
|
428 | translate_tag=True, maybe_unreachable=False, reference_obj=None) -> GitCommit: | |
429 | """ |
|
429 | """ | |
430 | Returns `GitCommit` object representing commit from git repository |
|
430 | Returns `GitCommit` object representing commit from git repository | |
431 | at the given `commit_id` or head (most recent commit) if None given. |
|
431 | at the given `commit_id` or head (most recent commit) if None given. |
@@ -20,20 +20,25 b'' | |||||
20 | HG commit module |
|
20 | HG commit module | |
21 | """ |
|
21 | """ | |
22 |
|
22 | |||
23 |
import |
|
23 | import logging | |
24 |
|
24 | |||
25 | from zope.cachedescriptors.property import Lazy as LazyProperty |
|
25 | from zope.cachedescriptors.property import Lazy as LazyProperty | |
26 |
|
26 | |||
27 | from rhodecode.lib.datelib import utcdate_fromtimestamp |
|
27 | from rhodecode.lib.datelib import utcdate_fromtimestamp | |
28 | from rhodecode.lib.str_utils import safe_bytes, safe_str |
|
28 | from rhodecode.lib.str_utils import safe_bytes, safe_str | |
29 | from rhodecode.lib.vcs import path as vcspath |
|
|||
30 | from rhodecode.lib.vcs.backends import base |
|
29 | from rhodecode.lib.vcs.backends import base | |
31 | from rhodecode.lib.vcs.exceptions import CommitError |
|
30 | from rhodecode.lib.vcs.exceptions import CommitError | |
32 | from rhodecode.lib.vcs.nodes import ( |
|
31 | from rhodecode.lib.vcs.nodes import ( | |
33 | AddedFileNodesGenerator, ChangedFileNodesGenerator, DirNode, FileNode, |
|
32 | DirNode, | |
34 | NodeKind, RemovedFileNodesGenerator, RootNode, SubModuleNode, |
|
33 | FileNode, | |
35 | LargeFileNode) |
|
34 | NodeKind, | |
36 | from rhodecode.lib.vcs.utils.paths import get_dirs_for_path |
|
35 | RootNode, | |
|
36 | SubModuleNode, | |||
|
37 | LargeFileNode, | |||
|
38 | ) | |||
|
39 | from rhodecode.lib.vcs_common import FILEMODE_LINK | |||
|
40 | ||||
|
41 | log = logging.getLogger(__name__) | |||
37 |
|
42 | |||
38 |
|
43 | |||
39 | class MercurialCommit(base.BaseCommit): |
|
44 | class MercurialCommit(base.BaseCommit): | |
@@ -59,13 +64,13 b' class MercurialCommit(base.BaseCommit):' | |||||
59 |
|
64 | |||
60 | # caches |
|
65 | # caches | |
61 | self.nodes = {} |
|
66 | self.nodes = {} | |
62 |
self._ |
|
67 | self._path_mode_cache = {} # path stats cache, e.g filemode etc | |
|
68 | self._path_type_cache = {} # path type dir/file/link etc cache | |||
63 |
|
69 | |||
64 | def _set_bulk_properties(self, pre_load): |
|
70 | def _set_bulk_properties(self, pre_load): | |
65 | if not pre_load: |
|
71 | if not pre_load: | |
66 | return |
|
72 | return | |
67 | pre_load = [entry for entry in pre_load |
|
73 | pre_load = [entry for entry in pre_load if entry not in self._filter_pre_load] | |
68 | if entry not in self._filter_pre_load] |
|
|||
69 | if not pre_load: |
|
74 | if not pre_load: | |
70 | return |
|
75 | return | |
71 |
|
76 | |||
@@ -86,8 +91,7 b' class MercurialCommit(base.BaseCommit):' | |||||
86 |
|
91 | |||
87 | @LazyProperty |
|
92 | @LazyProperty | |
88 | def tags(self): |
|
93 | def tags(self): | |
89 | tags = [name for name, commit_id in self.repository.tags.items() |
|
94 | tags = [name for name, commit_id in self.repository.tags.items() if commit_id == self.raw_id] | |
90 | if commit_id == self.raw_id] |
|
|||
91 | return tags |
|
95 | return tags | |
92 |
|
96 | |||
93 | @LazyProperty |
|
97 | @LazyProperty | |
@@ -96,9 +100,7 b' class MercurialCommit(base.BaseCommit):' | |||||
96 |
|
100 | |||
97 | @LazyProperty |
|
101 | @LazyProperty | |
98 | def bookmarks(self): |
|
102 | def bookmarks(self): | |
99 | bookmarks = [ |
|
103 | bookmarks = [name for name, commit_id in self.repository.bookmarks.items() if commit_id == self.raw_id] | |
100 | name for name, commit_id in self.repository.bookmarks.items() |
|
|||
101 | if commit_id == self.raw_id] |
|
|||
102 | return bookmarks |
|
104 | return bookmarks | |
103 |
|
105 | |||
104 | @LazyProperty |
|
106 | @LazyProperty | |
@@ -122,27 +124,13 b' class MercurialCommit(base.BaseCommit):' | |||||
122 | """ |
|
124 | """ | |
123 | Returns modified, added, removed, deleted files for current commit |
|
125 | Returns modified, added, removed, deleted files for current commit | |
124 | """ |
|
126 | """ | |
125 |
|
|
127 | modified, added, deleted, *_ = self._remote.ctx_status(self.raw_id) | |
126 |
|
128 | return modified, added, deleted | ||
127 | @LazyProperty |
|
|||
128 | def _file_paths(self): |
|
|||
129 | return self._remote.ctx_list(self.raw_id) |
|
|||
130 |
|
||||
131 | @LazyProperty |
|
|||
132 | def _dir_paths(self): |
|
|||
133 | dir_paths = [''] |
|
|||
134 | dir_paths.extend(list(set(get_dirs_for_path(*self._file_paths)))) |
|
|||
135 |
|
||||
136 | return dir_paths |
|
|||
137 |
|
||||
138 | @LazyProperty |
|
|||
139 | def _paths(self): |
|
|||
140 | return self._dir_paths + self._file_paths |
|
|||
141 |
|
129 | |||
142 | @LazyProperty |
|
130 | @LazyProperty | |
143 | def id(self): |
|
131 | def id(self): | |
144 | if self.last: |
|
132 | if self.last: | |
145 |
return |
|
133 | return "tip" | |
146 | return self.short_id |
|
134 | return self.short_id | |
147 |
|
135 | |||
148 | @LazyProperty |
|
136 | @LazyProperty | |
@@ -150,8 +138,7 b' class MercurialCommit(base.BaseCommit):' | |||||
150 | return self.raw_id[:12] |
|
138 | return self.raw_id[:12] | |
151 |
|
139 | |||
152 | def _make_commits(self, commit_ids, pre_load=None): |
|
140 | def _make_commits(self, commit_ids, pre_load=None): | |
153 | return [self.repository.get_commit(commit_id=commit_id, pre_load=pre_load) |
|
141 | return [self.repository.get_commit(commit_id=commit_id, pre_load=pre_load) for commit_id in commit_ids] | |
154 | for commit_id in commit_ids] |
|
|||
155 |
|
142 | |||
156 | @LazyProperty |
|
143 | @LazyProperty | |
157 | def parents(self): |
|
144 | def parents(self): | |
@@ -163,10 +150,10 b' class MercurialCommit(base.BaseCommit):' | |||||
163 |
|
150 | |||
164 | def _get_phase_text(self, phase_id): |
|
151 | def _get_phase_text(self, phase_id): | |
165 | return { |
|
152 | return { | |
166 |
|
|
153 | 0: "public", | |
167 |
|
|
154 | 1: "draft", | |
168 |
|
|
155 | 2: "secret", | |
169 |
|
|
156 | }.get(phase_id) or "" | |
170 |
|
157 | |||
171 | @LazyProperty |
|
158 | @LazyProperty | |
172 | def phase(self): |
|
159 | def phase(self): | |
@@ -195,17 +182,14 b' class MercurialCommit(base.BaseCommit):' | |||||
195 |
|
182 | |||
196 | def _get_kind(self, path): |
|
183 | def _get_kind(self, path): | |
197 | path = self._fix_path(path) |
|
184 | path = self._fix_path(path) | |
198 |
|
|
185 | path_type = self._get_path_type(path) | |
199 | return NodeKind.FILE |
|
186 | return path_type | |
200 | elif path in self._dir_paths: |
|
|||
201 | return NodeKind.DIR |
|
|||
202 | else: |
|
|||
203 | raise CommitError(f"Node does not exist at the given path '{path}'") |
|
|||
204 |
|
187 | |||
205 | def _assert_is_path(self, path) -> str: |
|
188 | def _assert_is_path(self, path) -> str | bytes: | |
206 | path = self._fix_path(path) |
|
189 | path = self._fix_path(path) | |
|
190 | ||||
207 | if self._get_kind(path) != NodeKind.FILE: |
|
191 | if self._get_kind(path) != NodeKind.FILE: | |
208 |
raise CommitError(f"File does not exist for commit {self.raw_id} |
|
192 | raise CommitError(f"File at path={path} does not exist for commit {self.raw_id}") | |
209 |
|
193 | |||
210 | return path |
|
194 | return path | |
211 |
|
195 | |||
@@ -214,20 +198,17 b' class MercurialCommit(base.BaseCommit):' | |||||
214 | Returns stat mode of the file at the given ``path``. |
|
198 | Returns stat mode of the file at the given ``path``. | |
215 | """ |
|
199 | """ | |
216 | path = self._assert_is_path(path) |
|
200 | path = self._assert_is_path(path) | |
|
201 | if path not in self._path_mode_cache: | |||
|
202 | self._path_mode_cache[path] = self._remote.fctx_flags(self.raw_id, path) | |||
217 |
|
203 | |||
218 | if path not in self._stat_modes: |
|
204 | return self._path_mode_cache[path] | |
219 | self._stat_modes[path] = self._remote.fctx_flags(self.raw_id, path) |
|
|||
220 |
|
205 | |||
221 | if 'x' in self._stat_modes[path]: |
|
206 | def is_link(self, path: bytes): | |
222 | return base.FILEMODE_EXECUTABLE |
|
207 | path = self._assert_is_path(path) | |
223 | return base.FILEMODE_DEFAULT |
|
208 | if path not in self._path_mode_cache: | |
|
209 | self._path_mode_cache[path] = self._remote.fctx_flags(self.raw_id, path) | |||
224 |
|
210 | |||
225 | def is_link(self, path): |
|
211 | return self._path_mode_cache[path] == FILEMODE_LINK | |
226 | path = self._assert_is_path(path) |
|
|||
227 | if path not in self._stat_modes: |
|
|||
228 | self._stat_modes[path] = self._remote.fctx_flags(self.raw_id, path) |
|
|||
229 |
|
||||
230 | return 'l' in self._stat_modes[path] |
|
|||
231 |
|
212 | |||
232 | def is_node_binary(self, path): |
|
213 | def is_node_binary(self, path): | |
233 | path = self._assert_is_path(path) |
|
214 | path = self._assert_is_path(path) | |
@@ -246,7 +227,7 b' class MercurialCommit(base.BaseCommit):' | |||||
246 |
|
227 | |||
247 | def get_file_content_streamed(self, path): |
|
228 | def get_file_content_streamed(self, path): | |
248 | path = self._assert_is_path(path) |
|
229 | path = self._assert_is_path(path) | |
249 |
stream_method = getattr(self._remote, |
|
230 | stream_method = getattr(self._remote, "stream:fctx_node_data") | |
250 | return stream_method(self.raw_id, path) |
|
231 | return stream_method(self.raw_id, path) | |
251 |
|
232 | |||
252 | def get_file_size(self, path): |
|
233 | def get_file_size(self, path): | |
@@ -262,10 +243,8 b' class MercurialCommit(base.BaseCommit):' | |||||
262 | for which file at given ``path`` has been modified. |
|
243 | for which file at given ``path`` has been modified. | |
263 | """ |
|
244 | """ | |
264 | path = self._assert_is_path(path) |
|
245 | path = self._assert_is_path(path) | |
265 | hist = self._remote.node_history(self.raw_id, path, limit) |
|
246 | history = self._remote.node_history(self.raw_id, path, limit) | |
266 | return [ |
|
247 | return [self.repository.get_commit(commit_id=commit_id, pre_load=pre_load) for commit_id in history] | |
267 | self.repository.get_commit(commit_id=commit_id, pre_load=pre_load) |
|
|||
268 | for commit_id in hist] |
|
|||
269 |
|
248 | |||
270 | def get_file_annotate(self, path, pre_load=None): |
|
249 | def get_file_annotate(self, path, pre_load=None): | |
271 | """ |
|
250 | """ | |
@@ -276,11 +255,13 b' class MercurialCommit(base.BaseCommit):' | |||||
276 |
|
255 | |||
277 | for ln_no, commit_id, content in result: |
|
256 | for ln_no, commit_id, content in result: | |
278 | yield ( |
|
257 | yield ( | |
279 |
ln_no, |
|
258 | ln_no, | |
|
259 | commit_id, | |||
280 | lambda: self.repository.get_commit(commit_id=commit_id, pre_load=pre_load), |
|
260 | lambda: self.repository.get_commit(commit_id=commit_id, pre_load=pre_load), | |
281 |
content |
|
261 | content, | |
|
262 | ) | |||
282 |
|
263 | |||
283 | def get_nodes(self, path, pre_load=None): |
|
264 | def get_nodes(self, path: bytes, pre_load=None): | |
284 | """ |
|
265 | """ | |
285 | Returns combined ``DirNode`` and ``FileNode`` objects list representing |
|
266 | Returns combined ``DirNode`` and ``FileNode`` objects list representing | |
286 | state of commit at the given ``path``. If node at the given ``path`` |
|
267 | state of commit at the given ``path``. If node at the given ``path`` | |
@@ -288,59 +269,86 b' class MercurialCommit(base.BaseCommit):' | |||||
288 | """ |
|
269 | """ | |
289 |
|
270 | |||
290 | if self._get_kind(path) != NodeKind.DIR: |
|
271 | if self._get_kind(path) != NodeKind.DIR: | |
291 | raise CommitError( |
|
272 | raise CommitError(f"Directory does not exist for idx {self.raw_id} at '{path}'") | |
292 | f"Directory does not exist for idx {self.raw_id} at '{path}'") |
|
|||
293 | path = self._fix_path(path) |
|
273 | path = self._fix_path(path) | |
294 |
|
274 | |||
295 |
|
|
275 | path_nodes = [] | |
296 | FileNode(safe_bytes(f), commit=self, pre_load=pre_load) for f in self._file_paths |
|
276 | ||
297 | if os.path.dirname(f) == path] |
|
277 | for obj_path, node_kind in self._remote.dir_items(self.raw_id, path): | |
298 | # TODO: johbo: Check if this can be done in a more obvious way |
|
278 | ||
299 | dirs = path == '' and '' or [ |
|
279 | if node_kind is None: | |
300 | d for d in self._dir_paths |
|
280 | raise CommitError(f"Requested object type={node_kind} cannot be mapped to a proper type") | |
301 | if d and vcspath.dirname(d) == path] |
|
281 | ||
302 | dirnodes = [ |
|
282 | # TODO: implement it ?? | |
303 | DirNode(safe_bytes(d), commit=self) for d in dirs |
|
283 | stat_ = None | |
304 | if os.path.dirname(d) == path] |
|
284 | # # cache file mode | |
|
285 | # if obj_path not in self._path_mode_cache: | |||
|
286 | # self._path_mode_cache[obj_path] = stat_ | |||
|
287 | ||||
|
288 | # cache type | |||
|
289 | if node_kind not in self._path_type_cache: | |||
|
290 | self._path_type_cache[obj_path] = node_kind | |||
305 |
|
291 | |||
306 | alias = self.repository.alias |
|
292 | entry = None | |
307 | for k, vals in self._submodules.items(): |
|
293 | if obj_path in self.nodes: | |
308 | if vcspath.dirname(k) == path: |
|
294 | entry = self.nodes[obj_path] | |
309 |
|
|
295 | else: | |
310 | commit = vals[1] |
|
296 | if node_kind == NodeKind.DIR: | |
311 | dirnodes.append(SubModuleNode(k, url=loc, commit=commit, alias=alias)) |
|
297 | entry = DirNode(safe_bytes(obj_path), commit=self) | |
|
298 | elif node_kind == NodeKind.FILE: | |||
|
299 | entry = FileNode(safe_bytes(obj_path), commit=self, mode=stat_, pre_load=pre_load) | |||
|
300 | if entry: | |||
|
301 | self.nodes[obj_path] = entry | |||
|
302 | path_nodes.append(entry) | |||
312 |
|
303 | |||
313 | nodes = dirnodes + filenodes |
|
304 | path_nodes.sort() | |
314 |
|
|
305 | return path_nodes | |
315 | if node.path not in self.nodes: |
|
|||
316 | self.nodes[node.path] = node |
|
|||
317 | nodes.sort() |
|
|||
318 |
|
306 | |||
319 | return nodes |
|
307 | def get_node(self, path: bytes, pre_load=None): | |
320 |
|
||||
321 | def get_node(self, path, pre_load=None): |
|
|||
322 | """ |
|
308 | """ | |
323 | Returns `Node` object from the given `path`. If there is no node at |
|
309 | Returns `Node` object from the given `path`. If there is no node at | |
324 | the given `path`, `NodeDoesNotExistError` would be raised. |
|
310 | the given `path`, `NodeDoesNotExistError` would be raised. | |
325 | """ |
|
311 | """ | |
326 | path = self._fix_path(path) |
|
312 | path = self._fix_path(path) | |
327 |
|
313 | |||
328 | if path not in self.nodes: |
|
314 | # use cached, if we have one | |
329 |
|
|
315 | if path in self.nodes: | |
|
316 | return self.nodes[path] | |||
|
317 | ||||
|
318 | path_type = self._get_path_type(path) | |||
|
319 | if path == b"": | |||
|
320 | node = RootNode(commit=self) | |||
|
321 | else: | |||
|
322 | if path_type == NodeKind.DIR: | |||
|
323 | node = DirNode(safe_bytes(path), commit=self) | |||
|
324 | elif path_type == NodeKind.FILE: | |||
330 | node = FileNode(safe_bytes(path), commit=self, pre_load=pre_load) |
|
325 | node = FileNode(safe_bytes(path), commit=self, pre_load=pre_load) | |
331 | elif path in self._dir_paths: |
|
326 | self._path_mode_cache[path] = node.mode | |
332 | if path == '': |
|
|||
333 | node = RootNode(commit=self) |
|
|||
334 | else: |
|
|||
335 | node = DirNode(safe_bytes(path), commit=self) |
|
|||
336 | else: |
|
327 | else: | |
337 | raise self.no_node_at_path(path) |
|
328 | raise self.no_node_at_path(path) | |
338 |
|
329 | # cache node | ||
339 | # cache node |
|
330 | self.nodes[path] = node | |
340 | self.nodes[path] = node |
|
|||
341 | return self.nodes[path] |
|
331 | return self.nodes[path] | |
342 |
|
332 | |||
343 |
def |
|
333 | def _get_path_type(self, path: bytes): | |
|
334 | if path in self._path_type_cache: | |||
|
335 | return self._path_type_cache[path] | |||
|
336 | ||||
|
337 | if path == b"": | |||
|
338 | self._path_type_cache[b""] = NodeKind.DIR | |||
|
339 | return NodeKind.DIR | |||
|
340 | ||||
|
341 | path_type, flags = self._remote.get_path_type(self.raw_id, path) | |||
|
342 | ||||
|
343 | if not path_type: | |||
|
344 | raise self.no_node_at_path(path) | |||
|
345 | ||||
|
346 | self._path_type_cache[path] = path_type | |||
|
347 | self._path_mode_cache[path] = flags | |||
|
348 | ||||
|
349 | return self._path_type_cache[path] | |||
|
350 | ||||
|
351 | def get_largefile_node(self, path: bytes): | |||
344 | pointer_spec = self._remote.is_large_file(self.raw_id, path) |
|
352 | pointer_spec = self._remote.is_large_file(self.raw_id, path) | |
345 | if pointer_spec: |
|
353 | if pointer_spec: | |
346 | # content of that file regular FileNode is the hash of largefile |
|
354 | # content of that file regular FileNode is the hash of largefile | |
@@ -363,40 +371,20 b' class MercurialCommit(base.BaseCommit):' | |||||
363 | return self._remote.ctx_substate(self.raw_id) |
|
371 | return self._remote.ctx_substate(self.raw_id) | |
364 |
|
372 | |||
365 | @LazyProperty |
|
373 | @LazyProperty | |
366 | def affected_files(self): |
|
374 | def affected_files(self) -> list[bytes]: | |
367 | """ |
|
375 | """ | |
368 | Gets a fast accessible file changes for given commit |
|
376 | Gets a fast accessible file changes for given commit | |
369 | """ |
|
377 | """ | |
370 | return self._remote.ctx_files(self.raw_id) |
|
378 | return self._remote.ctx_files(self.raw_id) | |
371 |
|
379 | |||
372 | @property |
|
|||
373 | def added(self): |
|
|||
374 | """ |
|
|||
375 | Returns list of added ``FileNode`` objects. |
|
|||
376 | """ |
|
|||
377 | return AddedFileNodesGenerator(self.added_paths, self) |
|
|||
378 |
|
||||
379 | @LazyProperty |
|
380 | @LazyProperty | |
380 | def added_paths(self): |
|
381 | def added_paths(self): | |
381 | return [n for n in self.status[1]] |
|
382 | return [n for n in self.status[1]] | |
382 |
|
383 | |||
383 | @property |
|
|||
384 | def changed(self): |
|
|||
385 | """ |
|
|||
386 | Returns list of modified ``FileNode`` objects. |
|
|||
387 | """ |
|
|||
388 | return ChangedFileNodesGenerator(self.changed_paths, self) |
|
|||
389 |
|
||||
390 | @LazyProperty |
|
384 | @LazyProperty | |
391 | def changed_paths(self): |
|
385 | def changed_paths(self): | |
392 | return [n for n in self.status[0]] |
|
386 | return [n for n in self.status[0]] | |
393 |
|
387 | |||
394 | @property |
|
|||
395 | def removed(self): |
|
|||
396 | """ |
|
|||
397 | Returns list of removed ``FileNode`` objects. |
|
|||
398 | """ |
|
|||
399 | return RemovedFileNodesGenerator(self.removed_paths, self) |
|
|||
400 |
|
388 | |||
401 | @LazyProperty |
|
389 | @LazyProperty | |
402 | def removed_paths(self): |
|
390 | def removed_paths(self): |
@@ -450,7 +450,7 b' class MercurialRepository(BaseRepository' | |||||
450 | return os.path.join(self.path, '.hg', '.hgrc') |
|
450 | return os.path.join(self.path, '.hg', '.hgrc') | |
451 |
|
451 | |||
452 | def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, |
|
452 | def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, | |
453 | translate_tag=None, maybe_unreachable=False, reference_obj=None): |
|
453 | translate_tag=None, maybe_unreachable=False, reference_obj=None) -> MercurialCommit: | |
454 | """ |
|
454 | """ | |
455 | Returns ``MercurialCommit`` object representing repository's |
|
455 | Returns ``MercurialCommit`` object representing repository's | |
456 | commit at the given `commit_id` or `commit_idx`. |
|
456 | commit at the given `commit_id` or `commit_idx`. | |
@@ -598,8 +598,7 b' class MercurialRepository(BaseRepository' | |||||
598 | """ |
|
598 | """ | |
599 | Create a local clone of the current repo. |
|
599 | Create a local clone of the current repo. | |
600 | """ |
|
600 | """ | |
601 | self._remote.clone(self.path, clone_path, update_after_clone=True, |
|
601 | self._remote.clone(self.path, clone_path, update_after_clone=True, hooks=False) | |
602 | hooks=False) |
|
|||
603 |
|
602 | |||
604 | def _update(self, revision, clean=False): |
|
603 | def _update(self, revision, clean=False): | |
605 | """ |
|
604 | """ |
@@ -19,8 +19,7 b'' | |||||
19 | """ |
|
19 | """ | |
20 | SVN commit module |
|
20 | SVN commit module | |
21 | """ |
|
21 | """ | |
22 |
|
22 | import logging | ||
23 |
|
||||
24 | import dateutil.parser |
|
23 | import dateutil.parser | |
25 | from zope.cachedescriptors.property import Lazy as LazyProperty |
|
24 | from zope.cachedescriptors.property import Lazy as LazyProperty | |
26 |
|
25 | |||
@@ -28,9 +27,10 b' from rhodecode.lib.str_utils import safe' | |||||
28 | from rhodecode.lib.vcs import nodes, path as vcspath |
|
27 | from rhodecode.lib.vcs import nodes, path as vcspath | |
29 | from rhodecode.lib.vcs.backends import base |
|
28 | from rhodecode.lib.vcs.backends import base | |
30 | from rhodecode.lib.vcs.exceptions import CommitError |
|
29 | from rhodecode.lib.vcs.exceptions import CommitError | |
31 |
|
30 | from vcsserver.lib.vcs_common import NodeKind, FILEMODE_EXECUTABLE, FILEMODE_DEFAULT, FILEMODE_LINK | ||
|
31 | _SVN_PROP_TRUE = "*" | |||
32 |
|
32 | |||
33 | _SVN_PROP_TRUE = '*' |
|
33 | log = logging.getLogger(__name__) | |
34 |
|
34 | |||
35 |
|
35 | |||
36 | class SubversionCommit(base.BaseCommit): |
|
36 | class SubversionCommit(base.BaseCommit): | |
@@ -53,15 +53,16 b' class SubversionCommit(base.BaseCommit):' | |||||
53 | # which knows how to translate commit index and commit id |
|
53 | # which knows how to translate commit index and commit id | |
54 | self.raw_id = commit_id |
|
54 | self.raw_id = commit_id | |
55 | self.short_id = commit_id |
|
55 | self.short_id = commit_id | |
56 |
self.id = f |
|
56 | self.id = f"r{commit_id}" | |
57 |
|
57 | |||
58 | # TODO: Implement the following placeholder attributes |
|
|||
59 | self.nodes = {} |
|
58 | self.nodes = {} | |
|
59 | self._path_mode_cache = {} # path stats cache, e.g filemode etc | |||
|
60 | self._path_type_cache = {} # path type dir/file/link etc cache | |||
60 | self.tags = [] |
|
61 | self.tags = [] | |
61 |
|
62 | |||
62 | @property |
|
63 | @property | |
63 | def author(self): |
|
64 | def author(self): | |
64 |
return safe_str(self._properties.get( |
|
65 | return safe_str(self._properties.get("svn:author")) | |
65 |
|
66 | |||
66 | @property |
|
67 | @property | |
67 | def date(self): |
|
68 | def date(self): | |
@@ -69,7 +70,7 b' class SubversionCommit(base.BaseCommit):' | |||||
69 |
|
70 | |||
70 | @property |
|
71 | @property | |
71 | def message(self): |
|
72 | def message(self): | |
72 |
return safe_str(self._properties.get( |
|
73 | return safe_str(self._properties.get("svn:log")) | |
73 |
|
74 | |||
74 | @LazyProperty |
|
75 | @LazyProperty | |
75 | def _properties(self): |
|
76 | def _properties(self): | |
@@ -91,19 +92,46 b' class SubversionCommit(base.BaseCommit):' | |||||
91 | return [child] |
|
92 | return [child] | |
92 | return [] |
|
93 | return [] | |
93 |
|
94 | |||
94 |
def |
|
95 | def _calculate_file_mode(self, path: bytes): | |
95 | # Note: Subversion flags files which are executable with a special |
|
96 | # Note: Subversion flags files which are executable with a special | |
96 | # property `svn:executable` which is set to the value ``"*"``. |
|
97 | # property `svn:executable` which is set to the value ``"*"``. | |
97 |
if self._get_file_property(path, |
|
98 | if self._get_file_property(path, "svn:executable") == _SVN_PROP_TRUE: | |
98 |
return |
|
99 | return FILEMODE_EXECUTABLE | |
99 | else: |
|
100 | else: | |
100 |
return |
|
101 | return FILEMODE_DEFAULT | |
|
102 | ||||
|
103 | def get_file_mode(self, path: bytes): | |||
|
104 | path = self._fix_path(path) | |||
|
105 | ||||
|
106 | if path not in self._path_mode_cache: | |||
|
107 | self._path_mode_cache[path] = self._calculate_file_mode(path) | |||
|
108 | ||||
|
109 | return self._path_mode_cache[path] | |||
|
110 | ||||
|
111 | def _get_path_type(self, path: bytes): | |||
|
112 | if path in self._path_type_cache: | |||
|
113 | return self._path_type_cache[path] | |||
101 |
|
114 | |||
102 | def is_link(self, path): |
|
115 | if path == b"": | |
|
116 | self._path_type_cache[b""] = NodeKind.DIR | |||
|
117 | return NodeKind.DIR | |||
|
118 | ||||
|
119 | path_type = self._remote.get_node_type(self._svn_rev, path) | |||
|
120 | ||||
|
121 | if not path_type: | |||
|
122 | raise self.no_node_at_path(path) | |||
|
123 | ||||
|
124 | #flags = None | |||
|
125 | self._path_type_cache[path] = path_type | |||
|
126 | #self._path_mode_cache[path] = flags | |||
|
127 | ||||
|
128 | return self._path_type_cache[path] | |||
|
129 | ||||
|
130 | def is_link(self, path: bytes): | |||
103 | # Note: Subversion has a flag for special files, the content of the |
|
131 | # Note: Subversion has a flag for special files, the content of the | |
104 | # file contains the type of that file. |
|
132 | # file contains the type of that file. | |
105 |
if self._get_file_property(path, |
|
133 | if self._get_file_property(path, "svn:special") == _SVN_PROP_TRUE: | |
106 |
return self.get_file_content(path).startswith(b |
|
134 | return self.get_file_content(path).startswith(b"link") | |
107 | return False |
|
135 | return False | |
108 |
|
136 | |||
109 | def is_node_binary(self, path): |
|
137 | def is_node_binary(self, path): | |
@@ -115,8 +143,7 b' class SubversionCommit(base.BaseCommit):' | |||||
115 | return self._remote.md5_hash(self._svn_rev, safe_str(path)) |
|
143 | return self._remote.md5_hash(self._svn_rev, safe_str(path)) | |
116 |
|
144 | |||
117 | def _get_file_property(self, path, name): |
|
145 | def _get_file_property(self, path, name): | |
118 | file_properties = self._remote.node_properties( |
|
146 | file_properties = self._remote.node_properties(safe_str(path), self._svn_rev) | |
119 | safe_str(path), self._svn_rev) |
|
|||
120 | return file_properties.get(name) |
|
147 | return file_properties.get(name) | |
121 |
|
148 | |||
122 | def get_file_content(self, path): |
|
149 | def get_file_content(self, path): | |
@@ -126,7 +153,7 b' class SubversionCommit(base.BaseCommit):' | |||||
126 | def get_file_content_streamed(self, path): |
|
153 | def get_file_content_streamed(self, path): | |
127 | path = self._fix_path(path) |
|
154 | path = self._fix_path(path) | |
128 |
|
155 | |||
129 |
stream_method = getattr(self._remote, |
|
156 | stream_method = getattr(self._remote, "stream:get_file_content") | |
130 | return stream_method(self._svn_rev, safe_str(path)) |
|
157 | return stream_method(self._svn_rev, safe_str(path)) | |
131 |
|
158 | |||
132 | def get_file_size(self, path): |
|
159 | def get_file_size(self, path): | |
@@ -134,11 +161,9 b' class SubversionCommit(base.BaseCommit):' | |||||
134 | return self._remote.get_file_size(self._svn_rev, safe_str(path)) |
|
161 | return self._remote.get_file_size(self._svn_rev, safe_str(path)) | |
135 |
|
162 | |||
136 | def get_path_history(self, path, limit=None, pre_load=None): |
|
163 | def get_path_history(self, path, limit=None, pre_load=None): | |
137 |
path = |
|
164 | path = self._fix_path(path) | |
138 |
history = self._remote.node_history( |
|
165 | history = self._remote.node_history(self._svn_rev, safe_str(path), limit) | |
139 | return [ |
|
166 | return [self.repository.get_commit(commit_id=str(svn_rev)) for svn_rev in history] | |
140 | self.repository.get_commit(commit_id=str(svn_rev)) |
|
|||
141 | for svn_rev in history] |
|
|||
142 |
|
167 | |||
143 | def get_file_annotate(self, path, pre_load=None): |
|
168 | def get_file_annotate(self, path, pre_load=None): | |
144 | result = self._remote.file_annotate(safe_str(path), self._svn_rev) |
|
169 | result = self._remote.file_annotate(safe_str(path), self._svn_rev) | |
@@ -146,67 +171,78 b' class SubversionCommit(base.BaseCommit):' | |||||
146 | for zero_based_line_no, svn_rev, content in result: |
|
171 | for zero_based_line_no, svn_rev, content in result: | |
147 | commit_id = str(svn_rev) |
|
172 | commit_id = str(svn_rev) | |
148 | line_no = zero_based_line_no + 1 |
|
173 | line_no = zero_based_line_no + 1 | |
149 | yield ( |
|
174 | yield line_no, commit_id, lambda: self.repository.get_commit(commit_id=commit_id), content | |
150 | line_no, |
|
|||
151 | commit_id, |
|
|||
152 | lambda: self.repository.get_commit(commit_id=commit_id), |
|
|||
153 | content) |
|
|||
154 |
|
175 | |||
155 | def get_node(self, path, pre_load=None): |
|
176 | def get_node(self, path: bytes, pre_load=None): | |
156 | path = self._fix_path(path) |
|
177 | path = self._fix_path(path) | |
157 | if path not in self.nodes: |
|
178 | ||
|
179 | # use cached, if we have one | |||
|
180 | if path in self.nodes: | |||
|
181 | return self.nodes[path] | |||
158 |
|
182 | |||
159 | if path == '': |
|
183 | path_type = self._get_path_type(path) | |
160 | node = nodes.RootNode(commit=self) |
|
184 | if path == b"": | |
|
185 | node = nodes.RootNode(commit=self) | |||
|
186 | else: | |||
|
187 | if path_type == NodeKind.DIR: | |||
|
188 | node = nodes.DirNode(safe_bytes(path), commit=self) | |||
|
189 | elif path_type == NodeKind.FILE: | |||
|
190 | node = nodes.FileNode(safe_bytes(path), commit=self, pre_load=pre_load) | |||
|
191 | self._path_mode_cache[path] = node.mode | |||
161 | else: |
|
192 | else: | |
162 |
|
|
193 | raise self.no_node_at_path(path) | |
163 | if node_type == 'dir': |
|
|||
164 | node = nodes.DirNode(safe_bytes(path), commit=self) |
|
|||
165 | elif node_type == 'file': |
|
|||
166 | node = nodes.FileNode(safe_bytes(path), commit=self, pre_load=pre_load) |
|
|||
167 | else: |
|
|||
168 | raise self.no_node_at_path(path) |
|
|||
169 |
|
194 | |||
170 |
|
|
195 | self.nodes[path] = node | |
171 | return self.nodes[path] |
|
196 | return self.nodes[path] | |
172 |
|
197 | |||
173 | def get_nodes(self, path, pre_load=None): |
|
198 | def get_nodes(self, path: bytes, pre_load=None): | |
174 | if self._get_kind(path) != nodes.NodeKind.DIR: |
|
199 | if self._get_kind(path) != nodes.NodeKind.DIR: | |
175 | raise CommitError( |
|
200 | raise CommitError(f"Directory does not exist for commit {self.raw_id} at '{path}'") | |
176 | f"Directory does not exist for commit {self.raw_id} at '{path}'") |
|
201 | path = self._fix_path(path) | |
177 | path = safe_str(self._fix_path(path)) |
|
|||
178 |
|
202 | |||
179 | path_nodes = [] |
|
203 | path_nodes = [] | |
180 | for name, kind in self._remote.get_nodes(self._svn_rev, path): |
|
204 | for name, node_kind in self._remote.get_nodes(self._svn_rev, path): | |
181 |
|
|
205 | obj_path = vcspath.join(path, name) | |
182 | if kind == 'dir': |
|
206 | ||
183 | node = nodes.DirNode(safe_bytes(node_path), commit=self) |
|
207 | if node_kind is None: | |
184 | elif kind == 'file': |
|
208 | raise CommitError(f"Requested object type={node_kind} cannot be determined") | |
185 | node = nodes.FileNode(safe_bytes(node_path), commit=self, pre_load=pre_load) |
|
209 | ||
|
210 | # TODO: implement it ?? | |||
|
211 | stat_ = None | |||
|
212 | # # cache file mode | |||
|
213 | # if obj_path not in self._path_mode_cache: | |||
|
214 | # self._path_mode_cache[obj_path] = stat_ | |||
|
215 | ||||
|
216 | # cache type | |||
|
217 | if node_kind not in self._path_type_cache: | |||
|
218 | self._path_type_cache[obj_path] = node_kind | |||
|
219 | ||||
|
220 | entry = None | |||
|
221 | if obj_path in self.nodes: | |||
|
222 | entry = self.nodes[obj_path] | |||
186 | else: |
|
223 | else: | |
187 | raise ValueError(f"Node kind {kind} not supported.") |
|
224 | if node_kind == NodeKind.DIR: | |
188 | self.nodes[node_path] = node |
|
225 | entry = nodes.DirNode(safe_bytes(obj_path), commit=self) | |
189 | path_nodes.append(node) |
|
226 | elif node_kind == NodeKind.FILE: | |
|
227 | entry = nodes.FileNode(safe_bytes(obj_path), commit=self, mode=stat_, pre_load=pre_load) | |||
|
228 | if entry: | |||
|
229 | self.nodes[obj_path] = entry | |||
|
230 | path_nodes.append(entry) | |||
190 |
|
231 | |||
|
232 | path_nodes.sort() | |||
191 | return path_nodes |
|
233 | return path_nodes | |
192 |
|
234 | |||
193 | def _get_kind(self, path): |
|
235 | def _get_kind(self, path): | |
194 | path = self._fix_path(path) |
|
236 | path = self._fix_path(path) | |
195 | kind = self._remote.get_node_type(self._svn_rev, path) |
|
237 | path_type = self._get_path_type(path) | |
196 | if kind == 'file': |
|
238 | return path_type | |
197 | return nodes.NodeKind.FILE |
|
|||
198 | elif kind == 'dir': |
|
|||
199 | return nodes.NodeKind.DIR |
|
|||
200 | else: |
|
|||
201 | raise CommitError( |
|
|||
202 | f"Node does not exist at the given path '{path}'") |
|
|||
203 |
|
239 | |||
204 | @LazyProperty |
|
240 | @LazyProperty | |
205 | def _changes_cache(self): |
|
241 | def _changes_cache(self): | |
206 | return self._remote.revision_changes(self._svn_rev) |
|
242 | return self._remote.revision_changes(self._svn_rev) | |
207 |
|
243 | |||
208 | @LazyProperty |
|
244 | @LazyProperty | |
209 | def affected_files(self): |
|
245 | def affected_files(self) -> list[bytes]: | |
210 | changed_files = set() |
|
246 | changed_files = set() | |
211 | for files in self._changes_cache.values(): |
|
247 | for files in self._changes_cache.values(): | |
212 | changed_files.update(files) |
|
248 | changed_files.update(files) | |
@@ -216,29 +252,17 b' class SubversionCommit(base.BaseCommit):' | |||||
216 | def id(self): |
|
252 | def id(self): | |
217 | return self.raw_id |
|
253 | return self.raw_id | |
218 |
|
254 | |||
219 | @property |
|
|||
220 | def added(self): |
|
|||
221 | return nodes.AddedFileNodesGenerator(self.added_paths, self) |
|
|||
222 |
|
||||
223 | @LazyProperty |
|
255 | @LazyProperty | |
224 | def added_paths(self): |
|
256 | def added_paths(self): | |
225 |
return [n for n in self._changes_cache[ |
|
257 | return [n for n in self._changes_cache["added"]] | |
226 |
|
||||
227 | @property |
|
|||
228 | def changed(self): |
|
|||
229 | return nodes.ChangedFileNodesGenerator(self.changed_paths, self) |
|
|||
230 |
|
258 | |||
231 | @LazyProperty |
|
259 | @LazyProperty | |
232 | def changed_paths(self): |
|
260 | def changed_paths(self): | |
233 |
return [n for n in self._changes_cache[ |
|
261 | return [n for n in self._changes_cache["changed"]] | |
234 |
|
||||
235 | @property |
|
|||
236 | def removed(self): |
|
|||
237 | return nodes.RemovedFileNodesGenerator(self.removed_paths, self) |
|
|||
238 |
|
262 | |||
239 | @LazyProperty |
|
263 | @LazyProperty | |
240 | def removed_paths(self): |
|
264 | def removed_paths(self): | |
241 |
return [n for n in self._changes_cache[ |
|
265 | return [n for n in self._changes_cache["removed"]] | |
242 |
|
266 | |||
243 |
|
267 | |||
244 | def _date_from_svn_properties(properties): |
|
268 | def _date_from_svn_properties(properties): | |
@@ -248,7 +272,7 b' def _date_from_svn_properties(properties' | |||||
248 | :return: :class:`datetime.datetime` instance. The object is naive. |
|
272 | :return: :class:`datetime.datetime` instance. The object is naive. | |
249 | """ |
|
273 | """ | |
250 |
|
274 | |||
251 |
aware_date = dateutil.parser.parse(properties.get( |
|
275 | aware_date = dateutil.parser.parse(properties.get("svn:date")) | |
252 | # final_date = aware_date.astimezone(dateutil.tz.tzlocal()) |
|
276 | # final_date = aware_date.astimezone(dateutil.tz.tzlocal()) | |
253 | final_date = aware_date |
|
277 | final_date = aware_date | |
254 | return final_date.replace(tzinfo=None) |
|
278 | return final_date.replace(tzinfo=None) |
@@ -30,7 +30,7 b' from zope.cachedescriptors.property impo' | |||||
30 |
|
30 | |||
31 | from collections import OrderedDict |
|
31 | from collections import OrderedDict | |
32 | from rhodecode.lib.datelib import date_astimestamp |
|
32 | from rhodecode.lib.datelib import date_astimestamp | |
33 | from rhodecode.lib.str_utils import safe_str |
|
33 | from rhodecode.lib.str_utils import safe_str, safe_bytes | |
34 | from rhodecode.lib.utils2 import CachedProperty |
|
34 | from rhodecode.lib.utils2 import CachedProperty | |
35 | from rhodecode.lib.vcs import connection, path as vcspath |
|
35 | from rhodecode.lib.vcs import connection, path as vcspath | |
36 | from rhodecode.lib.vcs.backends import base |
|
36 | from rhodecode.lib.vcs.backends import base | |
@@ -157,16 +157,18 b' class SubversionRepository(base.BaseRepo' | |||||
157 |
|
157 | |||
158 | for pattern in self._patterns_from_section(config_section): |
|
158 | for pattern in self._patterns_from_section(config_section): | |
159 | pattern = vcspath.sanitize(pattern) |
|
159 | pattern = vcspath.sanitize(pattern) | |
|
160 | bytes_pattern = safe_bytes(pattern) | |||
|
161 | ||||
160 | tip = self.get_commit() |
|
162 | tip = self.get_commit() | |
161 | try: |
|
163 | try: | |
162 | if pattern.endswith('*'): |
|
164 | if bytes_pattern.endswith(b'*'): | |
163 | basedir = tip.get_node(vcspath.dirname(pattern)) |
|
165 | basedir = tip.get_node(vcspath.dirname(bytes_pattern)) | |
164 | directories = basedir.dirs |
|
166 | directories = basedir.dirs | |
165 | else: |
|
167 | else: | |
166 | directories = (tip.get_node(pattern), ) |
|
168 | directories = (tip.get_node(bytes_pattern), ) | |
167 | except NodeDoesNotExistError: |
|
169 | except NodeDoesNotExistError: | |
168 | continue |
|
170 | continue | |
169 |
found_items.update(( |
|
171 | found_items.update((dir_node.str_path, self.commit_ids[-1]) for dir_node in directories) | |
170 |
|
172 | |||
171 | def get_name(item): |
|
173 | def get_name(item): | |
172 | return item[0] |
|
174 | return item[0] | |
@@ -216,7 +218,7 b' class SubversionRepository(base.BaseRepo' | |||||
216 | def _get_commit_idx(self, commit_id): |
|
218 | def _get_commit_idx(self, commit_id): | |
217 | try: |
|
219 | try: | |
218 | svn_rev = int(commit_id) |
|
220 | svn_rev = int(commit_id) | |
219 | except: |
|
221 | except Exception: | |
220 | # TODO: johbo: this might be only one case, HEAD, check this |
|
222 | # TODO: johbo: this might be only one case, HEAD, check this | |
221 | svn_rev = self._remote.lookup(commit_id) |
|
223 | svn_rev = self._remote.lookup(commit_id) | |
222 | commit_idx = svn_rev - 1 |
|
224 | commit_idx = svn_rev - 1 | |
@@ -321,8 +323,7 b' class SubversionRepository(base.BaseRepo' | |||||
321 | # TODO: johbo: Reconsider impact of DEFAULT_BRANCH_NAME here |
|
323 | # TODO: johbo: Reconsider impact of DEFAULT_BRANCH_NAME here | |
322 | if branch_name not in [None, self.DEFAULT_BRANCH_NAME]: |
|
324 | if branch_name not in [None, self.DEFAULT_BRANCH_NAME]: | |
323 | svn_rev = int(self.commit_ids[-1]) |
|
325 | svn_rev = int(self.commit_ids[-1]) | |
324 | commit_ids = self._remote.node_history( |
|
326 | commit_ids = self._remote.node_history(svn_rev, branch_name, None) | |
325 | path=branch_name, revision=svn_rev, limit=None) |
|
|||
326 | commit_ids = [str(i) for i in reversed(commit_ids)] |
|
327 | commit_ids = [str(i) for i in reversed(commit_ids)] | |
327 |
|
328 | |||
328 | if start_pos or end_pos: |
|
329 | if start_pos or end_pos: |
@@ -27,14 +27,13 b' import time' | |||||
27 | import urllib.request |
|
27 | import urllib.request | |
28 | import urllib.error |
|
28 | import urllib.error | |
29 | import urllib.parse |
|
29 | import urllib.parse | |
30 | import urllib.parse |
|
|||
31 | import uuid |
|
30 | import uuid | |
32 | import traceback |
|
31 | import traceback | |
33 |
|
32 | |||
34 | import pycurl |
|
33 | import pycurl | |
35 | import msgpack |
|
34 | import msgpack | |
36 | import requests |
|
35 | import requests | |
37 |
from |
|
36 | from urllib3.util.retry import Retry | |
38 |
|
37 | |||
39 | import rhodecode |
|
38 | import rhodecode | |
40 | from rhodecode.lib import rc_cache |
|
39 | from rhodecode.lib import rc_cache | |
@@ -287,7 +286,7 b' class RemoteRepo(object):' | |||||
287 | 'fctx_size', 'stream:fctx_node_data', 'blob_raw_length', |
|
286 | 'fctx_size', 'stream:fctx_node_data', 'blob_raw_length', | |
288 | 'node_history', |
|
287 | 'node_history', | |
289 | 'revision', 'tree_items', |
|
288 | 'revision', 'tree_items', | |
290 |
|
|
289 | 'ctx_branch', 'ctx_description', | |
291 | 'bulk_request', |
|
290 | 'bulk_request', | |
292 | 'assert_correct_path', |
|
291 | 'assert_correct_path', | |
293 | 'is_path_valid_repository', |
|
292 | 'is_path_valid_repository', |
@@ -1,1 +1,1 b'' | |||||
1 | from pyramid.compat import configparser No newline at end of file |
|
1 | from pyramid.compat import configparser |
@@ -20,9 +20,6 b'' | |||||
20 | Internal settings for vcs-lib |
|
20 | Internal settings for vcs-lib | |
21 | """ |
|
21 | """ | |
22 |
|
22 | |||
23 | # list of default encoding used in safe_str methods |
|
|||
24 | DEFAULT_ENCODINGS = ['utf8'] |
|
|||
25 |
|
||||
26 |
|
23 | |||
27 | # Compatibility version when creating SVN repositories. None means newest. |
|
24 | # Compatibility version when creating SVN repositories. None means newest. | |
28 | # Other available options are: pre-1.4-compatible, pre-1.5-compatible, |
|
25 | # Other available options are: pre-1.4-compatible, pre-1.5-compatible, |
@@ -102,10 +102,6 b' class NodeError(VCSError):' | |||||
102 | pass |
|
102 | pass | |
103 |
|
103 | |||
104 |
|
104 | |||
105 | class RemovedFileNodeError(NodeError): |
|
|||
106 | pass |
|
|||
107 |
|
||||
108 |
|
||||
109 | class NodeAlreadyExistsError(CommittingError): |
|
105 | class NodeAlreadyExistsError(CommittingError): | |
110 | pass |
|
106 | pass | |
111 |
|
107 |
@@ -19,6 +19,7 b'' | |||||
19 | """ |
|
19 | """ | |
20 | Module holding everything related to vcs nodes, with vcs2 architecture. |
|
20 | Module holding everything related to vcs nodes, with vcs2 architecture. | |
21 | """ |
|
21 | """ | |
|
22 | ||||
22 | import functools |
|
23 | import functools | |
23 | import os |
|
24 | import os | |
24 | import stat |
|
25 | import stat | |
@@ -29,83 +30,25 b' from rhodecode.config.conf import LANGUA' | |||||
29 | from rhodecode.lib.str_utils import safe_str, safe_bytes |
|
30 | from rhodecode.lib.str_utils import safe_str, safe_bytes | |
30 | from rhodecode.lib.hash_utils import md5 |
|
31 | from rhodecode.lib.hash_utils import md5 | |
31 | from rhodecode.lib.vcs import path as vcspath |
|
32 | from rhodecode.lib.vcs import path as vcspath | |
32 |
from rhodecode.lib.vcs.backends.base import EmptyCommit |
|
33 | from rhodecode.lib.vcs.backends.base import EmptyCommit | |
33 | from rhodecode.lib.vcs.conf.mtypes import get_mimetypes_db |
|
34 | from rhodecode.lib.vcs.conf.mtypes import get_mimetypes_db | |
34 |
from rhodecode.lib.vcs.exceptions import NodeError |
|
35 | from rhodecode.lib.vcs.exceptions import NodeError | |
35 |
|
36 | from rhodecode.lib.vcs_common import NodeKind, FILEMODE_DEFAULT | ||
36 | LARGEFILE_PREFIX = '.hglf' |
|
|||
37 |
|
37 | |||
38 |
|
38 | LARGEFILE_PREFIX = ".hglf" | ||
39 | class NodeKind: |
|
|||
40 | SUBMODULE = -1 |
|
|||
41 | DIR = 1 |
|
|||
42 | FILE = 2 |
|
|||
43 | LARGEFILE = 3 |
|
|||
44 |
|
39 | |||
45 |
|
40 | |||
46 | class NodeState: |
|
41 | class NodeState: | |
47 |
ADDED = |
|
42 | ADDED = "added" | |
48 |
CHANGED = |
|
43 | CHANGED = "changed" | |
49 |
NOT_CHANGED = |
|
44 | NOT_CHANGED = "not changed" | |
50 |
REMOVED = |
|
45 | REMOVED = "removed" | |
51 |
|
||||
52 | #TODO: not sure if that should be bytes or str ? |
|
|||
53 | # most probably bytes because content should be bytes and we check it |
|
|||
54 | BIN_BYTE_MARKER = b'\0' |
|
|||
55 |
|
46 | |||
56 |
|
47 | |||
57 | class NodeGeneratorBase(object): |
|
48 | # TODO: not sure if that should be bytes or str ? | |
58 | """ |
|
49 | # most probably bytes because content should be bytes and we check it | |
59 | Base class for removed added and changed filenodes, it's a lazy generator |
|
50 | BIN_BYTE_MARKER = b"\0" | |
60 | class that will create filenodes only on iteration or call |
|
|||
61 |
|
||||
62 | The len method doesn't need to create filenodes at all |
|
|||
63 | """ |
|
|||
64 |
|
||||
65 | def __init__(self, current_paths, cs): |
|
|||
66 | self.cs = cs |
|
|||
67 | self.current_paths = current_paths |
|
|||
68 |
|
||||
69 | def __call__(self): |
|
|||
70 | return [n for n in self] |
|
|||
71 |
|
||||
72 | def __getitem__(self, key): |
|
|||
73 | if isinstance(key, slice): |
|
|||
74 | for p in self.current_paths[key.start:key.stop]: |
|
|||
75 | yield self.cs.get_node(p) |
|
|||
76 |
|
||||
77 | def __len__(self): |
|
|||
78 | return len(self.current_paths) |
|
|||
79 |
|
51 | |||
80 | def __iter__(self): |
|
|||
81 | for p in self.current_paths: |
|
|||
82 | yield self.cs.get_node(p) |
|
|||
83 |
|
||||
84 |
|
||||
85 | class AddedFileNodesGenerator(NodeGeneratorBase): |
|
|||
86 | """ |
|
|||
87 | Class holding added files for current commit |
|
|||
88 | """ |
|
|||
89 |
|
||||
90 |
|
||||
91 | class ChangedFileNodesGenerator(NodeGeneratorBase): |
|
|||
92 | """ |
|
|||
93 | Class holding changed files for current commit |
|
|||
94 | """ |
|
|||
95 |
|
||||
96 |
|
||||
97 | class RemovedFileNodesGenerator(NodeGeneratorBase): |
|
|||
98 | """ |
|
|||
99 | Class holding removed files for current commit |
|
|||
100 | """ |
|
|||
101 | def __iter__(self): |
|
|||
102 | for p in self.current_paths: |
|
|||
103 | yield RemovedFileNode(path=safe_bytes(p)) |
|
|||
104 |
|
||||
105 | def __getitem__(self, key): |
|
|||
106 | if isinstance(key, slice): |
|
|||
107 | for p in self.current_paths[key.start:key.stop]: |
|
|||
108 | yield RemovedFileNode(path=safe_bytes(p)) |
|
|||
109 |
|
52 | |||
110 |
|
53 | |||
111 | @functools.total_ordering |
|
54 | @functools.total_ordering | |
@@ -119,21 +62,22 b' class Node(object):' | |||||
119 | only. Moreover, every single node is identified by the ``path`` attribute, |
|
62 | only. Moreover, every single node is identified by the ``path`` attribute, | |
120 | so it cannot end with slash, too. Otherwise, path could lead to mistakes. |
|
63 | so it cannot end with slash, too. Otherwise, path could lead to mistakes. | |
121 | """ |
|
64 | """ | |
|
65 | ||||
122 | # RTLO marker allows swapping text, and certain |
|
66 | # RTLO marker allows swapping text, and certain | |
123 | # security attacks could be used with this |
|
67 | # security attacks could be used with this | |
124 |
RTLO_MARKER = "\u202 |
|
68 | RTLO_MARKER = "\u202e" | |
125 |
|
69 | |||
126 | commit = None |
|
70 | commit = None | |
127 |
|
71 | |||
128 | def __init__(self, path: bytes, kind): |
|
72 | def __init__(self, path: bytes, kind): | |
129 | self._validate_path(path) # can throw exception if path is invalid |
|
73 | self._validate_path(path) # can throw exception if path is invalid | |
130 |
|
74 | |||
131 |
self.bytes_path = path.rstrip(b |
|
75 | self.bytes_path: bytes = path.rstrip(b"/") # store for mixed encoding, and raw version | |
132 | self.path = safe_str(self.bytes_path) # we store paths as str |
|
76 | self.str_path: str = safe_str(self.bytes_path) # we store paths as str | |
|
77 | self.path: str = self.str_path | |||
133 |
|
78 | |||
134 |
if self.bytes_path == b |
|
79 | if self.bytes_path == b"" and kind != NodeKind.DIR: | |
135 | raise NodeError("Only DirNode and its subclasses may be " |
|
80 | raise NodeError("Only DirNode and its subclasses may be initialized with empty path") | |
136 | "initialized with empty path") |
|
|||
137 | self.kind = kind |
|
81 | self.kind = kind | |
138 |
|
82 | |||
139 | if self.is_root() and not self.is_dir(): |
|
83 | if self.is_root() and not self.is_dir(): | |
@@ -142,7 +86,7 b' class Node(object):' | |||||
142 | def __eq__(self, other): |
|
86 | def __eq__(self, other): | |
143 | if type(self) is not type(other): |
|
87 | if type(self) is not type(other): | |
144 | return False |
|
88 | return False | |
145 |
for attr in [ |
|
89 | for attr in ["name", "path", "kind"]: | |
146 | if getattr(self, attr) != getattr(other, attr): |
|
90 | if getattr(self, attr) != getattr(other, attr): | |
147 | return False |
|
91 | return False | |
148 | if self.is_file(): |
|
92 | if self.is_file(): | |
@@ -166,22 +110,9 b' class Node(object):' | |||||
166 | if self.path > other.path: |
|
110 | if self.path > other.path: | |
167 | return False |
|
111 | return False | |
168 |
|
112 | |||
169 | # def __cmp__(self, other): |
|
|||
170 | # """ |
|
|||
171 | # Comparator using name of the node, needed for quick list sorting. |
|
|||
172 | # """ |
|
|||
173 | # |
|
|||
174 | # kind_cmp = cmp(self.kind, other.kind) |
|
|||
175 | # if kind_cmp: |
|
|||
176 | # if isinstance(self, SubModuleNode): |
|
|||
177 | # # we make submodules equal to dirnode for "sorting" purposes |
|
|||
178 | # return NodeKind.DIR |
|
|||
179 | # return kind_cmp |
|
|||
180 | # return cmp(self.name, other.name) |
|
|||
181 |
|
||||
182 | def __repr__(self): |
|
113 | def __repr__(self): | |
183 |
maybe_path = getattr(self, |
|
114 | maybe_path = getattr(self, "path", "UNKNOWN_PATH") | |
184 |
return f |
|
115 | return f"<{self.__class__.__name__} {maybe_path!r}>" | |
185 |
|
116 | |||
186 | def __str__(self): |
|
117 | def __str__(self): | |
187 | return self.name |
|
118 | return self.name | |
@@ -189,19 +120,21 b' class Node(object):' | |||||
189 | def _validate_path(self, path: bytes): |
|
120 | def _validate_path(self, path: bytes): | |
190 | self._assert_bytes(path) |
|
121 | self._assert_bytes(path) | |
191 |
|
122 | |||
192 |
if path.startswith(b |
|
123 | if path.startswith(b"/"): | |
193 | raise NodeError( |
|
124 | raise NodeError( | |
194 | f"Cannot initialize Node objects with slash at " |
|
125 | f"Cannot initialize Node objects with slash at " | |
195 | f"the beginning as only relative paths are supported. " |
|
126 | f"the beginning as only relative paths are supported. " | |
196 |
f"Got {path}" |
|
127 | f"Got {path}" | |
|
128 | ) | |||
197 |
|
129 | |||
198 | def _assert_bytes(self, value): |
|
130 | @classmethod | |
|
131 | def _assert_bytes(cls, value): | |||
199 | if not isinstance(value, bytes): |
|
132 | if not isinstance(value, bytes): | |
200 | raise TypeError(f"Bytes required as input, got {type(value)} of {value}.") |
|
133 | raise TypeError(f"Bytes required as input, got {type(value)} of {value}.") | |
201 |
|
134 | |||
202 | @LazyProperty |
|
135 | @LazyProperty | |
203 | def parent(self): |
|
136 | def parent(self): | |
204 | parent_path = self.get_parent_path() |
|
137 | parent_path: bytes = self.get_parent_path() | |
205 | if parent_path: |
|
138 | if parent_path: | |
206 | if self.commit: |
|
139 | if self.commit: | |
207 | return self.commit.get_node(parent_path) |
|
140 | return self.commit.get_node(parent_path) | |
@@ -209,10 +142,6 b' class Node(object):' | |||||
209 | return None |
|
142 | return None | |
210 |
|
143 | |||
211 | @LazyProperty |
|
144 | @LazyProperty | |
212 | def str_path(self) -> str: |
|
|||
213 | return safe_str(self.path) |
|
|||
214 |
|
||||
215 | @LazyProperty |
|
|||
216 | def has_rtlo(self): |
|
145 | def has_rtlo(self): | |
217 | """Detects if a path has right-to-left-override marker""" |
|
146 | """Detects if a path has right-to-left-override marker""" | |
218 | return self.RTLO_MARKER in self.str_path |
|
147 | return self.RTLO_MARKER in self.str_path | |
@@ -223,10 +152,10 b' class Node(object):' | |||||
223 | Returns name of the directory from full path of this vcs node. Empty |
|
152 | Returns name of the directory from full path of this vcs node. Empty | |
224 | string is returned if there's no directory in the path |
|
153 | string is returned if there's no directory in the path | |
225 | """ |
|
154 | """ | |
226 |
_parts = self.path.rstrip( |
|
155 | _parts = self.path.rstrip("/").rsplit("/", 1) | |
227 | if len(_parts) == 2: |
|
156 | if len(_parts) == 2: | |
228 | return _parts[0] |
|
157 | return _parts[0] | |
229 |
return |
|
158 | return "" | |
230 |
|
159 | |||
231 | @LazyProperty |
|
160 | @LazyProperty | |
232 | def name(self): |
|
161 | def name(self): | |
@@ -234,7 +163,7 b' class Node(object):' | |||||
234 | Returns name of the node so if its path |
|
163 | Returns name of the node so if its path | |
235 | then only last part is returned. |
|
164 | then only last part is returned. | |
236 | """ |
|
165 | """ | |
237 |
return self.path.rstrip( |
|
166 | return self.str_path.rstrip("/").split("/")[-1] | |
238 |
|
167 | |||
239 | @property |
|
168 | @property | |
240 | def kind(self): |
|
169 | def kind(self): | |
@@ -242,12 +171,12 b' class Node(object):' | |||||
242 |
|
171 | |||
243 | @kind.setter |
|
172 | @kind.setter | |
244 | def kind(self, kind): |
|
173 | def kind(self, kind): | |
245 |
if hasattr(self, |
|
174 | if hasattr(self, "_kind"): | |
246 | raise NodeError("Cannot change node's kind") |
|
175 | raise NodeError("Cannot change node's kind") | |
247 | else: |
|
176 | else: | |
248 | self._kind = kind |
|
177 | self._kind = kind | |
249 | # Post setter check (path's trailing slash) |
|
178 | # Post setter check (path's trailing slash) | |
250 |
if self.path.endswith( |
|
179 | if self.str_path.endswith("/"): | |
251 | raise NodeError("Node's path cannot end with slash") |
|
180 | raise NodeError("Node's path cannot end with slash") | |
252 |
|
181 | |||
253 | def get_parent_path(self) -> bytes: |
|
182 | def get_parent_path(self) -> bytes: | |
@@ -255,8 +184,8 b' class Node(object):' | |||||
255 | Returns node's parent path or empty string if node is root. |
|
184 | Returns node's parent path or empty string if node is root. | |
256 | """ |
|
185 | """ | |
257 | if self.is_root(): |
|
186 | if self.is_root(): | |
258 |
return b |
|
187 | return b"" | |
259 |
str_path = vcspath.dirname(self.path.rstrip( |
|
188 | str_path = vcspath.dirname(self.bytes_path.rstrip(b"/")) + b"/" | |
260 |
|
189 | |||
261 | return safe_bytes(str_path) |
|
190 | return safe_bytes(str_path) | |
262 |
|
191 | |||
@@ -278,7 +207,7 b' class Node(object):' | |||||
278 | """ |
|
207 | """ | |
279 | Returns ``True`` if node is a root node and ``False`` otherwise. |
|
208 | Returns ``True`` if node is a root node and ``False`` otherwise. | |
280 | """ |
|
209 | """ | |
281 |
return self.kind == NodeKind.DIR and self.path == |
|
210 | return self.kind == NodeKind.DIR and self.path == "" | |
282 |
|
211 | |||
283 | def is_submodule(self): |
|
212 | def is_submodule(self): | |
284 | """ |
|
213 | """ | |
@@ -292,29 +221,13 b' class Node(object):' | |||||
292 | Returns ``True`` if node's kind is ``NodeKind.LARGEFILE``, ``False`` |
|
221 | Returns ``True`` if node's kind is ``NodeKind.LARGEFILE``, ``False`` | |
293 | otherwise |
|
222 | otherwise | |
294 | """ |
|
223 | """ | |
295 | return self.kind == NodeKind.LARGEFILE |
|
224 | return self.kind == NodeKind.LARGE_FILE | |
296 |
|
225 | |||
297 | def is_link(self): |
|
226 | def is_link(self): | |
298 | if self.commit: |
|
227 | if self.commit: | |
299 | return self.commit.is_link(self.path) |
|
228 | return self.commit.is_link(self.bytes_path) | |
300 | return False |
|
229 | return False | |
301 |
|
230 | |||
302 | @LazyProperty |
|
|||
303 | def added(self): |
|
|||
304 | return self.state is NodeState.ADDED |
|
|||
305 |
|
||||
306 | @LazyProperty |
|
|||
307 | def changed(self): |
|
|||
308 | return self.state is NodeState.CHANGED |
|
|||
309 |
|
||||
310 | @LazyProperty |
|
|||
311 | def not_changed(self): |
|
|||
312 | return self.state is NodeState.NOT_CHANGED |
|
|||
313 |
|
||||
314 | @LazyProperty |
|
|||
315 | def removed(self): |
|
|||
316 | return self.state is NodeState.REMOVED |
|
|||
317 |
|
||||
318 |
|
231 | |||
319 | class FileNode(Node): |
|
232 | class FileNode(Node): | |
320 | """ |
|
233 | """ | |
@@ -325,6 +238,7 b' class FileNode(Node):' | |||||
325 | :attribute: commit: if given, first time content is accessed, callback |
|
238 | :attribute: commit: if given, first time content is accessed, callback | |
326 | :attribute: mode: stat mode for a node. Default is `FILEMODE_DEFAULT`. |
|
239 | :attribute: mode: stat mode for a node. Default is `FILEMODE_DEFAULT`. | |
327 | """ |
|
240 | """ | |
|
241 | ||||
328 | _filter_pre_load = [] |
|
242 | _filter_pre_load = [] | |
329 |
|
243 | |||
330 | def __init__(self, path: bytes, content: bytes | None = None, commit=None, mode=None, pre_load=None): |
|
244 | def __init__(self, path: bytes, content: bytes | None = None, commit=None, mode=None, pre_load=None): | |
@@ -359,7 +273,7 b' class FileNode(Node):' | |||||
359 | return self.content == other.content |
|
273 | return self.content == other.content | |
360 |
|
274 | |||
361 | def __hash__(self): |
|
275 | def __hash__(self): | |
362 |
raw_id = getattr(self.commit, |
|
276 | raw_id = getattr(self.commit, "raw_id", "") | |
363 | return hash((self.path, raw_id)) |
|
277 | return hash((self.path, raw_id)) | |
364 |
|
278 | |||
365 | def __lt__(self, other): |
|
279 | def __lt__(self, other): | |
@@ -369,33 +283,32 b' class FileNode(Node):' | |||||
369 | return self.content < other.content |
|
283 | return self.content < other.content | |
370 |
|
284 | |||
371 | def __repr__(self): |
|
285 | def __repr__(self): | |
372 |
short_id = getattr(self.commit, |
|
286 | short_id = getattr(self.commit, "short_id", "") | |
373 |
return f |
|
287 | return f"<{self.__class__.__name__} path={self.str_path!r}, short_id={short_id}>" | |
374 |
|
288 | |||
375 | def _set_bulk_properties(self, pre_load): |
|
289 | def _set_bulk_properties(self, pre_load): | |
376 | if not pre_load: |
|
290 | if not pre_load: | |
377 | return |
|
291 | return | |
378 | pre_load = [entry for entry in pre_load |
|
292 | pre_load = [entry for entry in pre_load if entry not in self._filter_pre_load] | |
379 | if entry not in self._filter_pre_load] |
|
|||
380 | if not pre_load: |
|
293 | if not pre_load: | |
381 | return |
|
294 | return | |
382 |
|
295 | |||
383 | remote = self.commit.get_remote() |
|
296 | remote = self.commit.get_remote() | |
384 | result = remote.bulk_file_request(self.commit.raw_id, self.path, pre_load) |
|
297 | result = remote.bulk_file_request(self.commit.raw_id, self.bytes_path, pre_load) | |
385 |
|
298 | |||
386 | for attr, value in result.items(): |
|
299 | for attr, value in result.items(): | |
387 | if attr == "flags": |
|
300 | if attr == "flags": | |
388 |
self.__dict__[ |
|
301 | self.__dict__["mode"] = safe_str(value) | |
389 | elif attr == "size": |
|
302 | elif attr == "size": | |
390 |
self.__dict__[ |
|
303 | self.__dict__["size"] = value | |
391 | elif attr == "data": |
|
304 | elif attr == "data": | |
392 |
self.__dict__[ |
|
305 | self.__dict__["_content"] = value | |
393 | elif attr == "is_binary": |
|
306 | elif attr == "is_binary": | |
394 |
self.__dict__[ |
|
307 | self.__dict__["is_binary"] = value | |
395 | elif attr == "md5": |
|
308 | elif attr == "md5": | |
396 |
self.__dict__[ |
|
309 | self.__dict__["md5"] = value | |
397 | else: |
|
310 | else: | |
398 |
raise ValueError(f |
|
311 | raise ValueError(f"Unsupported attr in bulk_property: {attr}") | |
399 |
|
312 | |||
400 | @LazyProperty |
|
313 | @LazyProperty | |
401 | def mode(self): |
|
314 | def mode(self): | |
@@ -404,7 +317,7 b' class FileNode(Node):' | |||||
404 | use value given at initialization or `FILEMODE_DEFAULT` (default). |
|
317 | use value given at initialization or `FILEMODE_DEFAULT` (default). | |
405 | """ |
|
318 | """ | |
406 | if self.commit: |
|
319 | if self.commit: | |
407 | mode = self.commit.get_file_mode(self.path) |
|
320 | mode = self.commit.get_file_mode(self.bytes_path) | |
408 | else: |
|
321 | else: | |
409 | mode = self._mode |
|
322 | mode = self._mode | |
410 | return mode |
|
323 | return mode | |
@@ -416,7 +329,7 b' class FileNode(Node):' | |||||
416 | """ |
|
329 | """ | |
417 | if self.commit: |
|
330 | if self.commit: | |
418 | if self._content is None: |
|
331 | if self._content is None: | |
419 | self._content = self.commit.get_file_content(self.path) |
|
332 | self._content = self.commit.get_file_content(self.bytes_path) | |
420 | content = self._content |
|
333 | content = self._content | |
421 | else: |
|
334 | else: | |
422 | content = self._content |
|
335 | content = self._content | |
@@ -427,7 +340,7 b' class FileNode(Node):' | |||||
427 | Returns lazily content of the FileNode. |
|
340 | Returns lazily content of the FileNode. | |
428 | """ |
|
341 | """ | |
429 | if self.commit: |
|
342 | if self.commit: | |
430 | content = self.commit.get_file_content(self.path) |
|
343 | content = self.commit.get_file_content(self.bytes_path) | |
431 | else: |
|
344 | else: | |
432 | content = self._content |
|
345 | content = self._content | |
433 | return content |
|
346 | return content | |
@@ -438,7 +351,7 b' class FileNode(Node):' | |||||
438 | vcsserver without loading it to memory. |
|
351 | vcsserver without loading it to memory. | |
439 | """ |
|
352 | """ | |
440 | if self.commit: |
|
353 | if self.commit: | |
441 | return self.commit.get_file_content_streamed(self.path) |
|
354 | return self.commit.get_file_content_streamed(self.bytes_path) | |
442 | raise NodeError("Cannot retrieve stream_bytes without related commit attribute") |
|
355 | raise NodeError("Cannot retrieve stream_bytes without related commit attribute") | |
443 |
|
356 | |||
444 | def metadata_uncached(self): |
|
357 | def metadata_uncached(self): | |
@@ -462,7 +375,7 b' class FileNode(Node):' | |||||
462 | """ |
|
375 | """ | |
463 | content = self.raw_bytes |
|
376 | content = self.raw_bytes | |
464 | if content and not isinstance(content, bytes): |
|
377 | if content and not isinstance(content, bytes): | |
465 |
raise ValueError(f |
|
378 | raise ValueError(f"Content is of type {type(content)} instead of bytes") | |
466 | return content |
|
379 | return content | |
467 |
|
380 | |||
468 | @LazyProperty |
|
381 | @LazyProperty | |
@@ -472,27 +385,21 b' class FileNode(Node):' | |||||
472 | @LazyProperty |
|
385 | @LazyProperty | |
473 | def size(self): |
|
386 | def size(self): | |
474 | if self.commit: |
|
387 | if self.commit: | |
475 | return self.commit.get_file_size(self.path) |
|
388 | return self.commit.get_file_size(self.bytes_path) | |
476 | raise NodeError( |
|
389 | raise NodeError("Cannot retrieve size of the file without related commit attribute") | |
477 | "Cannot retrieve size of the file without related " |
|
|||
478 | "commit attribute") |
|
|||
479 |
|
390 | |||
480 | @LazyProperty |
|
391 | @LazyProperty | |
481 | def message(self): |
|
392 | def message(self): | |
482 | if self.commit: |
|
393 | if self.commit: | |
483 | return self.last_commit.message |
|
394 | return self.last_commit.message | |
484 | raise NodeError( |
|
395 | raise NodeError("Cannot retrieve message of the file without related " "commit attribute") | |
485 | "Cannot retrieve message of the file without related " |
|
|||
486 | "commit attribute") |
|
|||
487 |
|
396 | |||
488 | @LazyProperty |
|
397 | @LazyProperty | |
489 | def last_commit(self): |
|
398 | def last_commit(self): | |
490 | if self.commit: |
|
399 | if self.commit: | |
491 | pre_load = ["author", "date", "message", "parents"] |
|
400 | pre_load = ["author", "date", "message", "parents"] | |
492 | return self.commit.get_path_commit(self.path, pre_load=pre_load) |
|
401 | return self.commit.get_path_commit(self.bytes_path, pre_load=pre_load) | |
493 | raise NodeError( |
|
402 | raise NodeError("Cannot retrieve last commit of the file without related commit attribute") | |
494 | "Cannot retrieve last commit of the file without " |
|
|||
495 | "related commit attribute") |
|
|||
496 |
|
403 | |||
497 | def get_mimetype(self): |
|
404 | def get_mimetype(self): | |
498 | """ |
|
405 | """ | |
@@ -502,28 +409,27 b' class FileNode(Node):' | |||||
502 | attribute to indicate that type should *NOT* be calculated). |
|
409 | attribute to indicate that type should *NOT* be calculated). | |
503 | """ |
|
410 | """ | |
504 |
|
411 | |||
505 |
if hasattr(self, |
|
412 | if hasattr(self, "_mimetype"): | |
506 |
if |
|
413 | if isinstance(self._mimetype, (tuple, list)) and len(self._mimetype) == 2: | |
507 | len(self._mimetype) == 2): |
|
|||
508 | return self._mimetype |
|
414 | return self._mimetype | |
509 | else: |
|
415 | else: | |
510 |
raise NodeError( |
|
416 | raise NodeError("given _mimetype attribute must be an 2 element list or tuple") | |
511 | 'element list or tuple') |
|
|||
512 |
|
417 | |||
513 | db = get_mimetypes_db() |
|
418 | db = get_mimetypes_db() | |
514 | mtype, encoding = db.guess_type(self.name) |
|
419 | mtype, encoding = db.guess_type(self.name) | |
515 |
|
420 | |||
516 | if mtype is None: |
|
421 | if mtype is None: | |
517 | if not self.is_largefile() and self.is_binary: |
|
422 | if not self.is_largefile() and self.is_binary: | |
518 |
mtype = |
|
423 | mtype = "application/octet-stream" | |
519 | encoding = None |
|
424 | encoding = None | |
520 | else: |
|
425 | else: | |
521 |
mtype = |
|
426 | mtype = "text/plain" | |
522 | encoding = None |
|
427 | encoding = None | |
523 |
|
428 | |||
524 | # try with pygments |
|
429 | # try with pygments | |
525 | try: |
|
430 | try: | |
526 | from pygments.lexers import get_lexer_for_filename |
|
431 | from pygments.lexers import get_lexer_for_filename | |
|
432 | ||||
527 | mt = get_lexer_for_filename(self.name).mimetypes |
|
433 | mt = get_lexer_for_filename(self.name).mimetypes | |
528 | except Exception: |
|
434 | except Exception: | |
529 | mt = None |
|
435 | mt = None | |
@@ -544,18 +450,17 b' class FileNode(Node):' | |||||
544 |
|
450 | |||
545 | @LazyProperty |
|
451 | @LazyProperty | |
546 | def mimetype_main(self): |
|
452 | def mimetype_main(self): | |
547 |
return self.mimetype.split( |
|
453 | return self.mimetype.split("/")[0] | |
548 |
|
454 | |||
549 | @classmethod |
|
455 | @classmethod | |
550 | def get_lexer(cls, filename, content=None): |
|
456 | def get_lexer(cls, filename, content=None): | |
551 | from pygments import lexers |
|
457 | from pygments import lexers | |
552 |
|
458 | |||
553 |
extension = filename.split( |
|
459 | extension = filename.split(".")[-1] | |
554 | lexer = None |
|
460 | lexer = None | |
555 |
|
461 | |||
556 | try: |
|
462 | try: | |
557 | lexer = lexers.guess_lexer_for_filename( |
|
463 | lexer = lexers.guess_lexer_for_filename(filename, content, stripnl=False) | |
558 | filename, content, stripnl=False) |
|
|||
559 | except lexers.ClassNotFound: |
|
464 | except lexers.ClassNotFound: | |
560 | pass |
|
465 | pass | |
561 |
|
466 | |||
@@ -580,7 +485,7 b' class FileNode(Node):' | |||||
580 | content, name and mimetype. |
|
485 | content, name and mimetype. | |
581 | """ |
|
486 | """ | |
582 | # TODO: this is more proper, but super heavy on investigating the type based on the content |
|
487 | # TODO: this is more proper, but super heavy on investigating the type based on the content | |
583 | #self.get_lexer(self.name, self.content) |
|
488 | # self.get_lexer(self.name, self.content) | |
584 |
|
489 | |||
585 | return self.get_lexer(self.name) |
|
490 | return self.get_lexer(self.name) | |
586 |
|
491 | |||
@@ -597,8 +502,8 b' class FileNode(Node):' | |||||
597 | Returns a list of commit for this file in which the file was changed |
|
502 | Returns a list of commit for this file in which the file was changed | |
598 | """ |
|
503 | """ | |
599 | if self.commit is None: |
|
504 | if self.commit is None: | |
600 |
raise NodeError( |
|
505 | raise NodeError("Unable to get commit for this FileNode") | |
601 | return self.commit.get_path_history(self.path) |
|
506 | return self.commit.get_path_history(self.bytes_path) | |
602 |
|
507 | |||
603 | @LazyProperty |
|
508 | @LazyProperty | |
604 | def annotate(self): |
|
509 | def annotate(self): | |
@@ -606,22 +511,9 b' class FileNode(Node):' | |||||
606 | Returns a list of three element tuples with lineno, commit and line |
|
511 | Returns a list of three element tuples with lineno, commit and line | |
607 | """ |
|
512 | """ | |
608 | if self.commit is None: |
|
513 | if self.commit is None: | |
609 |
raise NodeError( |
|
514 | raise NodeError("Unable to get commit for this FileNode") | |
610 | pre_load = ["author", "date", "message", "parents"] |
|
515 | pre_load = ["author", "date", "message", "parents"] | |
611 | return self.commit.get_file_annotate(self.path, pre_load=pre_load) |
|
516 | return self.commit.get_file_annotate(self.bytes_path, pre_load=pre_load) | |
612 |
|
||||
613 | @LazyProperty |
|
|||
614 | def state(self): |
|
|||
615 | if not self.commit: |
|
|||
616 | raise NodeError( |
|
|||
617 | "Cannot check state of the node if it's not " |
|
|||
618 | "linked with commit") |
|
|||
619 | elif self.path in (node.path for node in self.commit.added): |
|
|||
620 | return NodeState.ADDED |
|
|||
621 | elif self.path in (node.path for node in self.commit.changed): |
|
|||
622 | return NodeState.CHANGED |
|
|||
623 | else: |
|
|||
624 | return NodeState.NOT_CHANGED |
|
|||
625 |
|
517 | |||
626 | @LazyProperty |
|
518 | @LazyProperty | |
627 | def is_binary(self): |
|
519 | def is_binary(self): | |
@@ -629,7 +521,7 b' class FileNode(Node):' | |||||
629 | Returns True if file has binary content. |
|
521 | Returns True if file has binary content. | |
630 | """ |
|
522 | """ | |
631 | if self.commit: |
|
523 | if self.commit: | |
632 | return self.commit.is_node_binary(self.path) |
|
524 | return self.commit.is_node_binary(self.bytes_path) | |
633 | else: |
|
525 | else: | |
634 | raw_bytes = self._content |
|
526 | raw_bytes = self._content | |
635 | return bool(raw_bytes and BIN_BYTE_MARKER in raw_bytes) |
|
527 | return bool(raw_bytes and BIN_BYTE_MARKER in raw_bytes) | |
@@ -641,7 +533,7 b' class FileNode(Node):' | |||||
641 | """ |
|
533 | """ | |
642 |
|
534 | |||
643 | if self.commit: |
|
535 | if self.commit: | |
644 | return self.commit.node_md5_hash(self.path) |
|
536 | return self.commit.node_md5_hash(self.bytes_path) | |
645 | else: |
|
537 | else: | |
646 | raw_bytes = self._content |
|
538 | raw_bytes = self._content | |
647 | # TODO: this sucks, we're computing md5 on potentially super big stream data... |
|
539 | # TODO: this sucks, we're computing md5 on potentially super big stream data... | |
@@ -650,7 +542,7 b' class FileNode(Node):' | |||||
650 | @LazyProperty |
|
542 | @LazyProperty | |
651 | def extension(self): |
|
543 | def extension(self): | |
652 | """Returns filenode extension""" |
|
544 | """Returns filenode extension""" | |
653 |
return self.name.split( |
|
545 | return self.name.split(".")[-1] | |
654 |
|
546 | |||
655 | @property |
|
547 | @property | |
656 | def is_executable(self): |
|
548 | def is_executable(self): | |
@@ -667,15 +559,15 b' class FileNode(Node):' | |||||
667 | LF store. |
|
559 | LF store. | |
668 | """ |
|
560 | """ | |
669 | if self.commit: |
|
561 | if self.commit: | |
670 | return self.commit.get_largefile_node(self.path) |
|
562 | return self.commit.get_largefile_node(self.bytes_path) | |
671 |
|
563 | |||
672 | def count_lines(self, content: str | bytes, count_empty=False): |
|
564 | def count_lines(self, content: str | bytes, count_empty=False): | |
673 | if isinstance(content, str): |
|
565 | if isinstance(content, str): | |
674 |
newline_marker = |
|
566 | newline_marker = "\n" | |
675 | elif isinstance(content, bytes): |
|
567 | elif isinstance(content, bytes): | |
676 |
newline_marker = b |
|
568 | newline_marker = b"\n" | |
677 | else: |
|
569 | else: | |
678 |
raise ValueError( |
|
570 | raise ValueError("content must be bytes or str got {type(content)} instead") | |
679 |
|
571 | |||
680 | if count_empty: |
|
572 | if count_empty: | |
681 | all_lines = 0 |
|
573 | all_lines = 0 | |
@@ -704,33 +596,6 b' class FileNode(Node):' | |||||
704 | return all_lines, empty_lines |
|
596 | return all_lines, empty_lines | |
705 |
|
597 | |||
706 |
|
598 | |||
707 | class RemovedFileNode(FileNode): |
|
|||
708 | """ |
|
|||
709 | Dummy FileNode class - trying to access any public attribute except path, |
|
|||
710 | name, kind or state (or methods/attributes checking those two) would raise |
|
|||
711 | RemovedFileNodeError. |
|
|||
712 | """ |
|
|||
713 | ALLOWED_ATTRIBUTES = [ |
|
|||
714 | 'name', 'path', 'state', 'is_root', 'is_file', 'is_dir', 'kind', |
|
|||
715 | 'added', 'changed', 'not_changed', 'removed', 'bytes_path' |
|
|||
716 | ] |
|
|||
717 |
|
||||
718 | def __init__(self, path): |
|
|||
719 | """ |
|
|||
720 | :param path: relative path to the node |
|
|||
721 | """ |
|
|||
722 | super().__init__(path=path) |
|
|||
723 |
|
||||
724 | def __getattribute__(self, attr): |
|
|||
725 | if attr.startswith('_') or attr in RemovedFileNode.ALLOWED_ATTRIBUTES: |
|
|||
726 | return super().__getattribute__(attr) |
|
|||
727 | raise RemovedFileNodeError(f"Cannot access attribute {attr} on RemovedFileNode. Not in allowed attributes") |
|
|||
728 |
|
||||
729 | @LazyProperty |
|
|||
730 | def state(self): |
|
|||
731 | return NodeState.REMOVED |
|
|||
732 |
|
||||
733 |
|
||||
734 | class DirNode(Node): |
|
599 | class DirNode(Node): | |
735 | """ |
|
600 | """ | |
736 | DirNode stores list of files and directories within this node. |
|
601 | DirNode stores list of files and directories within this node. | |
@@ -752,7 +617,7 b' class DirNode(Node):' | |||||
752 | super().__init__(path, NodeKind.DIR) |
|
617 | super().__init__(path, NodeKind.DIR) | |
753 | self.commit = commit |
|
618 | self.commit = commit | |
754 | self._nodes = nodes |
|
619 | self._nodes = nodes | |
755 |
self.default_pre_load = default_pre_load or [ |
|
620 | self.default_pre_load = default_pre_load or ["is_binary", "size"] | |
756 |
|
621 | |||
757 | def __iter__(self): |
|
622 | def __iter__(self): | |
758 | yield from self.nodes |
|
623 | yield from self.nodes | |
@@ -782,10 +647,9 b' class DirNode(Node):' | |||||
782 | @LazyProperty |
|
647 | @LazyProperty | |
783 | def nodes(self): |
|
648 | def nodes(self): | |
784 | if self.commit: |
|
649 | if self.commit: | |
785 | nodes = self.commit.get_nodes(self.path, pre_load=self.default_pre_load) |
|
650 | nodes = self.commit.get_nodes(self.bytes_path, pre_load=self.default_pre_load) | |
786 | else: |
|
651 | else: | |
787 | nodes = self._nodes |
|
652 | nodes = self._nodes | |
788 | self._nodes_dict = {node.path: node for node in nodes} |
|
|||
789 | return sorted(nodes) |
|
653 | return sorted(nodes) | |
790 |
|
654 | |||
791 | @LazyProperty |
|
655 | @LazyProperty | |
@@ -796,47 +660,6 b' class DirNode(Node):' | |||||
796 | def dirs(self): |
|
660 | def dirs(self): | |
797 | return sorted(node for node in self.nodes if node.is_dir()) |
|
661 | return sorted(node for node in self.nodes if node.is_dir()) | |
798 |
|
662 | |||
799 | def get_node(self, path): |
|
|||
800 | """ |
|
|||
801 | Returns node from within this particular ``DirNode``, so it is now |
|
|||
802 | allowed to fetch, i.e. node located at 'docs/api/index.rst' from node |
|
|||
803 | 'docs'. In order to access deeper nodes one must fetch nodes between |
|
|||
804 | them first - this would work:: |
|
|||
805 |
|
||||
806 | docs = root.get_node('docs') |
|
|||
807 | docs.get_node('api').get_node('index.rst') |
|
|||
808 |
|
||||
809 | :param: path - relative to the current node |
|
|||
810 |
|
||||
811 | .. note:: |
|
|||
812 | To access lazily (as in example above) node have to be initialized |
|
|||
813 | with related commit object - without it node is out of |
|
|||
814 | context and may know nothing about anything else than nearest |
|
|||
815 | (located at same level) nodes. |
|
|||
816 | """ |
|
|||
817 | try: |
|
|||
818 | path = path.rstrip('/') |
|
|||
819 | if path == '': |
|
|||
820 | raise NodeError("Cannot retrieve node without path") |
|
|||
821 | self.nodes # access nodes first in order to set _nodes_dict |
|
|||
822 | paths = path.split('/') |
|
|||
823 | if len(paths) == 1: |
|
|||
824 | if not self.is_root(): |
|
|||
825 | path = '/'.join((self.path, paths[0])) |
|
|||
826 | else: |
|
|||
827 | path = paths[0] |
|
|||
828 | return self._nodes_dict[path] |
|
|||
829 | elif len(paths) > 1: |
|
|||
830 | if self.commit is None: |
|
|||
831 | raise NodeError("Cannot access deeper nodes without commit") |
|
|||
832 | else: |
|
|||
833 | path1, path2 = paths[0], '/'.join(paths[1:]) |
|
|||
834 | return self.get_node(path1).get_node(path2) |
|
|||
835 | else: |
|
|||
836 | raise KeyError |
|
|||
837 | except KeyError: |
|
|||
838 | raise NodeError(f"Node does not exist at {path}") |
|
|||
839 |
|
||||
840 | @LazyProperty |
|
663 | @LazyProperty | |
841 | def state(self): |
|
664 | def state(self): | |
842 | raise NodeError("Cannot access state of DirNode") |
|
665 | raise NodeError("Cannot access state of DirNode") | |
@@ -844,7 +667,7 b' class DirNode(Node):' | |||||
844 | @LazyProperty |
|
667 | @LazyProperty | |
845 | def size(self): |
|
668 | def size(self): | |
846 | size = 0 |
|
669 | size = 0 | |
847 | for root, dirs, files in self.commit.walk(self.path): |
|
670 | for root, dirs, files in self.commit.walk(self.bytes_path): | |
848 | for f in files: |
|
671 | for f in files: | |
849 | size += f.size |
|
672 | size += f.size | |
850 |
|
673 | |||
@@ -854,14 +677,12 b' class DirNode(Node):' | |||||
854 | def last_commit(self): |
|
677 | def last_commit(self): | |
855 | if self.commit: |
|
678 | if self.commit: | |
856 | pre_load = ["author", "date", "message", "parents"] |
|
679 | pre_load = ["author", "date", "message", "parents"] | |
857 | return self.commit.get_path_commit(self.path, pre_load=pre_load) |
|
680 | return self.commit.get_path_commit(self.bytes_path, pre_load=pre_load) | |
858 | raise NodeError( |
|
681 | raise NodeError("Cannot retrieve last commit of the file without related commit attribute") | |
859 | "Cannot retrieve last commit of the file without " |
|
|||
860 | "related commit attribute") |
|
|||
861 |
|
682 | |||
862 | def __repr__(self): |
|
683 | def __repr__(self): | |
863 |
short_id = getattr(self.commit, |
|
684 | short_id = getattr(self.commit, "short_id", "") | |
864 |
return f |
|
685 | return f"<{self.__class__.__name__} path={self.str_path!r}, short_id={short_id}>" | |
865 |
|
686 | |||
866 |
|
687 | |||
867 | class RootNode(DirNode): |
|
688 | class RootNode(DirNode): | |
@@ -870,21 +691,24 b' class RootNode(DirNode):' | |||||
870 | """ |
|
691 | """ | |
871 |
|
692 | |||
872 | def __init__(self, nodes=(), commit=None): |
|
693 | def __init__(self, nodes=(), commit=None): | |
873 |
super().__init__(path=b |
|
694 | super().__init__(path=b"", nodes=nodes, commit=commit) | |
874 |
|
695 | |||
875 | def __repr__(self): |
|
696 | def __repr__(self): | |
876 | return f'<{self.__class__.__name__}>' |
|
697 | short_id = getattr(self.commit, "short_id", "") | |
|
698 | return f"<{self.__class__.__name__} path={self.str_path!r}, short_id={short_id}>" | |||
877 |
|
699 | |||
878 |
|
700 | |||
879 | class SubModuleNode(Node): |
|
701 | class SubModuleNode(Node): | |
880 | """ |
|
702 | """ | |
881 | represents a SubModule of Git or SubRepo of Mercurial |
|
703 | represents a SubModule of Git or SubRepo of Mercurial | |
882 | """ |
|
704 | """ | |
|
705 | ||||
883 | is_binary = False |
|
706 | is_binary = False | |
884 | size = 0 |
|
707 | size = 0 | |
885 |
|
708 | |||
886 | def __init__(self, name, url=None, commit=None, alias=None): |
|
709 | def __init__(self, name, url=None, commit=None, alias=None): | |
887 | self.path = name |
|
710 | self.path = name | |
|
711 | self.str_path: str = safe_str(self.path) # we store paths as str | |||
888 | self.kind = NodeKind.SUBMODULE |
|
712 | self.kind = NodeKind.SUBMODULE | |
889 | self.alias = alias |
|
713 | self.alias = alias | |
890 |
|
714 | |||
@@ -894,8 +718,8 b' class SubModuleNode(Node):' | |||||
894 | self.url = url or self._extract_submodule_url() |
|
718 | self.url = url or self._extract_submodule_url() | |
895 |
|
719 | |||
896 | def __repr__(self): |
|
720 | def __repr__(self): | |
897 |
short_id = getattr(self.commit, |
|
721 | short_id = getattr(self.commit, "short_id", "") | |
898 |
return f |
|
722 | return f"<{self.__class__.__name__} {self.str_path!r} @ {short_id}>" | |
899 |
|
723 | |||
900 | def _extract_submodule_url(self): |
|
724 | def _extract_submodule_url(self): | |
901 | # TODO: find a way to parse gits submodule file and extract the |
|
725 | # TODO: find a way to parse gits submodule file and extract the | |
@@ -908,22 +732,22 b' class SubModuleNode(Node):' | |||||
908 | Returns name of the node so if its path |
|
732 | Returns name of the node so if its path | |
909 | then only last part is returned. |
|
733 | then only last part is returned. | |
910 | """ |
|
734 | """ | |
911 |
org = |
|
735 | org = self.str_path.rstrip("/").split("/")[-1] | |
912 |
return f |
|
736 | return f"{org} @ {self.commit.short_id}" | |
913 |
|
737 | |||
914 |
|
738 | |||
915 | class LargeFileNode(FileNode): |
|
739 | class LargeFileNode(FileNode): | |
916 |
|
||||
917 | def __init__(self, path, url=None, commit=None, alias=None, org_path=None): |
|
740 | def __init__(self, path, url=None, commit=None, alias=None, org_path=None): | |
918 | self._validate_path(path) # can throw exception if path is invalid |
|
741 | self._validate_path(path) # can throw exception if path is invalid | |
919 | self.org_path = org_path # as stored in VCS as LF pointer |
|
742 | self.org_path = org_path # as stored in VCS as LF pointer | |
920 |
|
743 | |||
921 |
self.bytes_path = path.rstrip(b |
|
744 | self.bytes_path = path.rstrip(b"/") # store for __repr__ | |
922 |
self.path = safe_str(self.bytes_path) |
|
745 | self.str_path = safe_str(self.bytes_path) | |
|
746 | self.path = self.str_path | |||
923 |
|
747 | |||
924 | self.kind = NodeKind.LARGEFILE |
|
748 | self.kind = NodeKind.LARGE_FILE | |
925 | self.alias = alias |
|
749 | self.alias = alias | |
926 |
self._content = b |
|
750 | self._content = b"" | |
927 |
|
751 | |||
928 | def _validate_path(self, path: bytes): |
|
752 | def _validate_path(self, path: bytes): | |
929 | """ |
|
753 | """ | |
@@ -932,7 +756,7 b' class LargeFileNode(FileNode):' | |||||
932 | self._assert_bytes(path) |
|
756 | self._assert_bytes(path) | |
933 |
|
757 | |||
934 | def __repr__(self): |
|
758 | def __repr__(self): | |
935 |
return f |
|
759 | return f"<{self.__class__.__name__} {self.org_path} -> {self.str_path!r}>" | |
936 |
|
760 | |||
937 | @LazyProperty |
|
761 | @LazyProperty | |
938 | def size(self): |
|
762 | def size(self): | |
@@ -940,7 +764,7 b' class LargeFileNode(FileNode):' | |||||
940 |
|
764 | |||
941 | @LazyProperty |
|
765 | @LazyProperty | |
942 | def raw_bytes(self): |
|
766 | def raw_bytes(self): | |
943 |
with open(self.path, |
|
767 | with open(self.path, "rb") as f: | |
944 | content = f.read() |
|
768 | content = f.read() | |
945 | return content |
|
769 | return content | |
946 |
|
770 | |||
@@ -952,7 +776,7 b' class LargeFileNode(FileNode):' | |||||
952 | return self.org_path |
|
776 | return self.org_path | |
953 |
|
777 | |||
954 | def stream_bytes(self): |
|
778 | def stream_bytes(self): | |
955 |
with open(self.path, |
|
779 | with open(self.path, "rb") as stream: | |
956 | while True: |
|
780 | while True: | |
957 | data = stream.read(16 * 1024) |
|
781 | data = stream.read(16 * 1024) | |
958 | if not data: |
|
782 | if not data: |
@@ -103,7 +103,7 b' class GistModel(BaseModel):' | |||||
103 | raise VCSError(f'Failed to load gist repository for {repo}') |
|
103 | raise VCSError(f'Failed to load gist repository for {repo}') | |
104 |
|
104 | |||
105 | commit = vcs_repo.get_commit(commit_id=revision) |
|
105 | commit = vcs_repo.get_commit(commit_id=revision) | |
106 | return commit, [n for n in commit.get_node('/')] |
|
106 | return commit, [n for n in commit.get_node(b'/')] | |
107 |
|
107 | |||
108 | def create(self, description, owner, gist_mapping, |
|
108 | def create(self, description, owner, gist_mapping, | |
109 | gist_type=Gist.GIST_PUBLIC, lifetime=-1, gist_id=None, |
|
109 | gist_type=Gist.GIST_PUBLIC, lifetime=-1, gist_id=None, |
@@ -178,6 +178,7 b' def get_diff_info(' | |||||
178 | log.debug('Calculating authors of changed files') |
|
178 | log.debug('Calculating authors of changed files') | |
179 | target_commit = source_repo.get_commit(ancestor_id) |
|
179 | target_commit = source_repo.get_commit(ancestor_id) | |
180 |
|
180 | |||
|
181 | # TODO: change to operate in bytes.. | |||
181 | for fname, lines in changed_lines.items(): |
|
182 | for fname, lines in changed_lines.items(): | |
182 |
|
183 | |||
183 | try: |
|
184 | try: | |
@@ -2223,8 +2224,7 b' class MergeCheck(object):' | |||||
2223 | ) |
|
2224 | ) | |
2224 |
|
2225 | |||
2225 | @classmethod |
|
2226 | @classmethod | |
2226 | def validate(cls, pull_request, auth_user, translator, fail_early=False, |
|
2227 | def validate(cls, pull_request, auth_user, translator, fail_early=False, force_shadow_repo_refresh=False): | |
2227 | force_shadow_repo_refresh=False): |
|
|||
2228 | _ = translator |
|
2228 | _ = translator | |
2229 | merge_check = cls() |
|
2229 | merge_check = cls() | |
2230 |
|
2230 | |||
@@ -2285,12 +2285,10 b' class MergeCheck(object):' | |||||
2285 | # left over TODOs |
|
2285 | # left over TODOs | |
2286 | todos = CommentsModel().get_pull_request_unresolved_todos(pull_request) |
|
2286 | todos = CommentsModel().get_pull_request_unresolved_todos(pull_request) | |
2287 | if todos: |
|
2287 | if todos: | |
2288 |
log.debug("MergeCheck: cannot merge, |
|
2288 | log.debug("MergeCheck: cannot merge, %s unresolved TODOs left.", len(todos)) | |
2289 | "unresolved TODOs left.".format(len(todos))) |
|
|||
2290 |
|
2289 | |||
2291 | if len(todos) == 1: |
|
2290 | if len(todos) == 1: | |
2292 | msg = _('Cannot merge, {} TODO still not resolved.').format( |
|
2291 | msg = _('Cannot merge, {} TODO still not resolved.').format(len(todos)) | |
2293 | len(todos)) |
|
|||
2294 | else: |
|
2292 | else: | |
2295 | msg = _('Cannot merge, {} TODOs still not resolved.').format( |
|
2293 | msg = _('Cannot merge, {} TODOs still not resolved.').format( | |
2296 | len(todos)) |
|
2294 | len(todos)) |
@@ -33,6 +33,7 b' from rhodecode.lib.auth import HasUserGr' | |||||
33 | from rhodecode.lib.caching_query import FromCache |
|
33 | from rhodecode.lib.caching_query import FromCache | |
34 | from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError, AttachedArtifactsError |
|
34 | from rhodecode.lib.exceptions import AttachedForksError, AttachedPullRequestsError, AttachedArtifactsError | |
35 | from rhodecode.lib import hooks_base |
|
35 | from rhodecode.lib import hooks_base | |
|
36 | from rhodecode.lib.str_utils import safe_bytes | |||
36 | from rhodecode.lib.user_log_filter import user_log_filter |
|
37 | from rhodecode.lib.user_log_filter import user_log_filter | |
37 | from rhodecode.lib.utils import make_db_config |
|
38 | from rhodecode.lib.utils import make_db_config | |
38 | from rhodecode.lib.utils2 import ( |
|
39 | from rhodecode.lib.utils2 import ( | |
@@ -1109,47 +1110,47 b' class ReadmeFinder:' | |||||
1109 | different. |
|
1110 | different. | |
1110 | """ |
|
1111 | """ | |
1111 |
|
1112 | |||
1112 |
readme_re = re.compile(r'^readme(\.[^ |
|
1113 | readme_re = re.compile(br'^readme(\.[^.]+)?$', re.IGNORECASE) | |
1113 | path_re = re.compile(r'^docs?', re.IGNORECASE) |
|
1114 | path_re = re.compile(br'^docs?', re.IGNORECASE) | |
1114 |
|
1115 | |||
1115 | default_priorities = { |
|
1116 | default_priorities = { | |
1116 | None: 0, |
|
1117 | None: 0, | |
1117 | '.rst': 1, |
|
1118 | b'.rst': 1, | |
1118 | '.md': 1, |
|
1119 | b'.md': 1, | |
1119 | '.rest': 2, |
|
1120 | b'.rest': 2, | |
1120 | '.mkdn': 2, |
|
1121 | b'.mkdn': 2, | |
1121 | '.text': 2, |
|
1122 | b'.text': 2, | |
1122 | '.txt': 3, |
|
1123 | b'.txt': 3, | |
1123 | '.mdown': 3, |
|
1124 | b'.mdown': 3, | |
1124 | '.markdown': 4, |
|
1125 | b'.markdown': 4, | |
1125 | } |
|
1126 | } | |
1126 |
|
1127 | |||
1127 | path_priority = { |
|
1128 | path_priority = { | |
1128 | 'doc': 0, |
|
1129 | b'doc': 0, | |
1129 | 'docs': 1, |
|
1130 | b'docs': 1, | |
1130 | } |
|
1131 | } | |
1131 |
|
1132 | |||
1132 | FALLBACK_PRIORITY = 99 |
|
1133 | FALLBACK_PRIORITY = 99 | |
1133 |
|
1134 | |||
1134 | RENDERER_TO_EXTENSION = { |
|
1135 | RENDERER_TO_EXTENSION = { | |
1135 | 'rst': ['.rst', '.rest'], |
|
1136 | 'rst': [b'.rst', b'.rest'], | |
1136 | 'markdown': ['.md', 'mkdn', '.mdown', '.markdown'], |
|
1137 | 'markdown': [b'.md', b'mkdn', b'.mdown', b'.markdown'], | |
1137 | } |
|
1138 | } | |
1138 |
|
1139 | |||
1139 | def __init__(self, default_renderer=None): |
|
1140 | def __init__(self, default_renderer=None): | |
1140 | self._default_renderer = default_renderer |
|
1141 | self._default_renderer = default_renderer | |
1141 | self._renderer_extensions = self.RENDERER_TO_EXTENSION.get( |
|
1142 | self._renderer_extensions = self.RENDERER_TO_EXTENSION.get(default_renderer, []) | |
1142 | default_renderer, []) |
|
|||
1143 |
|
1143 | |||
1144 | def search(self, commit, path='/'): |
|
1144 | def search(self, commit, path=b'/'): | |
1145 | """ |
|
1145 | """ | |
1146 | Find a readme in the given `commit`. |
|
1146 | Find a readme in the given `commit`. | |
1147 | """ |
|
1147 | """ | |
1148 | # firstly, check the PATH type if it is actually a DIR |
|
1148 | # firstly, check the PATH type if it is actually a DIR | |
1149 | if commit.get_node(path).kind != NodeKind.DIR: |
|
1149 | bytes_path = safe_bytes(path) | |
|
1150 | if commit.get_node(bytes_path).kind != NodeKind.DIR: | |||
1150 | return None |
|
1151 | return None | |
1151 |
|
1152 | |||
1152 | nodes = commit.get_nodes(path) |
|
1153 | nodes = commit.get_nodes(bytes_path) | |
1153 | matches = self._match_readmes(nodes) |
|
1154 | matches = self._match_readmes(nodes) | |
1154 | matches = self._sort_according_to_priority(matches) |
|
1155 | matches = self._sort_according_to_priority(matches) | |
1155 | if matches: |
|
1156 | if matches: | |
@@ -1157,8 +1158,8 b' class ReadmeFinder:' | |||||
1157 |
|
1158 | |||
1158 | paths = self._match_paths(nodes) |
|
1159 | paths = self._match_paths(nodes) | |
1159 | paths = self._sort_paths_according_to_priority(paths) |
|
1160 | paths = self._sort_paths_according_to_priority(paths) | |
1160 | for path in paths: |
|
1161 | for bytes_path in paths: | |
1161 | match = self.search(commit, path=path) |
|
1162 | match = self.search(commit, path=bytes_path) | |
1162 | if match: |
|
1163 | if match: | |
1163 | return match |
|
1164 | return match | |
1164 |
|
1165 | |||
@@ -1168,7 +1169,7 b' class ReadmeFinder:' | |||||
1168 | for node in nodes: |
|
1169 | for node in nodes: | |
1169 | if not node.is_file(): |
|
1170 | if not node.is_file(): | |
1170 | continue |
|
1171 | continue | |
1171 | path = node.path.rsplit('/', 1)[-1] |
|
1172 | path = node.bytes_path.rsplit(b'/', 1)[-1] | |
1172 | match = self.readme_re.match(path) |
|
1173 | match = self.readme_re.match(path) | |
1173 | if match: |
|
1174 | if match: | |
1174 | extension = match.group(1) |
|
1175 | extension = match.group(1) | |
@@ -1178,28 +1179,26 b' class ReadmeFinder:' | |||||
1178 | for node in nodes: |
|
1179 | for node in nodes: | |
1179 | if not node.is_dir(): |
|
1180 | if not node.is_dir(): | |
1180 | continue |
|
1181 | continue | |
1181 | match = self.path_re.match(node.path) |
|
1182 | match = self.path_re.match(node.bytes_path) | |
1182 | if match: |
|
1183 | if match: | |
1183 | yield node.path |
|
1184 | yield node.bytes_path | |
1184 |
|
1185 | |||
1185 | def _priority(self, extension): |
|
1186 | def _priority(self, extension): | |
1186 | renderer_priority = ( |
|
1187 | renderer_priority = 0 if extension in self._renderer_extensions else 1 | |
1187 | 0 if extension in self._renderer_extensions else 1) |
|
1188 | extension_priority = self.default_priorities.get(extension, self.FALLBACK_PRIORITY) | |
1188 | extension_priority = self.default_priorities.get( |
|
1189 | return renderer_priority, extension_priority | |
1189 | extension, self.FALLBACK_PRIORITY) |
|
|||
1190 | return (renderer_priority, extension_priority) |
|
|||
1191 |
|
1190 | |||
1192 | def _sort_according_to_priority(self, matches): |
|
1191 | def _sort_according_to_priority(self, matches): | |
1193 |
|
1192 | |||
1194 | def priority_and_path(match): |
|
1193 | def priority_and_path(match): | |
1195 |
return |
|
1194 | return match.priority, match.path | |
1196 |
|
1195 | |||
1197 | return sorted(matches, key=priority_and_path) |
|
1196 | return sorted(matches, key=priority_and_path) | |
1198 |
|
1197 | |||
1199 | def _sort_paths_according_to_priority(self, paths): |
|
1198 | def _sort_paths_according_to_priority(self, paths): | |
1200 |
|
1199 | |||
1201 | def priority_and_path(path): |
|
1200 | def priority_and_path(path): | |
1202 |
return |
|
1201 | return self.path_priority.get(path, self.FALLBACK_PRIORITY), path | |
1203 |
|
1202 | |||
1204 | return sorted(paths, key=priority_and_path) |
|
1203 | return sorted(paths, key=priority_and_path) | |
1205 |
|
1204 |
@@ -543,7 +543,7 b' class ScmModel(BaseModel):' | |||||
543 | root_path = root_path.lstrip('/') |
|
543 | root_path = root_path.lstrip('/') | |
544 |
|
544 | |||
545 | # get RootNode, inject pre-load options before walking |
|
545 | # get RootNode, inject pre-load options before walking | |
546 | top_node = commit.get_node(root_path) |
|
546 | top_node = commit.get_node(safe_bytes(root_path)) | |
547 | extended_info_pre_load = [] |
|
547 | extended_info_pre_load = [] | |
548 | if extended_info: |
|
548 | if extended_info: | |
549 | extended_info_pre_load += ['md5'] |
|
549 | extended_info_pre_load += ['md5'] | |
@@ -614,12 +614,13 b' class ScmModel(BaseModel):' | |||||
614 |
|
614 | |||
615 | _files = list() |
|
615 | _files = list() | |
616 | _dirs = list() |
|
616 | _dirs = list() | |
|
617 | bytes_path = safe_bytes(root_path) | |||
617 | try: |
|
618 | try: | |
618 | _repo = self._get_repo(repo_name) |
|
619 | _repo = self._get_repo(repo_name) | |
619 | commit = _repo.scm_instance().get_commit(commit_id=commit_id) |
|
620 | commit = _repo.scm_instance().get_commit(commit_id=commit_id) | |
620 |
root_path = |
|
621 | root_path = bytes_path.lstrip(b'/') | |
621 |
|
622 | |||
622 | top_node = commit.get_node(root_path) |
|
623 | top_node = commit.get_node(safe_bytes(root_path)) | |
623 | top_node.default_pre_load = [] |
|
624 | top_node.default_pre_load = [] | |
624 |
|
625 | |||
625 | for __, dirs, files in commit.walk(top_node): |
|
626 | for __, dirs, files in commit.walk(top_node): | |
@@ -736,7 +737,7 b' class ScmModel(BaseModel):' | |||||
736 | _repo = self._get_repo(repo_name) |
|
737 | _repo = self._get_repo(repo_name) | |
737 | commit = _repo.scm_instance().get_commit(commit_id=commit_id) |
|
738 | commit = _repo.scm_instance().get_commit(commit_id=commit_id) | |
738 | root_path = root_path.lstrip('/') |
|
739 | root_path = root_path.lstrip('/') | |
739 | top_node = commit.get_node(root_path) |
|
740 | top_node = commit.get_node(safe_bytes(root_path)) | |
740 | top_node.default_pre_load = [] |
|
741 | top_node.default_pre_load = [] | |
741 |
|
742 | |||
742 | for __, dirs, files in commit.walk(top_node): |
|
743 | for __, dirs, files in commit.walk(top_node): | |
@@ -774,7 +775,7 b' class ScmModel(BaseModel):' | |||||
774 | only for git |
|
775 | only for git | |
775 | :param trigger_push_hook: trigger push hooks |
|
776 | :param trigger_push_hook: trigger push hooks | |
776 |
|
777 | |||
777 |
:returns: new commit |
|
778 | :returns: new commit | |
778 | """ |
|
779 | """ | |
779 | user, scm_instance, message, commiter, author, imc = self.initialize_inmemory_vars( |
|
780 | user, scm_instance, message, commiter, author, imc = self.initialize_inmemory_vars( | |
780 | user, repo, message, author) |
|
781 | user, repo, message, author) |
@@ -36,8 +36,8 b' connection_available = pytest.mark.skipi' | |||||
36 |
|
36 | |||
37 |
|
37 | |||
38 | import requests |
|
38 | import requests | |
|
39 | from urllib3.util.retry import Retry | |||
39 | from requests.adapters import HTTPAdapter |
|
40 | from requests.adapters import HTTPAdapter | |
40 | from requests.packages.urllib3.util.retry import Retry |
|
|||
41 |
|
41 | |||
42 |
|
42 | |||
43 | def requests_retry_session( |
|
43 | def requests_retry_session( |
@@ -92,8 +92,8 b' class TestRepoModel(object):' | |||||
92 | is not None) |
|
92 | is not None) | |
93 |
|
93 | |||
94 | @pytest.mark.parametrize("filename, expected", [ |
|
94 | @pytest.mark.parametrize("filename, expected", [ | |
95 | ("README", True), |
|
95 | (b"README", True), | |
96 | ("README.rst", False), |
|
96 | (b"README.rst", False), | |
97 | ]) |
|
97 | ]) | |
98 | def test_filenode_is_link(self, vcsbackend, filename, expected): |
|
98 | def test_filenode_is_link(self, vcsbackend, filename, expected): | |
99 | repo = vcsbackend.repo |
|
99 | repo = vcsbackend.repo |
@@ -28,7 +28,7 b' import pytest' | |||||
28 |
|
28 | |||
29 | import rhodecode |
|
29 | import rhodecode | |
30 | from rhodecode.lib.archive_cache import get_archival_config |
|
30 | from rhodecode.lib.archive_cache import get_archival_config | |
31 | from rhodecode.lib.str_utils import ascii_bytes |
|
31 | from rhodecode.lib.str_utils import ascii_bytes, safe_bytes, safe_str | |
32 | from rhodecode.lib.vcs.backends import base |
|
32 | from rhodecode.lib.vcs.backends import base | |
33 | from rhodecode.lib.vcs.exceptions import ImproperArchiveTypeError, VCSError |
|
33 | from rhodecode.lib.vcs.exceptions import ImproperArchiveTypeError, VCSError | |
34 | from rhodecode.lib.vcs.nodes import FileNode |
|
34 | from rhodecode.lib.vcs.nodes import FileNode | |
@@ -80,8 +80,8 b' class TestArchives(BackendTestMixin):' | |||||
80 | out_file.close() |
|
80 | out_file.close() | |
81 |
|
81 | |||
82 | for x in range(5): |
|
82 | for x in range(5): | |
83 | node_path = "%d/file_%d.txt" % (x, x) |
|
83 | node_path = b"%d/file_%d.txt" % (x, x) | |
84 | with open(os.path.join(out_dir, "repo/" + node_path), "rb") as f: |
|
84 | with open(os.path.join(safe_bytes(str(out_dir)), b"repo/" + node_path), "rb") as f: | |
85 | file_content = f.read() |
|
85 | file_content = f.read() | |
86 | assert file_content == self.tip.get_node(node_path).content |
|
86 | assert file_content == self.tip.get_node(node_path).content | |
87 |
|
87 | |||
@@ -120,8 +120,9 b' class TestArchives(BackendTestMixin):' | |||||
120 | zip_file = zipfile.ZipFile(str(archive_lnk)) |
|
120 | zip_file = zipfile.ZipFile(str(archive_lnk)) | |
121 |
|
121 | |||
122 | for x in range(5): |
|
122 | for x in range(5): | |
123 | node_path = "%d/file_%d.txt" % (x, x) |
|
123 | node_path = b"%d/file_%d.txt" % (x, x) | |
124 | data = zip_file.read(f"repo/{node_path}") |
|
124 | # NOTE: zipfile operates only on strings inside the archive | |
|
125 | data = zip_file.read(safe_str(b"repo/%s" % node_path)) | |||
125 |
|
126 | |||
126 | decompressed = io.BytesIO() |
|
127 | decompressed = io.BytesIO() | |
127 | decompressed.write(data) |
|
128 | decompressed.write(data) | |
@@ -143,8 +144,9 b' class TestArchives(BackendTestMixin):' | |||||
143 | assert b"commit_id:%b" % raw_id in metafile |
|
144 | assert b"commit_id:%b" % raw_id in metafile | |
144 |
|
145 | |||
145 | for x in range(5): |
|
146 | for x in range(5): | |
146 | node_path = "%d/file_%d.txt" % (x, x) |
|
147 | node_path = b"%d/file_%d.txt" % (x, x) | |
147 | data = zip_file.read(f"repo/{node_path}") |
|
148 | # NOTE: zipfile operates only on strings inside the archive | |
|
149 | data = zip_file.read(safe_str(b"repo/%s" % node_path)) | |||
148 | decompressed = io.BytesIO() |
|
150 | decompressed = io.BytesIO() | |
149 | decompressed.write(data) |
|
151 | decompressed.write(data) | |
150 | assert decompressed.getvalue() == self.tip.get_node(node_path).content |
|
152 | assert decompressed.getvalue() == self.tip.get_node(node_path).content |
@@ -22,21 +22,17 b' import time' | |||||
22 | import pytest |
|
22 | import pytest | |
23 |
|
23 | |||
24 | from rhodecode.lib.str_utils import safe_bytes |
|
24 | from rhodecode.lib.str_utils import safe_bytes | |
25 |
from rhodecode.lib.vcs.backends.base import CollectionGenerator, |
|
25 | from rhodecode.lib.vcs.backends.base import CollectionGenerator, EmptyCommit | |
26 | from rhodecode.lib.vcs.exceptions import ( |
|
26 | from rhodecode.lib.vcs.exceptions import ( | |
27 | BranchDoesNotExistError, |
|
27 | BranchDoesNotExistError, | |
28 | CommitDoesNotExistError, |
|
28 | CommitDoesNotExistError, | |
29 | RepositoryError, |
|
29 | RepositoryError, | |
30 | EmptyRepositoryError, |
|
30 | EmptyRepositoryError, | |
31 | ) |
|
31 | ) | |
32 |
from rhodecode.lib.vcs.nodes import |
|
32 | from rhodecode.lib.vcs.nodes import FileNode | |
33 | FileNode, |
|
|||
34 | AddedFileNodesGenerator, |
|
|||
35 | ChangedFileNodesGenerator, |
|
|||
36 | RemovedFileNodesGenerator, |
|
|||
37 | ) |
|
|||
38 | from rhodecode.tests import get_new_dir |
|
33 | from rhodecode.tests import get_new_dir | |
39 | from rhodecode.tests.vcs.conftest import BackendTestMixin |
|
34 | from rhodecode.tests.vcs.conftest import BackendTestMixin | |
|
35 | from rhodecode.lib.vcs_common import NodeKind, FILEMODE_EXECUTABLE, FILEMODE_DEFAULT, FILEMODE_LINK | |||
40 |
|
36 | |||
41 |
|
37 | |||
42 | class TestBaseChangeset(object): |
|
38 | class TestBaseChangeset(object): | |
@@ -70,7 +66,7 b' class TestCommitsInNonEmptyRepo(BackendT' | |||||
70 | } |
|
66 | } | |
71 |
|
67 | |||
72 | def test_walk_returns_empty_list_in_case_of_file(self): |
|
68 | def test_walk_returns_empty_list_in_case_of_file(self): | |
73 | result = list(self.tip.walk("file_0.txt")) |
|
69 | result = list(self.tip.walk(b"file_0.txt")) | |
74 | assert result == [] |
|
70 | assert result == [] | |
75 |
|
71 | |||
76 | @pytest.mark.backends("git", "hg") |
|
72 | @pytest.mark.backends("git", "hg") | |
@@ -319,7 +315,7 b' class TestCommits(BackendTestMixin):' | |||||
319 |
|
315 | |||
320 | def test_get_path_commit(self): |
|
316 | def test_get_path_commit(self): | |
321 | commit = self.repo.get_commit() |
|
317 | commit = self.repo.get_commit() | |
322 | commit.get_path_commit("file_4.txt") |
|
318 | commit.get_path_commit(b"file_4.txt") | |
323 | assert commit.message == "Commit 4" |
|
319 | assert commit.message == "Commit 4" | |
324 |
|
320 | |||
325 | def test_get_filenodes_generator(self): |
|
321 | def test_get_filenodes_generator(self): | |
@@ -500,8 +496,8 b' class TestCommits(BackendTestMixin):' | |||||
500 | @pytest.mark.parametrize( |
|
496 | @pytest.mark.parametrize( | |
501 | "filename, expected", |
|
497 | "filename, expected", | |
502 | [ |
|
498 | [ | |
503 | ("README.rst", False), |
|
499 | (b"README.rst", False), | |
504 | ("README", True), |
|
500 | (b"README", True), | |
505 | ], |
|
501 | ], | |
506 | ) |
|
502 | ) | |
507 | def test_commit_is_link(vcsbackend, filename, expected): |
|
503 | def test_commit_is_link(vcsbackend, filename, expected): | |
@@ -543,49 +539,41 b' class TestCommitsChanges(BackendTestMixi' | |||||
543 |
|
539 | |||
544 | def test_initial_commit(self, local_dt_to_utc): |
|
540 | def test_initial_commit(self, local_dt_to_utc): | |
545 | commit = self.repo.get_commit(commit_idx=0) |
|
541 | commit = self.repo.get_commit(commit_idx=0) | |
546 | assert set(commit.added) == { |
|
542 | assert sorted(commit.added_paths) == sorted([b"foo/bar", b"foo/ba\xc5\x82", b"foobar", b"qwe"]) | |
547 | commit.get_node("foo/bar"), |
|
543 | assert commit.changed_paths == [] | |
548 | commit.get_node("foo/baΕ"), |
|
544 | assert commit.removed_paths == [] | |
549 | commit.get_node("foobar"), |
|
545 | assert sorted(commit.affected_files) == sorted([b"foo/bar", b"foo/ba\xc5\x82", b"foobar", b"qwe"]) | |
550 | commit.get_node("qwe"), |
|
|||
551 | } |
|
|||
552 | assert set(commit.changed) == set() |
|
|||
553 | assert set(commit.removed) == set() |
|
|||
554 | assert set(commit.affected_files) == {"foo/bar", "foo/baΕ", "foobar", "qwe"} |
|
|||
555 | assert commit.date == local_dt_to_utc(datetime.datetime(2010, 1, 1, 20, 0)) |
|
546 | assert commit.date == local_dt_to_utc(datetime.datetime(2010, 1, 1, 20, 0)) | |
556 |
|
547 | |||
557 | def test_head_added(self): |
|
548 | def test_head_added(self): | |
558 | commit = self.repo.get_commit() |
|
549 | commit = self.repo.get_commit() | |
559 | assert isinstance(commit.added, AddedFileNodesGenerator) |
|
550 | ||
560 |
assert |
|
551 | assert commit.added_paths == [b"fallout"] | |
561 | assert isinstance(commit.changed, ChangedFileNodesGenerator) |
|
552 | assert commit.changed_paths == [b"foo/bar", b"foobar"] | |
562 | assert set(commit.changed) == {commit.get_node("foo/bar"), commit.get_node("foobar")} |
|
553 | assert commit.removed_paths == [b"qwe"] | |
563 | assert isinstance(commit.removed, RemovedFileNodesGenerator) |
|
|||
564 | assert len(commit.removed) == 1 |
|
|||
565 | assert list(commit.removed)[0].path == "qwe" |
|
|||
566 |
|
554 | |||
567 | def test_get_filemode(self): |
|
555 | def test_get_filemode(self): | |
568 | commit = self.repo.get_commit() |
|
556 | commit = self.repo.get_commit() | |
569 | assert FILEMODE_DEFAULT == commit.get_file_mode("foo/bar") |
|
557 | assert FILEMODE_DEFAULT == commit.get_file_mode(b"foo/bar") | |
570 |
|
558 | |||
571 | def test_get_filemode_non_ascii(self): |
|
559 | def test_get_filemode_non_ascii(self): | |
572 | commit = self.repo.get_commit() |
|
560 | commit = self.repo.get_commit() | |
573 |
assert FILEMODE_DEFAULT == commit.get_file_mode("foo/ba |
|
561 | assert FILEMODE_DEFAULT == commit.get_file_mode(b"foo/ba\xc5\x82") | |
574 |
assert FILEMODE_DEFAULT == commit.get_file_mode("foo/ba |
|
562 | assert FILEMODE_DEFAULT == commit.get_file_mode(b"foo/ba\xc5\x82") | |
575 |
|
563 | |||
576 | def test_get_path_history(self): |
|
564 | def test_get_path_history(self): | |
577 | commit = self.repo.get_commit() |
|
565 | commit = self.repo.get_commit() | |
578 | history = commit.get_path_history("foo/bar") |
|
566 | history = commit.get_path_history(b"foo/bar") | |
579 | assert len(history) == 2 |
|
567 | assert len(history) == 2 | |
580 |
|
568 | |||
581 | def test_get_path_history_with_limit(self): |
|
569 | def test_get_path_history_with_limit(self): | |
582 | commit = self.repo.get_commit() |
|
570 | commit = self.repo.get_commit() | |
583 | history = commit.get_path_history("foo/bar", limit=1) |
|
571 | history = commit.get_path_history(b"foo/bar", limit=1) | |
584 | assert len(history) == 1 |
|
572 | assert len(history) == 1 | |
585 |
|
573 | |||
586 | def test_get_path_history_first_commit(self): |
|
574 | def test_get_path_history_first_commit(self): | |
587 | commit = self.repo[0] |
|
575 | commit = self.repo[0] | |
588 | history = commit.get_path_history("foo/bar") |
|
576 | history = commit.get_path_history(b"foo/bar") | |
589 | assert len(history) == 1 |
|
577 | assert len(history) == 1 | |
590 |
|
578 | |||
591 |
|
579 |
@@ -28,7 +28,7 b' from rhodecode.lib.utils import make_db_' | |||||
28 | from rhodecode.lib.vcs.backends.base import Reference |
|
28 | from rhodecode.lib.vcs.backends.base import Reference | |
29 | from rhodecode.lib.vcs.backends.git import GitRepository, GitCommit, discover_git_version |
|
29 | from rhodecode.lib.vcs.backends.git import GitRepository, GitCommit, discover_git_version | |
30 | from rhodecode.lib.vcs.exceptions import RepositoryError, VCSError, NodeDoesNotExistError |
|
30 | from rhodecode.lib.vcs.exceptions import RepositoryError, VCSError, NodeDoesNotExistError | |
31 | from rhodecode.lib.vcs.nodes import NodeKind, FileNode, DirNode, NodeState, SubModuleNode |
|
31 | from rhodecode.lib.vcs.nodes import NodeKind, FileNode, DirNode, NodeState, SubModuleNode, RootNode | |
32 | from rhodecode.tests import TEST_GIT_REPO, TEST_GIT_REPO_CLONE, get_new_dir |
|
32 | from rhodecode.tests import TEST_GIT_REPO, TEST_GIT_REPO_CLONE, get_new_dir | |
33 | from rhodecode.tests.vcs.conftest import BackendTestMixin |
|
33 | from rhodecode.tests.vcs.conftest import BackendTestMixin | |
34 |
|
34 | |||
@@ -219,23 +219,38 b' class TestGitRepository(object):' | |||||
219 | assert init_commit.message == "initial import\n" |
|
219 | assert init_commit.message == "initial import\n" | |
220 | assert init_author == "Marcin Kuzminski <marcin@python-blog.com>" |
|
220 | assert init_author == "Marcin Kuzminski <marcin@python-blog.com>" | |
221 | assert init_author == init_commit.committer |
|
221 | assert init_author == init_commit.committer | |
222 | for path in ("vcs/__init__.py", "vcs/backends/BaseRepository.py", "vcs/backends/__init__.py"): |
|
222 | assert sorted(init_commit.added_paths) == sorted( | |
|
223 | [ | |||
|
224 | b"vcs/__init__.py", | |||
|
225 | b"vcs/backends/BaseRepository.py", | |||
|
226 | b"vcs/backends/__init__.py", | |||
|
227 | ] | |||
|
228 | ) | |||
|
229 | assert sorted(init_commit.affected_files) == sorted( | |||
|
230 | [ | |||
|
231 | b"vcs/__init__.py", | |||
|
232 | b"vcs/backends/BaseRepository.py", | |||
|
233 | b"vcs/backends/__init__.py", | |||
|
234 | ] | |||
|
235 | ) | |||
|
236 | ||||
|
237 | for path in (b"vcs/__init__.py", b"vcs/backends/BaseRepository.py", b"vcs/backends/__init__.py"): | |||
223 | assert isinstance(init_commit.get_node(path), FileNode) |
|
238 | assert isinstance(init_commit.get_node(path), FileNode) | |
224 | for path in ("", "vcs", "vcs/backends"): |
|
239 | for path in (b"", b"vcs", b"vcs/backends"): | |
225 | assert isinstance(init_commit.get_node(path), DirNode) |
|
240 | assert isinstance(init_commit.get_node(path), DirNode) | |
226 |
|
241 | |||
227 | with pytest.raises(NodeDoesNotExistError): |
|
242 | with pytest.raises(NodeDoesNotExistError): | |
228 | init_commit.get_node(path="foobar") |
|
243 | init_commit.get_node(path=b"foobar") | |
229 |
|
244 | |||
230 | node = init_commit.get_node("vcs/") |
|
245 | node = init_commit.get_node(b"vcs/") | |
231 | assert hasattr(node, "kind") |
|
246 | assert hasattr(node, "kind") | |
232 | assert node.kind == NodeKind.DIR |
|
247 | assert node.kind == NodeKind.DIR | |
233 |
|
248 | |||
234 | node = init_commit.get_node("vcs") |
|
249 | node = init_commit.get_node(b"vcs") | |
235 | assert hasattr(node, "kind") |
|
250 | assert hasattr(node, "kind") | |
236 | assert node.kind == NodeKind.DIR |
|
251 | assert node.kind == NodeKind.DIR | |
237 |
|
252 | |||
238 | node = init_commit.get_node("vcs/__init__.py") |
|
253 | node = init_commit.get_node(b"vcs/__init__.py") | |
239 | assert hasattr(node, "kind") |
|
254 | assert hasattr(node, "kind") | |
240 | assert node.kind == NodeKind.FILE |
|
255 | assert node.kind == NodeKind.FILE | |
241 |
|
256 | |||
@@ -257,7 +272,7 b' Introduction' | |||||
257 | TODO: To be written... |
|
272 | TODO: To be written... | |
258 |
|
273 | |||
259 | """ |
|
274 | """ | |
260 | node = commit10.get_node("README.rst") |
|
275 | node = commit10.get_node(b"README.rst") | |
261 | assert node.kind == NodeKind.FILE |
|
276 | assert node.kind == NodeKind.FILE | |
262 | assert node.str_content == README |
|
277 | assert node.str_content == README | |
263 |
|
278 | |||
@@ -615,7 +630,7 b' class TestGitCommit(object):' | |||||
615 |
|
630 | |||
616 | def test_root_node(self): |
|
631 | def test_root_node(self): | |
617 | tip = self.repo.get_commit() |
|
632 | tip = self.repo.get_commit() | |
618 | assert tip.root is tip.get_node("") |
|
633 | assert tip.root is tip.get_node(b"") | |
619 |
|
634 | |||
620 | def test_lazy_fetch(self): |
|
635 | def test_lazy_fetch(self): | |
621 | """ |
|
636 | """ | |
@@ -633,29 +648,29 b' class TestGitCommit(object):' | |||||
633 | # accessing root.nodes updates commit.nodes |
|
648 | # accessing root.nodes updates commit.nodes | |
634 | assert len(commit.nodes) == 9 |
|
649 | assert len(commit.nodes) == 9 | |
635 |
|
650 | |||
636 |
docs = |
|
651 | docs = commit.get_node(b"docs") | |
637 | # we haven't yet accessed anything new as docs dir was already cached |
|
652 | # we haven't yet accessed anything new as docs dir was already cached | |
638 | assert len(commit.nodes) == 9 |
|
653 | assert len(commit.nodes) == 9 | |
639 | assert len(docs.nodes) == 8 |
|
654 | assert len(docs.nodes) == 8 | |
640 | # accessing docs.nodes updates commit.nodes |
|
655 | # accessing docs.nodes updates commit.nodes | |
641 | assert len(commit.nodes) == 17 |
|
656 | assert len(commit.nodes) == 17 | |
642 |
|
657 | |||
643 | assert docs is commit.get_node("docs") |
|
658 | assert docs is commit.get_node(b"docs") | |
644 | assert docs is root.nodes[0] |
|
659 | assert docs is root.nodes[0] | |
645 | assert docs is root.dirs[0] |
|
660 | assert docs is root.dirs[0] | |
646 | assert docs is commit.get_node("docs") |
|
661 | assert docs is commit.get_node(b"docs") | |
647 |
|
662 | |||
648 | def test_nodes_with_commit(self): |
|
663 | def test_nodes_with_commit(self): | |
649 | commit_id = "2a13f185e4525f9d4b59882791a2d397b90d5ddc" |
|
664 | commit_id = "2a13f185e4525f9d4b59882791a2d397b90d5ddc" | |
650 | commit = self.repo.get_commit(commit_id) |
|
665 | commit = self.repo.get_commit(commit_id) | |
651 | root = commit.root |
|
666 | root = commit.root | |
652 | docs = root.get_node("docs") |
|
667 | assert isinstance(root, RootNode) | |
653 |
|
|
668 | docs = commit.get_node(b"docs") | |
654 |
a |
|
669 | assert docs is commit.get_node(b"docs") | |
655 |
|
|
670 | api = commit.get_node(b"docs/api") | |
656 | index = api.get_node("index.rst") |
|
671 | assert api is commit.get_node(b"docs/api") | |
657 |
|
|
672 | index = commit.get_node(b"docs/api/index.rst") | |
658 |
assert index is commit.get_node("docs |
|
673 | assert index is commit.get_node(b"docs/api/index.rst") | |
659 |
|
674 | |||
660 | def test_branch_and_tags(self): |
|
675 | def test_branch_and_tags(self): | |
661 | """ |
|
676 | """ | |
@@ -682,12 +697,12 b' class TestGitCommit(object):' | |||||
682 |
|
697 | |||
683 | def test_file_size(self): |
|
698 | def test_file_size(self): | |
684 | to_check = ( |
|
699 | to_check = ( | |
685 | ("c1214f7e79e02fc37156ff215cd71275450cffc3", "vcs/backends/BaseRepository.py", 502), |
|
700 | ("c1214f7e79e02fc37156ff215cd71275450cffc3", b"vcs/backends/BaseRepository.py", 502), | |
686 | ("d7e0d30fbcae12c90680eb095a4f5f02505ce501", "vcs/backends/hg.py", 854), |
|
701 | ("d7e0d30fbcae12c90680eb095a4f5f02505ce501", b"vcs/backends/hg.py", 854), | |
687 | ("6e125e7c890379446e98980d8ed60fba87d0f6d1", "setup.py", 1068), |
|
702 | ("6e125e7c890379446e98980d8ed60fba87d0f6d1", b"setup.py", 1068), | |
688 | ("d955cd312c17b02143c04fa1099a352b04368118", "vcs/backends/base.py", 2921), |
|
703 | ("d955cd312c17b02143c04fa1099a352b04368118", b"vcs/backends/base.py", 2921), | |
689 | ("ca1eb7957a54bce53b12d1a51b13452f95bc7c7e", "vcs/backends/base.py", 3936), |
|
704 | ("ca1eb7957a54bce53b12d1a51b13452f95bc7c7e", b"vcs/backends/base.py", 3936), | |
690 | ("f50f42baeed5af6518ef4b0cb2f1423f3851a941", "vcs/backends/base.py", 6189), |
|
705 | ("f50f42baeed5af6518ef4b0cb2f1423f3851a941", b"vcs/backends/base.py", 6189), | |
691 | ) |
|
706 | ) | |
692 | for commit_id, path, size in to_check: |
|
707 | for commit_id, path, size in to_check: | |
693 | node = self.repo.get_commit(commit_id).get_node(path) |
|
708 | node = self.repo.get_commit(commit_id).get_node(path) | |
@@ -695,17 +710,17 b' class TestGitCommit(object):' | |||||
695 | assert node.size == size |
|
710 | assert node.size == size | |
696 |
|
711 | |||
697 | def test_file_history_from_commits(self): |
|
712 | def test_file_history_from_commits(self): | |
698 | node = self.repo[10].get_node("setup.py") |
|
713 | node = self.repo[10].get_node(b"setup.py") | |
699 | commit_ids = [commit.raw_id for commit in node.history] |
|
714 | commit_ids = [commit.raw_id for commit in node.history] | |
700 | assert ["ff7ca51e58c505fec0dd2491de52c622bb7a806b"] == commit_ids |
|
715 | assert ["ff7ca51e58c505fec0dd2491de52c622bb7a806b"] == commit_ids | |
701 |
|
716 | |||
702 | node = self.repo[20].get_node("setup.py") |
|
717 | node = self.repo[20].get_node(b"setup.py") | |
703 | node_ids = [commit.raw_id for commit in node.history] |
|
718 | node_ids = [commit.raw_id for commit in node.history] | |
704 | assert ["191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e", "ff7ca51e58c505fec0dd2491de52c622bb7a806b"] == node_ids |
|
719 | assert ["191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e", "ff7ca51e58c505fec0dd2491de52c622bb7a806b"] == node_ids | |
705 |
|
720 | |||
706 | # special case we check history from commit that has this particular |
|
721 | # special case we check history from commit that has this particular | |
707 | # file changed this means we check if it's included as well |
|
722 | # file changed this means we check if it's included as well | |
708 | node = self.repo.get_commit("191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e").get_node("setup.py") |
|
723 | node = self.repo.get_commit("191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e").get_node(b"setup.py") | |
709 | node_ids = [commit.raw_id for commit in node.history] |
|
724 | node_ids = [commit.raw_id for commit in node.history] | |
710 | assert ["191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e", "ff7ca51e58c505fec0dd2491de52c622bb7a806b"] == node_ids |
|
725 | assert ["191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e", "ff7ca51e58c505fec0dd2491de52c622bb7a806b"] == node_ids | |
711 |
|
726 | |||
@@ -713,7 +728,7 b' class TestGitCommit(object):' | |||||
713 | # we can only check if those commits are present in the history |
|
728 | # we can only check if those commits are present in the history | |
714 | # as we cannot update this test every time file is changed |
|
729 | # as we cannot update this test every time file is changed | |
715 | files = { |
|
730 | files = { | |
716 | "setup.py": [ |
|
731 | b"setup.py": [ | |
717 | "54386793436c938cff89326944d4c2702340037d", |
|
732 | "54386793436c938cff89326944d4c2702340037d", | |
718 | "51d254f0ecf5df2ce50c0b115741f4cf13985dab", |
|
733 | "51d254f0ecf5df2ce50c0b115741f4cf13985dab", | |
719 | "998ed409c795fec2012b1c0ca054d99888b22090", |
|
734 | "998ed409c795fec2012b1c0ca054d99888b22090", | |
@@ -724,7 +739,7 b' class TestGitCommit(object):' | |||||
724 | "191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e", |
|
739 | "191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e", | |
725 | "ff7ca51e58c505fec0dd2491de52c622bb7a806b", |
|
740 | "ff7ca51e58c505fec0dd2491de52c622bb7a806b", | |
726 | ], |
|
741 | ], | |
727 | "vcs/nodes.py": [ |
|
742 | b"vcs/nodes.py": [ | |
728 | "33fa3223355104431402a888fa77a4e9956feb3e", |
|
743 | "33fa3223355104431402a888fa77a4e9956feb3e", | |
729 | "fa014c12c26d10ba682fadb78f2a11c24c8118e1", |
|
744 | "fa014c12c26d10ba682fadb78f2a11c24c8118e1", | |
730 | "e686b958768ee96af8029fe19c6050b1a8dd3b2b", |
|
745 | "e686b958768ee96af8029fe19c6050b1a8dd3b2b", | |
@@ -757,7 +772,7 b' class TestGitCommit(object):' | |||||
757 | "dd80b0f6cf5052f17cc738c2951c4f2070200d7f", |
|
772 | "dd80b0f6cf5052f17cc738c2951c4f2070200d7f", | |
758 | "ff7ca51e58c505fec0dd2491de52c622bb7a806b", |
|
773 | "ff7ca51e58c505fec0dd2491de52c622bb7a806b", | |
759 | ], |
|
774 | ], | |
760 | "vcs/backends/git.py": [ |
|
775 | b"vcs/backends/git.py": [ | |
761 | "4cf116ad5a457530381135e2f4c453e68a1b0105", |
|
776 | "4cf116ad5a457530381135e2f4c453e68a1b0105", | |
762 | "9a751d84d8e9408e736329767387f41b36935153", |
|
777 | "9a751d84d8e9408e736329767387f41b36935153", | |
763 | "cb681fb539c3faaedbcdf5ca71ca413425c18f01", |
|
778 | "cb681fb539c3faaedbcdf5ca71ca413425c18f01", | |
@@ -778,7 +793,7 b' class TestGitCommit(object):' | |||||
778 |
|
793 | |||
779 | def test_file_annotate(self): |
|
794 | def test_file_annotate(self): | |
780 | files = { |
|
795 | files = { | |
781 | "vcs/backends/__init__.py": { |
|
796 | b"vcs/backends/__init__.py": { | |
782 | "c1214f7e79e02fc37156ff215cd71275450cffc3": { |
|
797 | "c1214f7e79e02fc37156ff215cd71275450cffc3": { | |
783 | "lines_no": 1, |
|
798 | "lines_no": 1, | |
784 | "commits": [ |
|
799 | "commits": [ | |
@@ -870,39 +885,31 b' class TestGitCommit(object):' | |||||
870 | """ |
|
885 | """ | |
871 | Tests state of FileNodes. |
|
886 | Tests state of FileNodes. | |
872 | """ |
|
887 | """ | |
873 |
|
|
888 | commit = self.repo.get_commit("e6ea6d16e2f26250124a1f4b4fe37a912f9d86a0") | |
874 | assert node.state, NodeState.ADDED |
|
889 | node = commit.get_node(b"vcs/utils/diffs.py") | |
875 | assert node.added |
|
890 | assert node.bytes_path in commit.added_paths | |
876 | assert not node.changed |
|
|||
877 | assert not node.not_changed |
|
|||
878 | assert not node.removed |
|
|||
879 |
|
891 | |||
880 |
|
|
892 | commit = self.repo.get_commit("33fa3223355104431402a888fa77a4e9956feb3e") | |
881 | assert node.state, NodeState.CHANGED |
|
893 | node = commit.get_node(b".hgignore") | |
882 | assert not node.added |
|
894 | assert node.bytes_path in commit.changed_paths | |
883 | assert node.changed |
|
|||
884 | assert not node.not_changed |
|
|||
885 | assert not node.removed |
|
|||
886 |
|
895 | |||
887 |
|
|
896 | commit = self.repo.get_commit("e29b67bd158580fc90fc5e9111240b90e6e86064") | |
888 | assert node.state, NodeState.NOT_CHANGED |
|
897 | node = commit.get_node(b"setup.py") | |
889 | assert not node.added |
|
898 | assert node.bytes_path not in commit.affected_files | |
890 | assert not node.changed |
|
|||
891 | assert node.not_changed |
|
|||
892 | assert not node.removed |
|
|||
893 |
|
899 | |||
894 | # If node has REMOVED state then trying to fetch it would raise |
|
900 | # If node has REMOVED state then trying to fetch it would raise | |
895 | # CommitError exception |
|
901 | # CommitError exception | |
896 | commit = self.repo.get_commit("fa6600f6848800641328adbf7811fd2372c02ab2") |
|
902 | commit = self.repo.get_commit("fa6600f6848800641328adbf7811fd2372c02ab2") | |
897 | path = "vcs/backends/BaseRepository.py" |
|
903 | path = b"vcs/backends/BaseRepository.py" | |
898 | with pytest.raises(NodeDoesNotExistError): |
|
904 | with pytest.raises(NodeDoesNotExistError): | |
899 | commit.get_node(path) |
|
905 | commit.get_node(path) | |
|
906 | ||||
900 | # but it would be one of ``removed`` (commit's attribute) |
|
907 | # but it would be one of ``removed`` (commit's attribute) | |
901 |
assert path in [rf |
|
908 | assert path in [rf for rf in commit.removed_paths] | |
902 |
|
909 | |||
903 | commit = self.repo.get_commit("54386793436c938cff89326944d4c2702340037d") |
|
910 | commit = self.repo.get_commit("54386793436c938cff89326944d4c2702340037d") | |
904 | changed = ["setup.py", "tests/test_nodes.py", "vcs/backends/hg.py", "vcs/nodes.py"] |
|
911 | changed = [b"setup.py", b"tests/test_nodes.py", b"vcs/backends/hg.py", b"vcs/nodes.py"] | |
905 |
assert set(changed) == set([f |
|
912 | assert set(changed) == set([f for f in commit.changed_paths]) | |
906 |
|
913 | |||
907 | def test_unicode_branch_refs(self): |
|
914 | def test_unicode_branch_refs(self): | |
908 | unicode_branches = { |
|
915 | unicode_branches = { | |
@@ -936,14 +943,14 b' class TestGitCommit(object):' | |||||
936 |
|
943 | |||
937 | def test_repo_files_content_types(self): |
|
944 | def test_repo_files_content_types(self): | |
938 | commit = self.repo.get_commit() |
|
945 | commit = self.repo.get_commit() | |
939 | for node in commit.get_node("/"): |
|
946 | for node in commit.get_node(b"/"): | |
940 | if node.is_file(): |
|
947 | if node.is_file(): | |
941 | assert type(node.content) == bytes |
|
948 | assert type(node.content) == bytes | |
942 | assert type(node.str_content) == str |
|
949 | assert type(node.str_content) == str | |
943 |
|
950 | |||
944 | def test_wrong_path(self): |
|
951 | def test_wrong_path(self): | |
945 | # There is 'setup.py' in the root dir but not there: |
|
952 | # There is 'setup.py' in the root dir but not there: | |
946 | path = "foo/bar/setup.py" |
|
953 | path = b"foo/bar/setup.py" | |
947 | tip = self.repo.get_commit() |
|
954 | tip = self.repo.get_commit() | |
948 | with pytest.raises(VCSError): |
|
955 | with pytest.raises(VCSError): | |
949 | tip.get_node(path) |
|
956 | tip.get_node(path) | |
@@ -981,8 +988,7 b' class TestLargeFileRepo(object):' | |||||
981 | repo = backend_git.create_test_repo("largefiles", conf) |
|
988 | repo = backend_git.create_test_repo("largefiles", conf) | |
982 |
|
989 | |||
983 | tip = repo.scm_instance().get_commit() |
|
990 | tip = repo.scm_instance().get_commit() | |
984 | node = tip.get_node("1MB.zip") |
|
991 | node = tip.get_node(b"1MB.zip") | |
985 |
|
||||
986 |
|
992 | |||
987 | # extract stored LF node into the origin cache |
|
993 | # extract stored LF node into the origin cache | |
988 | repo_lfs_store: str = os.path.join(repo.repo_path, repo.repo_name, "lfs_store") |
|
994 | repo_lfs_store: str = os.path.join(repo.repo_path, repo.repo_name, "lfs_store") | |
@@ -1002,7 +1008,7 b' class TestLargeFileRepo(object):' | |||||
1002 |
|
1008 | |||
1003 | assert lf_node.is_largefile() is True |
|
1009 | assert lf_node.is_largefile() is True | |
1004 | assert lf_node.size == 1024000 |
|
1010 | assert lf_node.size == 1024000 | |
1005 | assert lf_node.name == "1MB.zip" |
|
1011 | assert lf_node.name == b"1MB.zip" | |
1006 |
|
1012 | |||
1007 |
|
1013 | |||
1008 | @pytest.mark.usefixtures("vcs_repository_support") |
|
1014 | @pytest.mark.usefixtures("vcs_repository_support") | |
@@ -1032,14 +1038,11 b' class TestGitSpecificWithRepo(BackendTes' | |||||
1032 |
|
1038 | |||
1033 | def test_paths_slow_traversing(self): |
|
1039 | def test_paths_slow_traversing(self): | |
1034 | commit = self.repo.get_commit() |
|
1040 | commit = self.repo.get_commit() | |
1035 | assert ( |
|
1041 | assert commit.get_node(b"foobar/static/js/admin/base.js").content == b"base" | |
1036 | commit.get_node("foobar").get_node("static").get_node("js").get_node("admin").get_node("base.js").content |
|
|||
1037 | == b"base" |
|
|||
1038 | ) |
|
|||
1039 |
|
1042 | |||
1040 | def test_paths_fast_traversing(self): |
|
1043 | def test_paths_fast_traversing(self): | |
1041 | commit = self.repo.get_commit() |
|
1044 | commit = self.repo.get_commit() | |
1042 | assert commit.get_node("foobar/static/js/admin/base.js").content == b"base" |
|
1045 | assert commit.get_node(b"foobar/static/js/admin/base.js").content == b"base" | |
1043 |
|
1046 | |||
1044 | def test_get_diff_runs_git_command_with_hashes(self): |
|
1047 | def test_get_diff_runs_git_command_with_hashes(self): | |
1045 | comm1 = self.repo[0] |
|
1048 | comm1 = self.repo[0] | |
@@ -1110,12 +1113,15 b' class TestGitRegression(BackendTestMixin' | |||||
1110 | @pytest.mark.parametrize( |
|
1113 | @pytest.mark.parametrize( | |
1111 | "path, expected_paths", |
|
1114 | "path, expected_paths", | |
1112 | [ |
|
1115 | [ | |
1113 | ("bot", ["bot/build", "bot/templates", "bot/__init__.py"]), |
|
1116 | (b"bot", ["bot/build", "bot/templates", "bot/__init__.py"]), | |
1114 | ("bot/build", ["bot/build/migrations", "bot/build/static", "bot/build/templates"]), |
|
1117 | (b"bot/build", ["bot/build/migrations", "bot/build/static", "bot/build/templates"]), | |
1115 | ("bot/build/static", ["bot/build/static/templates"]), |
|
1118 | (b"bot/build/static", ["bot/build/static/templates"]), | |
1116 | ("bot/build/static/templates", ["bot/build/static/templates/f.html", "bot/build/static/templates/f1.html"]), |
|
1119 | ( | |
1117 | ("bot/build/templates", ["bot/build/templates/err.html", "bot/build/templates/err2.html"]), |
|
1120 | b"bot/build/static/templates", | |
1118 |
|
|
1121 | ["bot/build/static/templates/f.html", "bot/build/static/templates/f1.html"], | |
|
1122 | ), | |||
|
1123 | (b"bot/build/templates", ["bot/build/templates/err.html", "bot/build/templates/err2.html"]), | |||
|
1124 | (b"bot/templates/", ["bot/templates/404.html", "bot/templates/500.html"]), | |||
1119 | ], |
|
1125 | ], | |
1120 | ) |
|
1126 | ) | |
1121 | def test_similar_paths(self, path, expected_paths): |
|
1127 | def test_similar_paths(self, path, expected_paths): | |
@@ -1146,8 +1152,8 b' class TestGetSubmoduleUrl(object):' | |||||
1146 | node.str_content = ( |
|
1152 | node.str_content = ( | |
1147 | '[submodule "subrepo1"]\n' "\tpath = subrepo1\n" "\turl = https://code.rhodecode.com/dulwich\n" |
|
1153 | '[submodule "subrepo1"]\n' "\tpath = subrepo1\n" "\turl = https://code.rhodecode.com/dulwich\n" | |
1148 | ) |
|
1154 | ) | |
1149 | result = commit._get_submodule_url("subrepo1") |
|
1155 | result = commit._get_submodule_url(b"subrepo1") | |
1150 | get_node_mock.assert_called_once_with(".gitmodules") |
|
1156 | get_node_mock.assert_called_once_with(b".gitmodules") | |
1151 | assert result == "https://code.rhodecode.com/dulwich" |
|
1157 | assert result == "https://code.rhodecode.com/dulwich" | |
1152 |
|
1158 | |||
1153 | def test_complex_submodule_path(self): |
|
1159 | def test_complex_submodule_path(self): | |
@@ -1160,14 +1166,14 b' class TestGetSubmoduleUrl(object):' | |||||
1160 | "\tpath = complex/subrepo/path\n" |
|
1166 | "\tpath = complex/subrepo/path\n" | |
1161 | "\turl = https://code.rhodecode.com/dulwich\n" |
|
1167 | "\turl = https://code.rhodecode.com/dulwich\n" | |
1162 | ) |
|
1168 | ) | |
1163 | result = commit._get_submodule_url("complex/subrepo/path") |
|
1169 | result = commit._get_submodule_url(b"complex/subrepo/path") | |
1164 | get_node_mock.assert_called_once_with(".gitmodules") |
|
1170 | get_node_mock.assert_called_once_with(b".gitmodules") | |
1165 | assert result == "https://code.rhodecode.com/dulwich" |
|
1171 | assert result == "https://code.rhodecode.com/dulwich" | |
1166 |
|
1172 | |||
1167 | def test_submodules_file_not_found(self): |
|
1173 | def test_submodules_file_not_found(self): | |
1168 | commit = GitCommit(repository=mock.Mock(), raw_id="abcdef12", idx=1) |
|
1174 | commit = GitCommit(repository=mock.Mock(), raw_id="abcdef12", idx=1) | |
1169 | with mock.patch.object(commit, "get_node", side_effect=NodeDoesNotExistError): |
|
1175 | with mock.patch.object(commit, "get_node", side_effect=NodeDoesNotExistError): | |
1170 | result = commit._get_submodule_url("complex/subrepo/path") |
|
1176 | result = commit._get_submodule_url(b"complex/subrepo/path") | |
1171 | assert result is None |
|
1177 | assert result is None | |
1172 |
|
1178 | |||
1173 | def test_path_not_found(self): |
|
1179 | def test_path_not_found(self): | |
@@ -1178,8 +1184,8 b' class TestGetSubmoduleUrl(object):' | |||||
1178 | node.str_content = ( |
|
1184 | node.str_content = ( | |
1179 | '[submodule "subrepo1"]\n' "\tpath = subrepo1\n" "\turl = https://code.rhodecode.com/dulwich\n" |
|
1185 | '[submodule "subrepo1"]\n' "\tpath = subrepo1\n" "\turl = https://code.rhodecode.com/dulwich\n" | |
1180 | ) |
|
1186 | ) | |
1181 | result = commit._get_submodule_url("subrepo2") |
|
1187 | result = commit._get_submodule_url(b"subrepo2") | |
1182 | get_node_mock.assert_called_once_with(".gitmodules") |
|
1188 | get_node_mock.assert_called_once_with(b".gitmodules") | |
1183 | assert result is None |
|
1189 | assert result is None | |
1184 |
|
1190 | |||
1185 | def test_returns_cached_values(self): |
|
1191 | def test_returns_cached_values(self): | |
@@ -1191,44 +1197,43 b' class TestGetSubmoduleUrl(object):' | |||||
1191 | '[submodule "subrepo1"]\n' "\tpath = subrepo1\n" "\turl = https://code.rhodecode.com/dulwich\n" |
|
1197 | '[submodule "subrepo1"]\n' "\tpath = subrepo1\n" "\turl = https://code.rhodecode.com/dulwich\n" | |
1192 | ) |
|
1198 | ) | |
1193 | for _ in range(3): |
|
1199 | for _ in range(3): | |
1194 | commit._get_submodule_url("subrepo1") |
|
1200 | commit._get_submodule_url(b"subrepo1") | |
1195 | get_node_mock.assert_called_once_with(".gitmodules") |
|
1201 | get_node_mock.assert_called_once_with(b".gitmodules") | |
1196 |
|
1202 | |||
1197 | def test_get_node_returns_a_link(self): |
|
1203 | def test_get_node_returns_a_link(self): | |
1198 | repository = mock.Mock() |
|
1204 | repository = mock.Mock() | |
1199 | repository.alias = "git" |
|
1205 | repository.alias = "git" | |
1200 | commit = GitCommit(repository=repository, raw_id="abcdef12", idx=1) |
|
1206 | commit = GitCommit(repository=repository, raw_id="abcdef12", idx=1) | |
1201 | submodule_url = "https://code.rhodecode.com/dulwich" |
|
1207 | submodule_url = "https://code.rhodecode.com/dulwich" | |
1202 |
get_id_patch = mock.patch.object(commit, "_get_tree_id_ |
|
1208 | get_id_patch = mock.patch.object(commit, "_get_path_tree_id_and_type", return_value=(1, NodeKind.SUBMODULE)) | |
1203 | get_submodule_patch = mock.patch.object(commit, "_get_submodule_url", return_value=submodule_url) |
|
1209 | get_submodule_patch = mock.patch.object(commit, "_get_submodule_url", return_value=submodule_url) | |
1204 |
|
1210 | |||
1205 | with get_id_patch, get_submodule_patch as submodule_mock: |
|
1211 | with get_id_patch, get_submodule_patch as submodule_mock: | |
1206 | node = commit.get_node("/abcde") |
|
1212 | node = commit.get_node(b"/abcde") | |
1207 |
|
1213 | |||
1208 | submodule_mock.assert_called_once_with("/abcde") |
|
1214 | submodule_mock.assert_called_once_with(b"/abcde") | |
1209 | assert type(node) == SubModuleNode |
|
1215 | assert type(node) == SubModuleNode | |
1210 | assert node.url == submodule_url |
|
1216 | assert node.url == submodule_url | |
1211 |
|
1217 | |||
1212 | def test_get_nodes_returns_links(self): |
|
1218 | def test_get_nodes_returns_links(self): | |
1213 | repository = mock.MagicMock() |
|
1219 | repository = mock.MagicMock() | |
1214 | repository.alias = "git" |
|
1220 | repository.alias = "git" | |
1215 |
repository._remote.tree_items.return_value = [("subrepo", "stat", 1, |
|
1221 | repository._remote.tree_items.return_value = [(b"subrepo", "stat", 1, NodeKind.SUBMODULE)] | |
1216 | commit = GitCommit(repository=repository, raw_id="abcdef12", idx=1) |
|
1222 | commit = GitCommit(repository=repository, raw_id="abcdef12", idx=1) | |
1217 | submodule_url = "https://code.rhodecode.com/dulwich" |
|
1223 | submodule_url = "https://code.rhodecode.com/dulwich" | |
1218 |
get_id_patch = mock.patch.object(commit, "_get_tree_id_ |
|
1224 | get_id_patch = mock.patch.object(commit, "_get_path_tree_id_and_type", return_value=(1, NodeKind.DIR)) | |
1219 | get_submodule_patch = mock.patch.object(commit, "_get_submodule_url", return_value=submodule_url) |
|
1225 | get_submodule_patch = mock.patch.object(commit, "_get_submodule_url", return_value=submodule_url) | |
1220 |
|
1226 | |||
1221 | with get_id_patch, get_submodule_patch as submodule_mock: |
|
1227 | with get_id_patch, get_submodule_patch as submodule_mock: | |
1222 | nodes = commit.get_nodes("/abcde") |
|
1228 | nodes = commit.get_nodes(b"/abcde") | |
1223 |
|
1229 | |||
1224 | submodule_mock.assert_called_once_with("/abcde/subrepo") |
|
1230 | submodule_mock.assert_called_once_with(b"/abcde/subrepo") | |
1225 | assert len(nodes) == 1 |
|
1231 | assert len(nodes) == 1 | |
1226 | assert type(nodes[0]) == SubModuleNode |
|
1232 | assert type(nodes[0]) == SubModuleNode | |
1227 | assert nodes[0].url == submodule_url |
|
1233 | assert nodes[0].url == submodule_url | |
1228 |
|
1234 | |||
1229 |
|
1235 | |||
1230 | class TestGetShadowInstance(object): |
|
1236 | class TestGetShadowInstance(object): | |
1231 |
|
||||
1232 | @pytest.fixture() |
|
1237 | @pytest.fixture() | |
1233 | def repo(self, vcsbackend_git): |
|
1238 | def repo(self, vcsbackend_git): | |
1234 | _git_repo = vcsbackend_git.repo |
|
1239 | _git_repo = vcsbackend_git.repo |
@@ -27,7 +27,7 b' from rhodecode.lib.vcs import backends' | |||||
27 | from rhodecode.lib.vcs.backends.base import Reference, MergeResponse, MergeFailureReason |
|
27 | from rhodecode.lib.vcs.backends.base import Reference, MergeResponse, MergeFailureReason | |
28 | from rhodecode.lib.vcs.backends.hg import MercurialRepository, MercurialCommit |
|
28 | from rhodecode.lib.vcs.backends.hg import MercurialRepository, MercurialCommit | |
29 | from rhodecode.lib.vcs.exceptions import RepositoryError, VCSError, NodeDoesNotExistError, CommitDoesNotExistError |
|
29 | from rhodecode.lib.vcs.exceptions import RepositoryError, VCSError, NodeDoesNotExistError, CommitDoesNotExistError | |
30 |
from rhodecode.lib.vcs.nodes import FileNode, NodeKind, |
|
30 | from rhodecode.lib.vcs.nodes import FileNode, NodeKind, DirNode, RootNode | |
31 | from rhodecode.tests import TEST_HG_REPO, TEST_HG_REPO_CLONE, repo_id_generator |
|
31 | from rhodecode.tests import TEST_HG_REPO, TEST_HG_REPO_CLONE, repo_id_generator | |
32 |
|
32 | |||
33 |
|
33 | |||
@@ -41,22 +41,17 b' def repo_path_generator():' | |||||
41 | i = 0 |
|
41 | i = 0 | |
42 | while True: |
|
42 | while True: | |
43 | i += 1 |
|
43 | i += 1 | |
44 |
yield " |
|
44 | yield f"{TEST_HG_REPO_CLONE}-{i:d}" | |
45 |
|
45 | |||
46 |
|
46 | |||
47 | REPO_PATH_GENERATOR = repo_path_generator() |
|
47 | REPO_PATH_GENERATOR = repo_path_generator() | |
48 |
|
48 | |||
49 |
|
49 | |||
50 | @pytest.fixture(scope="class", autouse=True) |
|
50 | class TestMercurialRepository: | |
51 | def repo(request, baseapp): |
|
|||
52 | repo = MercurialRepository(TEST_HG_REPO) |
|
|||
53 | if request.cls: |
|
|||
54 | request.cls.repo = repo |
|
|||
55 | return repo |
|
|||
56 |
|
||||
57 |
|
||||
58 | class TestMercurialRepository(object): |
|
|||
59 | # pylint: disable=protected-access |
|
51 | # pylint: disable=protected-access | |
|
52 | @pytest.fixture(autouse=True) | |||
|
53 | def prepare(self): | |||
|
54 | self.repo = MercurialRepository(TEST_HG_REPO) | |||
60 |
|
55 | |||
61 | def get_clone_repo(self): |
|
56 | def get_clone_repo(self): | |
62 | """ |
|
57 | """ | |
@@ -100,9 +95,8 b' class TestMercurialRepository(object):' | |||||
100 |
|
95 | |||
101 | def test_repo_clone(self): |
|
96 | def test_repo_clone(self): | |
102 | if os.path.exists(TEST_HG_REPO_CLONE): |
|
97 | if os.path.exists(TEST_HG_REPO_CLONE): | |
103 |
|
|
98 | pytest.fail( | |
104 |
"Cannot test mercurial clone repo as location |
|
99 | f"Cannot test mercurial clone repo as location {TEST_HG_REPO_CLONE} already exists. You should manually remove it first." | |
105 | "exists. You should manually remove it first." % TEST_HG_REPO_CLONE |
|
|||
106 | ) |
|
100 | ) | |
107 |
|
101 | |||
108 | repo = MercurialRepository(TEST_HG_REPO) |
|
102 | repo = MercurialRepository(TEST_HG_REPO) | |
@@ -217,8 +211,8 b' class TestMercurialRepository(object):' | |||||
217 | assert "git" in self.repo._get_branches(closed=True) |
|
211 | assert "git" in self.repo._get_branches(closed=True) | |
218 | assert "web" in self.repo._get_branches(closed=True) |
|
212 | assert "web" in self.repo._get_branches(closed=True) | |
219 |
|
213 | |||
220 | for name, id in self.repo.branches.items(): |
|
214 | for name, commit_id in self.repo.branches.items(): | |
221 | assert isinstance(self.repo.get_commit(id), MercurialCommit) |
|
215 | assert isinstance(self.repo.get_commit(commit_id), MercurialCommit) | |
222 |
|
216 | |||
223 | def test_tip_in_tags(self): |
|
217 | def test_tip_in_tags(self): | |
224 | # tip is always a tag |
|
218 | # tip is always a tag | |
@@ -235,29 +229,38 b' class TestMercurialRepository(object):' | |||||
235 | assert init_commit.message == "initial import" |
|
229 | assert init_commit.message == "initial import" | |
236 | assert init_author == "Marcin Kuzminski <marcin@python-blog.com>" |
|
230 | assert init_author == "Marcin Kuzminski <marcin@python-blog.com>" | |
237 | assert init_author == init_commit.committer |
|
231 | assert init_author == init_commit.committer | |
238 |
assert sorted(init_commit. |
|
232 | assert sorted(init_commit.added_paths) == sorted( | |
239 | [ |
|
233 | [ | |
240 | "vcs/__init__.py", |
|
234 | b"vcs/__init__.py", | |
241 | "vcs/backends/BaseRepository.py", |
|
235 | b"vcs/backends/BaseRepository.py", | |
242 | "vcs/backends/__init__.py", |
|
236 | b"vcs/backends/__init__.py", | |
243 | ] |
|
237 | ] | |
244 | ) |
|
238 | ) | |
245 |
assert sorted(init_commit. |
|
239 | assert sorted(init_commit.affected_files) == sorted( | |
|
240 | [ | |||
|
241 | b"vcs/__init__.py", | |||
|
242 | b"vcs/backends/BaseRepository.py", | |||
|
243 | b"vcs/backends/__init__.py", | |||
|
244 | ] | |||
|
245 | ) | |||
246 |
|
246 | |||
247 | assert init_commit._dir_paths + init_commit._file_paths == init_commit._paths |
|
247 | for path in (b"vcs/__init__.py", b"vcs/backends/BaseRepository.py", b"vcs/backends/__init__.py"): | |
|
248 | assert isinstance(init_commit.get_node(path), FileNode) | |||
|
249 | for path in (b"", b"vcs", b"vcs/backends"): | |||
|
250 | assert isinstance(init_commit.get_node(path), DirNode) | |||
248 |
|
251 | |||
249 | with pytest.raises(NodeDoesNotExistError): |
|
252 | with pytest.raises(NodeDoesNotExistError): | |
250 | init_commit.get_node(path="foobar") |
|
253 | init_commit.get_node(path=b"foobar") | |
251 |
|
254 | |||
252 | node = init_commit.get_node("vcs/") |
|
255 | node = init_commit.get_node(b"vcs/") | |
253 | assert hasattr(node, "kind") |
|
256 | assert hasattr(node, "kind") | |
254 | assert node.kind == NodeKind.DIR |
|
257 | assert node.kind == NodeKind.DIR | |
255 |
|
258 | |||
256 | node = init_commit.get_node("vcs") |
|
259 | node = init_commit.get_node(b"vcs") | |
257 | assert hasattr(node, "kind") |
|
260 | assert hasattr(node, "kind") | |
258 | assert node.kind == NodeKind.DIR |
|
261 | assert node.kind == NodeKind.DIR | |
259 |
|
262 | |||
260 | node = init_commit.get_node("vcs/__init__.py") |
|
263 | node = init_commit.get_node(b"vcs/__init__.py") | |
261 | assert hasattr(node, "kind") |
|
264 | assert hasattr(node, "kind") | |
262 | assert node.kind == NodeKind.FILE |
|
265 | assert node.kind == NodeKind.FILE | |
263 |
|
266 | |||
@@ -279,7 +282,7 b' class TestMercurialRepository(object):' | |||||
279 |
|
282 | |||
280 | def test_commit10(self): |
|
283 | def test_commit10(self): | |
281 | commit10 = self.repo.get_commit(commit_idx=10) |
|
284 | commit10 = self.repo.get_commit(commit_idx=10) | |
282 |
|
|
285 | readme = """=== | |
283 | VCS |
|
286 | VCS | |
284 | === |
|
287 | === | |
285 |
|
288 | |||
@@ -291,9 +294,9 b' Introduction' | |||||
291 | TODO: To be written... |
|
294 | TODO: To be written... | |
292 |
|
295 | |||
293 | """ |
|
296 | """ | |
294 | node = commit10.get_node("README.rst") |
|
297 | node = commit10.get_node(b"README.rst") | |
295 | assert node.kind == NodeKind.FILE |
|
298 | assert node.kind == NodeKind.FILE | |
296 |
assert node.str_content == |
|
299 | assert node.str_content == readme | |
297 |
|
300 | |||
298 | def test_local_clone(self): |
|
301 | def test_local_clone(self): | |
299 | clone_path = next(REPO_PATH_GENERATOR) |
|
302 | clone_path = next(REPO_PATH_GENERATOR) | |
@@ -370,7 +373,7 b' TODO: To be written...' | |||||
370 | assert target_repo.branches["default"] == commit_id |
|
373 | assert target_repo.branches["default"] == commit_id | |
371 |
|
374 | |||
372 | def test_local_pull_from_same_repo(self): |
|
375 | def test_local_pull_from_same_repo(self): | |
373 |
reference = Reference("branch", "default", |
|
376 | reference = Reference("branch", "default", "") | |
374 | with pytest.raises(ValueError): |
|
377 | with pytest.raises(ValueError): | |
375 | self.repo._local_pull(self.repo.path, reference) |
|
378 | self.repo._local_pull(self.repo.path, reference) | |
376 |
|
379 | |||
@@ -503,7 +506,7 b' TODO: To be written...' | |||||
503 | # Check we are not left in an intermediate merge state |
|
506 | # Check we are not left in an intermediate merge state | |
504 | assert not os.path.exists(os.path.join(target_repo.path, ".hg", "merge", "state")) |
|
507 | assert not os.path.exists(os.path.join(target_repo.path, ".hg", "merge", "state")) | |
505 |
|
508 | |||
506 | def test_local_merge_of_two_branches_of_the_same_repo(self, backend_hg): |
|
509 | def test_local_merge_of_two_branches_of_the_same_repo(self, backend_hg, vcs_repo): | |
507 | commits = [ |
|
510 | commits = [ | |
508 | {"message": "a"}, |
|
511 | {"message": "a"}, | |
509 | {"message": "b", "branch": "b"}, |
|
512 | {"message": "b", "branch": "b"}, | |
@@ -639,7 +642,7 b' TODO: To be written...' | |||||
639 |
|
642 | |||
640 | # add an extra head to the target repo |
|
643 | # add an extra head to the target repo | |
641 | imc = target_repo.in_memory_commit |
|
644 | imc = target_repo.in_memory_commit | |
642 | imc.add(FileNode(b"file_x", content="foo")) |
|
645 | imc.add(FileNode(b"file_x", content=b"foo")) | |
643 | commits = list(target_repo.get_commits()) |
|
646 | commits = list(target_repo.get_commits()) | |
644 | imc.commit( |
|
647 | imc.commit( | |
645 | message="Automatic commit from repo merge test", |
|
648 | message="Automatic commit from repo merge test", | |
@@ -728,8 +731,7 b' TODO: To be written...' | |||||
728 | assert len(target_repo.commit_ids) == 2 + 2 |
|
731 | assert len(target_repo.commit_ids) == 2 + 2 | |
729 |
|
732 | |||
730 |
|
733 | |||
731 |
class TestGetShadowInstance |
|
734 | class TestGetShadowInstance: | |
732 |
|
||||
733 | @pytest.fixture() |
|
735 | @pytest.fixture() | |
734 | def repo(self, vcsbackend_hg): |
|
736 | def repo(self, vcsbackend_hg): | |
735 | _hg_repo = vcsbackend_hg.repo |
|
737 | _hg_repo = vcsbackend_hg.repo | |
@@ -742,17 +744,21 b' class TestGetShadowInstance(object):' | |||||
742 | assert shadow.config.serialize() == repo.config.serialize() |
|
744 | assert shadow.config.serialize() == repo.config.serialize() | |
743 |
|
745 | |||
744 | def test_disables_hooks_section(self, repo): |
|
746 | def test_disables_hooks_section(self, repo): | |
745 |
repo.config.set( |
|
747 | repo.config.set("hooks", "foo", "val") | |
746 | shadow = repo.get_shadow_instance(repo.path) |
|
748 | shadow = repo.get_shadow_instance(repo.path) | |
747 |
assert not shadow.config.items( |
|
749 | assert not shadow.config.items("hooks") | |
748 |
|
750 | |||
749 | def test_allows_to_keep_hooks(self, repo): |
|
751 | def test_allows_to_keep_hooks(self, repo): | |
750 |
repo.config.set( |
|
752 | repo.config.set("hooks", "foo", "val") | |
751 | shadow = repo.get_shadow_instance(repo.path, enable_hooks=True) |
|
753 | shadow = repo.get_shadow_instance(repo.path, enable_hooks=True) | |
752 |
assert shadow.config.items( |
|
754 | assert shadow.config.items("hooks") | |
753 |
|
755 | |||
754 |
|
756 | |||
755 |
class TestMercurialCommit |
|
757 | class TestMercurialCommit: | |
|
758 | @pytest.fixture(autouse=True) | |||
|
759 | def prepare(self): | |||
|
760 | self.repo = MercurialRepository(TEST_HG_REPO) | |||
|
761 | ||||
756 | def _test_equality(self, commit): |
|
762 | def _test_equality(self, commit): | |
757 | idx = commit.idx |
|
763 | idx = commit.idx | |
758 | assert commit == self.repo.get_commit(commit_idx=idx) |
|
764 | assert commit == self.repo.get_commit(commit_idx=idx) | |
@@ -772,7 +778,7 b' class TestMercurialCommit(object):' | |||||
772 |
|
778 | |||
773 | def test_root_node(self): |
|
779 | def test_root_node(self): | |
774 | tip = self.repo.get_commit("tip") |
|
780 | tip = self.repo.get_commit("tip") | |
775 | assert tip.root is tip.get_node("") |
|
781 | assert tip.root is tip.get_node(b"") | |
776 |
|
782 | |||
777 | def test_lazy_fetch(self): |
|
783 | def test_lazy_fetch(self): | |
778 | """ |
|
784 | """ | |
@@ -788,28 +794,28 b' class TestMercurialCommit(object):' | |||||
788 | # accessing root.nodes updates commit.nodes |
|
794 | # accessing root.nodes updates commit.nodes | |
789 | assert len(commit.nodes) == 9 |
|
795 | assert len(commit.nodes) == 9 | |
790 |
|
796 | |||
791 |
docs = |
|
797 | docs = commit.get_node(b"docs") | |
792 | # we haven't yet accessed anything new as docs dir was already cached |
|
798 | # we haven't yet accessed anything new as docs dir was already cached | |
793 | assert len(commit.nodes) == 9 |
|
799 | assert len(commit.nodes) == 9 | |
794 | assert len(docs.nodes) == 8 |
|
800 | assert len(docs.nodes) == 8 | |
795 | # accessing docs.nodes updates commit.nodes |
|
801 | # accessing docs.nodes updates commit.nodes | |
796 | assert len(commit.nodes) == 17 |
|
802 | assert len(commit.nodes) == 17 | |
797 |
|
803 | |||
798 | assert docs is commit.get_node("docs") |
|
804 | assert docs is commit.get_node(b"docs") | |
799 | assert docs is root.nodes[0] |
|
805 | assert docs is root.nodes[0] | |
800 | assert docs is root.dirs[0] |
|
806 | assert docs is root.dirs[0] | |
801 | assert docs is commit.get_node("docs") |
|
807 | assert docs is commit.get_node(b"docs") | |
802 |
|
808 | |||
803 | def test_nodes_with_commit(self): |
|
809 | def test_nodes_with_commit(self): | |
804 | commit = self.repo.get_commit(commit_idx=45) |
|
810 | commit = self.repo.get_commit(commit_idx=45) | |
805 | root = commit.root |
|
811 | root = commit.root | |
806 | docs = root.get_node("docs") |
|
812 | assert isinstance(root, RootNode) | |
807 |
|
|
813 | docs = commit.get_node(b"docs") | |
808 |
a |
|
814 | assert docs is commit.get_node(b"docs") | |
809 |
|
|
815 | api = commit.get_node(b"docs/api") | |
810 | index = api.get_node("index.rst") |
|
816 | assert api is commit.get_node(b"docs/api") | |
811 |
|
|
817 | index = commit.get_node(b"docs/api/index.rst") | |
812 |
assert index is commit.get_node("docs |
|
818 | assert index is commit.get_node(b"docs/api/index.rst") | |
813 |
|
819 | |||
814 | def test_branch_and_tags(self): |
|
820 | def test_branch_and_tags(self): | |
815 | commit0 = self.repo.get_commit(commit_idx=0) |
|
821 | commit0 = self.repo.get_commit(commit_idx=0) | |
@@ -837,28 +843,28 b' class TestMercurialCommit(object):' | |||||
837 |
|
843 | |||
838 | def test_file_size(self): |
|
844 | def test_file_size(self): | |
839 | to_check = ( |
|
845 | to_check = ( | |
840 | (10, "setup.py", 1068), |
|
846 | (10, b"setup.py", 1068), | |
841 | (20, "setup.py", 1106), |
|
847 | (20, b"setup.py", 1106), | |
842 | (60, "setup.py", 1074), |
|
848 | (60, b"setup.py", 1074), | |
843 | (10, "vcs/backends/base.py", 2921), |
|
849 | (10, b"vcs/backends/base.py", 2921), | |
844 | (20, "vcs/backends/base.py", 3936), |
|
850 | (20, b"vcs/backends/base.py", 3936), | |
845 | (60, "vcs/backends/base.py", 6189), |
|
851 | (60, b"vcs/backends/base.py", 6189), | |
846 | ) |
|
852 | ) | |
847 | for idx, path, size in to_check: |
|
853 | for idx, path, size in to_check: | |
848 | self._test_file_size(idx, path, size) |
|
854 | self._test_file_size(idx, path, size) | |
849 |
|
855 | |||
850 | def test_file_history_from_commits(self): |
|
856 | def test_file_history_from_commits(self): | |
851 | node = self.repo[10].get_node("setup.py") |
|
857 | node = self.repo[10].get_node(b"setup.py") | |
852 | commit_ids = [commit.raw_id for commit in node.history] |
|
858 | commit_ids = [commit.raw_id for commit in node.history] | |
853 | assert ["3803844fdbd3b711175fc3da9bdacfcd6d29a6fb"] == commit_ids |
|
859 | assert ["3803844fdbd3b711175fc3da9bdacfcd6d29a6fb"] == commit_ids | |
854 |
|
860 | |||
855 | node = self.repo[20].get_node("setup.py") |
|
861 | node = self.repo[20].get_node(b"setup.py") | |
856 | node_ids = [commit.raw_id for commit in node.history] |
|
862 | node_ids = [commit.raw_id for commit in node.history] | |
857 | assert ["eada5a770da98ab0dd7325e29d00e0714f228d09", "3803844fdbd3b711175fc3da9bdacfcd6d29a6fb"] == node_ids |
|
863 | assert ["eada5a770da98ab0dd7325e29d00e0714f228d09", "3803844fdbd3b711175fc3da9bdacfcd6d29a6fb"] == node_ids | |
858 |
|
864 | |||
859 | # special case we check history from commit that has this particular |
|
865 | # special case we check history from commit that has this particular | |
860 | # file changed this means we check if it's included as well |
|
866 | # file changed this means we check if it's included as well | |
861 | node = self.repo.get_commit("eada5a770da98ab0dd7325e29d00e0714f228d09").get_node("setup.py") |
|
867 | node = self.repo.get_commit("eada5a770da98ab0dd7325e29d00e0714f228d09").get_node(b"setup.py") | |
862 | node_ids = [commit.raw_id for commit in node.history] |
|
868 | node_ids = [commit.raw_id for commit in node.history] | |
863 | assert ["eada5a770da98ab0dd7325e29d00e0714f228d09", "3803844fdbd3b711175fc3da9bdacfcd6d29a6fb"] == node_ids |
|
869 | assert ["eada5a770da98ab0dd7325e29d00e0714f228d09", "3803844fdbd3b711175fc3da9bdacfcd6d29a6fb"] == node_ids | |
864 |
|
870 | |||
@@ -866,9 +872,9 b' class TestMercurialCommit(object):' | |||||
866 | # we can only check if those commits are present in the history |
|
872 | # we can only check if those commits are present in the history | |
867 | # as we cannot update this test every time file is changed |
|
873 | # as we cannot update this test every time file is changed | |
868 | files = { |
|
874 | files = { | |
869 | "setup.py": [7, 18, 45, 46, 47, 69, 77], |
|
875 | b"setup.py": [7, 18, 45, 46, 47, 69, 77], | |
870 | "vcs/nodes.py": [7, 8, 24, 26, 30, 45, 47, 49, 56, 57, 58, 59, 60, 61, 73, 76], |
|
876 | b"vcs/nodes.py": [7, 8, 24, 26, 30, 45, 47, 49, 56, 57, 58, 59, 60, 61, 73, 76], | |
871 | "vcs/backends/hg.py": [ |
|
877 | b"vcs/backends/hg.py": [ | |
872 | 4, |
|
878 | 4, | |
873 | 5, |
|
879 | 5, | |
874 | 6, |
|
880 | 6, | |
@@ -927,7 +933,7 b' class TestMercurialCommit(object):' | |||||
927 |
|
933 | |||
928 | def test_file_annotate(self): |
|
934 | def test_file_annotate(self): | |
929 | files = { |
|
935 | files = { | |
930 | "vcs/backends/__init__.py": { |
|
936 | b"vcs/backends/__init__.py": { | |
931 | 89: { |
|
937 | 89: { | |
932 | "lines_no": 31, |
|
938 | "lines_no": 31, | |
933 | "commits": [ |
|
939 | "commits": [ | |
@@ -1002,7 +1008,7 b' class TestMercurialCommit(object):' | |||||
1002 | ], |
|
1008 | ], | |
1003 | }, |
|
1009 | }, | |
1004 | }, |
|
1010 | }, | |
1005 | "vcs/exceptions.py": { |
|
1011 | b"vcs/exceptions.py": { | |
1006 | 89: { |
|
1012 | 89: { | |
1007 | "lines_no": 18, |
|
1013 | "lines_no": 18, | |
1008 | "commits": [16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 16, 16, 18, 18, 18], |
|
1014 | "commits": [16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 16, 16, 18, 18, 18], | |
@@ -1016,25 +1022,25 b' class TestMercurialCommit(object):' | |||||
1016 | "commits": [16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 16, 16, 18, 18, 18], |
|
1022 | "commits": [16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 16, 16, 18, 18, 18], | |
1017 | }, |
|
1023 | }, | |
1018 | }, |
|
1024 | }, | |
1019 | "MANIFEST.in": { |
|
1025 | b"MANIFEST.in": { | |
1020 | 89: {"lines_no": 5, "commits": [7, 7, 7, 71, 71]}, |
|
1026 | 89: {"lines_no": 5, "commits": [7, 7, 7, 71, 71]}, | |
1021 | 20: {"lines_no": 3, "commits": [7, 7, 7]}, |
|
1027 | 20: {"lines_no": 3, "commits": [7, 7, 7]}, | |
1022 | 55: {"lines_no": 3, "commits": [7, 7, 7]}, |
|
1028 | 55: {"lines_no": 3, "commits": [7, 7, 7]}, | |
1023 | }, |
|
1029 | }, | |
1024 | } |
|
1030 | } | |
1025 |
|
1031 | |||
1026 | for fname, commit_dict in files.items(): |
|
1032 | for file_name, commit_dict in files.items(): | |
1027 | for idx, __ in commit_dict.items(): |
|
1033 | for idx, __ in commit_dict.items(): | |
1028 | commit = self.repo.get_commit(commit_idx=idx) |
|
1034 | commit = self.repo.get_commit(commit_idx=idx) | |
1029 | l1_1 = [x[1] for x in commit.get_file_annotate(fname)] |
|
1035 | l1_1 = [x[1] for x in commit.get_file_annotate(file_name)] | |
1030 | l1_2 = [x[2]().raw_id for x in commit.get_file_annotate(fname)] |
|
1036 | l1_2 = [x[2]().raw_id for x in commit.get_file_annotate(file_name)] | |
1031 | assert l1_1 == l1_2 |
|
1037 | assert l1_1 == l1_2 | |
1032 | l1 = l1_2 = [x[2]().idx for x in commit.get_file_annotate(fname)] |
|
1038 | l1 = l1_2 = [x[2]().idx for x in commit.get_file_annotate(file_name)] | |
1033 | l2 = files[fname][idx]["commits"] |
|
1039 | l2 = files[file_name][idx]["commits"] | |
1034 | assert l1 == l2, ( |
|
1040 | assert l1 == l2, ( | |
1035 | "The lists of commit for %s@commit_id%s" |
|
1041 | "The lists of commit for %s@commit_id%s" | |
1036 | "from annotation list should match each other," |
|
1042 | "from annotation list should match each other," | |
1037 | "got \n%s \nvs \n%s " % (fname, idx, l1, l2) |
|
1043 | "got \n%s \nvs \n%s " % (file_name, idx, l1, l2) | |
1038 | ) |
|
1044 | ) | |
1039 |
|
1045 | |||
1040 | def test_commit_state(self): |
|
1046 | def test_commit_state(self): | |
@@ -1047,55 +1053,51 b' class TestMercurialCommit(object):' | |||||
1047 | # changed: 13 |
|
1053 | # changed: 13 | |
1048 | # added: 20 |
|
1054 | # added: 20 | |
1049 | # removed: 1 |
|
1055 | # removed: 1 | |
1050 |
changed = |
|
1056 | changed = { | |
1051 |
|
|
1057 | b".hgignore", | |
1052 |
|
|
1058 | b"README.rst", | |
1053 | "README.rst", |
|
1059 | b"docs/conf.py", | |
1054 |
|
|
1060 | b"docs/index.rst", | |
1055 |
|
|
1061 | b"setup.py", | |
1056 |
|
|
1062 | b"tests/test_hg.py", | |
1057 |
|
|
1063 | b"tests/test_nodes.py", | |
1058 |
|
|
1064 | b"vcs/__init__.py", | |
1059 |
|
|
1065 | b"vcs/backends/__init__.py", | |
1060 |
|
|
1066 | b"vcs/backends/base.py", | |
1061 |
|
|
1067 | b"vcs/backends/hg.py", | |
1062 |
|
|
1068 | b"vcs/nodes.py", | |
1063 |
|
|
1069 | b"vcs/utils/__init__.py", | |
1064 | "vcs/utils/__init__.py", |
|
1070 | } | |
1065 | ] |
|
|||
1066 | ) |
|
|||
1067 |
|
1071 | |||
1068 |
added = |
|
1072 | added = { | |
1069 | [ |
|
1073 | b"docs/api/backends/hg.rst", | |
1070 |
|
|
1074 | b"docs/api/backends/index.rst", | |
1071 |
|
|
1075 | b"docs/api/index.rst", | |
1072 |
|
|
1076 | b"docs/api/nodes.rst", | |
1073 |
|
|
1077 | b"docs/api/web/index.rst", | |
1074 |
|
|
1078 | b"docs/api/web/simplevcs.rst", | |
1075 |
|
|
1079 | b"docs/installation.rst", | |
1076 |
|
|
1080 | b"docs/quickstart.rst", | |
1077 | "docs/quickstart.rst", |
|
1081 | b"setup.cfg", | |
1078 | "setup.cfg", |
|
1082 | b"vcs/utils/baseui_config.py", | |
1079 |
|
|
1083 | b"vcs/utils/web.py", | |
1080 |
|
|
1084 | b"vcs/web/__init__.py", | |
1081 |
|
|
1085 | b"vcs/web/exceptions.py", | |
1082 |
|
|
1086 | b"vcs/web/simplevcs/__init__.py", | |
1083 |
|
|
1087 | b"vcs/web/simplevcs/exceptions.py", | |
1084 |
|
|
1088 | b"vcs/web/simplevcs/middleware.py", | |
1085 |
|
|
1089 | b"vcs/web/simplevcs/models.py", | |
1086 |
|
|
1090 | b"vcs/web/simplevcs/settings.py", | |
1087 |
|
|
1091 | b"vcs/web/simplevcs/utils.py", | |
1088 |
|
|
1092 | b"vcs/web/simplevcs/views.py", | |
1089 | "vcs/web/simplevcs/views.py", |
|
1093 | } | |
1090 | ] |
|
|||
1091 | ) |
|
|||
1092 |
|
1094 | |||
1093 |
removed = |
|
1095 | removed = {b"docs/api.rst"} | |
1094 |
|
1096 | |||
1095 | commit64 = self.repo.get_commit("46ad32a4f974") |
|
1097 | commit64 = self.repo.get_commit("46ad32a4f974") | |
1096 |
assert set((node |
|
1098 | assert set((node for node in commit64.added_paths)) == added | |
1097 |
assert set((node |
|
1099 | assert set((node for node in commit64.changed_paths)) == changed | |
1098 |
assert set((node |
|
1100 | assert set((node for node in commit64.removed_paths)) == removed | |
1099 |
|
1101 | |||
1100 | # commit_id b090f22d27d6: |
|
1102 | # commit_id b090f22d27d6: | |
1101 | # hg st --rev b090f22d27d6 |
|
1103 | # hg st --rev b090f22d27d6 | |
@@ -1103,9 +1105,9 b' class TestMercurialCommit(object):' | |||||
1103 | # added: 20 |
|
1105 | # added: 20 | |
1104 | # removed: 1 |
|
1106 | # removed: 1 | |
1105 | commit88 = self.repo.get_commit("b090f22d27d6") |
|
1107 | commit88 = self.repo.get_commit("b090f22d27d6") | |
1106 |
assert set((node |
|
1108 | assert set((node for node in commit88.added_paths)) == set() | |
1107 |
assert set((node |
|
1109 | assert set((node for node in commit88.changed_paths)) == {b".hgignore"} | |
1108 |
assert set((node |
|
1110 | assert set((node for node in commit88.removed_paths)) == set() | |
1109 |
|
1111 | |||
1110 | # |
|
1112 | # | |
1111 | # 85: |
|
1113 | # 85: | |
@@ -1114,55 +1116,40 b' class TestMercurialCommit(object):' | |||||
1114 | # changed: 4 ['vcs/web/simplevcs/models.py', ...] |
|
1116 | # changed: 4 ['vcs/web/simplevcs/models.py', ...] | |
1115 | # removed: 1 ['vcs/utils/web.py'] |
|
1117 | # removed: 1 ['vcs/utils/web.py'] | |
1116 | commit85 = self.repo.get_commit(commit_idx=85) |
|
1118 | commit85 = self.repo.get_commit(commit_idx=85) | |
1117 |
assert set((node |
|
1119 | assert set((node for node in commit85.added_paths)) == {b"vcs/utils/diffs.py", b"vcs/web/simplevcs/views/diffs.py"} | |
1118 | ["vcs/utils/diffs.py", "vcs/web/simplevcs/views/diffs.py"] |
|
1120 | assert set((node for node in commit85.changed_paths)) == { | |
1119 | ) |
|
1121 | b"vcs/web/simplevcs/models.py", | |
1120 | assert set((node.path for node in commit85.changed)) == set( |
|
1122 | b"vcs/web/simplevcs/utils.py", | |
1121 | [ |
|
1123 | b"vcs/web/simplevcs/views/__init__.py", | |
1122 |
|
|
1124 | b"vcs/web/simplevcs/views/repository.py", | |
1123 | "vcs/web/simplevcs/utils.py", |
|
1125 | } | |
1124 | "vcs/web/simplevcs/views/__init__.py", |
|
1126 | assert set((node for node in commit85.removed_paths)) == {b"vcs/utils/web.py"} | |
1125 | "vcs/web/simplevcs/views/repository.py", |
|
|||
1126 | ] |
|
|||
1127 | ) |
|
|||
1128 | assert set((node.path for node in commit85.removed)) == set(["vcs/utils/web.py"]) |
|
|||
1129 |
|
1127 | |||
1130 | def test_files_state(self): |
|
1128 | def test_files_state(self): | |
1131 | """ |
|
1129 | """ | |
1132 | Tests state of FileNodes. |
|
1130 | Tests state of FileNodes. | |
1133 | """ |
|
1131 | """ | |
1134 | commit = self.repo.get_commit(commit_idx=85) |
|
1132 | commit = self.repo.get_commit(commit_idx=85) | |
1135 | node = commit.get_node("vcs/utils/diffs.py") |
|
1133 | node = commit.get_node(b"vcs/utils/diffs.py") | |
1136 |
assert node. |
|
1134 | assert node.bytes_path in commit.added_paths | |
1137 | assert node.added |
|
|||
1138 | assert not node.changed |
|
|||
1139 | assert not node.not_changed |
|
|||
1140 | assert not node.removed |
|
|||
1141 |
|
1135 | |||
1142 | commit = self.repo.get_commit(commit_idx=88) |
|
1136 | commit = self.repo.get_commit(commit_idx=88) | |
1143 | node = commit.get_node(".hgignore") |
|
1137 | node = commit.get_node(b".hgignore") | |
1144 | assert node.state, NodeState.CHANGED |
|
1138 | assert node.bytes_path in commit.changed_paths | |
1145 | assert not node.added |
|
|||
1146 | assert node.changed |
|
|||
1147 | assert not node.not_changed |
|
|||
1148 | assert not node.removed |
|
|||
1149 |
|
1139 | |||
1150 | commit = self.repo.get_commit(commit_idx=85) |
|
1140 | commit = self.repo.get_commit(commit_idx=85) | |
1151 | node = commit.get_node("setup.py") |
|
1141 | node = commit.get_node(b"setup.py") | |
1152 | assert node.state, NodeState.NOT_CHANGED |
|
1142 | assert node.bytes_path not in commit.affected_files | |
1153 | assert not node.added |
|
|||
1154 | assert not node.changed |
|
|||
1155 | assert node.not_changed |
|
|||
1156 | assert not node.removed |
|
|||
1157 |
|
1143 | |||
1158 | # If node has REMOVED state then trying to fetch it would raise |
|
1144 | # If node has REMOVED state then trying to fetch it would raise | |
1159 | # CommitError exception |
|
1145 | # CommitError exception | |
1160 | commit = self.repo.get_commit(commit_idx=2) |
|
1146 | commit = self.repo.get_commit(commit_idx=2) | |
1161 | path = "vcs/backends/BaseRepository.py" |
|
1147 | path = b"vcs/backends/BaseRepository.py" | |
1162 | with pytest.raises(NodeDoesNotExistError): |
|
1148 | with pytest.raises(NodeDoesNotExistError): | |
1163 | commit.get_node(path) |
|
1149 | commit.get_node(path) | |
|
1150 | ||||
1164 | # but it would be one of ``removed`` (commit's attribute) |
|
1151 | # but it would be one of ``removed`` (commit's attribute) | |
1165 |
assert path in [rf |
|
1152 | assert path in [rf for rf in commit.removed_paths] | |
1166 |
|
1153 | |||
1167 | def test_commit_message_is_unicode(self): |
|
1154 | def test_commit_message_is_unicode(self): | |
1168 | for cm in self.repo: |
|
1155 | for cm in self.repo: | |
@@ -1174,14 +1161,14 b' class TestMercurialCommit(object):' | |||||
1174 |
|
1161 | |||
1175 | def test_repo_files_content_type(self): |
|
1162 | def test_repo_files_content_type(self): | |
1176 | test_commit = self.repo.get_commit(commit_idx=100) |
|
1163 | test_commit = self.repo.get_commit(commit_idx=100) | |
1177 | for node in test_commit.get_node("/"): |
|
1164 | for node in test_commit.get_node(b"/"): | |
1178 | if node.is_file(): |
|
1165 | if node.is_file(): | |
1179 | assert type(node.content) == bytes |
|
1166 | assert type(node.content) == bytes | |
1180 | assert type(node.str_content) == str |
|
1167 | assert type(node.str_content) == str | |
1181 |
|
1168 | |||
1182 | def test_wrong_path(self): |
|
1169 | def test_wrong_path(self): | |
1183 | # There is 'setup.py' in the root dir but not there: |
|
1170 | # There is 'setup.py' in the root dir but not there: | |
1184 | path = "foo/bar/setup.py" |
|
1171 | path = b"foo/bar/setup.py" | |
1185 | with pytest.raises(VCSError): |
|
1172 | with pytest.raises(VCSError): | |
1186 | self.repo.get_commit().get_node(path) |
|
1173 | self.repo.get_commit().get_node(path) | |
1187 |
|
1174 | |||
@@ -1196,23 +1183,27 b' class TestMercurialCommit(object):' | |||||
1196 | assert "marcink" == self.repo.get_commit("84478366594b").author_name |
|
1183 | assert "marcink" == self.repo.get_commit("84478366594b").author_name | |
1197 |
|
1184 | |||
1198 |
|
1185 | |||
1199 |
class TestLargeFileRepo |
|
1186 | class TestLargeFileRepo: | |
1200 | def test_large_file(self, backend_hg): |
|
1187 | def test_large_file(self, backend_hg): | |
1201 | conf = make_db_config() |
|
1188 | conf = make_db_config() | |
1202 | hg_largefiles_store = conf.get("largefiles", "usercache") |
|
1189 | hg_largefiles_store = conf.get("largefiles", "usercache") | |
1203 | repo = backend_hg.create_test_repo("largefiles", conf) |
|
1190 | repo = backend_hg.create_test_repo("largefiles", conf) | |
1204 |
|
1191 | |||
1205 | tip = repo.scm_instance().get_commit() |
|
1192 | tip = repo.scm_instance().get_commit() | |
1206 | node = tip.get_node(".hglf/thisfileislarge") |
|
1193 | node = tip.get_node(b".hglf/thisfileislarge") | |
1207 |
|
1194 | |||
1208 | lf_node = node.get_largefile_node() |
|
1195 | lf_node = node.get_largefile_node() | |
1209 |
|
1196 | |||
1210 | assert lf_node.is_largefile() is True |
|
1197 | assert lf_node.is_largefile() is True | |
1211 | assert lf_node.size == 1024000 |
|
1198 | assert lf_node.size == 1024000 | |
1212 | assert lf_node.name == ".hglf/thisfileislarge" |
|
1199 | assert lf_node.name == b".hglf/thisfileislarge" | |
1213 |
|
1200 | |||
1214 |
|
1201 | |||
1215 |
class TestGetBranchName |
|
1202 | class TestGetBranchName: | |
|
1203 | @pytest.fixture(autouse=True) | |||
|
1204 | def prepare(self): | |||
|
1205 | self.repo = MercurialRepository(TEST_HG_REPO) | |||
|
1206 | ||||
1216 | def test_returns_ref_name_when_type_is_branch(self): |
|
1207 | def test_returns_ref_name_when_type_is_branch(self): | |
1217 | ref = self._create_ref("branch", "fake-name") |
|
1208 | ref = self._create_ref("branch", "fake-name") | |
1218 | result = self.repo._get_branch_name(ref) |
|
1209 | result = self.repo._get_branch_name(ref) | |
@@ -1235,7 +1226,11 b' class TestGetBranchName(object):' | |||||
1235 | return ref |
|
1226 | return ref | |
1236 |
|
1227 | |||
1237 |
|
1228 | |||
1238 |
class TestIsTheSameBranch |
|
1229 | class TestIsTheSameBranch: | |
|
1230 | @pytest.fixture(autouse=True) | |||
|
1231 | def prepare(self): | |||
|
1232 | self.repo = MercurialRepository(TEST_HG_REPO) | |||
|
1233 | ||||
1239 | def test_returns_true_when_branches_are_equal(self): |
|
1234 | def test_returns_true_when_branches_are_equal(self): | |
1240 | source_ref = mock.Mock(name="source-ref") |
|
1235 | source_ref = mock.Mock(name="source-ref") | |
1241 | target_ref = mock.Mock(name="target-ref") |
|
1236 | target_ref = mock.Mock(name="target-ref") |
@@ -128,8 +128,8 b' class TestInMemoryCommit(BackendTestMixi' | |||||
128 | ] |
|
128 | ] | |
129 | self.imc.add(*to_add) |
|
129 | self.imc.add(*to_add) | |
130 | commit = self.imc.commit("Initial", "joe doe <joe.doe@example.com>") |
|
130 | commit = self.imc.commit("Initial", "joe doe <joe.doe@example.com>") | |
131 | assert isinstance(commit.get_node("foo"), DirNode) |
|
131 | assert isinstance(commit.get_node(b"foo"), DirNode) | |
132 | assert isinstance(commit.get_node("foo/bar"), DirNode) |
|
132 | assert isinstance(commit.get_node(b"foo/bar"), DirNode) | |
133 | self.assert_nodes_in_commit(commit, to_add) |
|
133 | self.assert_nodes_in_commit(commit, to_add) | |
134 |
|
134 | |||
135 | # commit some more files again |
|
135 | # commit some more files again | |
@@ -244,7 +244,7 b' class TestInMemoryCommit(BackendTestMixi' | |||||
244 |
|
244 | |||
245 | tip = self.repo.get_commit() |
|
245 | tip = self.repo.get_commit() | |
246 | node = nodes[0] |
|
246 | node = nodes[0] | |
247 | assert node.content == tip.get_node(node.path).content |
|
247 | assert node.content == tip.get_node(node.bytes_path).content | |
248 | self.imc.remove(node) |
|
248 | self.imc.remove(node) | |
249 | self.imc.commit(message=f"Removed {node.path}", author="Some Name <foo@bar.com>") |
|
249 | self.imc.commit(message=f"Removed {node.path}", author="Some Name <foo@bar.com>") | |
250 |
|
250 | |||
@@ -252,7 +252,7 b' class TestInMemoryCommit(BackendTestMixi' | |||||
252 | assert tip != newtip |
|
252 | assert tip != newtip | |
253 | assert tip.id != newtip.id |
|
253 | assert tip.id != newtip.id | |
254 | with pytest.raises(NodeDoesNotExistError): |
|
254 | with pytest.raises(NodeDoesNotExistError): | |
255 | newtip.get_node(node.path) |
|
255 | newtip.get_node(node.bytes_path) | |
256 |
|
256 | |||
257 | def test_remove_last_file_from_directory(self): |
|
257 | def test_remove_last_file_from_directory(self): | |
258 | node = FileNode(b"omg/qwe/foo/bar", content=b"foobar") |
|
258 | node = FileNode(b"omg/qwe/foo/bar", content=b"foobar") | |
@@ -262,7 +262,7 b' class TestInMemoryCommit(BackendTestMixi' | |||||
262 | self.imc.remove(node) |
|
262 | self.imc.remove(node) | |
263 | tip = self.imc.commit("removed", "joe doe <joe@doe.com>") |
|
263 | tip = self.imc.commit("removed", "joe doe <joe@doe.com>") | |
264 | with pytest.raises(NodeDoesNotExistError): |
|
264 | with pytest.raises(NodeDoesNotExistError): | |
265 | tip.get_node("omg/qwe/foo/bar") |
|
265 | tip.get_node(b"omg/qwe/foo/bar") | |
266 |
|
266 | |||
267 | def test_remove_raise_node_does_not_exist(self, nodes): |
|
267 | def test_remove_raise_node_does_not_exist(self, nodes): | |
268 | self.imc.remove(nodes[0]) |
|
268 | self.imc.remove(nodes[0]) | |
@@ -338,5 +338,5 b' class TestInMemoryCommit(BackendTestMixi' | |||||
338 |
|
338 | |||
339 | def assert_nodes_in_commit(self, commit, nodes): |
|
339 | def assert_nodes_in_commit(self, commit, nodes): | |
340 | for node in nodes: |
|
340 | for node in nodes: | |
341 | assert commit.get_node(node.path).path == node.path |
|
341 | assert commit.get_node(node.bytes_path).path == node.path | |
342 | assert commit.get_node(node.path).content == node.content |
|
342 | assert commit.get_node(node.bytes_path).content == node.content |
@@ -155,30 +155,6 b' class TestNodeBasics:' | |||||
155 | with pytest.raises(NodeError): |
|
155 | with pytest.raises(NodeError): | |
156 | node.content # noqa |
|
156 | node.content # noqa | |
157 |
|
157 | |||
158 | def test_dir_node_iter(self): |
|
|||
159 | nodes = [ |
|
|||
160 | DirNode(b"docs"), |
|
|||
161 | DirNode(b"tests"), |
|
|||
162 | FileNode(b"bar"), |
|
|||
163 | FileNode(b"foo"), |
|
|||
164 | FileNode(b"readme.txt"), |
|
|||
165 | FileNode(b"setup.py"), |
|
|||
166 | ] |
|
|||
167 | dirnode = DirNode(b"", nodes=nodes) |
|
|||
168 | for node in dirnode: |
|
|||
169 | assert node == dirnode.get_node(node.path) |
|
|||
170 |
|
||||
171 | def test_node_state(self): |
|
|||
172 | """ |
|
|||
173 | Without link to commit nodes should raise NodeError. |
|
|||
174 | """ |
|
|||
175 | node = FileNode(b"anything") |
|
|||
176 | with pytest.raises(NodeError): |
|
|||
177 | node.state # noqa |
|
|||
178 | node = DirNode(b"anything") |
|
|||
179 | with pytest.raises(NodeError): |
|
|||
180 | node.state # noqa |
|
|||
181 |
|
||||
182 | def test_file_node_stat(self): |
|
158 | def test_file_node_stat(self): | |
183 | node = FileNode(b"foobar", b"empty... almost") |
|
159 | node = FileNode(b"foobar", b"empty... almost") | |
184 | mode = node.mode # default should be 0100644 |
|
160 | mode = node.mode # default should be 0100644 | |
@@ -272,5 +248,5 b' class TestNodesCommits(BackendTestMixin)' | |||||
272 | last_commit = repo.get_commit() |
|
248 | last_commit = repo.get_commit() | |
273 |
|
249 | |||
274 | for x in range(3): |
|
250 | for x in range(3): | |
275 |
node = last_commit.get_node( |
|
251 | node = last_commit.get_node(b"file_%d.txt" % x) | |
276 | assert node.last_commit == repo[x] |
|
252 | assert node.last_commit == repo[x] |
@@ -128,7 +128,7 b' def test_read_full_file_tree(head):' | |||||
128 |
|
128 | |||
129 |
|
129 | |||
130 | def test_topnode_files_attribute(head): |
|
130 | def test_topnode_files_attribute(head): | |
131 | topnode = head.get_node("") |
|
131 | topnode = head.get_node(b"") | |
132 | topnode.files |
|
132 | topnode.files | |
133 |
|
133 | |||
134 |
|
134 | |||
@@ -173,23 +173,23 b' class TestSVNCommit(object):' | |||||
173 | self.repo = repo |
|
173 | self.repo = repo | |
174 |
|
174 | |||
175 | def test_file_history_from_commits(self): |
|
175 | def test_file_history_from_commits(self): | |
176 | node = self.repo[10].get_node("setup.py") |
|
176 | node = self.repo[10].get_node(b"setup.py") | |
177 | commit_ids = [commit.raw_id for commit in node.history] |
|
177 | commit_ids = [commit.raw_id for commit in node.history] | |
178 | assert ["8"] == commit_ids |
|
178 | assert ["8"] == commit_ids | |
179 |
|
179 | |||
180 | node = self.repo[20].get_node("setup.py") |
|
180 | node = self.repo[20].get_node(b"setup.py") | |
181 | node_ids = [commit.raw_id for commit in node.history] |
|
181 | node_ids = [commit.raw_id for commit in node.history] | |
182 | assert ["18", "8"] == node_ids |
|
182 | assert ["18", "8"] == node_ids | |
183 |
|
183 | |||
184 | # special case we check history from commit that has this particular |
|
184 | # special case we check history from commit that has this particular | |
185 | # file changed this means we check if it's included as well |
|
185 | # file changed this means we check if it's included as well | |
186 | node = self.repo.get_commit("18").get_node("setup.py") |
|
186 | node = self.repo.get_commit("18").get_node(b"setup.py") | |
187 | node_ids = [commit.raw_id for commit in node.history] |
|
187 | node_ids = [commit.raw_id for commit in node.history] | |
188 | assert ["18", "8"] == node_ids |
|
188 | assert ["18", "8"] == node_ids | |
189 |
|
189 | |||
190 | def test_repo_files_content_type(self): |
|
190 | def test_repo_files_content_type(self): | |
191 | test_commit = self.repo.get_commit(commit_idx=100) |
|
191 | test_commit = self.repo.get_commit(commit_idx=100) | |
192 | for node in test_commit.get_node("/"): |
|
192 | for node in test_commit.get_node(b"/"): | |
193 | if node.is_file(): |
|
193 | if node.is_file(): | |
194 | assert type(node.content) == bytes |
|
194 | assert type(node.content) == bytes | |
195 | assert type(node.str_content) == str |
|
195 | assert type(node.str_content) == str |
@@ -30,11 +30,11 b' class TestTags(BackendTestMixin):' | |||||
30 | def test_new_tag(self): |
|
30 | def test_new_tag(self): | |
31 | tip = self.repo.get_commit() |
|
31 | tip = self.repo.get_commit() | |
32 | tagsize = len(self.repo.tags) |
|
32 | tagsize = len(self.repo.tags) | |
33 | tag = self.repo.tag("last-commit", "joe", tip.raw_id) |
|
33 | tag_commit = self.repo.tag("last-commit", "joe", tip.raw_id) | |
34 |
|
34 | |||
35 | assert len(self.repo.tags) == tagsize + 1 |
|
35 | assert len(self.repo.tags) == tagsize + 1 | |
36 | for top, __, __ in tip.walk(): |
|
36 | for top, __, __ in tip.walk(): | |
37 | assert top == tag.get_node(top.path) |
|
37 | assert top == tag_commit.get_node(top.bytes_path) | |
38 |
|
38 | |||
39 | def test_tag_already_exist(self): |
|
39 | def test_tag_already_exist(self): | |
40 | tip = self.repo.get_commit() |
|
40 | tip = self.repo.get_commit() |
@@ -65,7 +65,7 b' class TestVCSOperationsOnUsingBadClient(' | |||||
65 | # push fails repo is locked by other user ! |
|
65 | # push fails repo is locked by other user ! | |
66 | push_url = rcstack.repo_clone_url(HG_REPO) |
|
66 | push_url = rcstack.repo_clone_url(HG_REPO) | |
67 | stdout, stderr = _add_files_and_push("hg", tmpdir.strpath, clone_url=push_url) |
|
67 | stdout, stderr = _add_files_and_push("hg", tmpdir.strpath, clone_url=push_url) | |
68 | msg = "Your hg client (ver=mercurial/proto-1.0 (Mercurial 6.7.4)) is forbidden by security rules" |
|
68 | msg = "Your hg client (version=mercurial/proto-1.0 (Mercurial 6.7.4)) is forbidden by security rules" | |
69 | assert msg in stderr |
|
69 | assert msg in stderr | |
70 |
|
70 | |||
71 | def test_push_with_bad_client_repo_by_other_user_git(self, rcstack, tmpdir): |
|
71 | def test_push_with_bad_client_repo_by_other_user_git(self, rcstack, tmpdir): | |
@@ -81,7 +81,7 b' class TestVCSOperationsOnUsingBadClient(' | |||||
81 | push_url = rcstack.repo_clone_url(GIT_REPO) |
|
81 | push_url = rcstack.repo_clone_url(GIT_REPO) | |
82 | stdout, stderr = _add_files_and_push("git", tmpdir.strpath, clone_url=push_url) |
|
82 | stdout, stderr = _add_files_and_push("git", tmpdir.strpath, clone_url=push_url) | |
83 |
|
83 | |||
84 | err = "Your git client (ver=git/2.45.2) is forbidden by security rules" |
|
84 | err = "Your git client (version=git/2.45.2) is forbidden by security rules" | |
85 | assert err in stderr |
|
85 | assert err in stderr | |
86 |
|
86 | |||
87 | @pytest.mark.xfail(reason="Lack of proper SVN support of cloning") |
|
87 | @pytest.mark.xfail(reason="Lack of proper SVN support of cloning") |
@@ -124,7 +124,6 b' class TestVCSOperationsSVN(object):' | |||||
124 |
|
124 | |||
125 | assert 'not found' in stderr |
|
125 | assert 'not found' in stderr | |
126 |
|
126 | |||
127 | @pytest.mark.xfail(reason='Lack of proper SVN support of cloning') |
|
|||
128 | def test_clone_existing_path_svn_not_in_database( |
|
127 | def test_clone_existing_path_svn_not_in_database( | |
129 | self, rcstack, tmpdir, fs_repo_only): |
|
128 | self, rcstack, tmpdir, fs_repo_only): | |
130 | db_name = fs_repo_only('not-in-db-git', repo_type='git') |
|
129 | db_name = fs_repo_only('not-in-db-git', repo_type='git') | |
@@ -136,7 +135,6 b' class TestVCSOperationsSVN(object):' | |||||
136 | f'svn checkout {flags} {auth}', clone_url, tmpdir.strpath) |
|
135 | f'svn checkout {flags} {auth}', clone_url, tmpdir.strpath) | |
137 | assert 'not found' in stderr |
|
136 | assert 'not found' in stderr | |
138 |
|
137 | |||
139 | @pytest.mark.xfail(reason='Lack of proper SVN support of cloning') |
|
|||
140 | def test_clone_existing_path_svn_not_in_database_different_scm( |
|
138 | def test_clone_existing_path_svn_not_in_database_different_scm( | |
141 | self, rcstack, tmpdir, fs_repo_only): |
|
139 | self, rcstack, tmpdir, fs_repo_only): | |
142 | db_name = fs_repo_only('not-in-db-hg', repo_type='hg') |
|
140 | db_name = fs_repo_only('not-in-db-hg', repo_type='hg') | |
@@ -149,7 +147,6 b' class TestVCSOperationsSVN(object):' | |||||
149 | f'svn checkout {flags} {auth}', clone_url, tmpdir.strpath) |
|
147 | f'svn checkout {flags} {auth}', clone_url, tmpdir.strpath) | |
150 | assert 'not found' in stderr |
|
148 | assert 'not found' in stderr | |
151 |
|
149 | |||
152 | @pytest.mark.xfail(reason='Lack of proper SVN support of cloning') |
|
|||
153 | def test_clone_non_existing_store_path_svn(self, rcstack, tmpdir, user_util): |
|
150 | def test_clone_non_existing_store_path_svn(self, rcstack, tmpdir, user_util): | |
154 | repo = user_util.create_repo(repo_type='git') |
|
151 | repo = user_util.create_repo(repo_type='git') | |
155 | clone_url = rcstack.repo_clone_url(repo.repo_name) |
|
152 | clone_url = rcstack.repo_clone_url(repo.repo_name) |
General Comments 0
You need to be logged in to leave comments.
Login now