multi-workspace 3.2.3__tar.gz → 3.2.5__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.
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/PKG-INFO +4 -1
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/README.md +3 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/_version.py +2 -2
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/collaborator.py +133 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/worktree.py +78 -11
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/.gitignore +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/LICENSE +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/__init__.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/__main__.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/add.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/api.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/app_api.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/bootstrap.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/cli.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/cli_helpers.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/convert_monorepo.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/doctor.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/errors.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/git_helpers.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/git_run.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/git_set_branch.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/ignore_files.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/init.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/logging.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/managed_blocks.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/open.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/paths.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/registry.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/remove.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/repo_urls.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/repos.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/resources/init_readme.md +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/service.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/settings.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/sync.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/sync_agents.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/sync_github.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/sync_vscode.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/sync_vscode_devcontainer.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/sync_vscode_extensions.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/sync_vscode_helpers.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/sync_vscode_launch.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/sync_vscode_settings.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/sync_vscode_tasks.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/multi/utils.py +0 -0
- {multi_workspace-3.2.3 → multi_workspace-3.2.5}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multi-workspace
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.5
|
|
4
4
|
Summary: Multi
|
|
5
5
|
Project-URL: Repository, https://github.com/gabemontague/multi
|
|
6
6
|
Project-URL: Issues, https://github.com/gabemontague/multi/issues
|
|
@@ -108,12 +108,15 @@ To grant or remove GitHub collaborator access across every GitHub repo in a work
|
|
|
108
108
|
```bash
|
|
109
109
|
multi collaborator add octocat --permission maintain --yes
|
|
110
110
|
multi collaborator add --yes
|
|
111
|
+
multi collaborator accept --yes
|
|
111
112
|
multi collaborator remove octocat --yes
|
|
112
113
|
multi collaborator recent-users
|
|
113
114
|
```
|
|
114
115
|
|
|
115
116
|
When the workspace root repository has a GitHub `origin`, `multi collaborator` applies the change there too.
|
|
116
117
|
|
|
118
|
+
The invited collaborator can run `multi collaborator accept --yes` with their own `gh` authentication to accept pending invitations for the workspace repos.
|
|
119
|
+
|
|
117
120
|
Recent collaborator usernames are saved under `~/.multi`, and `multi collaborator add` can prompt from that list when no username is supplied.
|
|
118
121
|
|
|
119
122
|
If one repo fails, `multi collaborator` continues through the rest of the workspace and reports the failures at the end.
|
|
@@ -81,12 +81,15 @@ To grant or remove GitHub collaborator access across every GitHub repo in a work
|
|
|
81
81
|
```bash
|
|
82
82
|
multi collaborator add octocat --permission maintain --yes
|
|
83
83
|
multi collaborator add --yes
|
|
84
|
+
multi collaborator accept --yes
|
|
84
85
|
multi collaborator remove octocat --yes
|
|
85
86
|
multi collaborator recent-users
|
|
86
87
|
```
|
|
87
88
|
|
|
88
89
|
When the workspace root repository has a GitHub `origin`, `multi collaborator` applies the change there too.
|
|
89
90
|
|
|
91
|
+
The invited collaborator can run `multi collaborator accept --yes` with their own `gh` authentication to accept pending invitations for the workspace repos.
|
|
92
|
+
|
|
90
93
|
Recent collaborator usernames are saved under `~/.multi`, and `multi collaborator add` can prompt from that list when no username is supplied.
|
|
91
94
|
|
|
92
95
|
If one repo fails, `multi collaborator` continues through the rest of the workspace and reports the failures at the end.
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '3.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (3, 2,
|
|
21
|
+
__version__ = version = '3.2.5'
|
|
22
|
+
__version_tuple__ = version_tuple = (3, 2, 5)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -135,11 +135,17 @@ def _run_gh_api(
|
|
|
135
135
|
method: str,
|
|
136
136
|
endpoint: str,
|
|
137
137
|
fields: dict[str, str] | None = None,
|
|
138
|
+
paginate: bool = False,
|
|
139
|
+
slurp: bool = False,
|
|
138
140
|
) -> subprocess.CompletedProcess[str]:
|
|
139
141
|
cmd = [gh_path, "api", "--method", method, endpoint]
|
|
140
142
|
if fields:
|
|
141
143
|
for key, value in fields.items():
|
|
142
144
|
cmd.extend(["-f", f"{key}={value}"])
|
|
145
|
+
if paginate:
|
|
146
|
+
cmd.append("--paginate")
|
|
147
|
+
if slurp:
|
|
148
|
+
cmd.append("--slurp")
|
|
143
149
|
|
|
144
150
|
try:
|
|
145
151
|
return subprocess.run(
|
|
@@ -155,6 +161,57 @@ def _run_gh_api(
|
|
|
155
161
|
raise click.ClickException(message) from exc
|
|
156
162
|
|
|
157
163
|
|
|
164
|
+
def _load_pending_repository_invitations(gh_path: str) -> list[dict[str, object]]:
|
|
165
|
+
result = _run_gh_api(
|
|
166
|
+
gh_path,
|
|
167
|
+
method="GET",
|
|
168
|
+
endpoint="user/repository_invitations?per_page=100",
|
|
169
|
+
paginate=True,
|
|
170
|
+
slurp=True,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
invitations = json.loads(result.stdout or "[]")
|
|
175
|
+
except json.JSONDecodeError as exc:
|
|
176
|
+
raise click.ClickException(
|
|
177
|
+
"GitHub returned invalid repository invitation data."
|
|
178
|
+
) from exc
|
|
179
|
+
|
|
180
|
+
if not isinstance(invitations, list):
|
|
181
|
+
raise click.ClickException(
|
|
182
|
+
"GitHub returned invalid repository invitation data."
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
if all(isinstance(page, list) for page in invitations):
|
|
186
|
+
return [
|
|
187
|
+
invitation
|
|
188
|
+
for page in invitations
|
|
189
|
+
for invitation in page
|
|
190
|
+
if isinstance(invitation, dict)
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
return [invitation for invitation in invitations if isinstance(invitation, dict)]
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _get_invitation_id(invitation: dict[str, object]) -> int | None:
|
|
197
|
+
invitation_id = invitation.get("id")
|
|
198
|
+
if isinstance(invitation_id, int):
|
|
199
|
+
return invitation_id
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _get_invitation_repo_slug(invitation: dict[str, object]) -> str | None:
|
|
204
|
+
repository = invitation.get("repository")
|
|
205
|
+
if not isinstance(repository, dict):
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
full_name = repository.get("full_name")
|
|
209
|
+
if isinstance(full_name, str) and full_name:
|
|
210
|
+
return full_name
|
|
211
|
+
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
|
|
158
215
|
def _load_workspace_github_repo_slug(paths: Paths) -> str | None:
|
|
159
216
|
try:
|
|
160
217
|
repo = git.Repo(paths.root_dir)
|
|
@@ -246,6 +303,70 @@ def _confirm_across_repos(
|
|
|
246
303
|
raise click.ClickException("Operation cancelled.")
|
|
247
304
|
|
|
248
305
|
|
|
306
|
+
def _confirm_accept_invitations(
|
|
307
|
+
*,
|
|
308
|
+
invitations: list[tuple[str, int]],
|
|
309
|
+
yes: bool,
|
|
310
|
+
) -> None:
|
|
311
|
+
if yes:
|
|
312
|
+
return
|
|
313
|
+
|
|
314
|
+
repo_list = "\n".join(f"- {slug}" for slug, _ in invitations)
|
|
315
|
+
confirmed = click.confirm(
|
|
316
|
+
f"Accept repository invitations for these repos?\n{repo_list}",
|
|
317
|
+
default=False,
|
|
318
|
+
)
|
|
319
|
+
if not confirmed:
|
|
320
|
+
raise click.ClickException("Operation cancelled.")
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _accept_collaborator_invitations(*, yes: bool) -> None:
|
|
324
|
+
paths = Paths(Path.cwd())
|
|
325
|
+
gh_path = _ensure_gh_available()
|
|
326
|
+
repo_targets = _load_github_repo_targets(paths)
|
|
327
|
+
target_slugs = {slug.lower() for _, slug in repo_targets}
|
|
328
|
+
invitations: list[tuple[str, int]] = []
|
|
329
|
+
failures: list[tuple[str, str]] = []
|
|
330
|
+
|
|
331
|
+
for invitation in _load_pending_repository_invitations(gh_path):
|
|
332
|
+
slug = _get_invitation_repo_slug(invitation)
|
|
333
|
+
invitation_id = _get_invitation_id(invitation)
|
|
334
|
+
if slug is None or invitation_id is None:
|
|
335
|
+
continue
|
|
336
|
+
if slug.lower() not in target_slugs:
|
|
337
|
+
continue
|
|
338
|
+
invitations.append((slug, invitation_id))
|
|
339
|
+
|
|
340
|
+
if not invitations:
|
|
341
|
+
click.echo("No pending repository invitations found for this workspace.")
|
|
342
|
+
return
|
|
343
|
+
|
|
344
|
+
_confirm_accept_invitations(invitations=invitations, yes=yes)
|
|
345
|
+
|
|
346
|
+
for slug, invitation_id in invitations:
|
|
347
|
+
try:
|
|
348
|
+
logger.info(f"Accepting repository invitation for {slug}")
|
|
349
|
+
_run_gh_api(
|
|
350
|
+
gh_path,
|
|
351
|
+
method="PATCH",
|
|
352
|
+
endpoint=f"user/repository_invitations/{invitation_id}",
|
|
353
|
+
)
|
|
354
|
+
except click.ClickException as exc:
|
|
355
|
+
message = exc.message or str(exc)
|
|
356
|
+
logger.error(
|
|
357
|
+
f"Failed accepting repository invitation for {slug}: {message}"
|
|
358
|
+
)
|
|
359
|
+
failures.append((slug, message))
|
|
360
|
+
|
|
361
|
+
if failures:
|
|
362
|
+
failure_lines = "\n".join(f"- {slug}: {message}" for slug, message in failures)
|
|
363
|
+
raise click.ClickException(
|
|
364
|
+
f"Finished accepting repository invitations with failures:\n{failure_lines}"
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
logger.info("✅ Accepted repository invitations for all workspace repos")
|
|
368
|
+
|
|
369
|
+
|
|
249
370
|
def _manage_collaborator(
|
|
250
371
|
*,
|
|
251
372
|
action: str,
|
|
@@ -335,6 +456,17 @@ def collaborator_add_cmd(username: str | None, permission: str, yes: bool) -> No
|
|
|
335
456
|
)
|
|
336
457
|
|
|
337
458
|
|
|
459
|
+
@click.command(name="accept")
|
|
460
|
+
@click.option(
|
|
461
|
+
"--yes",
|
|
462
|
+
is_flag=True,
|
|
463
|
+
help="Skip the confirmation prompt.",
|
|
464
|
+
)
|
|
465
|
+
def collaborator_accept_cmd(yes: bool) -> None:
|
|
466
|
+
"""Accept pending collaborator invitations for this workspace."""
|
|
467
|
+
_accept_collaborator_invitations(yes=yes)
|
|
468
|
+
|
|
469
|
+
|
|
338
470
|
@click.command(name="remove")
|
|
339
471
|
@click.argument("username")
|
|
340
472
|
@click.option(
|
|
@@ -365,5 +497,6 @@ def collaborator_recent_users_cmd() -> None:
|
|
|
365
497
|
|
|
366
498
|
|
|
367
499
|
collaborator_cmd.add_command(common_command_wrapper(collaborator_add_cmd))
|
|
500
|
+
collaborator_cmd.add_command(common_command_wrapper(collaborator_accept_cmd))
|
|
368
501
|
collaborator_cmd.add_command(common_command_wrapper(collaborator_remove_cmd))
|
|
369
502
|
collaborator_cmd.add_command(common_command_wrapper(collaborator_recent_users_cmd))
|
|
@@ -39,7 +39,12 @@ def _run_git(repo_path: Path, args: list[str]) -> None:
|
|
|
39
39
|
) from e
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
def _create_root_worktree(
|
|
42
|
+
def _create_root_worktree(
|
|
43
|
+
root_dir: Path,
|
|
44
|
+
destination: Path,
|
|
45
|
+
branch_name: str,
|
|
46
|
+
base_ref: str,
|
|
47
|
+
) -> None:
|
|
43
48
|
exists_locally, exists_remotely = check_branch_existence(root_dir, branch_name)
|
|
44
49
|
|
|
45
50
|
if exists_locally:
|
|
@@ -54,7 +59,7 @@ def _create_root_worktree(root_dir: Path, destination: Path, branch_name: str) -
|
|
|
54
59
|
f"origin/{branch_name}",
|
|
55
60
|
]
|
|
56
61
|
else:
|
|
57
|
-
args = ["worktree", "add", "-b", branch_name, str(destination),
|
|
62
|
+
args = ["worktree", "add", "-b", branch_name, str(destination), base_ref]
|
|
58
63
|
|
|
59
64
|
_run_git(root_dir, args)
|
|
60
65
|
logger.info(f"Created worktree at {destination}")
|
|
@@ -164,12 +169,42 @@ def _transfer_configured_paths(source_paths: Paths, destination_paths: Paths) ->
|
|
|
164
169
|
|
|
165
170
|
|
|
166
171
|
def _checkout_subrepos(
|
|
167
|
-
|
|
172
|
+
source_repos: Iterable[Repository],
|
|
173
|
+
destination_repos: Iterable[Repository],
|
|
168
174
|
branch_name: str,
|
|
169
175
|
allow_create: bool,
|
|
176
|
+
base_ref: str,
|
|
170
177
|
) -> None:
|
|
171
|
-
for repo in
|
|
178
|
+
source_by_name = {repo.name: repo for repo in source_repos}
|
|
179
|
+
for repo in destination_repos:
|
|
172
180
|
repo_branch_name = repo.fixed_branch or branch_name
|
|
181
|
+
if repo.fixed_branch:
|
|
182
|
+
create_and_switch_branch(
|
|
183
|
+
repo.path,
|
|
184
|
+
repo_branch_name,
|
|
185
|
+
allow_create=allow_create,
|
|
186
|
+
)
|
|
187
|
+
continue
|
|
188
|
+
source_repo = source_by_name.get(repo.name)
|
|
189
|
+
if (
|
|
190
|
+
source_repo is not None
|
|
191
|
+
and base_ref != "HEAD"
|
|
192
|
+
and not check_branch_existence(repo.path, repo_branch_name)[0]
|
|
193
|
+
):
|
|
194
|
+
try:
|
|
195
|
+
_run_git(repo.path, ["fetch", str(source_repo.path), base_ref])
|
|
196
|
+
_run_git(repo.path, ["checkout", "-b", repo_branch_name, "FETCH_HEAD"])
|
|
197
|
+
logger.info(f"✅ Switched to branch '{repo_branch_name}' in {repo.path}")
|
|
198
|
+
continue
|
|
199
|
+
except GitError as exc:
|
|
200
|
+
logger.warning(
|
|
201
|
+
"⚠️ Base ref %s was not available for %s; creating %s from "
|
|
202
|
+
"the current checkout instead. %s",
|
|
203
|
+
base_ref,
|
|
204
|
+
repo.name,
|
|
205
|
+
repo_branch_name,
|
|
206
|
+
exc,
|
|
207
|
+
)
|
|
173
208
|
create_and_switch_branch(
|
|
174
209
|
repo.path,
|
|
175
210
|
repo_branch_name,
|
|
@@ -177,8 +212,14 @@ def _checkout_subrepos(
|
|
|
177
212
|
)
|
|
178
213
|
|
|
179
214
|
|
|
180
|
-
def add_worktree(
|
|
181
|
-
|
|
215
|
+
def add_worktree(
|
|
216
|
+
root_dir: Path,
|
|
217
|
+
name: str,
|
|
218
|
+
branch_name: str | None = None,
|
|
219
|
+
install_set: str | None = None,
|
|
220
|
+
base_ref: str = "HEAD",
|
|
221
|
+
) -> Path:
|
|
222
|
+
paths = Paths(root_dir, install_set=install_set)
|
|
182
223
|
if paths.settings.is_monorepo():
|
|
183
224
|
raise click.UsageError(
|
|
184
225
|
"The 'multi worktree add' command is not available in monorepo mode. "
|
|
@@ -198,14 +239,16 @@ def add_worktree(root_dir: Path, name: str, branch_name: str | None = None) -> P
|
|
|
198
239
|
"already exist for all repos, this command will fix the situation."
|
|
199
240
|
)
|
|
200
241
|
|
|
201
|
-
_create_root_worktree(paths.root_dir, destination, branch_name)
|
|
202
|
-
sync(root_dir=destination)
|
|
242
|
+
_create_root_worktree(paths.root_dir, destination, branch_name, base_ref)
|
|
243
|
+
sync(root_dir=destination, install_set=install_set)
|
|
203
244
|
|
|
204
|
-
destination_paths = Paths(destination)
|
|
245
|
+
destination_paths = Paths(destination, install_set=install_set)
|
|
205
246
|
_checkout_subrepos(
|
|
247
|
+
load_repos(paths),
|
|
206
248
|
load_repos(destination_paths),
|
|
207
249
|
branch_name,
|
|
208
250
|
allow_create=all_on_same_branch,
|
|
251
|
+
base_ref=base_ref,
|
|
209
252
|
)
|
|
210
253
|
_transfer_configured_paths(paths, destination_paths)
|
|
211
254
|
return destination
|
|
@@ -223,13 +266,37 @@ def worktree_cmd() -> None:
|
|
|
223
266
|
"branch_name",
|
|
224
267
|
help="Branch name for the worktree. Defaults to NAME.",
|
|
225
268
|
)
|
|
226
|
-
|
|
269
|
+
@click.option(
|
|
270
|
+
"--install-set",
|
|
271
|
+
"--set",
|
|
272
|
+
"install_set",
|
|
273
|
+
metavar="NAME",
|
|
274
|
+
help="Only sync repositories included in the named install set.",
|
|
275
|
+
)
|
|
276
|
+
@click.option(
|
|
277
|
+
"--base-ref",
|
|
278
|
+
default="HEAD",
|
|
279
|
+
show_default=True,
|
|
280
|
+
help="Base ref for newly created root and sub-repo branches.",
|
|
281
|
+
)
|
|
282
|
+
def worktree_add_cmd(
|
|
283
|
+
name: str,
|
|
284
|
+
branch_name: str | None = None,
|
|
285
|
+
install_set: str | None = None,
|
|
286
|
+
base_ref: str = "HEAD",
|
|
287
|
+
) -> None:
|
|
227
288
|
"""Create a sibling worktree.
|
|
228
289
|
|
|
229
290
|
NAME: Directory name for the sibling worktree. Used as the branch name when
|
|
230
291
|
--branch is omitted.
|
|
231
292
|
"""
|
|
232
|
-
add_worktree(
|
|
293
|
+
add_worktree(
|
|
294
|
+
Path.cwd(),
|
|
295
|
+
name=name,
|
|
296
|
+
branch_name=branch_name,
|
|
297
|
+
install_set=install_set,
|
|
298
|
+
base_ref=base_ref,
|
|
299
|
+
)
|
|
233
300
|
|
|
234
301
|
|
|
235
302
|
worktree_cmd.add_command(common_command_wrapper(worktree_add_cmd))
|
|
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
|