git2svn 0.5.0__tar.gz → 0.5.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.
- {git2svn-0.5.0 → git2svn-0.5.1}/.github/workflows/ci.yml +3 -1
- {git2svn-0.5.0/git2svn.egg-info → git2svn-0.5.1}/PKG-INFO +2 -2
- {git2svn-0.5.0 → git2svn-0.5.1}/README.md +1 -1
- {git2svn-0.5.0 → git2svn-0.5.1}/git2svn/_version.py +2 -2
- {git2svn-0.5.0 → git2svn-0.5.1}/git2svn/git.py +6 -6
- {git2svn-0.5.0 → git2svn-0.5.1}/git2svn/patcher.py +23 -16
- {git2svn-0.5.0 → git2svn-0.5.1/git2svn.egg-info}/PKG-INFO +2 -2
- {git2svn-0.5.0 → git2svn-0.5.1}/tests/test_e2e.py +148 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/.githooks/pre-commit +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/.github/dependabot.yml +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/.github/workflows/release.yml +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/.gitignore +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/.pre-commit-config.yaml +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/CONTRIBUTING.md +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/LICENSE.md +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/docs/arc42/README.md +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/docs/arc42/adrs.md +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/docs/arc42/concepts.md +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/docs/req42.md +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/docs/user-guide/README.md +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/docs/user-guide/commands.md +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/docs/user-guide/integration-workflow.md +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/docs/user-guide/troubleshooting.md +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/git2svn/__init__.py +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/git2svn/__main__.py +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/git2svn/cli.py +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/git2svn/core.py +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/git2svn/eol.py +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/git2svn/state.py +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/git2svn/svn.py +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/git2svn.egg-info/SOURCES.txt +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/git2svn.egg-info/dependency_links.txt +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/git2svn.egg-info/entry_points.txt +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/git2svn.egg-info/requires.txt +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/git2svn.egg-info/scm_file_list.json +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/git2svn.egg-info/top_level.txt +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/git2svn.py +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/pyproject.toml +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/setup.cfg +0 -0
- {git2svn-0.5.0 → git2svn-0.5.1}/tests/test_git2svn.py +0 -0
|
@@ -65,7 +65,9 @@ jobs:
|
|
|
65
65
|
echo "C:\Program Files\TortoiseSVN\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
|
66
66
|
|
|
67
67
|
- name: Install Dependencies
|
|
68
|
-
run:
|
|
68
|
+
run: |
|
|
69
|
+
python -m pip install --upgrade pip setuptools wheel
|
|
70
|
+
pip install --retries 5 -e .[dev]
|
|
69
71
|
|
|
70
72
|
- name: Run Unit & Integration Tests (with Coverage)
|
|
71
73
|
if: runner.os == 'Linux' && matrix.python-version == '3.12'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git2svn
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: CLI utility to synchronize changes from a local Git repository to a local SVN working copy
|
|
5
5
|
Author: Simon
|
|
6
6
|
License: Apache-2.0
|
|
@@ -35,7 +35,7 @@ Dynamic: license-file
|
|
|
35
35
|
<p align="center">
|
|
36
36
|
<a href="https://github.com/simon123h/git2svn/actions/workflows/ci.yml"><img src="https://github.com/simon123h/git2svn/actions/workflows/ci.yml/badge.svg?branch=main" alt="Build Status"></a>
|
|
37
37
|
<a href="https://codecov.io/gh/simon123h/git2svn"><img src="https://codecov.io/gh/simon123h/git2svn/branch/main/graph/badge.svg" alt="Coverage"></a>
|
|
38
|
-
<a href="https://pypi.org/project/git2svn/"><img src="https://img.shields.io/pypi/v/git2svn?color=
|
|
38
|
+
<a href="https://pypi.org/project/git2svn/"><img src="https://img.shields.io/pypi/v/git2svn?color=green" alt="PyPI Version"></a>
|
|
39
39
|
<img src="https://img.shields.io/badge/Python-v3.11+-3776AB?logo=python&logoColor=white" alt="Python Version">
|
|
40
40
|
<img src="https://img.shields.io/endpoint?url=https%3A%2F%2Ftokei.kojix2.net%2Fbadge%2Fgithub%2Fsimon123h%2Fgit2svn%2Flines" alt="Lines of Code">
|
|
41
41
|
<a href="https://github.com/simon123h/git2svn/releases"><img src="https://badgen.net/github/release/simon123h/git2svn?color=orange" alt="Latest Release"></a>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<a href="https://github.com/simon123h/git2svn/actions/workflows/ci.yml"><img src="https://github.com/simon123h/git2svn/actions/workflows/ci.yml/badge.svg?branch=main" alt="Build Status"></a>
|
|
5
5
|
<a href="https://codecov.io/gh/simon123h/git2svn"><img src="https://codecov.io/gh/simon123h/git2svn/branch/main/graph/badge.svg" alt="Coverage"></a>
|
|
6
|
-
<a href="https://pypi.org/project/git2svn/"><img src="https://img.shields.io/pypi/v/git2svn?color=
|
|
6
|
+
<a href="https://pypi.org/project/git2svn/"><img src="https://img.shields.io/pypi/v/git2svn?color=green" alt="PyPI Version"></a>
|
|
7
7
|
<img src="https://img.shields.io/badge/Python-v3.11+-3776AB?logo=python&logoColor=white" alt="Python Version">
|
|
8
8
|
<img src="https://img.shields.io/endpoint?url=https%3A%2F%2Ftokei.kojix2.net%2Fbadge%2Fgithub%2Fsimon123h%2Fgit2svn%2Flines" alt="Lines of Code">
|
|
9
9
|
<a href="https://github.com/simon123h/git2svn/releases"><img src="https://badgen.net/github/release/simon123h/git2svn?color=orange" alt="Latest Release"></a>
|
|
@@ -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 = '0.5.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 5,
|
|
21
|
+
__version__ = version = '0.5.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 5, 1)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -218,15 +218,15 @@ class GitRepo:
|
|
|
218
218
|
return res.stdout.strip()
|
|
219
219
|
return None
|
|
220
220
|
|
|
221
|
-
def get_diff(self, ref_spec: str) ->
|
|
222
|
-
"""Get unified diff for the given reference specification."""
|
|
223
|
-
res = self.
|
|
221
|
+
def get_diff(self, ref_spec: str) -> bytes:
|
|
222
|
+
"""Get unified diff for the given reference specification as raw bytes."""
|
|
223
|
+
res = self.run_cmd_bytes(["diff", "--binary", ref_spec])
|
|
224
224
|
return res.stdout
|
|
225
225
|
|
|
226
|
-
def get_diff_root(self, commit_hash: str) ->
|
|
227
|
-
"""Get unified diff for a root commit."""
|
|
226
|
+
def get_diff_root(self, commit_hash: str) -> bytes:
|
|
227
|
+
"""Get unified diff for a root commit as raw bytes."""
|
|
228
228
|
empty_tree_hash = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
|
|
229
|
-
res = self.
|
|
229
|
+
res = self.run_cmd_bytes(["diff", "--binary", empty_tree_hash, commit_hash])
|
|
230
230
|
return res.stdout
|
|
231
231
|
|
|
232
232
|
def get_name_status(self, ref_spec: str) -> List[FileChange]:
|
|
@@ -4,6 +4,7 @@ import logging
|
|
|
4
4
|
import subprocess
|
|
5
5
|
import sys
|
|
6
6
|
from pathlib import Path
|
|
7
|
+
from typing import Union
|
|
7
8
|
|
|
8
9
|
logger = logging.getLogger("git2svn")
|
|
9
10
|
|
|
@@ -16,43 +17,49 @@ class Patcher:
|
|
|
16
17
|
self.git_bin = git_bin
|
|
17
18
|
self.dry_run = dry_run
|
|
18
19
|
|
|
19
|
-
def apply_diff(self, diff_content: str) -> None:
|
|
20
|
-
"""Apply unified diff text to target_dir using git apply."""
|
|
21
|
-
if
|
|
20
|
+
def apply_diff(self, diff_content: Union[str, bytes]) -> None:
|
|
21
|
+
"""Apply unified diff text or bytes to target_dir using git apply."""
|
|
22
|
+
if isinstance(diff_content, str):
|
|
23
|
+
diff_bytes = diff_content.encode("utf-8")
|
|
24
|
+
else:
|
|
25
|
+
diff_bytes = diff_content
|
|
26
|
+
|
|
27
|
+
if not diff_bytes.strip():
|
|
22
28
|
logger.info("Diff is empty. Nothing to patch.")
|
|
23
29
|
return
|
|
24
30
|
|
|
25
31
|
cmd = [self.git_bin, "apply", "--ignore-whitespace", "--unsafe-paths", "--reject"]
|
|
26
32
|
if self.dry_run:
|
|
27
|
-
|
|
33
|
+
preview = diff_bytes.strip()[:200].decode("utf-8", errors="replace")
|
|
34
|
+
print(f"[DRY-RUN] (in {self.target_dir}) {' '.join(cmd)} << EOF\n{preview}...\nEOF")
|
|
28
35
|
return
|
|
29
36
|
|
|
30
37
|
logger.debug("Applying patch to %s using %s", self.target_dir, " ".join(cmd))
|
|
31
38
|
proc = subprocess.run(
|
|
32
39
|
cmd,
|
|
33
40
|
cwd=self.target_dir,
|
|
34
|
-
input=
|
|
41
|
+
input=diff_bytes,
|
|
35
42
|
capture_output=True,
|
|
36
|
-
text=True,
|
|
37
|
-
encoding="utf-8",
|
|
38
|
-
errors="replace",
|
|
39
43
|
)
|
|
40
44
|
|
|
45
|
+
stdout_str = proc.stdout.decode("utf-8", errors="replace") if proc.stdout else ""
|
|
46
|
+
stderr_str = proc.stderr.decode("utf-8", errors="replace") if proc.stderr else ""
|
|
47
|
+
|
|
41
48
|
if proc.returncode != 0:
|
|
42
49
|
logger.error(
|
|
43
|
-
"git apply failed with code %d:\nSTDOUT:\n%s\nSTDERR:\n%s", proc.returncode,
|
|
50
|
+
"git apply failed with code %d:\nSTDOUT:\n%s\nSTDERR:\n%s", proc.returncode, stdout_str, stderr_str
|
|
44
51
|
)
|
|
45
52
|
print(f"Error: git apply failed (exit code {proc.returncode}).", file=sys.stderr)
|
|
46
|
-
if
|
|
47
|
-
print(
|
|
48
|
-
if
|
|
49
|
-
print(
|
|
53
|
+
if stdout_str:
|
|
54
|
+
print(stdout_str, file=sys.stderr)
|
|
55
|
+
if stderr_str:
|
|
56
|
+
print(stderr_str, file=sys.stderr)
|
|
50
57
|
print(
|
|
51
58
|
"Tip: Check for .rej reject files in the SVN workspace or use 'git2svn stage --copy' to copy files directly.",
|
|
52
59
|
file=sys.stderr,
|
|
53
60
|
)
|
|
54
|
-
raise subprocess.CalledProcessError(proc.returncode, cmd,
|
|
61
|
+
raise subprocess.CalledProcessError(proc.returncode, cmd, stdout_str, stderr_str)
|
|
55
62
|
else:
|
|
56
|
-
if
|
|
57
|
-
msg = (
|
|
63
|
+
if stdout_str or stderr_str:
|
|
64
|
+
msg = (stdout_str + "\n" + stderr_str).strip()
|
|
58
65
|
logger.info("Patch applied successfully:\n%s", msg)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git2svn
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: CLI utility to synchronize changes from a local Git repository to a local SVN working copy
|
|
5
5
|
Author: Simon
|
|
6
6
|
License: Apache-2.0
|
|
@@ -35,7 +35,7 @@ Dynamic: license-file
|
|
|
35
35
|
<p align="center">
|
|
36
36
|
<a href="https://github.com/simon123h/git2svn/actions/workflows/ci.yml"><img src="https://github.com/simon123h/git2svn/actions/workflows/ci.yml/badge.svg?branch=main" alt="Build Status"></a>
|
|
37
37
|
<a href="https://codecov.io/gh/simon123h/git2svn"><img src="https://codecov.io/gh/simon123h/git2svn/branch/main/graph/badge.svg" alt="Coverage"></a>
|
|
38
|
-
<a href="https://pypi.org/project/git2svn/"><img src="https://img.shields.io/pypi/v/git2svn?color=
|
|
38
|
+
<a href="https://pypi.org/project/git2svn/"><img src="https://img.shields.io/pypi/v/git2svn?color=green" alt="PyPI Version"></a>
|
|
39
39
|
<img src="https://img.shields.io/badge/Python-v3.11+-3776AB?logo=python&logoColor=white" alt="Python Version">
|
|
40
40
|
<img src="https://img.shields.io/endpoint?url=https%3A%2F%2Ftokei.kojix2.net%2Fbadge%2Fgithub%2Fsimon123h%2Fgit2svn%2Flines" alt="Lines of Code">
|
|
41
41
|
<a href="https://github.com/simon123h/git2svn/releases"><img src="https://badgen.net/github/release/simon123h/git2svn?color=orange" alt="Latest Release"></a>
|
|
@@ -232,3 +232,151 @@ class TestGit2SvnE2E(unittest.TestCase):
|
|
|
232
232
|
status = self._svn_status()
|
|
233
233
|
self.assertIn("D obsolete.txt", status)
|
|
234
234
|
self.assertIn("A brand_new.txt", status)
|
|
235
|
+
|
|
236
|
+
def test_e2e_replay_conflict_pause_and_continue(self):
|
|
237
|
+
"""Test replay pausing on a patch conflict in a real SVN WC and resuming with --continue."""
|
|
238
|
+
# 1. Base commit in Git and SVN
|
|
239
|
+
f_git = self.git_dir / "conflict_target.txt"
|
|
240
|
+
f_git.write_text("line 1\nline 2\nline 3\n", encoding="utf-8")
|
|
241
|
+
base_hash = self._git_commit("base: conflict target")
|
|
242
|
+
|
|
243
|
+
(self.svn_wc_dir / "conflict_target.txt").write_text("line 1\nline 2\nline 3\n", encoding="utf-8")
|
|
244
|
+
subprocess.run([SVN_BIN, "add", "conflict_target.txt"], cwd=self.svn_wc_dir, check=True)
|
|
245
|
+
subprocess.run([SVN_BIN, "commit", "-m", "init conflict target"], cwd=self.svn_wc_dir, check=True)
|
|
246
|
+
|
|
247
|
+
# 2. Modify in SVN out-of-band to cause conflict
|
|
248
|
+
(self.svn_wc_dir / "conflict_target.txt").write_text("line 1 DIFFERENT\nline 2\nline 3\n", encoding="utf-8")
|
|
249
|
+
subprocess.run([SVN_BIN, "commit", "-m", "remote edit in svn"], cwd=self.svn_wc_dir, check=True)
|
|
250
|
+
subprocess.run([SVN_BIN, "update"], cwd=self.svn_wc_dir, check=True)
|
|
251
|
+
|
|
252
|
+
# 3. Git commit 1 (touches line 1 and 2, which conflicts)
|
|
253
|
+
f_git.write_text("line 1 GIT_EDIT\nline 2 GIT_EDIT\nline 3\n", encoding="utf-8")
|
|
254
|
+
self._git_commit("feat: conflicting git commit")
|
|
255
|
+
|
|
256
|
+
# 4. Git commit 2 (subsequent non-conflicting commit)
|
|
257
|
+
f_next = self.git_dir / "subsequent.txt"
|
|
258
|
+
f_next.write_text("subsequent content\n", encoding="utf-8")
|
|
259
|
+
c2_hash = self._git_commit("feat: subsequent commit")
|
|
260
|
+
|
|
261
|
+
# 5. Run replay range - should pause with non-zero exit code due to conflict
|
|
262
|
+
exit_code = cli_main(["replay", f"{base_hash}..{c2_hash}", "-g", str(self.git_dir), "-s", str(self.svn_wc_dir)])
|
|
263
|
+
self.assertNotEqual(exit_code, 0)
|
|
264
|
+
|
|
265
|
+
# Verify .rej artifact exists
|
|
266
|
+
rej_file = self.svn_wc_dir / "conflict_target.txt.rej"
|
|
267
|
+
self.assertTrue(rej_file.exists())
|
|
268
|
+
|
|
269
|
+
# 6. Resolve conflict manually in SVN working copy and remove .rej artifact
|
|
270
|
+
(self.svn_wc_dir / "conflict_target.txt").write_text(
|
|
271
|
+
"line 1 RESOLVED\nline 2 GIT_EDIT\nline 3\n", encoding="utf-8"
|
|
272
|
+
)
|
|
273
|
+
rej_file.unlink()
|
|
274
|
+
|
|
275
|
+
# 7. Continue replay
|
|
276
|
+
cont_code = cli_main(["replay", "--continue", "-g", str(self.git_dir), "-s", str(self.svn_wc_dir)])
|
|
277
|
+
self.assertEqual(cont_code, 0)
|
|
278
|
+
|
|
279
|
+
# Verify SVN working copy is clean and both commits are in SVN log
|
|
280
|
+
self.assertEqual(self._svn_status().strip(), "")
|
|
281
|
+
_, full_log = self._svn_log_messages()
|
|
282
|
+
self.assertIn("conflicting git commit", full_log)
|
|
283
|
+
self.assertIn("subsequent commit", full_log)
|
|
284
|
+
|
|
285
|
+
# Verify final files on disk in SVN
|
|
286
|
+
self.assertEqual(
|
|
287
|
+
(self.svn_wc_dir / "conflict_target.txt").read_text(encoding="utf-8"),
|
|
288
|
+
"line 1 RESOLVED\nline 2 GIT_EDIT\nline 3\n",
|
|
289
|
+
)
|
|
290
|
+
self.assertEqual((self.svn_wc_dir / "subsequent.txt").read_text(encoding="utf-8"), "subsequent content\n")
|
|
291
|
+
|
|
292
|
+
def test_e2e_file_renames_and_moves(self):
|
|
293
|
+
"""Test replay correctly handles file renames and moves across directories in real SVN."""
|
|
294
|
+
# 1. Base commit: create src/old_name.txt
|
|
295
|
+
src_dir = self.git_dir / "src"
|
|
296
|
+
src_dir.mkdir()
|
|
297
|
+
orig_file = src_dir / "old_name.txt"
|
|
298
|
+
orig_file.write_text("print('hello rename')\n", encoding="utf-8")
|
|
299
|
+
base_hash = self._git_commit("init: original file")
|
|
300
|
+
|
|
301
|
+
# Replay base to SVN
|
|
302
|
+
code_base = cli_main(["replay", base_hash, "-g", str(self.git_dir), "-s", str(self.svn_wc_dir)])
|
|
303
|
+
self.assertEqual(code_base, 0)
|
|
304
|
+
|
|
305
|
+
# 2. Git commit: rename src/old_name.txt -> src/nested/renamed.txt
|
|
306
|
+
nested_dir = src_dir / "nested"
|
|
307
|
+
nested_dir.mkdir()
|
|
308
|
+
subprocess.run([GIT_BIN, "mv", "src/old_name.txt", "src/nested/renamed.txt"], cwd=self.git_dir, check=True)
|
|
309
|
+
rename_hash = self._git_commit("refactor: move and rename file")
|
|
310
|
+
|
|
311
|
+
# 3. Replay rename commit to SVN
|
|
312
|
+
code_rename = cli_main(
|
|
313
|
+
["replay", f"{base_hash}..{rename_hash}", "-g", str(self.git_dir), "-s", str(self.svn_wc_dir)]
|
|
314
|
+
)
|
|
315
|
+
self.assertEqual(code_rename, 0)
|
|
316
|
+
|
|
317
|
+
# 4. Verify SVN state
|
|
318
|
+
self.assertFalse((self.svn_wc_dir / "src" / "old_name.txt").exists())
|
|
319
|
+
renamed_svn = self.svn_wc_dir / "src" / "nested" / "renamed.txt"
|
|
320
|
+
self.assertTrue(renamed_svn.is_file())
|
|
321
|
+
self.assertEqual(renamed_svn.read_text(encoding="utf-8"), "print('hello rename')\n")
|
|
322
|
+
self.assertEqual(self._svn_status().strip(), "")
|
|
323
|
+
|
|
324
|
+
def test_e2e_binary_files(self):
|
|
325
|
+
"""Test replay correctly handles adding and modifying binary files with null bytes in real SVN."""
|
|
326
|
+
# 1. Root commit
|
|
327
|
+
(self.git_dir / "init.txt").write_text("init\n")
|
|
328
|
+
root_hash = self._git_commit("init: root commit")
|
|
329
|
+
|
|
330
|
+
# Replay root commit to SVN
|
|
331
|
+
code_root = cli_main(["replay", root_hash, "-g", str(self.git_dir), "-s", str(self.svn_wc_dir)])
|
|
332
|
+
self.assertEqual(code_root, 0)
|
|
333
|
+
|
|
334
|
+
# 2. Add binary file
|
|
335
|
+
bin_git = self.git_dir / "image.bin"
|
|
336
|
+
initial_bytes = bytes([0x00, 0xFF, 0xFE, 0x01, 0x00, 0x42, 0xAA, 0x55] * 32)
|
|
337
|
+
bin_git.write_bytes(initial_bytes)
|
|
338
|
+
self._git_commit("feat: add binary image")
|
|
339
|
+
|
|
340
|
+
# 3. Modify binary file
|
|
341
|
+
modified_bytes = bytes([0x00, 0x00, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA] * 40)
|
|
342
|
+
bin_git.write_bytes(modified_bytes)
|
|
343
|
+
mod_hash = self._git_commit("fix: update binary bytes")
|
|
344
|
+
|
|
345
|
+
# 4. Replay range to SVN
|
|
346
|
+
code = cli_main(["replay", f"{root_hash}..{mod_hash}", "-g", str(self.git_dir), "-s", str(self.svn_wc_dir)])
|
|
347
|
+
self.assertEqual(code, 0)
|
|
348
|
+
|
|
349
|
+
# 4. Verify SVN file content is bit-for-bit identical
|
|
350
|
+
bin_svn = self.svn_wc_dir / "image.bin"
|
|
351
|
+
self.assertTrue(bin_svn.is_file())
|
|
352
|
+
self.assertEqual(bin_svn.read_bytes(), modified_bytes)
|
|
353
|
+
self.assertEqual(self._svn_status().strip(), "")
|
|
354
|
+
|
|
355
|
+
def test_e2e_status_real_repo(self):
|
|
356
|
+
"""Test git2svn status command execution against real Git and real SVN repositories."""
|
|
357
|
+
# Setup base commit in Git and SVN
|
|
358
|
+
f = self.git_dir / "status_test.txt"
|
|
359
|
+
f.write_text("status test\n", encoding="utf-8")
|
|
360
|
+
base_hash = self._git_commit("init: status test file")
|
|
361
|
+
|
|
362
|
+
cli_main(["replay", base_hash, "-g", str(self.git_dir), "-s", str(self.svn_wc_dir)])
|
|
363
|
+
|
|
364
|
+
# Setup pending commit in Git
|
|
365
|
+
f.write_text("status test modified\n", encoding="utf-8")
|
|
366
|
+
pending_hash = self._git_commit("feat: pending feature for status")
|
|
367
|
+
|
|
368
|
+
# Configure defaultRange and svnDir in Git config
|
|
369
|
+
subprocess.run(
|
|
370
|
+
[GIT_BIN, "config", "git2svn.defaultRange", f"{base_hash}..{pending_hash}"],
|
|
371
|
+
cwd=self.git_dir,
|
|
372
|
+
check=True,
|
|
373
|
+
)
|
|
374
|
+
subprocess.run(
|
|
375
|
+
[GIT_BIN, "config", "git2svn.svnDir", str(self.svn_wc_dir)],
|
|
376
|
+
cwd=self.git_dir,
|
|
377
|
+
check=True,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
# Run git2svn status
|
|
381
|
+
exit_code = cli_main(["status", "-g", str(self.git_dir)])
|
|
382
|
+
self.assertEqual(exit_code, 0)
|
|
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
|