|
@@
-1,865
+1,863
b''
|
|
1
|
# RhodeCode VCSServer provides access to different vcs backends via network.
|
|
1
|
# RhodeCode VCSServer provides access to different vcs backends via network.
|
|
2
|
# Copyright (C) 2014-2020 RhodeCode GmbH
|
|
2
|
# Copyright (C) 2014-2020 RhodeCode GmbH
|
|
3
|
#
|
|
3
|
#
|
|
4
|
# This program is free software; you can redistribute it and/or modify
|
|
4
|
# This program is free software; you can redistribute it and/or modify
|
|
5
|
# it under the terms of the GNU General Public License as published by
|
|
5
|
# it under the terms of the GNU General Public License as published by
|
|
6
|
# the Free Software Foundation; either version 3 of the License, or
|
|
6
|
# the Free Software Foundation; either version 3 of the License, or
|
|
7
|
# (at your option) any later version.
|
|
7
|
# (at your option) any later version.
|
|
8
|
#
|
|
8
|
#
|
|
9
|
# This program is distributed in the hope that it will be useful,
|
|
9
|
# This program is distributed in the hope that it will be useful,
|
|
10
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
# GNU General Public License for more details.
|
|
12
|
# GNU General Public License for more details.
|
|
13
|
#
|
|
13
|
#
|
|
14
|
# You should have received a copy of the GNU General Public License
|
|
14
|
# You should have received a copy of the GNU General Public License
|
|
15
|
# along with this program; if not, write to the Free Software Foundation,
|
|
15
|
# along with this program; if not, write to the Free Software Foundation,
|
|
16
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
16
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17
|
|
|
17
|
|
|
18
|
|
|
18
|
|
|
19
|
import os
|
|
19
|
import os
|
|
20
|
import subprocess
|
|
20
|
import subprocess
|
|
21
|
from urllib.error import URLError
|
|
21
|
from urllib.error import URLError
|
|
22
|
import urllib.parse
|
|
22
|
import urllib.parse
|
|
23
|
import logging
|
|
23
|
import logging
|
|
24
|
import posixpath as vcspath
|
|
24
|
import posixpath as vcspath
|
|
25
|
import io
|
|
25
|
import io
|
|
26
|
import urllib.request, urllib.parse, urllib.error
|
|
26
|
import urllib.request
|
|
|
|
|
27
|
import urllib.parse
|
|
|
|
|
28
|
import urllib.error
|
|
27
|
import traceback
|
|
29
|
import traceback
|
|
28
|
|
|
30
|
|
|
29
|
import svn.client
|
|
31
|
import svn.client
|
|
30
|
import svn.core
|
|
32
|
import svn.core
|
|
31
|
import svn.delta
|
|
33
|
import svn.delta
|
|
32
|
import svn.diff
|
|
34
|
import svn.diff
|
|
33
|
import svn.fs
|
|
35
|
import svn.fs
|
|
34
|
import svn.repos
|
|
36
|
import svn.repos
|
|
35
|
|
|
37
|
|
|
36
|
from vcsserver import svn_diff, exceptions, subprocessio, settings
|
|
38
|
from vcsserver import svn_diff, exceptions, subprocessio, settings
|
|
37
|
from vcsserver.base import RepoFactory, raise_from_original, ArchiveNode, archive_repo
|
|
39
|
from vcsserver.base import RepoFactory, raise_from_original, ArchiveNode, archive_repo
|
|
38
|
from vcsserver.exceptions import NoContentException
|
|
40
|
from vcsserver.exceptions import NoContentException
|
|
39
|
from vcsserver.utils import safe_str
|
|
41
|
from vcsserver.utils import safe_str
|
|
40
|
from vcsserver.vcs_base import RemoteBase
|
|
42
|
from vcsserver.vcs_base import RemoteBase
|
|
41
|
|
|
43
|
from vcsserver.lib.svnremoterepo import svnremoterepo
|
|
42
|
log = logging.getLogger(__name__)
|
|
44
|
log = logging.getLogger(__name__)
|
|
43
|
|
|
45
|
|
|
44
|
|
|
46
|
|
|
45
|
svn_compatible_versions_map = {
|
|
47
|
svn_compatible_versions_map = {
|
|
46
|
'pre-1.4-compatible': '1.3',
|
|
48
|
'pre-1.4-compatible': '1.3',
|
|
47
|
'pre-1.5-compatible': '1.4',
|
|
49
|
'pre-1.5-compatible': '1.4',
|
|
48
|
'pre-1.6-compatible': '1.5',
|
|
50
|
'pre-1.6-compatible': '1.5',
|
|
49
|
'pre-1.8-compatible': '1.7',
|
|
51
|
'pre-1.8-compatible': '1.7',
|
|
50
|
'pre-1.9-compatible': '1.8',
|
|
52
|
'pre-1.9-compatible': '1.8',
|
|
51
|
}
|
|
53
|
}
|
|
52
|
|
|
54
|
|
|
53
|
current_compatible_version = '1.12'
|
|
55
|
current_compatible_version = '1.14'
|
|
54
|
|
|
56
|
|
|
55
|
|
|
57
|
|
|
56
|
def reraise_safe_exceptions(func):
|
|
58
|
def reraise_safe_exceptions(func):
|
|
57
|
"""Decorator for converting svn exceptions to something neutral."""
|
|
59
|
"""Decorator for converting svn exceptions to something neutral."""
|
|
58
|
def wrapper(*args, **kwargs):
|
|
60
|
def wrapper(*args, **kwargs):
|
|
59
|
try:
|
|
61
|
try:
|
|
60
|
return func(*args, **kwargs)
|
|
62
|
return func(*args, **kwargs)
|
|
61
|
except Exception as e:
|
|
63
|
except Exception as e:
|
|
62
|
if not hasattr(e, '_vcs_kind'):
|
|
64
|
if not hasattr(e, '_vcs_kind'):
|
|
63
|
log.exception("Unhandled exception in svn remote call")
|
|
65
|
log.exception("Unhandled exception in svn remote call")
|
|
64
|
raise_from_original(exceptions.UnhandledException(e))
|
|
66
|
raise_from_original(exceptions.UnhandledException(e))
|
|
65
|
raise
|
|
67
|
raise
|
|
66
|
return wrapper
|
|
68
|
return wrapper
|
|
67
|
|
|
69
|
|
|
68
|
|
|
70
|
|
|
69
|
class SubversionFactory(RepoFactory):
|
|
71
|
class SubversionFactory(RepoFactory):
|
|
70
|
repo_type = 'svn'
|
|
72
|
repo_type = 'svn'
|
|
71
|
|
|
73
|
|
|
72
|
def _create_repo(self, wire, create, compatible_version):
|
|
74
|
def _create_repo(self, wire, create, compatible_version):
|
|
73
|
path = svn.core.svn_path_canonicalize(wire['path'])
|
|
75
|
path = svn.core.svn_path_canonicalize(wire['path'])
|
|
74
|
if create:
|
|
76
|
if create:
|
|
75
|
fs_config = {'compatible-version': current_compatible_version}
|
|
77
|
fs_config = {'compatible-version': current_compatible_version}
|
|
76
|
if compatible_version:
|
|
78
|
if compatible_version:
|
|
77
|
|
|
79
|
|
|
78
|
compatible_version_string = \
|
|
80
|
compatible_version_string = \
|
|
79
|
svn_compatible_versions_map.get(compatible_version) \
|
|
81
|
svn_compatible_versions_map.get(compatible_version) \
|
|
80
|
or compatible_version
|
|
82
|
or compatible_version
|
|
81
|
fs_config['compatible-version'] = compatible_version_string
|
|
83
|
fs_config['compatible-version'] = compatible_version_string
|
|
82
|
|
|
84
|
|
|
83
|
log.debug('Create SVN repo with config "%s"', fs_config)
|
|
85
|
log.debug('Create SVN repo with config "%s"', fs_config)
|
|
84
|
repo = svn.repos.create(path, "", "", None, fs_config)
|
|
86
|
repo = svn.repos.create(path, "", "", None, fs_config)
|
|
85
|
else:
|
|
87
|
else:
|
|
86
|
repo = svn.repos.open(path)
|
|
88
|
repo = svn.repos.open(path)
|
|
87
|
|
|
89
|
|
|
88
|
log.debug('Got SVN object: %s', repo)
|
|
90
|
log.debug('Got SVN object: %s', repo)
|
|
89
|
return repo
|
|
91
|
return repo
|
|
90
|
|
|
92
|
|
|
91
|
def repo(self, wire, create=False, compatible_version=None):
|
|
93
|
def repo(self, wire, create=False, compatible_version=None):
|
|
92
|
"""
|
|
94
|
"""
|
|
93
|
Get a repository instance for the given path.
|
|
95
|
Get a repository instance for the given path.
|
|
94
|
"""
|
|
96
|
"""
|
|
95
|
return self._create_repo(wire, create, compatible_version)
|
|
97
|
return self._create_repo(wire, create, compatible_version)
|
|
96
|
|
|
98
|
|
|
97
|
|
|
99
|
|
|
98
|
NODE_TYPE_MAPPING = {
|
|
100
|
NODE_TYPE_MAPPING = {
|
|
99
|
svn.core.svn_node_file: 'file',
|
|
101
|
svn.core.svn_node_file: 'file',
|
|
100
|
svn.core.svn_node_dir: 'dir',
|
|
102
|
svn.core.svn_node_dir: 'dir',
|
|
101
|
}
|
|
103
|
}
|
|
102
|
|
|
104
|
|
|
103
|
|
|
105
|
|
|
104
|
class SvnRemote(RemoteBase):
|
|
106
|
class SvnRemote(RemoteBase):
|
|
105
|
|
|
107
|
|
|
106
|
def __init__(self, factory, hg_factory=None):
|
|
108
|
def __init__(self, factory, hg_factory=None):
|
|
107
|
self._factory = factory
|
|
109
|
self._factory = factory
|
|
108
|
# TODO: Remove once we do not use internal Mercurial objects anymore
|
|
|
|
|
109
|
# for subversion
|
|
|
|
|
110
|
self._hg_factory = hg_factory
|
|
|
|
|
111
|
|
|
110
|
|
|
112
|
@reraise_safe_exceptions
|
|
111
|
@reraise_safe_exceptions
|
|
113
|
def discover_svn_version(self):
|
|
112
|
def discover_svn_version(self):
|
|
114
|
try:
|
|
113
|
try:
|
|
115
|
import svn.core
|
|
114
|
import svn.core
|
|
116
|
svn_ver = svn.core.SVN_VERSION
|
|
115
|
svn_ver = svn.core.SVN_VERSION
|
|
117
|
except ImportError:
|
|
116
|
except ImportError:
|
|
118
|
svn_ver = None
|
|
117
|
svn_ver = None
|
|
119
|
return svn_ver
|
|
118
|
return svn_ver
|
|
120
|
|
|
119
|
|
|
121
|
@reraise_safe_exceptions
|
|
120
|
@reraise_safe_exceptions
|
|
122
|
def is_empty(self, wire):
|
|
121
|
def is_empty(self, wire):
|
|
123
|
|
|
122
|
|
|
124
|
try:
|
|
123
|
try:
|
|
125
|
return self.lookup(wire, -1) == 0
|
|
124
|
return self.lookup(wire, -1) == 0
|
|
126
|
except Exception:
|
|
125
|
except Exception:
|
|
127
|
log.exception("failed to read object_store")
|
|
126
|
log.exception("failed to read object_store")
|
|
128
|
return False
|
|
127
|
return False
|
|
129
|
|
|
128
|
|
|
130
|
def check_url(self, url, config_items):
|
|
129
|
def check_url(self, url):
|
|
131
|
# this can throw exception if not installed, but we detect this
|
|
|
|
|
132
|
from hgsubversion import svnrepo
|
|
|
|
|
133
|
|
|
130
|
|
|
134
|
baseui = self._hg_factory._create_config(config_items)
|
|
|
|
|
135
|
# uuid function get's only valid UUID from proper repo, else
|
|
131
|
# uuid function get's only valid UUID from proper repo, else
|
|
136
|
# throws exception
|
|
132
|
# throws exception
|
|
|
|
|
133
|
username, password, src_url = self.get_url_and_credentials(url)
|
|
137
|
try:
|
|
134
|
try:
|
|
138
|
svnrepo.svnremoterepo(baseui, url).svn.uuid
|
|
135
|
svnremoterepo(username, password, src_url).svn().uuid
|
|
139
|
except Exception:
|
|
136
|
except Exception:
|
|
140
|
tb = traceback.format_exc()
|
|
137
|
tb = traceback.format_exc()
|
|
141
|
log.debug("Invalid Subversion url: `%s`, tb: %s", url, tb)
|
|
138
|
log.debug("Invalid Subversion url: `%s`, tb: %s", url, tb)
|
|
142
|
raise URLError(
|
|
139
|
raise URLError(
|
|
143
|
'"%s" is not a valid Subversion source url.' % (url, ))
|
|
140
|
'"%s" is not a valid Subversion source url.' % (url, ))
|
|
144
|
return True
|
|
141
|
return True
|
|
145
|
|
|
142
|
|
|
146
|
def is_path_valid_repository(self, wire, path):
|
|
143
|
def is_path_valid_repository(self, wire, path):
|
|
147
|
|
|
144
|
|
|
148
|
# NOTE(marcink): short circuit the check for SVN repo
|
|
145
|
# NOTE(marcink): short circuit the check for SVN repo
|
|
149
|
# the repos.open might be expensive to check, but we have one cheap
|
|
146
|
# the repos.open might be expensive to check, but we have one cheap
|
|
150
|
# pre condition that we can use, to check for 'format' file
|
|
147
|
# pre condition that we can use, to check for 'format' file
|
|
151
|
|
|
148
|
|
|
152
|
if not os.path.isfile(os.path.join(path, 'format')):
|
|
149
|
if not os.path.isfile(os.path.join(path, 'format')):
|
|
153
|
return False
|
|
150
|
return False
|
|
154
|
|
|
151
|
|
|
155
|
try:
|
|
152
|
try:
|
|
156
|
svn.repos.open(path)
|
|
153
|
svn.repos.open(path)
|
|
157
|
except svn.core.SubversionException:
|
|
154
|
except svn.core.SubversionException:
|
|
158
|
tb = traceback.format_exc()
|
|
155
|
tb = traceback.format_exc()
|
|
159
|
log.debug("Invalid Subversion path `%s`, tb: %s", path, tb)
|
|
156
|
log.debug("Invalid Subversion path `%s`, tb: %s", path, tb)
|
|
160
|
return False
|
|
157
|
return False
|
|
161
|
return True
|
|
158
|
return True
|
|
162
|
|
|
159
|
|
|
163
|
@reraise_safe_exceptions
|
|
160
|
@reraise_safe_exceptions
|
|
164
|
def verify(self, wire,):
|
|
161
|
def verify(self, wire,):
|
|
165
|
repo_path = wire['path']
|
|
162
|
repo_path = wire['path']
|
|
166
|
if not self.is_path_valid_repository(wire, repo_path):
|
|
163
|
if not self.is_path_valid_repository(wire, repo_path):
|
|
167
|
raise Exception(
|
|
164
|
raise Exception(
|
|
168
|
"Path %s is not a valid Subversion repository." % repo_path)
|
|
165
|
"Path %s is not a valid Subversion repository." % repo_path)
|
|
169
|
|
|
166
|
|
|
170
|
cmd = ['svnadmin', 'info', repo_path]
|
|
167
|
cmd = ['svnadmin', 'info', repo_path]
|
|
171
|
stdout, stderr = subprocessio.run_command(cmd)
|
|
168
|
stdout, stderr = subprocessio.run_command(cmd)
|
|
172
|
return stdout
|
|
169
|
return stdout
|
|
173
|
|
|
170
|
|
|
174
|
def lookup(self, wire, revision):
|
|
171
|
def lookup(self, wire, revision):
|
|
175
|
if revision not in [-1, None, 'HEAD']:
|
|
172
|
if revision not in [-1, None, 'HEAD']:
|
|
176
|
raise NotImplementedError
|
|
173
|
raise NotImplementedError
|
|
177
|
repo = self._factory.repo(wire)
|
|
174
|
repo = self._factory.repo(wire)
|
|
178
|
fs_ptr = svn.repos.fs(repo)
|
|
175
|
fs_ptr = svn.repos.fs(repo)
|
|
179
|
head = svn.fs.youngest_rev(fs_ptr)
|
|
176
|
head = svn.fs.youngest_rev(fs_ptr)
|
|
180
|
return head
|
|
177
|
return head
|
|
181
|
|
|
178
|
|
|
182
|
def lookup_interval(self, wire, start_ts, end_ts):
|
|
179
|
def lookup_interval(self, wire, start_ts, end_ts):
|
|
183
|
repo = self._factory.repo(wire)
|
|
180
|
repo = self._factory.repo(wire)
|
|
184
|
fsobj = svn.repos.fs(repo)
|
|
181
|
fsobj = svn.repos.fs(repo)
|
|
185
|
start_rev = None
|
|
182
|
start_rev = None
|
|
186
|
end_rev = None
|
|
183
|
end_rev = None
|
|
187
|
if start_ts:
|
|
184
|
if start_ts:
|
|
188
|
start_ts_svn = apr_time_t(start_ts)
|
|
185
|
start_ts_svn = apr_time_t(start_ts)
|
|
189
|
start_rev = svn.repos.dated_revision(repo, start_ts_svn) + 1
|
|
186
|
start_rev = svn.repos.dated_revision(repo, start_ts_svn) + 1
|
|
190
|
else:
|
|
187
|
else:
|
|
191
|
start_rev = 1
|
|
188
|
start_rev = 1
|
|
192
|
if end_ts:
|
|
189
|
if end_ts:
|
|
193
|
end_ts_svn = apr_time_t(end_ts)
|
|
190
|
end_ts_svn = apr_time_t(end_ts)
|
|
194
|
end_rev = svn.repos.dated_revision(repo, end_ts_svn)
|
|
191
|
end_rev = svn.repos.dated_revision(repo, end_ts_svn)
|
|
195
|
else:
|
|
192
|
else:
|
|
196
|
end_rev = svn.fs.youngest_rev(fsobj)
|
|
193
|
end_rev = svn.fs.youngest_rev(fsobj)
|
|
197
|
return start_rev, end_rev
|
|
194
|
return start_rev, end_rev
|
|
198
|
|
|
195
|
|
|
199
|
def revision_properties(self, wire, revision):
|
|
196
|
def revision_properties(self, wire, revision):
|
|
200
|
|
|
197
|
|
|
201
|
cache_on, context_uid, repo_id = self._cache_on(wire)
|
|
198
|
cache_on, context_uid, repo_id = self._cache_on(wire)
|
|
202
|
region = self._region(wire)
|
|
199
|
region = self._region(wire)
|
|
203
|
@region.conditional_cache_on_arguments(condition=cache_on)
|
|
200
|
@region.conditional_cache_on_arguments(condition=cache_on)
|
|
204
|
def _revision_properties(_repo_id, _revision):
|
|
201
|
def _revision_properties(_repo_id, _revision):
|
|
205
|
repo = self._factory.repo(wire)
|
|
202
|
repo = self._factory.repo(wire)
|
|
206
|
fs_ptr = svn.repos.fs(repo)
|
|
203
|
fs_ptr = svn.repos.fs(repo)
|
|
207
|
return svn.fs.revision_proplist(fs_ptr, revision)
|
|
204
|
return svn.fs.revision_proplist(fs_ptr, revision)
|
|
208
|
return _revision_properties(repo_id, revision)
|
|
205
|
return _revision_properties(repo_id, revision)
|
|
209
|
|
|
206
|
|
|
210
|
def revision_changes(self, wire, revision):
|
|
207
|
def revision_changes(self, wire, revision):
|
|
211
|
|
|
208
|
|
|
212
|
repo = self._factory.repo(wire)
|
|
209
|
repo = self._factory.repo(wire)
|
|
213
|
fsobj = svn.repos.fs(repo)
|
|
210
|
fsobj = svn.repos.fs(repo)
|
|
214
|
rev_root = svn.fs.revision_root(fsobj, revision)
|
|
211
|
rev_root = svn.fs.revision_root(fsobj, revision)
|
|
215
|
|
|
212
|
|
|
216
|
editor = svn.repos.ChangeCollector(fsobj, rev_root)
|
|
213
|
editor = svn.repos.ChangeCollector(fsobj, rev_root)
|
|
217
|
editor_ptr, editor_baton = svn.delta.make_editor(editor)
|
|
214
|
editor_ptr, editor_baton = svn.delta.make_editor(editor)
|
|
218
|
base_dir = ""
|
|
215
|
base_dir = ""
|
|
219
|
send_deltas = False
|
|
216
|
send_deltas = False
|
|
220
|
svn.repos.replay2(
|
|
217
|
svn.repos.replay2(
|
|
221
|
rev_root, base_dir, svn.core.SVN_INVALID_REVNUM, send_deltas,
|
|
218
|
rev_root, base_dir, svn.core.SVN_INVALID_REVNUM, send_deltas,
|
|
222
|
editor_ptr, editor_baton, None)
|
|
219
|
editor_ptr, editor_baton, None)
|
|
223
|
|
|
220
|
|
|
224
|
added = []
|
|
221
|
added = []
|
|
225
|
changed = []
|
|
222
|
changed = []
|
|
226
|
removed = []
|
|
223
|
removed = []
|
|
227
|
|
|
224
|
|
|
228
|
# TODO: CHANGE_ACTION_REPLACE: Figure out where it belongs
|
|
225
|
# TODO: CHANGE_ACTION_REPLACE: Figure out where it belongs
|
|
229
|
for path, change in editor.changes.items():
|
|
226
|
for path, change in editor.changes.items():
|
|
230
|
# TODO: Decide what to do with directory nodes. Subversion can add
|
|
227
|
# TODO: Decide what to do with directory nodes. Subversion can add
|
|
231
|
# empty directories.
|
|
228
|
# empty directories.
|
|
232
|
|
|
229
|
|
|
233
|
if change.item_kind == svn.core.svn_node_dir:
|
|
230
|
if change.item_kind == svn.core.svn_node_dir:
|
|
234
|
continue
|
|
231
|
continue
|
|
235
|
if change.action in [svn.repos.CHANGE_ACTION_ADD]:
|
|
232
|
if change.action in [svn.repos.CHANGE_ACTION_ADD]:
|
|
236
|
added.append(path)
|
|
233
|
added.append(path)
|
|
237
|
elif change.action in [svn.repos.CHANGE_ACTION_MODIFY,
|
|
234
|
elif change.action in [svn.repos.CHANGE_ACTION_MODIFY,
|
|
238
|
svn.repos.CHANGE_ACTION_REPLACE]:
|
|
235
|
svn.repos.CHANGE_ACTION_REPLACE]:
|
|
239
|
changed.append(path)
|
|
236
|
changed.append(path)
|
|
240
|
elif change.action in [svn.repos.CHANGE_ACTION_DELETE]:
|
|
237
|
elif change.action in [svn.repos.CHANGE_ACTION_DELETE]:
|
|
241
|
removed.append(path)
|
|
238
|
removed.append(path)
|
|
242
|
else:
|
|
239
|
else:
|
|
243
|
raise NotImplementedError(
|
|
240
|
raise NotImplementedError(
|
|
244
|
"Action %s not supported on path %s" % (
|
|
241
|
"Action %s not supported on path %s" % (
|
|
245
|
change.action, path))
|
|
242
|
change.action, path))
|
|
246
|
|
|
243
|
|
|
247
|
changes = {
|
|
244
|
changes = {
|
|
248
|
'added': added,
|
|
245
|
'added': added,
|
|
249
|
'changed': changed,
|
|
246
|
'changed': changed,
|
|
250
|
'removed': removed,
|
|
247
|
'removed': removed,
|
|
251
|
}
|
|
248
|
}
|
|
252
|
return changes
|
|
249
|
return changes
|
|
253
|
|
|
250
|
|
|
254
|
@reraise_safe_exceptions
|
|
251
|
@reraise_safe_exceptions
|
|
255
|
def node_history(self, wire, path, revision, limit):
|
|
252
|
def node_history(self, wire, path, revision, limit):
|
|
256
|
cache_on, context_uid, repo_id = self._cache_on(wire)
|
|
253
|
cache_on, context_uid, repo_id = self._cache_on(wire)
|
|
257
|
region = self._region(wire)
|
|
254
|
region = self._region(wire)
|
|
258
|
@region.conditional_cache_on_arguments(condition=cache_on)
|
|
255
|
@region.conditional_cache_on_arguments(condition=cache_on)
|
|
259
|
def _assert_correct_path(_context_uid, _repo_id, _path, _revision, _limit):
|
|
256
|
def _assert_correct_path(_context_uid, _repo_id, _path, _revision, _limit):
|
|
260
|
cross_copies = False
|
|
257
|
cross_copies = False
|
|
261
|
repo = self._factory.repo(wire)
|
|
258
|
repo = self._factory.repo(wire)
|
|
262
|
fsobj = svn.repos.fs(repo)
|
|
259
|
fsobj = svn.repos.fs(repo)
|
|
263
|
rev_root = svn.fs.revision_root(fsobj, revision)
|
|
260
|
rev_root = svn.fs.revision_root(fsobj, revision)
|
|
264
|
|
|
261
|
|
|
265
|
history_revisions = []
|
|
262
|
history_revisions = []
|
|
266
|
history = svn.fs.node_history(rev_root, path)
|
|
263
|
history = svn.fs.node_history(rev_root, path)
|
|
267
|
history = svn.fs.history_prev(history, cross_copies)
|
|
264
|
history = svn.fs.history_prev(history, cross_copies)
|
|
268
|
while history:
|
|
265
|
while history:
|
|
269
|
__, node_revision = svn.fs.history_location(history)
|
|
266
|
__, node_revision = svn.fs.history_location(history)
|
|
270
|
history_revisions.append(node_revision)
|
|
267
|
history_revisions.append(node_revision)
|
|
271
|
if limit and len(history_revisions) >= limit:
|
|
268
|
if limit and len(history_revisions) >= limit:
|
|
272
|
break
|
|
269
|
break
|
|
273
|
history = svn.fs.history_prev(history, cross_copies)
|
|
270
|
history = svn.fs.history_prev(history, cross_copies)
|
|
274
|
return history_revisions
|
|
271
|
return history_revisions
|
|
275
|
return _assert_correct_path(context_uid, repo_id, path, revision, limit)
|
|
272
|
return _assert_correct_path(context_uid, repo_id, path, revision, limit)
|
|
276
|
|
|
273
|
|
|
277
|
def node_properties(self, wire, path, revision):
|
|
274
|
def node_properties(self, wire, path, revision):
|
|
278
|
cache_on, context_uid, repo_id = self._cache_on(wire)
|
|
275
|
cache_on, context_uid, repo_id = self._cache_on(wire)
|
|
279
|
region = self._region(wire)
|
|
276
|
region = self._region(wire)
|
|
280
|
@region.conditional_cache_on_arguments(condition=cache_on)
|
|
277
|
@region.conditional_cache_on_arguments(condition=cache_on)
|
|
281
|
def _node_properties(_repo_id, _path, _revision):
|
|
278
|
def _node_properties(_repo_id, _path, _revision):
|
|
282
|
repo = self._factory.repo(wire)
|
|
279
|
repo = self._factory.repo(wire)
|
|
283
|
fsobj = svn.repos.fs(repo)
|
|
280
|
fsobj = svn.repos.fs(repo)
|
|
284
|
rev_root = svn.fs.revision_root(fsobj, revision)
|
|
281
|
rev_root = svn.fs.revision_root(fsobj, revision)
|
|
285
|
return svn.fs.node_proplist(rev_root, path)
|
|
282
|
return svn.fs.node_proplist(rev_root, path)
|
|
286
|
return _node_properties(repo_id, path, revision)
|
|
283
|
return _node_properties(repo_id, path, revision)
|
|
287
|
|
|
284
|
|
|
288
|
def file_annotate(self, wire, path, revision):
|
|
285
|
def file_annotate(self, wire, path, revision):
|
|
289
|
abs_path = 'file://' + urllib.pathname2url(
|
|
286
|
abs_path = 'file://' + urllib.pathname2url(
|
|
290
|
vcspath.join(wire['path'], path))
|
|
287
|
vcspath.join(wire['path'], path))
|
|
291
|
file_uri = svn.core.svn_path_canonicalize(abs_path)
|
|
288
|
file_uri = svn.core.svn_path_canonicalize(abs_path)
|
|
292
|
|
|
289
|
|
|
293
|
start_rev = svn_opt_revision_value_t(0)
|
|
290
|
start_rev = svn_opt_revision_value_t(0)
|
|
294
|
peg_rev = svn_opt_revision_value_t(revision)
|
|
291
|
peg_rev = svn_opt_revision_value_t(revision)
|
|
295
|
end_rev = peg_rev
|
|
292
|
end_rev = peg_rev
|
|
296
|
|
|
293
|
|
|
297
|
annotations = []
|
|
294
|
annotations = []
|
|
298
|
|
|
295
|
|
|
299
|
def receiver(line_no, revision, author, date, line, pool):
|
|
296
|
def receiver(line_no, revision, author, date, line, pool):
|
|
300
|
annotations.append((line_no, revision, line))
|
|
297
|
annotations.append((line_no, revision, line))
|
|
301
|
|
|
298
|
|
|
302
|
# TODO: Cannot use blame5, missing typemap function in the swig code
|
|
299
|
# TODO: Cannot use blame5, missing typemap function in the swig code
|
|
303
|
try:
|
|
300
|
try:
|
|
304
|
svn.client.blame2(
|
|
301
|
svn.client.blame2(
|
|
305
|
file_uri, peg_rev, start_rev, end_rev,
|
|
302
|
file_uri, peg_rev, start_rev, end_rev,
|
|
306
|
receiver, svn.client.create_context())
|
|
303
|
receiver, svn.client.create_context())
|
|
307
|
except svn.core.SubversionException as exc:
|
|
304
|
except svn.core.SubversionException as exc:
|
|
308
|
log.exception("Error during blame operation.")
|
|
305
|
log.exception("Error during blame operation.")
|
|
309
|
raise Exception(
|
|
306
|
raise Exception(
|
|
310
|
"Blame not supported or file does not exist at path %s. "
|
|
307
|
"Blame not supported or file does not exist at path %s. "
|
|
311
|
"Error %s." % (path, exc))
|
|
308
|
"Error %s." % (path, exc))
|
|
312
|
|
|
309
|
|
|
313
|
return annotations
|
|
310
|
return annotations
|
|
314
|
|
|
311
|
|
|
315
|
def get_node_type(self, wire, path, revision=None):
|
|
312
|
def get_node_type(self, wire, path, revision=None):
|
|
316
|
|
|
313
|
|
|
317
|
cache_on, context_uid, repo_id = self._cache_on(wire)
|
|
314
|
cache_on, context_uid, repo_id = self._cache_on(wire)
|
|
318
|
region = self._region(wire)
|
|
315
|
region = self._region(wire)
|
|
319
|
@region.conditional_cache_on_arguments(condition=cache_on)
|
|
316
|
@region.conditional_cache_on_arguments(condition=cache_on)
|
|
320
|
def _get_node_type(_repo_id, _path, _revision):
|
|
317
|
def _get_node_type(_repo_id, _path, _revision):
|
|
321
|
repo = self._factory.repo(wire)
|
|
318
|
repo = self._factory.repo(wire)
|
|
322
|
fs_ptr = svn.repos.fs(repo)
|
|
319
|
fs_ptr = svn.repos.fs(repo)
|
|
323
|
if _revision is None:
|
|
320
|
if _revision is None:
|
|
324
|
_revision = svn.fs.youngest_rev(fs_ptr)
|
|
321
|
_revision = svn.fs.youngest_rev(fs_ptr)
|
|
325
|
root = svn.fs.revision_root(fs_ptr, _revision)
|
|
322
|
root = svn.fs.revision_root(fs_ptr, _revision)
|
|
326
|
node = svn.fs.check_path(root, path)
|
|
323
|
node = svn.fs.check_path(root, path)
|
|
327
|
return NODE_TYPE_MAPPING.get(node, None)
|
|
324
|
return NODE_TYPE_MAPPING.get(node, None)
|
|
328
|
return _get_node_type(repo_id, path, revision)
|
|
325
|
return _get_node_type(repo_id, path, revision)
|
|
329
|
|
|
326
|
|
|
330
|
def get_nodes(self, wire, path, revision=None):
|
|
327
|
def get_nodes(self, wire, path, revision=None):
|
|
331
|
|
|
328
|
|
|
332
|
cache_on, context_uid, repo_id = self._cache_on(wire)
|
|
329
|
cache_on, context_uid, repo_id = self._cache_on(wire)
|
|
333
|
region = self._region(wire)
|
|
330
|
region = self._region(wire)
|
|
334
|
@region.conditional_cache_on_arguments(condition=cache_on)
|
|
331
|
@region.conditional_cache_on_arguments(condition=cache_on)
|
|
335
|
def _get_nodes(_repo_id, _path, _revision):
|
|
332
|
def _get_nodes(_repo_id, _path, _revision):
|
|
336
|
repo = self._factory.repo(wire)
|
|
333
|
repo = self._factory.repo(wire)
|
|
337
|
fsobj = svn.repos.fs(repo)
|
|
334
|
fsobj = svn.repos.fs(repo)
|
|
338
|
if _revision is None:
|
|
335
|
if _revision is None:
|
|
339
|
_revision = svn.fs.youngest_rev(fsobj)
|
|
336
|
_revision = svn.fs.youngest_rev(fsobj)
|
|
340
|
root = svn.fs.revision_root(fsobj, _revision)
|
|
337
|
root = svn.fs.revision_root(fsobj, _revision)
|
|
341
|
entries = svn.fs.dir_entries(root, path)
|
|
338
|
entries = svn.fs.dir_entries(root, path)
|
|
342
|
result = []
|
|
339
|
result = []
|
|
343
|
for entry_path, entry_info in entries.items():
|
|
340
|
for entry_path, entry_info in entries.items():
|
|
344
|
result.append(
|
|
341
|
result.append(
|
|
345
|
(entry_path, NODE_TYPE_MAPPING.get(entry_info.kind, None)))
|
|
342
|
(entry_path, NODE_TYPE_MAPPING.get(entry_info.kind, None)))
|
|
346
|
return result
|
|
343
|
return result
|
|
347
|
return _get_nodes(repo_id, path, revision)
|
|
344
|
return _get_nodes(repo_id, path, revision)
|
|
348
|
|
|
345
|
|
|
349
|
def get_file_content(self, wire, path, rev=None):
|
|
346
|
def get_file_content(self, wire, path, rev=None):
|
|
350
|
repo = self._factory.repo(wire)
|
|
347
|
repo = self._factory.repo(wire)
|
|
351
|
fsobj = svn.repos.fs(repo)
|
|
348
|
fsobj = svn.repos.fs(repo)
|
|
352
|
if rev is None:
|
|
349
|
if rev is None:
|
|
353
|
rev = svn.fs.youngest_revision(fsobj)
|
|
350
|
rev = svn.fs.youngest_revision(fsobj)
|
|
354
|
root = svn.fs.revision_root(fsobj, rev)
|
|
351
|
root = svn.fs.revision_root(fsobj, rev)
|
|
355
|
content = svn.core.Stream(svn.fs.file_contents(root, path))
|
|
352
|
content = svn.core.Stream(svn.fs.file_contents(root, path))
|
|
356
|
return content.read()
|
|
353
|
return content.read()
|
|
357
|
|
|
354
|
|
|
358
|
def get_file_size(self, wire, path, revision=None):
|
|
355
|
def get_file_size(self, wire, path, revision=None):
|
|
359
|
|
|
356
|
|
|
360
|
cache_on, context_uid, repo_id = self._cache_on(wire)
|
|
357
|
cache_on, context_uid, repo_id = self._cache_on(wire)
|
|
361
|
region = self._region(wire)
|
|
358
|
region = self._region(wire)
|
|
|
|
|
359
|
|
|
362
|
@region.conditional_cache_on_arguments(condition=cache_on)
|
|
360
|
@region.conditional_cache_on_arguments(condition=cache_on)
|
|
363
|
def _get_file_size(_repo_id, _path, _revision):
|
|
361
|
def _get_file_size(_repo_id, _path, _revision):
|
|
364
|
repo = self._factory.repo(wire)
|
|
362
|
repo = self._factory.repo(wire)
|
|
365
|
fsobj = svn.repos.fs(repo)
|
|
363
|
fsobj = svn.repos.fs(repo)
|
|
366
|
if _revision is None:
|
|
364
|
if _revision is None:
|
|
367
|
_revision = svn.fs.youngest_revision(fsobj)
|
|
365
|
_revision = svn.fs.youngest_revision(fsobj)
|
|
368
|
root = svn.fs.revision_root(fsobj, _revision)
|
|
366
|
root = svn.fs.revision_root(fsobj, _revision)
|
|
369
|
size = svn.fs.file_length(root, path)
|
|
367
|
size = svn.fs.file_length(root, path)
|
|
370
|
return size
|
|
368
|
return size
|
|
371
|
return _get_file_size(repo_id, path, revision)
|
|
369
|
return _get_file_size(repo_id, path, revision)
|
|
372
|
|
|
370
|
|
|
373
|
def create_repository(self, wire, compatible_version=None):
|
|
371
|
def create_repository(self, wire, compatible_version=None):
|
|
374
|
log.info('Creating Subversion repository in path "%s"', wire['path'])
|
|
372
|
log.info('Creating Subversion repository in path "%s"', wire['path'])
|
|
375
|
self._factory.repo(wire, create=True,
|
|
373
|
self._factory.repo(wire, create=True,
|
|
376
|
compatible_version=compatible_version)
|
|
374
|
compatible_version=compatible_version)
|
|
377
|
|
|
375
|
|
|
378
|
def get_url_and_credentials(self, src_url):
|
|
376
|
def get_url_and_credentials(self, src_url):
|
|
379
|
obj = urllib.parse.urlparse(src_url)
|
|
377
|
obj = urllib.parse.urlparse(src_url)
|
|
380
|
username = obj.username or None
|
|
378
|
username = obj.username or None
|
|
381
|
password = obj.password or None
|
|
379
|
password = obj.password or None
|
|
382
|
return username, password, src_url
|
|
380
|
return username, password, src_url
|
|
383
|
|
|
381
|
|
|
384
|
def import_remote_repository(self, wire, src_url):
|
|
382
|
def import_remote_repository(self, wire, src_url):
|
|
385
|
repo_path = wire['path']
|
|
383
|
repo_path = wire['path']
|
|
386
|
if not self.is_path_valid_repository(wire, repo_path):
|
|
384
|
if not self.is_path_valid_repository(wire, repo_path):
|
|
387
|
raise Exception(
|
|
385
|
raise Exception(
|
|
388
|
"Path %s is not a valid Subversion repository." % repo_path)
|
|
386
|
"Path %s is not a valid Subversion repository." % repo_path)
|
|
389
|
|
|
387
|
|
|
390
|
username, password, src_url = self.get_url_and_credentials(src_url)
|
|
388
|
username, password, src_url = self.get_url_and_credentials(src_url)
|
|
391
|
rdump_cmd = ['svnrdump', 'dump', '--non-interactive',
|
|
389
|
rdump_cmd = ['svnrdump', 'dump', '--non-interactive',
|
|
392
|
'--trust-server-cert-failures=unknown-ca']
|
|
390
|
'--trust-server-cert-failures=unknown-ca']
|
|
393
|
if username and password:
|
|
391
|
if username and password:
|
|
394
|
rdump_cmd += ['--username', username, '--password', password]
|
|
392
|
rdump_cmd += ['--username', username, '--password', password]
|
|
395
|
rdump_cmd += [src_url]
|
|
393
|
rdump_cmd += [src_url]
|
|
396
|
|
|
394
|
|
|
397
|
rdump = subprocess.Popen(
|
|
395
|
rdump = subprocess.Popen(
|
|
398
|
rdump_cmd,
|
|
396
|
rdump_cmd,
|
|
399
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
397
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
400
|
load = subprocess.Popen(
|
|
398
|
load = subprocess.Popen(
|
|
401
|
['svnadmin', 'load', repo_path], stdin=rdump.stdout)
|
|
399
|
['svnadmin', 'load', repo_path], stdin=rdump.stdout)
|
|
402
|
|
|
400
|
|
|
403
|
# TODO: johbo: This can be a very long operation, might be better
|
|
401
|
# TODO: johbo: This can be a very long operation, might be better
|
|
404
|
# to track some kind of status and provide an api to check if the
|
|
402
|
# to track some kind of status and provide an api to check if the
|
|
405
|
# import is done.
|
|
403
|
# import is done.
|
|
406
|
rdump.wait()
|
|
404
|
rdump.wait()
|
|
407
|
load.wait()
|
|
405
|
load.wait()
|
|
408
|
|
|
406
|
|
|
409
|
log.debug('Return process ended with code: %s', rdump.returncode)
|
|
407
|
log.debug('Return process ended with code: %s', rdump.returncode)
|
|
410
|
if rdump.returncode != 0:
|
|
408
|
if rdump.returncode != 0:
|
|
411
|
errors = rdump.stderr.read()
|
|
409
|
errors = rdump.stderr.read()
|
|
412
|
log.error('svnrdump dump failed: statuscode %s: message: %s',
|
|
410
|
log.error('svnrdump dump failed: statuscode %s: message: %s',
|
|
413
|
rdump.returncode, errors)
|
|
411
|
rdump.returncode, errors)
|
|
414
|
reason = 'UNKNOWN'
|
|
412
|
reason = 'UNKNOWN'
|
|
415
|
if 'svnrdump: E230001:' in errors:
|
|
413
|
if 'svnrdump: E230001:' in errors:
|
|
416
|
reason = 'INVALID_CERTIFICATE'
|
|
414
|
reason = 'INVALID_CERTIFICATE'
|
|
417
|
|
|
415
|
|
|
418
|
if reason == 'UNKNOWN':
|
|
416
|
if reason == 'UNKNOWN':
|
|
419
|
reason = 'UNKNOWN:{}'.format(errors)
|
|
417
|
reason = 'UNKNOWN:{}'.format(errors)
|
|
420
|
raise Exception(
|
|
418
|
raise Exception(
|
|
421
|
'Failed to dump the remote repository from %s. Reason:%s' % (
|
|
419
|
'Failed to dump the remote repository from %s. Reason:%s' % (
|
|
422
|
src_url, reason))
|
|
420
|
src_url, reason))
|
|
423
|
if load.returncode != 0:
|
|
421
|
if load.returncode != 0:
|
|
424
|
raise Exception(
|
|
422
|
raise Exception(
|
|
425
|
'Failed to load the dump of remote repository from %s.' %
|
|
423
|
'Failed to load the dump of remote repository from %s.' %
|
|
426
|
(src_url, ))
|
|
424
|
(src_url, ))
|
|
427
|
|
|
425
|
|
|
428
|
def commit(self, wire, message, author, timestamp, updated, removed):
|
|
426
|
def commit(self, wire, message, author, timestamp, updated, removed):
|
|
429
|
assert isinstance(message, str)
|
|
427
|
assert isinstance(message, str)
|
|
430
|
assert isinstance(author, str)
|
|
428
|
assert isinstance(author, str)
|
|
431
|
|
|
429
|
|
|
432
|
repo = self._factory.repo(wire)
|
|
430
|
repo = self._factory.repo(wire)
|
|
433
|
fsobj = svn.repos.fs(repo)
|
|
431
|
fsobj = svn.repos.fs(repo)
|
|
434
|
|
|
432
|
|
|
435
|
rev = svn.fs.youngest_rev(fsobj)
|
|
433
|
rev = svn.fs.youngest_rev(fsobj)
|
|
436
|
txn = svn.repos.fs_begin_txn_for_commit(repo, rev, author, message)
|
|
434
|
txn = svn.repos.fs_begin_txn_for_commit(repo, rev, author, message)
|
|
437
|
txn_root = svn.fs.txn_root(txn)
|
|
435
|
txn_root = svn.fs.txn_root(txn)
|
|
438
|
|
|
436
|
|
|
439
|
for node in updated:
|
|
437
|
for node in updated:
|
|
440
|
TxnNodeProcessor(node, txn_root).update()
|
|
438
|
TxnNodeProcessor(node, txn_root).update()
|
|
441
|
for node in removed:
|
|
439
|
for node in removed:
|
|
442
|
TxnNodeProcessor(node, txn_root).remove()
|
|
440
|
TxnNodeProcessor(node, txn_root).remove()
|
|
443
|
|
|
441
|
|
|
444
|
commit_id = svn.repos.fs_commit_txn(repo, txn)
|
|
442
|
commit_id = svn.repos.fs_commit_txn(repo, txn)
|
|
445
|
|
|
443
|
|
|
446
|
if timestamp:
|
|
444
|
if timestamp:
|
|
447
|
apr_time = apr_time_t(timestamp)
|
|
445
|
apr_time = apr_time_t(timestamp)
|
|
448
|
ts_formatted = svn.core.svn_time_to_cstring(apr_time)
|
|
446
|
ts_formatted = svn.core.svn_time_to_cstring(apr_time)
|
|
449
|
svn.fs.change_rev_prop(fsobj, commit_id, 'svn:date', ts_formatted)
|
|
447
|
svn.fs.change_rev_prop(fsobj, commit_id, 'svn:date', ts_formatted)
|
|
450
|
|
|
448
|
|
|
451
|
log.debug('Committed revision "%s" to "%s".', commit_id, wire['path'])
|
|
449
|
log.debug('Committed revision "%s" to "%s".', commit_id, wire['path'])
|
|
452
|
return commit_id
|
|
450
|
return commit_id
|
|
453
|
|
|
451
|
|
|
454
|
def diff(self, wire, rev1, rev2, path1=None, path2=None,
|
|
452
|
def diff(self, wire, rev1, rev2, path1=None, path2=None,
|
|
455
|
ignore_whitespace=False, context=3):
|
|
453
|
ignore_whitespace=False, context=3):
|
|
456
|
|
|
454
|
|
|
457
|
wire.update(cache=False)
|
|
455
|
wire.update(cache=False)
|
|
458
|
repo = self._factory.repo(wire)
|
|
456
|
repo = self._factory.repo(wire)
|
|
459
|
diff_creator = SvnDiffer(
|
|
457
|
diff_creator = SvnDiffer(
|
|
460
|
repo, rev1, path1, rev2, path2, ignore_whitespace, context)
|
|
458
|
repo, rev1, path1, rev2, path2, ignore_whitespace, context)
|
|
461
|
try:
|
|
459
|
try:
|
|
462
|
return diff_creator.generate_diff()
|
|
460
|
return diff_creator.generate_diff()
|
|
463
|
except svn.core.SubversionException as e:
|
|
461
|
except svn.core.SubversionException as e:
|
|
464
|
log.exception(
|
|
462
|
log.exception(
|
|
465
|
"Error during diff operation operation. "
|
|
463
|
"Error during diff operation operation. "
|
|
466
|
"Path might not exist %s, %s" % (path1, path2))
|
|
464
|
"Path might not exist %s, %s" % (path1, path2))
|
|
467
|
return ""
|
|
465
|
return ""
|
|
468
|
|
|
466
|
|
|
469
|
@reraise_safe_exceptions
|
|
467
|
@reraise_safe_exceptions
|
|
470
|
def is_large_file(self, wire, path):
|
|
468
|
def is_large_file(self, wire, path):
|
|
471
|
return False
|
|
469
|
return False
|
|
472
|
|
|
470
|
|
|
473
|
@reraise_safe_exceptions
|
|
471
|
@reraise_safe_exceptions
|
|
474
|
def is_binary(self, wire, rev, path):
|
|
472
|
def is_binary(self, wire, rev, path):
|
|
475
|
cache_on, context_uid, repo_id = self._cache_on(wire)
|
|
473
|
cache_on, context_uid, repo_id = self._cache_on(wire)
|
|
476
|
|
|
474
|
|
|
477
|
region = self._region(wire)
|
|
475
|
region = self._region(wire)
|
|
478
|
@region.conditional_cache_on_arguments(condition=cache_on)
|
|
476
|
@region.conditional_cache_on_arguments(condition=cache_on)
|
|
479
|
def _is_binary(_repo_id, _rev, _path):
|
|
477
|
def _is_binary(_repo_id, _rev, _path):
|
|
480
|
raw_bytes = self.get_file_content(wire, path, rev)
|
|
478
|
raw_bytes = self.get_file_content(wire, path, rev)
|
|
481
|
return raw_bytes and '\0' in raw_bytes
|
|
479
|
return raw_bytes and '\0' in raw_bytes
|
|
482
|
|
|
480
|
|
|
483
|
return _is_binary(repo_id, rev, path)
|
|
481
|
return _is_binary(repo_id, rev, path)
|
|
484
|
|
|
482
|
|
|
485
|
@reraise_safe_exceptions
|
|
483
|
@reraise_safe_exceptions
|
|
486
|
def run_svn_command(self, wire, cmd, **opts):
|
|
484
|
def run_svn_command(self, wire, cmd, **opts):
|
|
487
|
path = wire.get('path', None)
|
|
485
|
path = wire.get('path', None)
|
|
488
|
|
|
486
|
|
|
489
|
if path and os.path.isdir(path):
|
|
487
|
if path and os.path.isdir(path):
|
|
490
|
opts['cwd'] = path
|
|
488
|
opts['cwd'] = path
|
|
491
|
|
|
489
|
|
|
492
|
safe_call = opts.pop('_safe', False)
|
|
490
|
safe_call = opts.pop('_safe', False)
|
|
493
|
|
|
491
|
|
|
494
|
svnenv = os.environ.copy()
|
|
492
|
svnenv = os.environ.copy()
|
|
495
|
svnenv.update(opts.pop('extra_env', {}))
|
|
493
|
svnenv.update(opts.pop('extra_env', {}))
|
|
496
|
|
|
494
|
|
|
497
|
_opts = {'env': svnenv, 'shell': False}
|
|
495
|
_opts = {'env': svnenv, 'shell': False}
|
|
498
|
|
|
496
|
|
|
499
|
try:
|
|
497
|
try:
|
|
500
|
_opts.update(opts)
|
|
498
|
_opts.update(opts)
|
|
501
|
p = subprocessio.SubprocessIOChunker(cmd, **_opts)
|
|
499
|
p = subprocessio.SubprocessIOChunker(cmd, **_opts)
|
|
502
|
|
|
500
|
|
|
503
|
return ''.join(p), ''.join(p.error)
|
|
501
|
return ''.join(p), ''.join(p.error)
|
|
504
|
except (EnvironmentError, OSError) as err:
|
|
502
|
except (EnvironmentError, OSError) as err:
|
|
505
|
if safe_call:
|
|
503
|
if safe_call:
|
|
506
|
return '', safe_str(err).strip()
|
|
504
|
return '', safe_str(err).strip()
|
|
507
|
else:
|
|
505
|
else:
|
|
508
|
cmd = ' '.join(cmd) # human friendly CMD
|
|
506
|
cmd = ' '.join(cmd) # human friendly CMD
|
|
509
|
tb_err = ("Couldn't run svn command (%s).\n"
|
|
507
|
tb_err = ("Couldn't run svn command (%s).\n"
|
|
510
|
"Original error was:%s\n"
|
|
508
|
"Original error was:%s\n"
|
|
511
|
"Call options:%s\n"
|
|
509
|
"Call options:%s\n"
|
|
512
|
% (cmd, err, _opts))
|
|
510
|
% (cmd, err, _opts))
|
|
513
|
log.exception(tb_err)
|
|
511
|
log.exception(tb_err)
|
|
514
|
raise exceptions.VcsException()(tb_err)
|
|
512
|
raise exceptions.VcsException()(tb_err)
|
|
515
|
|
|
513
|
|
|
516
|
@reraise_safe_exceptions
|
|
514
|
@reraise_safe_exceptions
|
|
517
|
def install_hooks(self, wire, force=False):
|
|
515
|
def install_hooks(self, wire, force=False):
|
|
518
|
from vcsserver.hook_utils import install_svn_hooks
|
|
516
|
from vcsserver.hook_utils import install_svn_hooks
|
|
519
|
repo_path = wire['path']
|
|
517
|
repo_path = wire['path']
|
|
520
|
binary_dir = settings.BINARY_DIR
|
|
518
|
binary_dir = settings.BINARY_DIR
|
|
521
|
executable = None
|
|
519
|
executable = None
|
|
522
|
if binary_dir:
|
|
520
|
if binary_dir:
|
|
523
|
executable = os.path.join(binary_dir, 'python')
|
|
521
|
executable = os.path.join(binary_dir, 'python')
|
|
524
|
return install_svn_hooks(
|
|
522
|
return install_svn_hooks(
|
|
525
|
repo_path, executable=executable, force_create=force)
|
|
523
|
repo_path, executable=executable, force_create=force)
|
|
526
|
|
|
524
|
|
|
527
|
@reraise_safe_exceptions
|
|
525
|
@reraise_safe_exceptions
|
|
528
|
def get_hooks_info(self, wire):
|
|
526
|
def get_hooks_info(self, wire):
|
|
529
|
from vcsserver.hook_utils import (
|
|
527
|
from vcsserver.hook_utils import (
|
|
530
|
get_svn_pre_hook_version, get_svn_post_hook_version)
|
|
528
|
get_svn_pre_hook_version, get_svn_post_hook_version)
|
|
531
|
repo_path = wire['path']
|
|
529
|
repo_path = wire['path']
|
|
532
|
return {
|
|
530
|
return {
|
|
533
|
'pre_version': get_svn_pre_hook_version(repo_path),
|
|
531
|
'pre_version': get_svn_pre_hook_version(repo_path),
|
|
534
|
'post_version': get_svn_post_hook_version(repo_path),
|
|
532
|
'post_version': get_svn_post_hook_version(repo_path),
|
|
535
|
}
|
|
533
|
}
|
|
536
|
|
|
534
|
|
|
537
|
@reraise_safe_exceptions
|
|
535
|
@reraise_safe_exceptions
|
|
538
|
def set_head_ref(self, wire, head_name):
|
|
536
|
def set_head_ref(self, wire, head_name):
|
|
539
|
pass
|
|
537
|
pass
|
|
540
|
|
|
538
|
|
|
541
|
@reraise_safe_exceptions
|
|
539
|
@reraise_safe_exceptions
|
|
542
|
def archive_repo(self, wire, archive_dest_path, kind, mtime, archive_at_path,
|
|
540
|
def archive_repo(self, wire, archive_dest_path, kind, mtime, archive_at_path,
|
|
543
|
archive_dir_name, commit_id):
|
|
541
|
archive_dir_name, commit_id):
|
|
544
|
|
|
542
|
|
|
545
|
def walk_tree(root, root_dir, _commit_id):
|
|
543
|
def walk_tree(root, root_dir, _commit_id):
|
|
546
|
"""
|
|
544
|
"""
|
|
547
|
Special recursive svn repo walker
|
|
545
|
Special recursive svn repo walker
|
|
548
|
"""
|
|
546
|
"""
|
|
549
|
|
|
547
|
|
|
550
|
filemode_default = 0o100644
|
|
548
|
filemode_default = 0o100644
|
|
551
|
filemode_executable = 0o100755
|
|
549
|
filemode_executable = 0o100755
|
|
552
|
|
|
550
|
|
|
553
|
file_iter = svn.fs.dir_entries(root, root_dir)
|
|
551
|
file_iter = svn.fs.dir_entries(root, root_dir)
|
|
554
|
for f_name in file_iter:
|
|
552
|
for f_name in file_iter:
|
|
555
|
f_type = NODE_TYPE_MAPPING.get(file_iter[f_name].kind, None)
|
|
553
|
f_type = NODE_TYPE_MAPPING.get(file_iter[f_name].kind, None)
|
|
556
|
|
|
554
|
|
|
557
|
if f_type == 'dir':
|
|
555
|
if f_type == 'dir':
|
|
558
|
# return only DIR, and then all entries in that dir
|
|
556
|
# return only DIR, and then all entries in that dir
|
|
559
|
yield os.path.join(root_dir, f_name), {'mode': filemode_default}, f_type
|
|
557
|
yield os.path.join(root_dir, f_name), {'mode': filemode_default}, f_type
|
|
560
|
new_root = os.path.join(root_dir, f_name)
|
|
558
|
new_root = os.path.join(root_dir, f_name)
|
|
561
|
for _f_name, _f_data, _f_type in walk_tree(root, new_root, _commit_id):
|
|
559
|
for _f_name, _f_data, _f_type in walk_tree(root, new_root, _commit_id):
|
|
562
|
yield _f_name, _f_data, _f_type
|
|
560
|
yield _f_name, _f_data, _f_type
|
|
563
|
else:
|
|
561
|
else:
|
|
564
|
f_path = os.path.join(root_dir, f_name).rstrip('/')
|
|
562
|
f_path = os.path.join(root_dir, f_name).rstrip('/')
|
|
565
|
prop_list = svn.fs.node_proplist(root, f_path)
|
|
563
|
prop_list = svn.fs.node_proplist(root, f_path)
|
|
566
|
|
|
564
|
|
|
567
|
f_mode = filemode_default
|
|
565
|
f_mode = filemode_default
|
|
568
|
if prop_list.get('svn:executable'):
|
|
566
|
if prop_list.get('svn:executable'):
|
|
569
|
f_mode = filemode_executable
|
|
567
|
f_mode = filemode_executable
|
|
570
|
|
|
568
|
|
|
571
|
f_is_link = False
|
|
569
|
f_is_link = False
|
|
572
|
if prop_list.get('svn:special'):
|
|
570
|
if prop_list.get('svn:special'):
|
|
573
|
f_is_link = True
|
|
571
|
f_is_link = True
|
|
574
|
|
|
572
|
|
|
575
|
data = {
|
|
573
|
data = {
|
|
576
|
'is_link': f_is_link,
|
|
574
|
'is_link': f_is_link,
|
|
577
|
'mode': f_mode,
|
|
575
|
'mode': f_mode,
|
|
578
|
'content_stream': svn.core.Stream(svn.fs.file_contents(root, f_path)).read
|
|
576
|
'content_stream': svn.core.Stream(svn.fs.file_contents(root, f_path)).read
|
|
579
|
}
|
|
577
|
}
|
|
580
|
|
|
578
|
|
|
581
|
yield f_path, data, f_type
|
|
579
|
yield f_path, data, f_type
|
|
582
|
|
|
580
|
|
|
583
|
def file_walker(_commit_id, path):
|
|
581
|
def file_walker(_commit_id, path):
|
|
584
|
repo = self._factory.repo(wire)
|
|
582
|
repo = self._factory.repo(wire)
|
|
585
|
root = svn.fs.revision_root(svn.repos.fs(repo), int(commit_id))
|
|
583
|
root = svn.fs.revision_root(svn.repos.fs(repo), int(commit_id))
|
|
586
|
|
|
584
|
|
|
587
|
def no_content():
|
|
585
|
def no_content():
|
|
588
|
raise NoContentException()
|
|
586
|
raise NoContentException()
|
|
589
|
|
|
587
|
|
|
590
|
for f_name, f_data, f_type in walk_tree(root, path, _commit_id):
|
|
588
|
for f_name, f_data, f_type in walk_tree(root, path, _commit_id):
|
|
591
|
file_path = f_name
|
|
589
|
file_path = f_name
|
|
592
|
|
|
590
|
|
|
593
|
if f_type == 'dir':
|
|
591
|
if f_type == 'dir':
|
|
594
|
mode = f_data['mode']
|
|
592
|
mode = f_data['mode']
|
|
595
|
yield ArchiveNode(file_path, mode, False, no_content)
|
|
593
|
yield ArchiveNode(file_path, mode, False, no_content)
|
|
596
|
else:
|
|
594
|
else:
|
|
597
|
mode = f_data['mode']
|
|
595
|
mode = f_data['mode']
|
|
598
|
is_link = f_data['is_link']
|
|
596
|
is_link = f_data['is_link']
|
|
599
|
data_stream = f_data['content_stream']
|
|
597
|
data_stream = f_data['content_stream']
|
|
600
|
yield ArchiveNode(file_path, mode, is_link, data_stream)
|
|
598
|
yield ArchiveNode(file_path, mode, is_link, data_stream)
|
|
601
|
|
|
599
|
|
|
602
|
return archive_repo(file_walker, archive_dest_path, kind, mtime, archive_at_path,
|
|
600
|
return archive_repo(file_walker, archive_dest_path, kind, mtime, archive_at_path,
|
|
603
|
archive_dir_name, commit_id)
|
|
601
|
archive_dir_name, commit_id)
|
|
604
|
|
|
602
|
|
|
605
|
|
|
603
|
|
|
606
|
class SvnDiffer(object):
|
|
604
|
class SvnDiffer(object):
|
|
607
|
"""
|
|
605
|
"""
|
|
608
|
Utility to create diffs based on difflib and the Subversion api
|
|
606
|
Utility to create diffs based on difflib and the Subversion api
|
|
609
|
"""
|
|
607
|
"""
|
|
610
|
|
|
608
|
|
|
611
|
binary_content = False
|
|
609
|
binary_content = False
|
|
612
|
|
|
610
|
|
|
613
|
def __init__(
|
|
611
|
def __init__(
|
|
614
|
self, repo, src_rev, src_path, tgt_rev, tgt_path,
|
|
612
|
self, repo, src_rev, src_path, tgt_rev, tgt_path,
|
|
615
|
ignore_whitespace, context):
|
|
613
|
ignore_whitespace, context):
|
|
616
|
self.repo = repo
|
|
614
|
self.repo = repo
|
|
617
|
self.ignore_whitespace = ignore_whitespace
|
|
615
|
self.ignore_whitespace = ignore_whitespace
|
|
618
|
self.context = context
|
|
616
|
self.context = context
|
|
619
|
|
|
617
|
|
|
620
|
fsobj = svn.repos.fs(repo)
|
|
618
|
fsobj = svn.repos.fs(repo)
|
|
621
|
|
|
619
|
|
|
622
|
self.tgt_rev = tgt_rev
|
|
620
|
self.tgt_rev = tgt_rev
|
|
623
|
self.tgt_path = tgt_path or ''
|
|
621
|
self.tgt_path = tgt_path or ''
|
|
624
|
self.tgt_root = svn.fs.revision_root(fsobj, tgt_rev)
|
|
622
|
self.tgt_root = svn.fs.revision_root(fsobj, tgt_rev)
|
|
625
|
self.tgt_kind = svn.fs.check_path(self.tgt_root, self.tgt_path)
|
|
623
|
self.tgt_kind = svn.fs.check_path(self.tgt_root, self.tgt_path)
|
|
626
|
|
|
624
|
|
|
627
|
self.src_rev = src_rev
|
|
625
|
self.src_rev = src_rev
|
|
628
|
self.src_path = src_path or self.tgt_path
|
|
626
|
self.src_path = src_path or self.tgt_path
|
|
629
|
self.src_root = svn.fs.revision_root(fsobj, src_rev)
|
|
627
|
self.src_root = svn.fs.revision_root(fsobj, src_rev)
|
|
630
|
self.src_kind = svn.fs.check_path(self.src_root, self.src_path)
|
|
628
|
self.src_kind = svn.fs.check_path(self.src_root, self.src_path)
|
|
631
|
|
|
629
|
|
|
632
|
self._validate()
|
|
630
|
self._validate()
|
|
633
|
|
|
631
|
|
|
634
|
def _validate(self):
|
|
632
|
def _validate(self):
|
|
635
|
if (self.tgt_kind != svn.core.svn_node_none and
|
|
633
|
if (self.tgt_kind != svn.core.svn_node_none and
|
|
636
|
self.src_kind != svn.core.svn_node_none and
|
|
634
|
self.src_kind != svn.core.svn_node_none and
|
|
637
|
self.src_kind != self.tgt_kind):
|
|
635
|
self.src_kind != self.tgt_kind):
|
|
638
|
# TODO: johbo: proper error handling
|
|
636
|
# TODO: johbo: proper error handling
|
|
639
|
raise Exception(
|
|
637
|
raise Exception(
|
|
640
|
"Source and target are not compatible for diff generation. "
|
|
638
|
"Source and target are not compatible for diff generation. "
|
|
641
|
"Source type: %s, target type: %s" %
|
|
639
|
"Source type: %s, target type: %s" %
|
|
642
|
(self.src_kind, self.tgt_kind))
|
|
640
|
(self.src_kind, self.tgt_kind))
|
|
643
|
|
|
641
|
|
|
644
|
def generate_diff(self):
|
|
642
|
def generate_diff(self):
|
|
645
|
buf = io.StringIO()
|
|
643
|
buf = io.StringIO()
|
|
646
|
if self.tgt_kind == svn.core.svn_node_dir:
|
|
644
|
if self.tgt_kind == svn.core.svn_node_dir:
|
|
647
|
self._generate_dir_diff(buf)
|
|
645
|
self._generate_dir_diff(buf)
|
|
648
|
else:
|
|
646
|
else:
|
|
649
|
self._generate_file_diff(buf)
|
|
647
|
self._generate_file_diff(buf)
|
|
650
|
return buf.getvalue()
|
|
648
|
return buf.getvalue()
|
|
651
|
|
|
649
|
|
|
652
|
def _generate_dir_diff(self, buf):
|
|
650
|
def _generate_dir_diff(self, buf):
|
|
653
|
editor = DiffChangeEditor()
|
|
651
|
editor = DiffChangeEditor()
|
|
654
|
editor_ptr, editor_baton = svn.delta.make_editor(editor)
|
|
652
|
editor_ptr, editor_baton = svn.delta.make_editor(editor)
|
|
655
|
svn.repos.dir_delta2(
|
|
653
|
svn.repos.dir_delta2(
|
|
656
|
self.src_root,
|
|
654
|
self.src_root,
|
|
657
|
self.src_path,
|
|
655
|
self.src_path,
|
|
658
|
'', # src_entry
|
|
656
|
'', # src_entry
|
|
659
|
self.tgt_root,
|
|
657
|
self.tgt_root,
|
|
660
|
self.tgt_path,
|
|
658
|
self.tgt_path,
|
|
661
|
editor_ptr, editor_baton,
|
|
659
|
editor_ptr, editor_baton,
|
|
662
|
authorization_callback_allow_all,
|
|
660
|
authorization_callback_allow_all,
|
|
663
|
False, # text_deltas
|
|
661
|
False, # text_deltas
|
|
664
|
svn.core.svn_depth_infinity, # depth
|
|
662
|
svn.core.svn_depth_infinity, # depth
|
|
665
|
False, # entry_props
|
|
663
|
False, # entry_props
|
|
666
|
False, # ignore_ancestry
|
|
664
|
False, # ignore_ancestry
|
|
667
|
)
|
|
665
|
)
|
|
668
|
|
|
666
|
|
|
669
|
for path, __, change in sorted(editor.changes):
|
|
667
|
for path, __, change in sorted(editor.changes):
|
|
670
|
self._generate_node_diff(
|
|
668
|
self._generate_node_diff(
|
|
671
|
buf, change, path, self.tgt_path, path, self.src_path)
|
|
669
|
buf, change, path, self.tgt_path, path, self.src_path)
|
|
672
|
|
|
670
|
|
|
673
|
def _generate_file_diff(self, buf):
|
|
671
|
def _generate_file_diff(self, buf):
|
|
674
|
change = None
|
|
672
|
change = None
|
|
675
|
if self.src_kind == svn.core.svn_node_none:
|
|
673
|
if self.src_kind == svn.core.svn_node_none:
|
|
676
|
change = "add"
|
|
674
|
change = "add"
|
|
677
|
elif self.tgt_kind == svn.core.svn_node_none:
|
|
675
|
elif self.tgt_kind == svn.core.svn_node_none:
|
|
678
|
change = "delete"
|
|
676
|
change = "delete"
|
|
679
|
tgt_base, tgt_path = vcspath.split(self.tgt_path)
|
|
677
|
tgt_base, tgt_path = vcspath.split(self.tgt_path)
|
|
680
|
src_base, src_path = vcspath.split(self.src_path)
|
|
678
|
src_base, src_path = vcspath.split(self.src_path)
|
|
681
|
self._generate_node_diff(
|
|
679
|
self._generate_node_diff(
|
|
682
|
buf, change, tgt_path, tgt_base, src_path, src_base)
|
|
680
|
buf, change, tgt_path, tgt_base, src_path, src_base)
|
|
683
|
|
|
681
|
|
|
684
|
def _generate_node_diff(
|
|
682
|
def _generate_node_diff(
|
|
685
|
self, buf, change, tgt_path, tgt_base, src_path, src_base):
|
|
683
|
self, buf, change, tgt_path, tgt_base, src_path, src_base):
|
|
686
|
|
|
684
|
|
|
687
|
if self.src_rev == self.tgt_rev and tgt_base == src_base:
|
|
685
|
if self.src_rev == self.tgt_rev and tgt_base == src_base:
|
|
688
|
# makes consistent behaviour with git/hg to return empty diff if
|
|
686
|
# makes consistent behaviour with git/hg to return empty diff if
|
|
689
|
# we compare same revisions
|
|
687
|
# we compare same revisions
|
|
690
|
return
|
|
688
|
return
|
|
691
|
|
|
689
|
|
|
692
|
tgt_full_path = vcspath.join(tgt_base, tgt_path)
|
|
690
|
tgt_full_path = vcspath.join(tgt_base, tgt_path)
|
|
693
|
src_full_path = vcspath.join(src_base, src_path)
|
|
691
|
src_full_path = vcspath.join(src_base, src_path)
|
|
694
|
|
|
692
|
|
|
695
|
self.binary_content = False
|
|
693
|
self.binary_content = False
|
|
696
|
mime_type = self._get_mime_type(tgt_full_path)
|
|
694
|
mime_type = self._get_mime_type(tgt_full_path)
|
|
697
|
|
|
695
|
|
|
698
|
if mime_type and not mime_type.startswith('text'):
|
|
696
|
if mime_type and not mime_type.startswith('text'):
|
|
699
|
self.binary_content = True
|
|
697
|
self.binary_content = True
|
|
700
|
buf.write("=" * 67 + '\n')
|
|
698
|
buf.write("=" * 67 + '\n')
|
|
701
|
buf.write("Cannot display: file marked as a binary type.\n")
|
|
699
|
buf.write("Cannot display: file marked as a binary type.\n")
|
|
702
|
buf.write("svn:mime-type = %s\n" % mime_type)
|
|
700
|
buf.write("svn:mime-type = %s\n" % mime_type)
|
|
703
|
buf.write("Index: %s\n" % (tgt_path, ))
|
|
701
|
buf.write("Index: %s\n" % (tgt_path, ))
|
|
704
|
buf.write("=" * 67 + '\n')
|
|
702
|
buf.write("=" * 67 + '\n')
|
|
705
|
buf.write("diff --git a/%(tgt_path)s b/%(tgt_path)s\n" % {
|
|
703
|
buf.write("diff --git a/%(tgt_path)s b/%(tgt_path)s\n" % {
|
|
706
|
'tgt_path': tgt_path})
|
|
704
|
'tgt_path': tgt_path})
|
|
707
|
|
|
705
|
|
|
708
|
if change == 'add':
|
|
706
|
if change == 'add':
|
|
709
|
# TODO: johbo: SVN is missing a zero here compared to git
|
|
707
|
# TODO: johbo: SVN is missing a zero here compared to git
|
|
710
|
buf.write("new file mode 10644\n")
|
|
708
|
buf.write("new file mode 10644\n")
|
|
711
|
|
|
709
|
|
|
712
|
#TODO(marcink): intro to binary detection of svn patches
|
|
710
|
#TODO(marcink): intro to binary detection of svn patches
|
|
713
|
# if self.binary_content:
|
|
711
|
# if self.binary_content:
|
|
714
|
# buf.write('GIT binary patch\n')
|
|
712
|
# buf.write('GIT binary patch\n')
|
|
715
|
|
|
713
|
|
|
716
|
buf.write("--- /dev/null\t(revision 0)\n")
|
|
714
|
buf.write("--- /dev/null\t(revision 0)\n")
|
|
717
|
src_lines = []
|
|
715
|
src_lines = []
|
|
718
|
else:
|
|
716
|
else:
|
|
719
|
if change == 'delete':
|
|
717
|
if change == 'delete':
|
|
720
|
buf.write("deleted file mode 10644\n")
|
|
718
|
buf.write("deleted file mode 10644\n")
|
|
721
|
|
|
719
|
|
|
722
|
#TODO(marcink): intro to binary detection of svn patches
|
|
720
|
#TODO(marcink): intro to binary detection of svn patches
|
|
723
|
# if self.binary_content:
|
|
721
|
# if self.binary_content:
|
|
724
|
# buf.write('GIT binary patch\n')
|
|
722
|
# buf.write('GIT binary patch\n')
|
|
725
|
|
|
723
|
|
|
726
|
buf.write("--- a/%s\t(revision %s)\n" % (
|
|
724
|
buf.write("--- a/%s\t(revision %s)\n" % (
|
|
727
|
src_path, self.src_rev))
|
|
725
|
src_path, self.src_rev))
|
|
728
|
src_lines = self._svn_readlines(self.src_root, src_full_path)
|
|
726
|
src_lines = self._svn_readlines(self.src_root, src_full_path)
|
|
729
|
|
|
727
|
|
|
730
|
if change == 'delete':
|
|
728
|
if change == 'delete':
|
|
731
|
buf.write("+++ /dev/null\t(revision %s)\n" % (self.tgt_rev, ))
|
|
729
|
buf.write("+++ /dev/null\t(revision %s)\n" % (self.tgt_rev, ))
|
|
732
|
tgt_lines = []
|
|
730
|
tgt_lines = []
|
|
733
|
else:
|
|
731
|
else:
|
|
734
|
buf.write("+++ b/%s\t(revision %s)\n" % (
|
|
732
|
buf.write("+++ b/%s\t(revision %s)\n" % (
|
|
735
|
tgt_path, self.tgt_rev))
|
|
733
|
tgt_path, self.tgt_rev))
|
|
736
|
tgt_lines = self._svn_readlines(self.tgt_root, tgt_full_path)
|
|
734
|
tgt_lines = self._svn_readlines(self.tgt_root, tgt_full_path)
|
|
737
|
|
|
735
|
|
|
738
|
if not self.binary_content:
|
|
736
|
if not self.binary_content:
|
|
739
|
udiff = svn_diff.unified_diff(
|
|
737
|
udiff = svn_diff.unified_diff(
|
|
740
|
src_lines, tgt_lines, context=self.context,
|
|
738
|
src_lines, tgt_lines, context=self.context,
|
|
741
|
ignore_blank_lines=self.ignore_whitespace,
|
|
739
|
ignore_blank_lines=self.ignore_whitespace,
|
|
742
|
ignore_case=False,
|
|
740
|
ignore_case=False,
|
|
743
|
ignore_space_changes=self.ignore_whitespace)
|
|
741
|
ignore_space_changes=self.ignore_whitespace)
|
|
744
|
buf.writelines(udiff)
|
|
742
|
buf.writelines(udiff)
|
|
745
|
|
|
743
|
|
|
746
|
def _get_mime_type(self, path):
|
|
744
|
def _get_mime_type(self, path):
|
|
747
|
try:
|
|
745
|
try:
|
|
748
|
mime_type = svn.fs.node_prop(
|
|
746
|
mime_type = svn.fs.node_prop(
|
|
749
|
self.tgt_root, path, svn.core.SVN_PROP_MIME_TYPE)
|
|
747
|
self.tgt_root, path, svn.core.SVN_PROP_MIME_TYPE)
|
|
750
|
except svn.core.SubversionException:
|
|
748
|
except svn.core.SubversionException:
|
|
751
|
mime_type = svn.fs.node_prop(
|
|
749
|
mime_type = svn.fs.node_prop(
|
|
752
|
self.src_root, path, svn.core.SVN_PROP_MIME_TYPE)
|
|
750
|
self.src_root, path, svn.core.SVN_PROP_MIME_TYPE)
|
|
753
|
return mime_type
|
|
751
|
return mime_type
|
|
754
|
|
|
752
|
|
|
755
|
def _svn_readlines(self, fs_root, node_path):
|
|
753
|
def _svn_readlines(self, fs_root, node_path):
|
|
756
|
if self.binary_content:
|
|
754
|
if self.binary_content:
|
|
757
|
return []
|
|
755
|
return []
|
|
758
|
node_kind = svn.fs.check_path(fs_root, node_path)
|
|
756
|
node_kind = svn.fs.check_path(fs_root, node_path)
|
|
759
|
if node_kind not in (
|
|
757
|
if node_kind not in (
|
|
760
|
svn.core.svn_node_file, svn.core.svn_node_symlink):
|
|
758
|
svn.core.svn_node_file, svn.core.svn_node_symlink):
|
|
761
|
return []
|
|
759
|
return []
|
|
762
|
content = svn.core.Stream(
|
|
760
|
content = svn.core.Stream(
|
|
763
|
svn.fs.file_contents(fs_root, node_path)).read()
|
|
761
|
svn.fs.file_contents(fs_root, node_path)).read()
|
|
764
|
return content.splitlines(True)
|
|
762
|
return content.splitlines(True)
|
|
765
|
|
|
763
|
|
|
766
|
|
|
764
|
|
|
767
|
class DiffChangeEditor(svn.delta.Editor):
|
|
765
|
class DiffChangeEditor(svn.delta.Editor):
|
|
768
|
"""
|
|
766
|
"""
|
|
769
|
Records changes between two given revisions
|
|
767
|
Records changes between two given revisions
|
|
770
|
"""
|
|
768
|
"""
|
|
771
|
|
|
769
|
|
|
772
|
def __init__(self):
|
|
770
|
def __init__(self):
|
|
773
|
self.changes = []
|
|
771
|
self.changes = []
|
|
774
|
|
|
772
|
|
|
775
|
def delete_entry(self, path, revision, parent_baton, pool=None):
|
|
773
|
def delete_entry(self, path, revision, parent_baton, pool=None):
|
|
776
|
self.changes.append((path, None, 'delete'))
|
|
774
|
self.changes.append((path, None, 'delete'))
|
|
777
|
|
|
775
|
|
|
778
|
def add_file(
|
|
776
|
def add_file(
|
|
779
|
self, path, parent_baton, copyfrom_path, copyfrom_revision,
|
|
777
|
self, path, parent_baton, copyfrom_path, copyfrom_revision,
|
|
780
|
file_pool=None):
|
|
778
|
file_pool=None):
|
|
781
|
self.changes.append((path, 'file', 'add'))
|
|
779
|
self.changes.append((path, 'file', 'add'))
|
|
782
|
|
|
780
|
|
|
783
|
def open_file(self, path, parent_baton, base_revision, file_pool=None):
|
|
781
|
def open_file(self, path, parent_baton, base_revision, file_pool=None):
|
|
784
|
self.changes.append((path, 'file', 'change'))
|
|
782
|
self.changes.append((path, 'file', 'change'))
|
|
785
|
|
|
783
|
|
|
786
|
|
|
784
|
|
|
787
|
def authorization_callback_allow_all(root, path, pool):
|
|
785
|
def authorization_callback_allow_all(root, path, pool):
|
|
788
|
return True
|
|
786
|
return True
|
|
789
|
|
|
787
|
|
|
790
|
|
|
788
|
|
|
791
|
class TxnNodeProcessor(object):
|
|
789
|
class TxnNodeProcessor(object):
|
|
792
|
"""
|
|
790
|
"""
|
|
793
|
Utility to process the change of one node within a transaction root.
|
|
791
|
Utility to process the change of one node within a transaction root.
|
|
794
|
|
|
792
|
|
|
795
|
It encapsulates the knowledge of how to add, update or remove
|
|
793
|
It encapsulates the knowledge of how to add, update or remove
|
|
796
|
a node for a given transaction root. The purpose is to support the method
|
|
794
|
a node for a given transaction root. The purpose is to support the method
|
|
797
|
`SvnRemote.commit`.
|
|
795
|
`SvnRemote.commit`.
|
|
798
|
"""
|
|
796
|
"""
|
|
799
|
|
|
797
|
|
|
800
|
def __init__(self, node, txn_root):
|
|
798
|
def __init__(self, node, txn_root):
|
|
801
|
assert isinstance(node['path'], str)
|
|
799
|
assert isinstance(node['path'], str)
|
|
802
|
|
|
800
|
|
|
803
|
self.node = node
|
|
801
|
self.node = node
|
|
804
|
self.txn_root = txn_root
|
|
802
|
self.txn_root = txn_root
|
|
805
|
|
|
803
|
|
|
806
|
def update(self):
|
|
804
|
def update(self):
|
|
807
|
self._ensure_parent_dirs()
|
|
805
|
self._ensure_parent_dirs()
|
|
808
|
self._add_file_if_node_does_not_exist()
|
|
806
|
self._add_file_if_node_does_not_exist()
|
|
809
|
self._update_file_content()
|
|
807
|
self._update_file_content()
|
|
810
|
self._update_file_properties()
|
|
808
|
self._update_file_properties()
|
|
811
|
|
|
809
|
|
|
812
|
def remove(self):
|
|
810
|
def remove(self):
|
|
813
|
svn.fs.delete(self.txn_root, self.node['path'])
|
|
811
|
svn.fs.delete(self.txn_root, self.node['path'])
|
|
814
|
# TODO: Clean up directory if empty
|
|
812
|
# TODO: Clean up directory if empty
|
|
815
|
|
|
813
|
|
|
816
|
def _ensure_parent_dirs(self):
|
|
814
|
def _ensure_parent_dirs(self):
|
|
817
|
curdir = vcspath.dirname(self.node['path'])
|
|
815
|
curdir = vcspath.dirname(self.node['path'])
|
|
818
|
dirs_to_create = []
|
|
816
|
dirs_to_create = []
|
|
819
|
while not self._svn_path_exists(curdir):
|
|
817
|
while not self._svn_path_exists(curdir):
|
|
820
|
dirs_to_create.append(curdir)
|
|
818
|
dirs_to_create.append(curdir)
|
|
821
|
curdir = vcspath.dirname(curdir)
|
|
819
|
curdir = vcspath.dirname(curdir)
|
|
822
|
|
|
820
|
|
|
823
|
for curdir in reversed(dirs_to_create):
|
|
821
|
for curdir in reversed(dirs_to_create):
|
|
824
|
log.debug('Creating missing directory "%s"', curdir)
|
|
822
|
log.debug('Creating missing directory "%s"', curdir)
|
|
825
|
svn.fs.make_dir(self.txn_root, curdir)
|
|
823
|
svn.fs.make_dir(self.txn_root, curdir)
|
|
826
|
|
|
824
|
|
|
827
|
def _svn_path_exists(self, path):
|
|
825
|
def _svn_path_exists(self, path):
|
|
828
|
path_status = svn.fs.check_path(self.txn_root, path)
|
|
826
|
path_status = svn.fs.check_path(self.txn_root, path)
|
|
829
|
return path_status != svn.core.svn_node_none
|
|
827
|
return path_status != svn.core.svn_node_none
|
|
830
|
|
|
828
|
|
|
831
|
def _add_file_if_node_does_not_exist(self):
|
|
829
|
def _add_file_if_node_does_not_exist(self):
|
|
832
|
kind = svn.fs.check_path(self.txn_root, self.node['path'])
|
|
830
|
kind = svn.fs.check_path(self.txn_root, self.node['path'])
|
|
833
|
if kind == svn.core.svn_node_none:
|
|
831
|
if kind == svn.core.svn_node_none:
|
|
834
|
svn.fs.make_file(self.txn_root, self.node['path'])
|
|
832
|
svn.fs.make_file(self.txn_root, self.node['path'])
|
|
835
|
|
|
833
|
|
|
836
|
def _update_file_content(self):
|
|
834
|
def _update_file_content(self):
|
|
837
|
assert isinstance(self.node['content'], str)
|
|
835
|
assert isinstance(self.node['content'], str)
|
|
838
|
handler, baton = svn.fs.apply_textdelta(
|
|
836
|
handler, baton = svn.fs.apply_textdelta(
|
|
839
|
self.txn_root, self.node['path'], None, None)
|
|
837
|
self.txn_root, self.node['path'], None, None)
|
|
840
|
svn.delta.svn_txdelta_send_string(self.node['content'], handler, baton)
|
|
838
|
svn.delta.svn_txdelta_send_string(self.node['content'], handler, baton)
|
|
841
|
|
|
839
|
|
|
842
|
def _update_file_properties(self):
|
|
840
|
def _update_file_properties(self):
|
|
843
|
properties = self.node.get('properties', {})
|
|
841
|
properties = self.node.get('properties', {})
|
|
844
|
for key, value in properties.items():
|
|
842
|
for key, value in properties.items():
|
|
845
|
svn.fs.change_node_prop(
|
|
843
|
svn.fs.change_node_prop(
|
|
846
|
self.txn_root, self.node['path'], key, value)
|
|
844
|
self.txn_root, self.node['path'], key, value)
|
|
847
|
|
|
845
|
|
|
848
|
|
|
846
|
|
|
849
|
def apr_time_t(timestamp):
|
|
847
|
def apr_time_t(timestamp):
|
|
850
|
"""
|
|
848
|
"""
|
|
851
|
Convert a Python timestamp into APR timestamp type apr_time_t
|
|
849
|
Convert a Python timestamp into APR timestamp type apr_time_t
|
|
852
|
"""
|
|
850
|
"""
|
|
853
|
return timestamp * 1E6
|
|
851
|
return timestamp * 1E6
|
|
854
|
|
|
852
|
|
|
855
|
|
|
853
|
|
|
856
|
def svn_opt_revision_value_t(num):
|
|
854
|
def svn_opt_revision_value_t(num):
|
|
857
|
"""
|
|
855
|
"""
|
|
858
|
Put `num` into a `svn_opt_revision_value_t` structure.
|
|
856
|
Put `num` into a `svn_opt_revision_value_t` structure.
|
|
859
|
"""
|
|
857
|
"""
|
|
860
|
value = svn.core.svn_opt_revision_value_t()
|
|
858
|
value = svn.core.svn_opt_revision_value_t()
|
|
861
|
value.number = num
|
|
859
|
value.number = num
|
|
862
|
revision = svn.core.svn_opt_revision_t()
|
|
860
|
revision = svn.core.svn_opt_revision_t()
|
|
863
|
revision.kind = svn.core.svn_opt_revision_number
|
|
861
|
revision.kind = svn.core.svn_opt_revision_number
|
|
864
|
revision.value = value
|
|
862
|
revision.value = value
|
|
865
|
return revision
|
|
863
|
return revision
|