marin-shellbox 0.2.152.dev37849924057__tar.gz → 0.2.153.dev37866254792__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.
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/PKG-INFO +2 -2
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/pyproject.toml +2 -2
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/shellsim/machine.py +22 -1
- marin_shellbox-0.2.153.dev37866254792/tests/test_shellsim_machine.py +106 -0
- marin_shellbox-0.2.152.dev37849924057/tests/test_shellsim_machine.py +0 -55
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/.gitignore +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/README.md +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/benchmarks/tasktrove/compare.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/benchmarks/tasktrove/sample.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/__init__.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/agent.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/__init__.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/daytona/__init__.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/daytona/machine.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/docker/__init__.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/docker/machine.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/gvisor/__init__.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/gvisor/machine.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/iris/__init__.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/iris/machine.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/local/__init__.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/local/launch.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/local/machine.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/local/python_environment.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/qemu/__init__.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/qemu/bundle.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/qemu/environment.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/qemu/guest/disk-init +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/qemu/guest/init +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/qemu/guest/pty-agent.c +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/qemu/image.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/qemu/machine.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/shellsim/__init__.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/backends/shellsim/environment.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/image.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/src/shellbox/machine.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/tests/harbor_smoke.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/tests/manual/README.md +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/tests/manual/check_machine.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/tests/manual/check_prepared_image.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/tests/manual/check_qemu_isolation.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/tests/manual/check_shell.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/tests/manual/task/instruction.md +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/tests/manual/task/task.toml +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/tests/manual/task/tests/test.sh +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/tests/manual/tasktrove_nl2bash_command.sh +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/tests/test_daytona_machine.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/tests/test_docker_machine.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/tests/test_gvisor_machine.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/tests/test_harbor_shellsim.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/tests/test_image_cache.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/tests/test_iris_machine.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/tests/test_local_machine.py +0 -0
- {marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/tests/test_qemu_machine.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: marin-shellbox
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.153.dev37866254792
|
|
4
4
|
Summary: Shell execution machines with local and remote backends
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Requires-Dist: bubblewrap-bin==0.11.0; sys_platform == 'linux'
|
|
@@ -10,7 +10,7 @@ Requires-Dist: daytona==0.200.2; extra == 'daytona'
|
|
|
10
10
|
Requires-Dist: marin-rigging>=0.2.0; extra == 'daytona'
|
|
11
11
|
Provides-Extra: gvisor
|
|
12
12
|
Provides-Extra: iris
|
|
13
|
-
Requires-Dist: marin-iris==0.2.
|
|
13
|
+
Requires-Dist: marin-iris==0.2.153.dev37866254792; extra == 'iris'
|
|
14
14
|
Provides-Extra: qemu
|
|
15
15
|
Requires-Dist: quicksand-qemu<0.6,>=0.5.12; extra == 'qemu'
|
|
16
16
|
Provides-Extra: shellsim
|
{marin_shellbox-0.2.152.dev37849924057 → marin_shellbox-0.2.153.dev37866254792}/pyproject.toml
RENAMED
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "marin-shellbox"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.153.dev37866254792"
|
|
8
8
|
description = "Shell execution machines with local and remote backends"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -16,7 +16,7 @@ shellsim = ["shellsim>=0.1.29,<0.2", "marin-rigging>=0.2.0"]
|
|
|
16
16
|
gvisor = []
|
|
17
17
|
daytona = ["daytona==0.200.2", "marin-rigging>=0.2.0"]
|
|
18
18
|
iris = [
|
|
19
|
-
"marin-iris==0.2.
|
|
19
|
+
"marin-iris==0.2.153.dev37866254792",
|
|
20
20
|
]
|
|
21
21
|
|
|
22
22
|
[project.scripts]
|
|
@@ -31,6 +31,7 @@ DEFAULT_CPU_LIMIT = 10_000_000_000
|
|
|
31
31
|
DEFAULT_DISK_LIMIT = 256 * 1024 * 1024
|
|
32
32
|
DEFAULT_OUTPUT_LIMIT = 128 * 1024 * 1024
|
|
33
33
|
DEFAULT_MEMORY_MB = 256
|
|
34
|
+
SIMULATOR_PANIC_EXIT_CODE = 1
|
|
34
35
|
ENV_NAME = re.compile(r"[A-Za-z_][A-Za-z0-9_]*\Z")
|
|
35
36
|
|
|
36
37
|
|
|
@@ -45,7 +46,16 @@ class ShellSimShellSession:
|
|
|
45
46
|
async def execute(
|
|
46
47
|
self, command: str, *, wait: float = 120, output_limit_bytes: int = DEFAULT_MACHINE_OUTPUT_LIMIT_BYTES
|
|
47
48
|
) -> ShellUpdate:
|
|
48
|
-
|
|
49
|
+
try:
|
|
50
|
+
result = await self.machine._run_source(command)
|
|
51
|
+
except shellsim.SimulationError as error:
|
|
52
|
+
output = str(error).encode()
|
|
53
|
+
return ShellUpdate(
|
|
54
|
+
output[:output_limit_bytes],
|
|
55
|
+
ShellStatus.COMPLETED,
|
|
56
|
+
SIMULATOR_PANIC_EXIT_CODE,
|
|
57
|
+
len(output) > output_limit_bytes,
|
|
58
|
+
)
|
|
49
59
|
output = result.stdout + result.stderr
|
|
50
60
|
return ShellUpdate(
|
|
51
61
|
output[:output_limit_bytes],
|
|
@@ -123,6 +133,17 @@ class ShellSimMachine:
|
|
|
123
133
|
except TimeoutError:
|
|
124
134
|
self._closed = True
|
|
125
135
|
return Result(None, b"", b"", False, False, ExitReason.TIMED_OUT)
|
|
136
|
+
except shellsim.SimulationError as error:
|
|
137
|
+
stderr = str(error).encode()
|
|
138
|
+
limit = command.output_limit_bytes
|
|
139
|
+
return Result(
|
|
140
|
+
SIMULATOR_PANIC_EXIT_CODE,
|
|
141
|
+
b"",
|
|
142
|
+
stderr[:limit],
|
|
143
|
+
False,
|
|
144
|
+
len(stderr) > limit,
|
|
145
|
+
ExitReason.EXITED,
|
|
146
|
+
)
|
|
126
147
|
limit = command.output_limit_bytes
|
|
127
148
|
return Result(
|
|
128
149
|
result.returncode,
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Copyright The Marin Authors
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Behavior checks for the simulated shell and verifier boundary."""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from shellbox.backends.shellsim.machine import ShellSimMachineFactory
|
|
10
|
+
from shellbox.machine import Command, ExitReason, MachineSpec, ShellSimBuiltins, ShellStatus
|
|
11
|
+
|
|
12
|
+
PANIC_COMMAND = "cat /workspace/numbers.txt | python3 -c 'import sys; print(sum(int(line) for line in sys.stdin))'"
|
|
13
|
+
PANIC_OUTPUT_LIMIT_BYTES = 64
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_shell_state_and_one_shot_command() -> None:
|
|
17
|
+
async def check() -> None:
|
|
18
|
+
machine = await ShellSimMachineFactory().create(MachineSpec(ShellSimBuiltins()))
|
|
19
|
+
try:
|
|
20
|
+
shell = await machine.open_shell()
|
|
21
|
+
first = await shell.execute(
|
|
22
|
+
'cd /tmp; export ANSWER=hello; answer() { echo "$ANSWER"; }; echo file >/workspace/result'
|
|
23
|
+
)
|
|
24
|
+
assert first.status is ShellStatus.COMPLETED
|
|
25
|
+
second = await shell.execute("pwd; answer")
|
|
26
|
+
assert second.output == b"/tmp\nhello\n"
|
|
27
|
+
|
|
28
|
+
verifier = await machine.run(Command(("sh", "-c", "pwd; cat /workspace/result")))
|
|
29
|
+
assert verifier.stdout == b"/workspace\nfile\n"
|
|
30
|
+
after = await shell.execute("pwd; answer")
|
|
31
|
+
assert after.output == b"/tmp\nhello\n"
|
|
32
|
+
finally:
|
|
33
|
+
await machine.close()
|
|
34
|
+
|
|
35
|
+
asyncio.run(check())
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_file_transfer_and_bounded_output(tmp_path: Path) -> None:
|
|
39
|
+
async def check() -> None:
|
|
40
|
+
machine = await ShellSimMachineFactory().create(MachineSpec(ShellSimBuiltins()))
|
|
41
|
+
try:
|
|
42
|
+
source = tmp_path / "source"
|
|
43
|
+
source.mkdir()
|
|
44
|
+
(source / "data.txt").write_text("payload")
|
|
45
|
+
await machine.upload(source, "/workspace/imported")
|
|
46
|
+
shell = await machine.open_shell()
|
|
47
|
+
result = await shell.execute("cat /workspace/imported/data.txt; printf 'x'", output_limit_bytes=4)
|
|
48
|
+
assert result.output == b"payl"
|
|
49
|
+
assert result.truncated
|
|
50
|
+
|
|
51
|
+
target = tmp_path / "downloaded"
|
|
52
|
+
target.mkdir()
|
|
53
|
+
await machine.download("/workspace/imported", target)
|
|
54
|
+
assert (target / "data.txt").read_text() == "payload"
|
|
55
|
+
finally:
|
|
56
|
+
await machine.close()
|
|
57
|
+
|
|
58
|
+
asyncio.run(check())
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_run_simulator_panic_returns_failed_result(tmp_path: Path) -> None:
|
|
62
|
+
async def check() -> None:
|
|
63
|
+
machine = await ShellSimMachineFactory().create(MachineSpec(ShellSimBuiltins()))
|
|
64
|
+
try:
|
|
65
|
+
source = tmp_path / "numbers.txt"
|
|
66
|
+
source.write_text("1\n2\n")
|
|
67
|
+
await machine.upload(source, "/workspace/numbers.txt")
|
|
68
|
+
|
|
69
|
+
result = await machine.run(Command(("sh", "-c", PANIC_COMMAND), output_limit_bytes=PANIC_OUTPUT_LIMIT_BYTES))
|
|
70
|
+
assert result.reason is ExitReason.EXITED
|
|
71
|
+
assert result.exit_code == 1
|
|
72
|
+
assert result.stdout == b""
|
|
73
|
+
assert result.stderr.startswith(b"shellsim operation panicked")
|
|
74
|
+
assert len(result.stderr) == PANIC_OUTPUT_LIMIT_BYTES
|
|
75
|
+
assert result.stderr_truncated
|
|
76
|
+
|
|
77
|
+
followup = await machine.run(Command(("cat", "/workspace/numbers.txt")))
|
|
78
|
+
assert (followup.exit_code, followup.stdout) == (0, b"1\n2\n")
|
|
79
|
+
finally:
|
|
80
|
+
await machine.close()
|
|
81
|
+
|
|
82
|
+
asyncio.run(check())
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_shell_session_simulator_panic_returns_failed_update(tmp_path: Path) -> None:
|
|
86
|
+
async def check() -> None:
|
|
87
|
+
machine = await ShellSimMachineFactory().create(MachineSpec(ShellSimBuiltins()))
|
|
88
|
+
try:
|
|
89
|
+
source = tmp_path / "numbers.txt"
|
|
90
|
+
source.write_text("1\n2\n")
|
|
91
|
+
await machine.upload(source, "/workspace/numbers.txt")
|
|
92
|
+
shell = await machine.open_shell()
|
|
93
|
+
|
|
94
|
+
update = await shell.execute(PANIC_COMMAND, output_limit_bytes=PANIC_OUTPUT_LIMIT_BYTES)
|
|
95
|
+
assert update.status is ShellStatus.COMPLETED
|
|
96
|
+
assert update.exit_code == 1
|
|
97
|
+
assert update.output.startswith(b"shellsim operation panicked")
|
|
98
|
+
assert len(update.output) == PANIC_OUTPUT_LIMIT_BYTES
|
|
99
|
+
assert update.truncated
|
|
100
|
+
|
|
101
|
+
followup = await shell.execute("cat /workspace/numbers.txt")
|
|
102
|
+
assert (followup.exit_code, followup.output) == (0, b"1\n2\n")
|
|
103
|
+
finally:
|
|
104
|
+
await machine.close()
|
|
105
|
+
|
|
106
|
+
asyncio.run(check())
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# Copyright The Marin Authors
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
"""Behavior checks for the simulated shell and verifier boundary."""
|
|
5
|
-
|
|
6
|
-
import asyncio
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
|
|
9
|
-
from shellbox.backends.shellsim.machine import ShellSimMachineFactory
|
|
10
|
-
from shellbox.machine import Command, MachineSpec, ShellSimBuiltins, ShellStatus
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def test_shell_state_and_one_shot_command() -> None:
|
|
14
|
-
async def check() -> None:
|
|
15
|
-
machine = await ShellSimMachineFactory().create(MachineSpec(ShellSimBuiltins()))
|
|
16
|
-
try:
|
|
17
|
-
shell = await machine.open_shell()
|
|
18
|
-
first = await shell.execute(
|
|
19
|
-
'cd /tmp; export ANSWER=hello; answer() { echo "$ANSWER"; }; echo file >/workspace/result'
|
|
20
|
-
)
|
|
21
|
-
assert first.status is ShellStatus.COMPLETED
|
|
22
|
-
second = await shell.execute("pwd; answer")
|
|
23
|
-
assert second.output == b"/tmp\nhello\n"
|
|
24
|
-
|
|
25
|
-
verifier = await machine.run(Command(("sh", "-c", "pwd; cat /workspace/result")))
|
|
26
|
-
assert verifier.stdout == b"/workspace\nfile\n"
|
|
27
|
-
after = await shell.execute("pwd; answer")
|
|
28
|
-
assert after.output == b"/tmp\nhello\n"
|
|
29
|
-
finally:
|
|
30
|
-
await machine.close()
|
|
31
|
-
|
|
32
|
-
asyncio.run(check())
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def test_file_transfer_and_bounded_output(tmp_path: Path) -> None:
|
|
36
|
-
async def check() -> None:
|
|
37
|
-
machine = await ShellSimMachineFactory().create(MachineSpec(ShellSimBuiltins()))
|
|
38
|
-
try:
|
|
39
|
-
source = tmp_path / "source"
|
|
40
|
-
source.mkdir()
|
|
41
|
-
(source / "data.txt").write_text("payload")
|
|
42
|
-
await machine.upload(source, "/workspace/imported")
|
|
43
|
-
shell = await machine.open_shell()
|
|
44
|
-
result = await shell.execute("cat /workspace/imported/data.txt; printf 'x'", output_limit_bytes=4)
|
|
45
|
-
assert result.output == b"payl"
|
|
46
|
-
assert result.truncated
|
|
47
|
-
|
|
48
|
-
target = tmp_path / "downloaded"
|
|
49
|
-
target.mkdir()
|
|
50
|
-
await machine.download("/workspace/imported", target)
|
|
51
|
-
assert (target / "data.txt").read_text() == "payload"
|
|
52
|
-
finally:
|
|
53
|
-
await machine.close()
|
|
54
|
-
|
|
55
|
-
asyncio.run(check())
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|