RepoPackPy 0.1.1__tar.gz → 0.1.8__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.
- {repopackpy-0.1.1 → repopackpy-0.1.8}/.github/workflows/publish.yml +3 -2
- {repopackpy-0.1.1 → repopackpy-0.1.8}/.github/workflows/security.yml +4 -2
- {repopackpy-0.1.1 → repopackpy-0.1.8}/PKG-INFO +30 -3
- {repopackpy-0.1.1 → repopackpy-0.1.8}/README.md +29 -2
- {repopackpy-0.1.1 → repopackpy-0.1.8}/pyproject.toml +1 -1
- repopackpy-0.1.8/repopack/__init__.py +1 -0
- repopackpy-0.1.8/repopack/cli.py +119 -0
- {repopackpy-0.1.1 → repopackpy-0.1.8}/repopack/mcp_server.py +18 -2
- {repopackpy-0.1.1 → repopackpy-0.1.8}/repopack/packer.py +18 -3
- {repopackpy-0.1.1 → repopackpy-0.1.8}/repopack/unpacker.py +24 -1
- repopackpy-0.1.1/repopack/__init__.py +0 -1
- repopackpy-0.1.1/repopack/cli.py +0 -58
- {repopackpy-0.1.1 → repopackpy-0.1.8}/.github/workflows/ci.yml +0 -0
- {repopackpy-0.1.1 → repopackpy-0.1.8}/.gitignore +0 -0
- {repopackpy-0.1.1 → repopackpy-0.1.8}/CLAUDE.md +0 -0
- {repopackpy-0.1.1 → repopackpy-0.1.8}/LICENSE +0 -0
- {repopackpy-0.1.1 → repopackpy-0.1.8}/repopack/utils.py +0 -0
- {repopackpy-0.1.1 → repopackpy-0.1.8}/system_prompt.txt +0 -0
- {repopackpy-0.1.1 → repopackpy-0.1.8}/tests/test_repopack.py +0 -0
|
@@ -23,11 +23,13 @@ jobs:
|
|
|
23
23
|
|
|
24
24
|
- name: Install project and audit tools
|
|
25
25
|
run: |
|
|
26
|
-
pip install
|
|
26
|
+
pip install ".[dev]"
|
|
27
27
|
pip install pip-audit bandit
|
|
28
28
|
|
|
29
29
|
- name: pip-audit (known CVEs)
|
|
30
|
-
run:
|
|
30
|
+
run: |
|
|
31
|
+
pip freeze | grep -Eiv "^(repopackpy|repopack-py|-e )" > /tmp/audit-reqs.txt
|
|
32
|
+
pip-audit --strict -r /tmp/audit-reqs.txt
|
|
31
33
|
|
|
32
34
|
- name: bandit (SAST)
|
|
33
35
|
run: bandit -r repopack/ -ll -ii
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: RepoPackPy
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.8
|
|
4
4
|
Summary: Pack/unpack any source workspace into portable JSON, with MCP server support.
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -27,18 +27,45 @@ Pack/unpack **any source workspace regardless of technology stack** (Python, Nod
|
|
|
27
27
|
|
|
28
28
|
## Installation
|
|
29
29
|
|
|
30
|
+
### From PyPI (recommended)
|
|
31
|
+
|
|
32
|
+
Install the latest stable release:
|
|
33
|
+
|
|
30
34
|
```bash
|
|
31
35
|
pip install RepoPackPy
|
|
32
36
|
```
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
Pin to a specific version:
|
|
35
39
|
|
|
36
40
|
```bash
|
|
37
|
-
pip install
|
|
41
|
+
pip install RepoPackPy==0.1.1
|
|
38
42
|
```
|
|
39
43
|
|
|
40
44
|
Requires Python 3.10+.
|
|
41
45
|
|
|
46
|
+
### Quick start after install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Verify the install
|
|
50
|
+
repopack --help
|
|
51
|
+
|
|
52
|
+
# Pack your project
|
|
53
|
+
repopack pack . -o my-workspace.json
|
|
54
|
+
|
|
55
|
+
# Restore it somewhere else
|
|
56
|
+
repopack unpack my-workspace.json -t ./restored
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### For development
|
|
60
|
+
|
|
61
|
+
Clone the repo and install in editable mode with test dependencies:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
git clone https://github.com/ShanKonduru/RepoPack.git
|
|
65
|
+
cd RepoPack
|
|
66
|
+
pip install -e ".[dev]"
|
|
67
|
+
```
|
|
68
|
+
|
|
42
69
|
## CLI Usage
|
|
43
70
|
|
|
44
71
|
```bash
|
|
@@ -13,18 +13,45 @@ Pack/unpack **any source workspace regardless of technology stack** (Python, Nod
|
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
16
|
+
### From PyPI (recommended)
|
|
17
|
+
|
|
18
|
+
Install the latest stable release:
|
|
19
|
+
|
|
16
20
|
```bash
|
|
17
21
|
pip install RepoPackPy
|
|
18
22
|
```
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
Pin to a specific version:
|
|
21
25
|
|
|
22
26
|
```bash
|
|
23
|
-
pip install
|
|
27
|
+
pip install RepoPackPy==0.1.1
|
|
24
28
|
```
|
|
25
29
|
|
|
26
30
|
Requires Python 3.10+.
|
|
27
31
|
|
|
32
|
+
### Quick start after install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Verify the install
|
|
36
|
+
repopack --help
|
|
37
|
+
|
|
38
|
+
# Pack your project
|
|
39
|
+
repopack pack . -o my-workspace.json
|
|
40
|
+
|
|
41
|
+
# Restore it somewhere else
|
|
42
|
+
repopack unpack my-workspace.json -t ./restored
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### For development
|
|
46
|
+
|
|
47
|
+
Clone the repo and install in editable mode with test dependencies:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git clone https://github.com/ShanKonduru/RepoPack.git
|
|
51
|
+
cd RepoPack
|
|
52
|
+
pip install -e ".[dev]"
|
|
53
|
+
```
|
|
54
|
+
|
|
28
55
|
## CLI Usage
|
|
29
56
|
|
|
30
57
|
```bash
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.8"
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import http.client
|
|
3
|
+
from typing import Optional
|
|
4
|
+
import typer
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from repopack import __version__
|
|
8
|
+
from repopack.packer import pack_workspace
|
|
9
|
+
from repopack.unpacker import unpack_workspace
|
|
10
|
+
from repopack.mcp_server import run_server
|
|
11
|
+
|
|
12
|
+
_PYPI_HOST = "pypi.org"
|
|
13
|
+
_PYPI_PATH = "/pypi/RepoPackPy/json"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _check_latest_version() -> Optional[str]:
|
|
17
|
+
try:
|
|
18
|
+
conn = http.client.HTTPSConnection(_PYPI_HOST, timeout=3)
|
|
19
|
+
conn.request("GET", _PYPI_PATH)
|
|
20
|
+
resp = conn.getresponse()
|
|
21
|
+
if resp.status == 200:
|
|
22
|
+
data = json.loads(resp.read().decode())
|
|
23
|
+
return data["info"]["version"]
|
|
24
|
+
return None
|
|
25
|
+
except Exception:
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _version_callback(value: bool) -> None:
|
|
30
|
+
if value:
|
|
31
|
+
typer.echo(f"repopack {__version__}")
|
|
32
|
+
latest = _check_latest_version()
|
|
33
|
+
if latest and latest != __version__:
|
|
34
|
+
typer.echo(
|
|
35
|
+
f"A new version is available: {latest} "
|
|
36
|
+
f"(run: pip install --upgrade RepoPackPy)",
|
|
37
|
+
err=True,
|
|
38
|
+
)
|
|
39
|
+
raise typer.Exit()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
app = typer.Typer(name="repopack", help="Pack/unpack any workspace into portable JSON.")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.callback()
|
|
46
|
+
def main_callback(
|
|
47
|
+
version: Optional[bool] = typer.Option(
|
|
48
|
+
None, "--version", "-V",
|
|
49
|
+
callback=_version_callback,
|
|
50
|
+
is_eager=True,
|
|
51
|
+
help="Show version and exit.",
|
|
52
|
+
),
|
|
53
|
+
) -> None:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@app.command()
|
|
58
|
+
def pack(
|
|
59
|
+
directory: str = typer.Argument(".", help="The workspace to pack."),
|
|
60
|
+
output: str = typer.Option(None, "-o", "--output", help="Output JSON file path."),
|
|
61
|
+
include_binary: bool = typer.Option(False, "--include-binary", help="Include binary files."),
|
|
62
|
+
custom_ignore: str = typer.Option(None, "--custom-ignore", help="Comma-separated extra ignore patterns."),
|
|
63
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Report files that would be packed without writing output."),
|
|
64
|
+
):
|
|
65
|
+
try:
|
|
66
|
+
result = pack_workspace(Path(directory), include_binary, custom_ignore, dry_run=dry_run)
|
|
67
|
+
if dry_run:
|
|
68
|
+
typer.echo(f"Dry run — would pack {result['total_files']} files ({result['total_bytes']} bytes)")
|
|
69
|
+
typer.echo(f"Root : {result['root_directory']}")
|
|
70
|
+
typer.echo(f"Stack : {', '.join(result['detected_stack']) or 'unknown'}")
|
|
71
|
+
for f in result["files"]:
|
|
72
|
+
typer.echo(f" {f['encoding']:6} {f['size']:>10} {f['path']}")
|
|
73
|
+
return
|
|
74
|
+
if output:
|
|
75
|
+
with open(output, "w", encoding="utf-8") as f:
|
|
76
|
+
json.dump(result, f, indent=2)
|
|
77
|
+
total_files = result.get("metadata", {}).get("total_files", 0)
|
|
78
|
+
total_bytes = result.get("metadata", {}).get("total_bytes", 0)
|
|
79
|
+
typer.echo(f"Packed {total_files} files ({total_bytes} bytes) -> {output}", err=True)
|
|
80
|
+
else:
|
|
81
|
+
print(json.dumps(result, indent=2))
|
|
82
|
+
except Exception as e:
|
|
83
|
+
typer.echo(str(e), err=True)
|
|
84
|
+
raise typer.Exit(1)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@app.command()
|
|
88
|
+
def unpack(
|
|
89
|
+
input: str = typer.Argument(..., help="File path or raw JSON string to unpack."),
|
|
90
|
+
target: str = typer.Option(".", "-t", "--target", help="Output directory."),
|
|
91
|
+
force: bool = typer.Option(False, "--force/--no-force", help="Overwrite existing files."),
|
|
92
|
+
dry_run: bool = typer.Option(False, "--dry-run", help="Report files that would be unpacked without writing anything."),
|
|
93
|
+
):
|
|
94
|
+
try:
|
|
95
|
+
result = unpack_workspace(input, Path(target), overwrite=force, dry_run=dry_run)
|
|
96
|
+
if dry_run:
|
|
97
|
+
typer.echo(f"Dry run — destination: {result['destination']}")
|
|
98
|
+
typer.echo(f" Would extract : {result['would_extract']}")
|
|
99
|
+
typer.echo(f" Would skip : {result['would_skip']}")
|
|
100
|
+
for f in result["files"]:
|
|
101
|
+
typer.echo(f" {f['action']:20} {f['path']}")
|
|
102
|
+
return
|
|
103
|
+
extracted = result.get("extracted", 0)
|
|
104
|
+
destination = result.get("destination", target)
|
|
105
|
+
skipped = result.get("skipped", 0)
|
|
106
|
+
typer.echo(f"Extracted {extracted} files to {destination} ({skipped} skipped)")
|
|
107
|
+
except Exception as e:
|
|
108
|
+
typer.echo(str(e), err=True)
|
|
109
|
+
raise typer.Exit(1)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@app.command()
|
|
113
|
+
def serve():
|
|
114
|
+
typer.echo("Starting RepoPack MCP server (stdio)...", err=True)
|
|
115
|
+
run_server()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def main():
|
|
119
|
+
app()
|
|
@@ -14,8 +14,16 @@ def export_workspace(
|
|
|
14
14
|
workspace_path: str = ".",
|
|
15
15
|
output_json_path: str | None = None,
|
|
16
16
|
include_binary: bool = False,
|
|
17
|
+
dry_run: bool = False,
|
|
17
18
|
) -> str:
|
|
18
|
-
result = pack_workspace(Path(workspace_path), include_binary=include_binary)
|
|
19
|
+
result = pack_workspace(Path(workspace_path), include_binary=include_binary, dry_run=dry_run)
|
|
20
|
+
if dry_run:
|
|
21
|
+
lines = [
|
|
22
|
+
f"Dry run — would pack {result['total_files']} files ({result['total_bytes']} bytes)",
|
|
23
|
+
f"Root : {result['root_directory']}",
|
|
24
|
+
f"Stack : {', '.join(result['detected_stack']) or 'unknown'}",
|
|
25
|
+
] + [f" {f['encoding']:6} {f['size']:>10} {f['path']}" for f in result["files"]]
|
|
26
|
+
return "\n".join(lines)
|
|
19
27
|
if output_json_path is not None:
|
|
20
28
|
with open(output_json_path, "w", encoding="utf-8") as f:
|
|
21
29
|
json.dump(result, f, indent=2)
|
|
@@ -28,8 +36,16 @@ def import_workspace(
|
|
|
28
36
|
json_input: str,
|
|
29
37
|
destination_path: str,
|
|
30
38
|
overwrite: bool = False,
|
|
39
|
+
dry_run: bool = False,
|
|
31
40
|
) -> str:
|
|
32
|
-
summary = unpack_workspace(json_input, Path(destination_path), overwrite=overwrite)
|
|
41
|
+
summary = unpack_workspace(json_input, Path(destination_path), overwrite=overwrite, dry_run=dry_run)
|
|
42
|
+
if dry_run:
|
|
43
|
+
lines = [
|
|
44
|
+
f"Dry run — destination: {summary['destination']}",
|
|
45
|
+
f" Would extract : {summary['would_extract']}",
|
|
46
|
+
f" Would skip : {summary['would_skip']}",
|
|
47
|
+
] + [f" {f['action']:20} {f['path']}" for f in summary["files"]]
|
|
48
|
+
return "\n".join(lines)
|
|
33
49
|
return f"Extracted {summary['extracted']} files to {summary['destination']} ({summary['skipped']} skipped)"
|
|
34
50
|
|
|
35
51
|
|
|
@@ -13,6 +13,7 @@ def pack_workspace(
|
|
|
13
13
|
directory: Path,
|
|
14
14
|
include_binary: bool = False,
|
|
15
15
|
custom_ignore: str | None = None,
|
|
16
|
+
dry_run: bool = False,
|
|
16
17
|
) -> dict:
|
|
17
18
|
directory = directory.resolve()
|
|
18
19
|
|
|
@@ -58,21 +59,25 @@ def pack_workspace(
|
|
|
58
59
|
if binary and not include_binary:
|
|
59
60
|
continue
|
|
60
61
|
|
|
62
|
+
encoding = "base64" if binary else "utf-8"
|
|
63
|
+
total_bytes += file_size
|
|
64
|
+
|
|
65
|
+
if dry_run:
|
|
66
|
+
files.append({"path": rel_path.as_posix(), "encoding": encoding, "size": file_size})
|
|
67
|
+
continue
|
|
68
|
+
|
|
61
69
|
if binary:
|
|
62
70
|
try:
|
|
63
71
|
raw = file_path.read_bytes()
|
|
64
72
|
except OSError:
|
|
65
73
|
continue
|
|
66
74
|
content = base64.b64encode(raw).decode("ascii")
|
|
67
|
-
encoding = "base64"
|
|
68
75
|
else:
|
|
69
76
|
try:
|
|
70
77
|
content = file_path.read_text(encoding="utf-8", errors="replace")
|
|
71
78
|
except OSError:
|
|
72
79
|
continue
|
|
73
|
-
encoding = "utf-8"
|
|
74
80
|
|
|
75
|
-
total_bytes += file_size
|
|
76
81
|
files.append(
|
|
77
82
|
{
|
|
78
83
|
"path": rel_path.as_posix(),
|
|
@@ -83,6 +88,16 @@ def pack_workspace(
|
|
|
83
88
|
|
|
84
89
|
detected_stack = detect_stack(directory)
|
|
85
90
|
|
|
91
|
+
if dry_run:
|
|
92
|
+
return {
|
|
93
|
+
"dry_run": True,
|
|
94
|
+
"root_directory": str(directory),
|
|
95
|
+
"detected_stack": detected_stack,
|
|
96
|
+
"total_files": len(files),
|
|
97
|
+
"total_bytes": total_bytes,
|
|
98
|
+
"files": files,
|
|
99
|
+
}
|
|
100
|
+
|
|
86
101
|
return {
|
|
87
102
|
"version": "1.0",
|
|
88
103
|
"metadata": {
|
|
@@ -11,6 +11,7 @@ def unpack_workspace(
|
|
|
11
11
|
destination_path: Path,
|
|
12
12
|
overwrite: bool = False,
|
|
13
13
|
force: bool = False,
|
|
14
|
+
dry_run: bool = False,
|
|
14
15
|
) -> dict:
|
|
15
16
|
if Path(json_input).is_file():
|
|
16
17
|
with open(json_input, "r", encoding="utf-8") as f:
|
|
@@ -25,6 +26,7 @@ def unpack_workspace(
|
|
|
25
26
|
|
|
26
27
|
extracted = 0
|
|
27
28
|
skipped = 0
|
|
29
|
+
dry_run_files: list[dict] = []
|
|
28
30
|
|
|
29
31
|
for entry in data["files"]:
|
|
30
32
|
rel = entry.get("path", "")
|
|
@@ -40,8 +42,20 @@ def unpack_workspace(
|
|
|
40
42
|
raise ValueError(f"Path traversal detected: {rel}")
|
|
41
43
|
|
|
42
44
|
output_path = destination_path / rel_path
|
|
45
|
+
would_skip = output_path.exists() and not overwrite
|
|
46
|
+
|
|
47
|
+
if dry_run:
|
|
48
|
+
dry_run_files.append({
|
|
49
|
+
"path": rel,
|
|
50
|
+
"action": "skip (exists)" if would_skip else "create",
|
|
51
|
+
})
|
|
52
|
+
if would_skip:
|
|
53
|
+
skipped += 1
|
|
54
|
+
else:
|
|
55
|
+
extracted += 1
|
|
56
|
+
continue
|
|
43
57
|
|
|
44
|
-
if
|
|
58
|
+
if would_skip:
|
|
45
59
|
skipped += 1
|
|
46
60
|
continue
|
|
47
61
|
|
|
@@ -57,4 +71,13 @@ def unpack_workspace(
|
|
|
57
71
|
|
|
58
72
|
extracted += 1
|
|
59
73
|
|
|
74
|
+
if dry_run:
|
|
75
|
+
return {
|
|
76
|
+
"dry_run": True,
|
|
77
|
+
"destination": str(destination_path),
|
|
78
|
+
"would_extract": extracted,
|
|
79
|
+
"would_skip": skipped,
|
|
80
|
+
"files": dry_run_files,
|
|
81
|
+
}
|
|
82
|
+
|
|
60
83
|
return {"extracted": extracted, "skipped": skipped, "destination": str(destination_path)}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.1"
|
repopackpy-0.1.1/repopack/cli.py
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import typer
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
from repopack.packer import pack_workspace
|
|
6
|
-
from repopack.unpacker import unpack_workspace
|
|
7
|
-
from repopack.mcp_server import run_server
|
|
8
|
-
|
|
9
|
-
app = typer.Typer(name="repopack", help="Pack/unpack any workspace into portable JSON.")
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@app.command()
|
|
13
|
-
def pack(
|
|
14
|
-
directory: str = typer.Argument(".", help="The workspace to pack."),
|
|
15
|
-
output: str = typer.Option(None, "-o", "--output", help="Output JSON file path."),
|
|
16
|
-
include_binary: bool = typer.Option(False, "--include-binary", help="Include binary files."),
|
|
17
|
-
custom_ignore: str = typer.Option(None, "--custom-ignore", help="Comma-separated extra ignore patterns."),
|
|
18
|
-
):
|
|
19
|
-
try:
|
|
20
|
-
result = pack_workspace(Path(directory), include_binary, custom_ignore)
|
|
21
|
-
if output:
|
|
22
|
-
with open(output, "w", encoding="utf-8") as f:
|
|
23
|
-
json.dump(result, f, indent=2)
|
|
24
|
-
total_files = result.get("total_files", 0)
|
|
25
|
-
total_bytes = result.get("total_bytes", 0)
|
|
26
|
-
typer.echo(f"Packed {total_files} files ({total_bytes} bytes) → {output}", err=True)
|
|
27
|
-
else:
|
|
28
|
-
print(json.dumps(result, indent=2))
|
|
29
|
-
except Exception as e:
|
|
30
|
-
typer.echo(str(e), err=True)
|
|
31
|
-
raise typer.Exit(1)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@app.command()
|
|
35
|
-
def unpack(
|
|
36
|
-
input: str = typer.Argument(..., help="File path or raw JSON string to unpack."),
|
|
37
|
-
target: str = typer.Option(".", "-t", "--target", help="Output directory."),
|
|
38
|
-
force: bool = typer.Option(False, "--force/--no-force", help="Overwrite existing files."),
|
|
39
|
-
):
|
|
40
|
-
try:
|
|
41
|
-
result = unpack_workspace(input, Path(target), overwrite=force)
|
|
42
|
-
extracted = result.get("extracted", 0)
|
|
43
|
-
destination = result.get("destination", target)
|
|
44
|
-
skipped = result.get("skipped", 0)
|
|
45
|
-
typer.echo(f"Extracted {extracted} files to {destination} ({skipped} skipped)")
|
|
46
|
-
except Exception as e:
|
|
47
|
-
typer.echo(str(e), err=True)
|
|
48
|
-
raise typer.Exit(1)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
@app.command()
|
|
52
|
-
def serve():
|
|
53
|
-
typer.echo("Starting RepoPack MCP server (stdio)...", err=True)
|
|
54
|
-
run_server()
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def main():
|
|
58
|
-
app()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|