cli2 5.1.0rc1__tar.gz → 5.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cli2-5.1.0rc1/cli2.egg-info → cli2-5.1.1}/PKG-INFO +1 -1
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/__init__.py +1 -1
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/log.py +51 -10
- {cli2-5.1.0rc1 → cli2-5.1.1/cli2.egg-info}/PKG-INFO +1 -1
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2.egg-info/SOURCES.txt +2 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/setup.py +1 -1
- {cli2-5.1.0rc1 → cli2-5.1.1}/tests/test_client.py +50 -20
- cli2-5.1.1/tests/test_client_test.py +19 -0
- cli2-5.1.1/tests/test_log.py +80 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/MANIFEST.in +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/README.rst +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/classifiers.txt +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/asyncio.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/cli.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/cli2.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/colors.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/configuration.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/decorators.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/display.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/examples/__init__.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/examples/conf.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/examples/example.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/examples/example_obj.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/examples/nesting.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/examples/obj.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/examples/obj2.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/examples/test.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/lock.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/mask.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/node.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/sphinx.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/table.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2/test.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2.egg-info/dependency_links.txt +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2.egg-info/entry_points.txt +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2.egg-info/requires.txt +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/cli2.egg-info/top_level.txt +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/setup.cfg +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/tests/test_ansible.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/tests/test_ansible_variables.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/tests/test_asyncio.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/tests/test_cli.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/tests/test_command.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/tests/test_configuration.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/tests/test_decorators.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/tests/test_display.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/tests/test_entry_point.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/tests/test_group.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/tests/test_inject.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/tests/test_lock.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/tests/test_mask.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/tests/test_node.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/tests/test_restful.py +0 -0
- {cli2-5.1.0rc1 → cli2-5.1.1}/tests/test_table.py +0 -0
|
@@ -64,6 +64,7 @@ import os
|
|
|
64
64
|
import re
|
|
65
65
|
import sys
|
|
66
66
|
import structlog
|
|
67
|
+
import yaml
|
|
67
68
|
from pathlib import Path
|
|
68
69
|
|
|
69
70
|
import cli2.display
|
|
@@ -80,9 +81,15 @@ class YAMLFormatter:
|
|
|
80
81
|
return '\n' + value
|
|
81
82
|
|
|
82
83
|
|
|
83
|
-
def configure():
|
|
84
|
+
def configure(log_file=None):
|
|
85
|
+
"""
|
|
86
|
+
Configure logging.
|
|
87
|
+
|
|
88
|
+
:param log_file: override for :envvar:`LOG_FILE`.
|
|
89
|
+
"""
|
|
84
90
|
LOG_LEVEL = os.getenv('LOG_LEVEL', 'WARNING').upper()
|
|
85
|
-
|
|
91
|
+
if log_file is None:
|
|
92
|
+
log_file = os.getenv('LOG_FILE', 'auto')
|
|
86
93
|
|
|
87
94
|
if os.getenv('DEBUG'):
|
|
88
95
|
LOG_LEVEL = 'DEBUG'
|
|
@@ -103,19 +110,19 @@ def configure():
|
|
|
103
110
|
for arg in sys.argv
|
|
104
111
|
])[:155]
|
|
105
112
|
|
|
106
|
-
if
|
|
113
|
+
if log_file == 'auto':
|
|
107
114
|
log_dir = Path(os.getenv("HOME")) / '.local/cli2/log'
|
|
108
115
|
log_dir.mkdir(parents=True, exist_ok=True)
|
|
109
116
|
ts = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
|
|
110
117
|
file_name = f'{sys.argv[0].split("/")[-1]}-{ts}-{cmd}.log'
|
|
111
|
-
|
|
112
|
-
elif
|
|
113
|
-
|
|
118
|
+
log_file = log_dir / file_name
|
|
119
|
+
elif log_file == 'none' or not log_file:
|
|
120
|
+
log_file = None
|
|
114
121
|
else:
|
|
115
|
-
|
|
122
|
+
log_file = Path(log_file)
|
|
116
123
|
|
|
117
124
|
handlers = ['default']
|
|
118
|
-
if
|
|
125
|
+
if log_file:
|
|
119
126
|
handlers.append('file')
|
|
120
127
|
|
|
121
128
|
LOGGING = {
|
|
@@ -196,12 +203,12 @@ def configure():
|
|
|
196
203
|
} for key in ('httpx', 'httpcore')
|
|
197
204
|
})
|
|
198
205
|
|
|
199
|
-
if
|
|
206
|
+
if log_file:
|
|
200
207
|
LOGGING['handlers']['file'] = {
|
|
201
208
|
'level': 'DEBUG',
|
|
202
209
|
'class': 'logging.handlers.WatchedFileHandler',
|
|
203
210
|
'formatter': 'plain',
|
|
204
|
-
'filename': str(
|
|
211
|
+
'filename': str(log_file),
|
|
205
212
|
}
|
|
206
213
|
|
|
207
214
|
logging.config.dictConfig(LOGGING)
|
|
@@ -221,5 +228,39 @@ def configure():
|
|
|
221
228
|
)
|
|
222
229
|
|
|
223
230
|
|
|
231
|
+
def parse(data):
|
|
232
|
+
"""
|
|
233
|
+
Parse log file data into a list of entries.
|
|
234
|
+
|
|
235
|
+
:param data: Contents of a log file.
|
|
236
|
+
"""
|
|
237
|
+
yaml_lines = []
|
|
238
|
+
entries = []
|
|
239
|
+
for line in data.split('\n'):
|
|
240
|
+
if 'event=' in line:
|
|
241
|
+
data = {}
|
|
242
|
+
for token in line.strip().split():
|
|
243
|
+
if match := re.match('^(\\w+)=(.*)', token):
|
|
244
|
+
key = match.group(1)
|
|
245
|
+
data[key] = match.group(2)
|
|
246
|
+
else:
|
|
247
|
+
data[key] += ' ' + token
|
|
248
|
+
|
|
249
|
+
if yaml_lines:
|
|
250
|
+
data['json'] = yaml.safe_load('\n'.join(yaml_lines))
|
|
251
|
+
|
|
252
|
+
if data['event'] == 'request':
|
|
253
|
+
entries.append(dict(request=data))
|
|
254
|
+
elif data['event'] == 'response':
|
|
255
|
+
entries[-1]['response'] = data
|
|
256
|
+
else:
|
|
257
|
+
entries.append(data)
|
|
258
|
+
|
|
259
|
+
yaml_lines = []
|
|
260
|
+
else:
|
|
261
|
+
yaml_lines.append(line)
|
|
262
|
+
return entries
|
|
263
|
+
|
|
264
|
+
|
|
224
265
|
configure()
|
|
225
266
|
log = structlog.get_logger('cli2')
|
|
@@ -36,6 +36,7 @@ tests/test_ansible_variables.py
|
|
|
36
36
|
tests/test_asyncio.py
|
|
37
37
|
tests/test_cli.py
|
|
38
38
|
tests/test_client.py
|
|
39
|
+
tests/test_client_test.py
|
|
39
40
|
tests/test_command.py
|
|
40
41
|
tests/test_configuration.py
|
|
41
42
|
tests/test_decorators.py
|
|
@@ -44,6 +45,7 @@ tests/test_entry_point.py
|
|
|
44
45
|
tests/test_group.py
|
|
45
46
|
tests/test_inject.py
|
|
46
47
|
tests/test_lock.py
|
|
48
|
+
tests/test_log.py
|
|
47
49
|
tests/test_mask.py
|
|
48
50
|
tests/test_node.py
|
|
49
51
|
tests/test_restful.py
|
|
@@ -7,8 +7,10 @@ import mock
|
|
|
7
7
|
import pytest
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
def _response(**kwargs):
|
|
11
|
+
response = httpx.Response(**kwargs)
|
|
12
|
+
response.request = httpx.Request('POST', '/', json=[1])
|
|
13
|
+
return response
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
class HandlerSentinel(chttpx.Handler):
|
|
@@ -17,7 +19,11 @@ class HandlerSentinel(chttpx.Handler):
|
|
|
17
19
|
self.calls = []
|
|
18
20
|
|
|
19
21
|
async def __call__(self, client, response, tries, log):
|
|
20
|
-
self.calls.append((
|
|
22
|
+
self.calls.append((
|
|
23
|
+
client,
|
|
24
|
+
getattr(response, 'status_code', response),
|
|
25
|
+
tries,
|
|
26
|
+
))
|
|
21
27
|
return await super().__call__(client, response, tries, log)
|
|
22
28
|
|
|
23
29
|
|
|
@@ -191,6 +197,7 @@ async def test_error_remote(httpx_mock, client_class):
|
|
|
191
197
|
return 'token'
|
|
192
198
|
|
|
193
199
|
client = TokenClient()
|
|
200
|
+
client.handler.tries = 2
|
|
194
201
|
httpx_mock.add_response(url='http://lol', json=[1])
|
|
195
202
|
|
|
196
203
|
async def raises(*a, **k):
|
|
@@ -230,10 +237,11 @@ async def test_client_handler(httpx_mock, client_class):
|
|
|
230
237
|
class Client(client_class):
|
|
231
238
|
def client_factory(self):
|
|
232
239
|
client = super().client_factory()
|
|
233
|
-
client.send = mock.
|
|
240
|
+
client.send = mock.AsyncMock()
|
|
234
241
|
return client
|
|
235
242
|
|
|
236
243
|
client = Client(handler=HandlerSentinel())
|
|
244
|
+
client.handler.tries = 3
|
|
237
245
|
|
|
238
246
|
# test response retry
|
|
239
247
|
client.client.send.side_effect = [
|
|
@@ -247,16 +255,33 @@ async def test_client_handler(httpx_mock, client_class):
|
|
|
247
255
|
(client, 200, 1),
|
|
248
256
|
]
|
|
249
257
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
258
|
+
|
|
259
|
+
@pytest.mark.asyncio
|
|
260
|
+
async def test_client_error_retry(httpx_mock, client_class):
|
|
261
|
+
class Client(client_class):
|
|
262
|
+
calls = 0
|
|
263
|
+
|
|
264
|
+
def client_factory(self):
|
|
265
|
+
self.calls += 1
|
|
266
|
+
|
|
267
|
+
client = super().client_factory()
|
|
268
|
+
client.send = mock.AsyncMock()
|
|
269
|
+
|
|
270
|
+
if self.calls == 1:
|
|
271
|
+
client.send.side_effect = httpx.TransportError('foo')
|
|
272
|
+
elif self.calls == 2:
|
|
273
|
+
client.send.return_value = _response(status_code=200)
|
|
274
|
+
|
|
275
|
+
return client
|
|
276
|
+
|
|
277
|
+
client = Client(handler=HandlerSentinel())
|
|
278
|
+
client.handler.tries = 3
|
|
279
|
+
|
|
280
|
+
response = await client.request('GET', '/')
|
|
255
281
|
assert response.status_code == 200
|
|
256
|
-
assert client.handler.calls
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
]
|
|
282
|
+
assert isinstance(client.handler.calls[0][1], httpx.TransportError)
|
|
283
|
+
assert client.handler.calls[1][1] == 200
|
|
284
|
+
assert len(client.handler.calls) == 2
|
|
260
285
|
|
|
261
286
|
|
|
262
287
|
@pytest.mark.asyncio
|
|
@@ -265,13 +290,19 @@ async def test_handler(client_class):
|
|
|
265
290
|
client = client_class()
|
|
266
291
|
client.client_reset = mock.AsyncMock()
|
|
267
292
|
client.token_reset = mock.AsyncMock()
|
|
268
|
-
handler = chttpx.Handler(
|
|
293
|
+
handler = chttpx.Handler(
|
|
294
|
+
accepts=[201],
|
|
295
|
+
refuses=[218],
|
|
296
|
+
retokens=[418],
|
|
297
|
+
tries=3,
|
|
298
|
+
)
|
|
269
299
|
|
|
270
300
|
response = httpx.Response(status_code=201)
|
|
271
301
|
result = await handler(client, response, 0, log)
|
|
272
302
|
assert result == response
|
|
273
303
|
|
|
274
304
|
response = httpx.Response(status_code=200)
|
|
305
|
+
response.request = httpx.Request('POST', '/', json=[1])
|
|
275
306
|
result = await handler(client, response, 0, log)
|
|
276
307
|
log.info.assert_called_once_with(
|
|
277
308
|
'retry', status_code=200, tries=0, sleep=.0
|
|
@@ -286,7 +317,7 @@ async def test_handler(client_class):
|
|
|
286
317
|
'retry', status_code=200, tries=0, sleep=.0
|
|
287
318
|
)
|
|
288
319
|
|
|
289
|
-
msg = 'Unacceptable response <Response [200 OK]> after
|
|
320
|
+
msg = 'Unacceptable response <Response [200 OK]> after 4 tries\n\x1b[0m\x1b[1mPOST /\x1b[0m\n-\x1b[37m \x1b[39;49;00m1\x1b[37m\x1b[39;49;00m\n\n\x1b[1mHTTP 200\x1b[0m\n-\x1b[37m \x1b[39;49;00m2\x1b[37m\x1b[39;49;00m\n' # noqa
|
|
290
321
|
assert str(exc.value) == msg
|
|
291
322
|
|
|
292
323
|
response = httpx.Response(status_code=200)
|
|
@@ -294,7 +325,7 @@ async def test_handler(client_class):
|
|
|
294
325
|
with pytest.raises(chttpx.RetriesExceededError) as exc:
|
|
295
326
|
await handler(client, response, handler.tries + 1, log)
|
|
296
327
|
|
|
297
|
-
msg = 'Unacceptable response <Response [200 OK]> after
|
|
328
|
+
msg = 'Unacceptable response <Response [200 OK]> after 4 tries\n\x1b[0m\x1b[1mGET /\x1b[0m\n\x1b[1mHTTP 200\x1b[0m' # noqa
|
|
298
329
|
assert str(exc.value) == msg
|
|
299
330
|
|
|
300
331
|
response = httpx.Response(status_code=218)
|
|
@@ -335,9 +366,7 @@ async def test_handler(client_class):
|
|
|
335
366
|
assert not client.token_reset.await_count
|
|
336
367
|
log.warn.reset_mock()
|
|
337
368
|
result = await handler(client, response, 0, log)
|
|
338
|
-
log.warn.assert_called_once_with(
|
|
339
|
-
'retoken', status_code=418, tries=0, sleep=.0
|
|
340
|
-
)
|
|
369
|
+
log.warn.assert_called_once_with('retoken')
|
|
341
370
|
assert not result
|
|
342
371
|
assert client.token_reset.await_count == 1
|
|
343
372
|
|
|
@@ -358,10 +387,11 @@ async def test_retry(httpx_mock, client_class):
|
|
|
358
387
|
|
|
359
388
|
def client_factory(self):
|
|
360
389
|
client = super().client_factory()
|
|
361
|
-
client.send = mock.
|
|
390
|
+
client.send = mock.AsyncMock()
|
|
362
391
|
return client
|
|
363
392
|
|
|
364
393
|
client = Client()
|
|
394
|
+
client.handler.tries = 3
|
|
365
395
|
|
|
366
396
|
current_client = client.client
|
|
367
397
|
client.client.send.side_effect = [
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import chttpx
|
|
2
|
+
import cli2
|
|
3
|
+
import pytest
|
|
4
|
+
from chttpx.example import APIClient
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.chttpx_mock
|
|
8
|
+
def test_object_story(ts, chttpx_vars):
|
|
9
|
+
chttpx_vars.setdefault('test_name', f'test{ts}')
|
|
10
|
+
test_name = chttpx_vars['test_name']
|
|
11
|
+
|
|
12
|
+
obj = APIClient.cli['object']['create'](f'name={test_name}')
|
|
13
|
+
assert obj.name == test_name
|
|
14
|
+
|
|
15
|
+
cli2.log.info('bogus')
|
|
16
|
+
|
|
17
|
+
with pytest.raises(chttpx.RefusedResponseError):
|
|
18
|
+
APIClient.cli['object']['create'](f'name={test_name}')
|
|
19
|
+
result = APIClient.cli['object']['delete'](f'{obj.id}')
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import cli2
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_log_parse():
|
|
5
|
+
logs = '''
|
|
6
|
+
|
|
7
|
+
name: tes2980898zzzyzy7
|
|
8
|
+
method=POST url=http://localhost:8000/objects/ event=request level=debug timestamp=2025-03-21 10:09:40
|
|
9
|
+
|
|
10
|
+
data: {}
|
|
11
|
+
id: 103
|
|
12
|
+
name: tes2980898zzzyzy7
|
|
13
|
+
method=POST url=http://localhost:8000/objects/ status_code=201 event=response level=info timestamp=2025-03-21 10:09:40
|
|
14
|
+
event=bogus level=info
|
|
15
|
+
|
|
16
|
+
name: tes2980898zzzyzy7
|
|
17
|
+
method=POST url=http://localhost:8000/objects/ event=request level=debug timestamp=2025-03-21 10:09:40
|
|
18
|
+
|
|
19
|
+
name:
|
|
20
|
+
- object with this name already exists.
|
|
21
|
+
method=POST url=http://localhost:8000/objects/ status_code=400 event=response level=info timestamp=2025-03-21 10:09:40
|
|
22
|
+
'''
|
|
23
|
+
|
|
24
|
+
result = cli2.parse(logs)
|
|
25
|
+
assert result == [
|
|
26
|
+
{
|
|
27
|
+
'request': {
|
|
28
|
+
'event': 'request',
|
|
29
|
+
'json': {
|
|
30
|
+
'name': 'tes2980898zzzyzy7',
|
|
31
|
+
},
|
|
32
|
+
'level': 'debug',
|
|
33
|
+
'method': 'POST',
|
|
34
|
+
'timestamp': '2025-03-21 10:09:40',
|
|
35
|
+
'url': 'http://localhost:8000/objects/',
|
|
36
|
+
},
|
|
37
|
+
'response': {
|
|
38
|
+
'event': 'response',
|
|
39
|
+
'json': {
|
|
40
|
+
'data': {},
|
|
41
|
+
'id': 103,
|
|
42
|
+
'name': 'tes2980898zzzyzy7',
|
|
43
|
+
},
|
|
44
|
+
'level': 'info',
|
|
45
|
+
'method': 'POST',
|
|
46
|
+
'status_code': '201',
|
|
47
|
+
'timestamp': '2025-03-21 10:09:40',
|
|
48
|
+
'url': 'http://localhost:8000/objects/',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
'event': 'bogus',
|
|
53
|
+
'level': 'info',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
'request': {
|
|
57
|
+
'event': 'request',
|
|
58
|
+
'json': {
|
|
59
|
+
'name': 'tes2980898zzzyzy7',
|
|
60
|
+
},
|
|
61
|
+
'level': 'debug',
|
|
62
|
+
'method': 'POST',
|
|
63
|
+
'timestamp': '2025-03-21 10:09:40',
|
|
64
|
+
'url': 'http://localhost:8000/objects/',
|
|
65
|
+
},
|
|
66
|
+
'response': {
|
|
67
|
+
'event': 'response',
|
|
68
|
+
'json': {
|
|
69
|
+
'name': [
|
|
70
|
+
'object with this name already exists.',
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
'level': 'info',
|
|
74
|
+
'method': 'POST',
|
|
75
|
+
'status_code': '400',
|
|
76
|
+
'timestamp': '2025-03-21 10:09:40',
|
|
77
|
+
'url': 'http://localhost:8000/objects/',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|