git-p4son 0.2.8__tar.gz → 0.2.9__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.
- {git_p4son-0.2.8 → git_p4son-0.2.9}/PKG-INFO +1 -1
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/__init__.py +1 -1
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/changelist_store.py +23 -2
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/common.py +16 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/new.py +10 -2
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/perforce.py +3 -18
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/review.py +9 -1
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/sync.py +3 -17
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son.egg-info/PKG-INFO +1 -1
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son.egg-info/SOURCES.txt +1 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/pyproject.toml +1 -1
- git_p4son-0.2.9/tests/test_changelist_store.py +76 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/tests/test_common.py +33 -14
- {git_p4son-0.2.8 → git_p4son-0.2.9}/tests/test_sync.py +4 -36
- {git_p4son-0.2.8 → git_p4son-0.2.9}/LICENSE +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/README.md +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/__main__.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/alias.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/cli.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/complete.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/completions/_git-p4son +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/completions/git-p4son.bash +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/completions/git-p4son.ps1 +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/config.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/git.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/init.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/lib.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/list_changes.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/log.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son/update.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son.egg-info/dependency_links.txt +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son.egg-info/entry_points.txt +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/git_p4son.egg-info/top_level.txt +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/setup.cfg +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/tests/test_cli.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/tests/test_complete.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/tests/test_config.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/tests/test_init.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/tests/test_lib_changelist.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/tests/test_lib_edit.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/tests/test_lib_review.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/tests/test_list_changes.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/tests/test_log.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/tests/test_perforce.py +0 -0
- {git_p4son-0.2.8 → git_p4son-0.2.9}/tests/test_review.py +0 -0
|
@@ -5,6 +5,7 @@ Stores named aliases for changelist numbers in .git-p4son/changelists/<name>.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
|
+
import re
|
|
8
9
|
|
|
9
10
|
from . import CONFIG_DIR
|
|
10
11
|
from .log import log
|
|
@@ -12,6 +13,25 @@ from .log import log
|
|
|
12
13
|
|
|
13
14
|
RESERVED_KEYWORDS = frozenset({'last-synced', 'branch'})
|
|
14
15
|
|
|
16
|
+
# Allowed characters: ASCII letters, digits, hyphen, underscore, dot.
|
|
17
|
+
# Must not start or end with a dot, so "." and ".." are rejected and the
|
|
18
|
+
# filename does not collide with hidden files or platform-specific quirks
|
|
19
|
+
# (Windows disallows trailing dots).
|
|
20
|
+
_ALIAS_NAME_RE = re.compile(r'^[A-Za-z0-9_-]([A-Za-z0-9._-]*[A-Za-z0-9_-])?$')
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def validate_alias_name(name: str) -> str | None:
|
|
24
|
+
"""Return an error message if alias name is invalid, else None."""
|
|
25
|
+
if not name:
|
|
26
|
+
return 'Alias name cannot be empty'
|
|
27
|
+
if name in RESERVED_KEYWORDS:
|
|
28
|
+
return f'Alias name "{name}" is a reserved keyword'
|
|
29
|
+
if not _ALIAS_NAME_RE.match(name):
|
|
30
|
+
return (
|
|
31
|
+
f'Invalid alias name "{name}": must contain only letters, digits, '
|
|
32
|
+
'hyphens, underscores, and dots, and must not start or end with a dot')
|
|
33
|
+
return None
|
|
34
|
+
|
|
15
35
|
|
|
16
36
|
def _changelists_dir(workspace_dir: str) -> str:
|
|
17
37
|
"""Return the path to the changelists alias directory."""
|
|
@@ -26,8 +46,9 @@ def alias_exists(name: str, workspace_dir: str) -> bool:
|
|
|
26
46
|
|
|
27
47
|
def save_changelist_alias(name: str, changelist: str, workspace_dir: str, force: bool = False) -> bool:
|
|
28
48
|
"""Save a changelist number under a named alias."""
|
|
29
|
-
|
|
30
|
-
|
|
49
|
+
error = validate_alias_name(name)
|
|
50
|
+
if error:
|
|
51
|
+
log.error(error)
|
|
31
52
|
return False
|
|
32
53
|
|
|
33
54
|
changelists_dir = _changelists_dir(workspace_dir)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Common utilities shared between sync and edit commands.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import os
|
|
5
6
|
import queue
|
|
6
7
|
import subprocess
|
|
7
8
|
import sys
|
|
@@ -13,6 +14,19 @@ from typing import IO, Callable
|
|
|
13
14
|
from .log import log
|
|
14
15
|
|
|
15
16
|
|
|
17
|
+
def _env_with_pwd(cwd: str) -> dict[str, str]:
|
|
18
|
+
"""Return a copy of os.environ with PWD set to abspath(cwd).
|
|
19
|
+
|
|
20
|
+
subprocess only changes the child's kernel cwd; PWD is inherited from
|
|
21
|
+
the parent. Tools that read PWD for relative-path resolution (notably
|
|
22
|
+
'p4 add') would otherwise resolve paths against the wrong directory
|
|
23
|
+
when git-p4son is invoked from a subdirectory of the workspace.
|
|
24
|
+
"""
|
|
25
|
+
env = os.environ.copy()
|
|
26
|
+
env['PWD'] = os.path.abspath(cwd)
|
|
27
|
+
return env
|
|
28
|
+
|
|
29
|
+
|
|
16
30
|
def branch_to_alias(branch_name: str) -> str:
|
|
17
31
|
"""Sanitize a branch name for use as an alias filename."""
|
|
18
32
|
return branch_name.replace('/', '-')
|
|
@@ -85,6 +99,7 @@ def run(command: list[str], cwd: str = '.', dry_run: bool = False,
|
|
|
85
99
|
|
|
86
100
|
result = subprocess.run(command,
|
|
87
101
|
cwd=cwd,
|
|
102
|
+
env=_env_with_pwd(cwd),
|
|
88
103
|
capture_output=True,
|
|
89
104
|
text=True,
|
|
90
105
|
input=input)
|
|
@@ -136,6 +151,7 @@ def run_with_output(command: list[str], cwd: str = '.', on_output: Callable[...,
|
|
|
136
151
|
|
|
137
152
|
with subprocess.Popen(command,
|
|
138
153
|
cwd=cwd,
|
|
154
|
+
env=_env_with_pwd(cwd),
|
|
139
155
|
stdout=subprocess.PIPE,
|
|
140
156
|
stderr=subprocess.PIPE,
|
|
141
157
|
text=True) as process:
|
|
@@ -6,7 +6,11 @@ creates a Swarm review.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import argparse
|
|
9
|
-
from .changelist_store import
|
|
9
|
+
from .changelist_store import (
|
|
10
|
+
alias_exists,
|
|
11
|
+
save_changelist_alias,
|
|
12
|
+
validate_alias_name,
|
|
13
|
+
)
|
|
10
14
|
from .lib import create_changelist, open_changes_for_edit
|
|
11
15
|
from .perforce import add_review_keyword_to_changelist, p4_shelve_changelist
|
|
12
16
|
from .log import log
|
|
@@ -16,8 +20,12 @@ def new_command(args: argparse.Namespace) -> int:
|
|
|
16
20
|
"""Execute the new command."""
|
|
17
21
|
workspace_dir = args.workspace_dir
|
|
18
22
|
|
|
19
|
-
#
|
|
23
|
+
# Validate alias name and availability before creating the changelist
|
|
20
24
|
if args.alias and not args.dry_run:
|
|
25
|
+
error = validate_alias_name(args.alias)
|
|
26
|
+
if error:
|
|
27
|
+
log.error(error)
|
|
28
|
+
return 1
|
|
21
29
|
if alias_exists(args.alias, workspace_dir) and not args.force:
|
|
22
30
|
log.error(
|
|
23
31
|
f'Alias "{args.alias}" already exists '
|
|
@@ -306,9 +306,8 @@ def parse_p4_sync_line(line: str) -> tuple[str | None, str | None]:
|
|
|
306
306
|
class P4SyncOutputProcessor:
|
|
307
307
|
"""Process p4 sync output in real-time."""
|
|
308
308
|
|
|
309
|
-
def __init__(self
|
|
309
|
+
def __init__(self) -> None:
|
|
310
310
|
self.synced_file_count: int = 0
|
|
311
|
-
self.file_count_to_sync: int = file_count_to_sync
|
|
312
311
|
self.stats: dict[str, int] = {
|
|
313
312
|
mode: 0 for mode in ['add', 'del', 'upd', 'clb']}
|
|
314
313
|
|
|
@@ -319,18 +318,12 @@ class P4SyncOutputProcessor:
|
|
|
319
318
|
|
|
320
319
|
mode, filename = parse_p4_sync_line(line)
|
|
321
320
|
if not mode or not filename:
|
|
322
|
-
log.
|
|
321
|
+
log.warning(f'Unparsable line: {line}')
|
|
323
322
|
return
|
|
324
323
|
|
|
325
324
|
self.stats[mode] += 1
|
|
326
325
|
self.synced_file_count += 1
|
|
327
326
|
|
|
328
|
-
if self.file_count_to_sync >= 0:
|
|
329
|
-
log.verbose(
|
|
330
|
-
f'{mode}: {filename} ({self.synced_file_count}/{self.file_count_to_sync})')
|
|
331
|
-
else:
|
|
332
|
-
log.verbose(f'{mode}: {filename}')
|
|
333
|
-
|
|
334
327
|
def get_summary(self) -> str:
|
|
335
328
|
"""Get a one-line sync summary."""
|
|
336
329
|
synced_count = self.stats['add'] + \
|
|
@@ -352,18 +345,10 @@ class P4SyncOutputProcessor:
|
|
|
352
345
|
|
|
353
346
|
def p4_force_sync_file(changelist: int, filename: str, workspace_dir: str) -> None:
|
|
354
347
|
"""Force sync a single file."""
|
|
355
|
-
output_processor = P4SyncOutputProcessor(
|
|
348
|
+
output_processor = P4SyncOutputProcessor()
|
|
356
349
|
result = run_with_output(
|
|
357
350
|
['p4', 'sync', '-f', f'{filename}@{changelist}'],
|
|
358
351
|
cwd=workspace_dir, on_output=output_processor)
|
|
359
352
|
log.info(output_processor.get_summary())
|
|
360
353
|
if result.elapsed:
|
|
361
354
|
log.elapsed(result.elapsed)
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
def get_file_count_to_sync(changelist: int, depot_root: str,
|
|
365
|
-
workspace_dir: str) -> int:
|
|
366
|
-
"""Get the number of files that need to be synced."""
|
|
367
|
-
res = run(['p4', 'sync', '-n', f'{depot_root}/...@{changelist}'],
|
|
368
|
-
cwd=workspace_dir)
|
|
369
|
-
return len(res.stdout)
|
|
@@ -10,7 +10,7 @@ import os
|
|
|
10
10
|
import shlex
|
|
11
11
|
import subprocess
|
|
12
12
|
from . import CONFIG_DIR
|
|
13
|
-
from .changelist_store import alias_exists
|
|
13
|
+
from .changelist_store import alias_exists, validate_alias_name
|
|
14
14
|
from .git import get_commit_lines_since, resolve_editor
|
|
15
15
|
from .log import log
|
|
16
16
|
|
|
@@ -58,6 +58,14 @@ def review_command(args: argparse.Namespace) -> int:
|
|
|
58
58
|
"""Execute the review command."""
|
|
59
59
|
workspace_dir = args.workspace_dir
|
|
60
60
|
|
|
61
|
+
# Validate alias name before starting
|
|
62
|
+
log.heading(f'Validating alias "{args.alias}"')
|
|
63
|
+
error = validate_alias_name(args.alias)
|
|
64
|
+
if error:
|
|
65
|
+
log.error(error)
|
|
66
|
+
return 1
|
|
67
|
+
log.success('Done')
|
|
68
|
+
|
|
61
69
|
# Check alias availability before starting
|
|
62
70
|
if not args.force:
|
|
63
71
|
log.heading(f'Checking alias "{args.alias}" is available')
|
|
@@ -11,7 +11,6 @@ from .config import get_depot_root
|
|
|
11
11
|
from .git import add_all_files, commit, get_dirty_files
|
|
12
12
|
from .log import log
|
|
13
13
|
from .perforce import (
|
|
14
|
-
get_file_count_to_sync,
|
|
15
14
|
get_latest_changelist,
|
|
16
15
|
p4_force_sync_file,
|
|
17
16
|
p4_get_opened_files,
|
|
@@ -62,9 +61,8 @@ def parse_p4_sync_line(line: str) -> tuple[str | None, str | None]:
|
|
|
62
61
|
class P4SyncOutputProcessor:
|
|
63
62
|
"""Process p4 sync output in real-time."""
|
|
64
63
|
|
|
65
|
-
def __init__(self
|
|
64
|
+
def __init__(self) -> None:
|
|
66
65
|
self.synced_file_count: int = 0
|
|
67
|
-
self.file_count_to_sync: int = file_count_to_sync
|
|
68
66
|
self.stats: dict[str, int] = {
|
|
69
67
|
mode: 0 for mode in ['add', 'del', 'upd', 'clb']}
|
|
70
68
|
|
|
@@ -75,18 +73,12 @@ class P4SyncOutputProcessor:
|
|
|
75
73
|
|
|
76
74
|
mode, filename = parse_p4_sync_line(line)
|
|
77
75
|
if not mode or not filename:
|
|
78
|
-
log.
|
|
76
|
+
log.warning(f'Unparsable line: {line}')
|
|
79
77
|
return
|
|
80
78
|
|
|
81
79
|
self.stats[mode] += 1
|
|
82
80
|
self.synced_file_count += 1
|
|
83
81
|
|
|
84
|
-
if self.file_count_to_sync >= 0:
|
|
85
|
-
log.verbose(
|
|
86
|
-
f'{mode}: {filename} ({self.synced_file_count}/{self.file_count_to_sync})')
|
|
87
|
-
else:
|
|
88
|
-
log.verbose(f'{mode}: {filename}')
|
|
89
|
-
|
|
90
82
|
def get_summary(self) -> str:
|
|
91
83
|
"""Get a one-line sync summary."""
|
|
92
84
|
synced_count = self.stats['add'] + \
|
|
@@ -114,14 +106,8 @@ def p4_sync(changelist: int, label: str, force: bool, depot_root: str,
|
|
|
114
106
|
Raises CommandError on actual command failures.
|
|
115
107
|
"""
|
|
116
108
|
log.heading(f'Syncing to {label} CL ({changelist})')
|
|
117
|
-
file_count_to_sync = get_file_count_to_sync(changelist, depot_root,
|
|
118
|
-
workspace_dir)
|
|
119
|
-
if file_count_to_sync == 0:
|
|
120
|
-
log.success('All files up to date')
|
|
121
|
-
return True
|
|
122
|
-
log.info(f'{file_count_to_sync} files to sync')
|
|
123
109
|
|
|
124
|
-
output_processor = P4SyncOutputProcessor(
|
|
110
|
+
output_processor = P4SyncOutputProcessor()
|
|
125
111
|
try:
|
|
126
112
|
result = run_with_output(
|
|
127
113
|
['p4', 'sync', f'{depot_root}/...@{changelist}'],
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Tests for git_p4son.changelist_store module."""
|
|
2
|
+
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
from git_p4son.changelist_store import validate_alias_name
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestValidateAliasName(unittest.TestCase):
|
|
9
|
+
def test_simple_name(self):
|
|
10
|
+
self.assertIsNone(validate_alias_name('my-feature'))
|
|
11
|
+
|
|
12
|
+
def test_alphanumeric(self):
|
|
13
|
+
self.assertIsNone(validate_alias_name('feat123'))
|
|
14
|
+
|
|
15
|
+
def test_underscores(self):
|
|
16
|
+
self.assertIsNone(validate_alias_name('my_feature'))
|
|
17
|
+
|
|
18
|
+
def test_dots_in_middle(self):
|
|
19
|
+
self.assertIsNone(validate_alias_name('feat.v1.0'))
|
|
20
|
+
|
|
21
|
+
def test_single_char(self):
|
|
22
|
+
self.assertIsNone(validate_alias_name('a'))
|
|
23
|
+
|
|
24
|
+
def test_leading_hyphen_allowed(self):
|
|
25
|
+
self.assertIsNone(validate_alias_name('-foo'))
|
|
26
|
+
|
|
27
|
+
def test_empty_rejected(self):
|
|
28
|
+
error = validate_alias_name('')
|
|
29
|
+
self.assertIsNotNone(error)
|
|
30
|
+
self.assertIn('empty', error)
|
|
31
|
+
|
|
32
|
+
def test_reserved_branch_rejected(self):
|
|
33
|
+
error = validate_alias_name('branch')
|
|
34
|
+
self.assertIsNotNone(error)
|
|
35
|
+
self.assertIn('reserved', error)
|
|
36
|
+
|
|
37
|
+
def test_reserved_last_synced_rejected(self):
|
|
38
|
+
error = validate_alias_name('last-synced')
|
|
39
|
+
self.assertIsNotNone(error)
|
|
40
|
+
self.assertIn('reserved', error)
|
|
41
|
+
|
|
42
|
+
def test_spaces_rejected(self):
|
|
43
|
+
error = validate_alias_name('Fix a couple of small bugs')
|
|
44
|
+
self.assertIsNotNone(error)
|
|
45
|
+
self.assertIn('Invalid alias name', error)
|
|
46
|
+
|
|
47
|
+
def test_slash_rejected(self):
|
|
48
|
+
error = validate_alias_name('feat/foo')
|
|
49
|
+
self.assertIsNotNone(error)
|
|
50
|
+
|
|
51
|
+
def test_backslash_rejected(self):
|
|
52
|
+
error = validate_alias_name('feat\\foo')
|
|
53
|
+
self.assertIsNotNone(error)
|
|
54
|
+
|
|
55
|
+
def test_leading_dot_rejected(self):
|
|
56
|
+
error = validate_alias_name('.hidden')
|
|
57
|
+
self.assertIsNotNone(error)
|
|
58
|
+
|
|
59
|
+
def test_trailing_dot_rejected(self):
|
|
60
|
+
error = validate_alias_name('foo.')
|
|
61
|
+
self.assertIsNotNone(error)
|
|
62
|
+
|
|
63
|
+
def test_dot_alone_rejected(self):
|
|
64
|
+
self.assertIsNotNone(validate_alias_name('.'))
|
|
65
|
+
|
|
66
|
+
def test_double_dot_rejected(self):
|
|
67
|
+
self.assertIsNotNone(validate_alias_name('..'))
|
|
68
|
+
|
|
69
|
+
def test_special_chars_rejected(self):
|
|
70
|
+
for ch in ['*', '?', ':', '"', '|', '<', '>', '\'', '(', ')', '!']:
|
|
71
|
+
with self.subTest(ch=ch):
|
|
72
|
+
self.assertIsNotNone(validate_alias_name(f'foo{ch}bar'))
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == '__main__':
|
|
76
|
+
unittest.main()
|
|
@@ -141,6 +141,25 @@ class TestBranchToAlias(unittest.TestCase):
|
|
|
141
141
|
self.assertEqual(branch_to_alias('a/b/c'), 'a-b-c')
|
|
142
142
|
|
|
143
143
|
|
|
144
|
+
class TestRunSetsPWD(unittest.TestCase):
|
|
145
|
+
@mock.patch('subprocess.run')
|
|
146
|
+
def test_pwd_overrides_inherited_value(self, mock_subprocess_run):
|
|
147
|
+
mock_subprocess_run.return_value = mock.Mock(
|
|
148
|
+
returncode=0, stdout='', stderr='')
|
|
149
|
+
with mock.patch.dict(os.environ, {'PWD': '/some/other/dir'}):
|
|
150
|
+
run(['p4', 'add', 'foo.txt'], cwd='/workspace')
|
|
151
|
+
env = mock_subprocess_run.call_args.kwargs['env']
|
|
152
|
+
self.assertEqual(env['PWD'], '/workspace')
|
|
153
|
+
|
|
154
|
+
@mock.patch('subprocess.run')
|
|
155
|
+
def test_pwd_uses_absolute_path(self, mock_subprocess_run):
|
|
156
|
+
mock_subprocess_run.return_value = mock.Mock(
|
|
157
|
+
returncode=0, stdout='', stderr='')
|
|
158
|
+
run(['p4', 'add', 'foo.txt'], cwd='.')
|
|
159
|
+
env = mock_subprocess_run.call_args.kwargs['env']
|
|
160
|
+
self.assertEqual(env['PWD'], os.path.abspath('.'))
|
|
161
|
+
|
|
162
|
+
|
|
144
163
|
class TestJoinCommandLine(unittest.TestCase):
|
|
145
164
|
def test_simple_command(self):
|
|
146
165
|
result = join_command_line(['git', 'status'])
|
|
@@ -210,13 +229,13 @@ class TestRun(unittest.TestCase):
|
|
|
210
229
|
stderr='',
|
|
211
230
|
)
|
|
212
231
|
result = run(['git', 'status'], cwd='/tmp')
|
|
213
|
-
mock_subprocess_run.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
)
|
|
232
|
+
call = mock_subprocess_run.call_args
|
|
233
|
+
self.assertEqual(call.args, (['git', 'status'],))
|
|
234
|
+
self.assertEqual(call.kwargs['cwd'], '/tmp')
|
|
235
|
+
self.assertEqual(call.kwargs['capture_output'], True)
|
|
236
|
+
self.assertEqual(call.kwargs['text'], True)
|
|
237
|
+
self.assertEqual(call.kwargs['input'], None)
|
|
238
|
+
self.assertEqual(call.kwargs['env']['PWD'], '/tmp')
|
|
220
239
|
self.assertEqual(result.returncode, 0)
|
|
221
240
|
self.assertEqual(result.stdout, ['line1', 'line2'])
|
|
222
241
|
self.assertEqual(result.stderr, [])
|
|
@@ -254,13 +273,13 @@ class TestRunWithOutput(unittest.TestCase):
|
|
|
254
273
|
mock_popen_cls.return_value = mock_process
|
|
255
274
|
|
|
256
275
|
result = run_with_output(['git', 'status'], cwd='/tmp')
|
|
257
|
-
mock_popen_cls.
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
)
|
|
276
|
+
call = mock_popen_cls.call_args
|
|
277
|
+
self.assertEqual(call.args, (['git', 'status'],))
|
|
278
|
+
self.assertEqual(call.kwargs['cwd'], '/tmp')
|
|
279
|
+
self.assertEqual(call.kwargs['stdout'], subprocess.PIPE)
|
|
280
|
+
self.assertEqual(call.kwargs['stderr'], subprocess.PIPE)
|
|
281
|
+
self.assertEqual(call.kwargs['text'], True)
|
|
282
|
+
self.assertEqual(call.kwargs['env']['PWD'], '/tmp')
|
|
264
283
|
self.assertEqual(result.returncode, 0)
|
|
265
284
|
|
|
266
285
|
@mock.patch('subprocess.Popen')
|
|
@@ -7,7 +7,6 @@ from unittest import mock
|
|
|
7
7
|
from git_p4son.common import CommandError, RunError
|
|
8
8
|
from git_p4son.perforce import (
|
|
9
9
|
P4SyncOutputProcessor,
|
|
10
|
-
get_file_count_to_sync,
|
|
11
10
|
get_latest_changelist,
|
|
12
11
|
get_writable_files,
|
|
13
12
|
p4_get_opened_files,
|
|
@@ -317,62 +316,31 @@ class TestGetLatestChangelist(unittest.TestCase):
|
|
|
317
316
|
get_latest_changelist('//myclient', '/ws')
|
|
318
317
|
|
|
319
318
|
|
|
320
|
-
class TestGetFileCountToSync(unittest.TestCase):
|
|
321
|
-
@mock.patch('git_p4son.perforce.run')
|
|
322
|
-
def test_returns_count(self, mock_run):
|
|
323
|
-
mock_run.return_value = make_run_result(stdout=[
|
|
324
|
-
'//depot/a.txt - added',
|
|
325
|
-
'//depot/b.txt - updating',
|
|
326
|
-
])
|
|
327
|
-
count = get_file_count_to_sync(12345, '//myclient', '/ws')
|
|
328
|
-
self.assertEqual(count, 2)
|
|
329
|
-
|
|
330
|
-
@mock.patch('git_p4son.perforce.run')
|
|
331
|
-
def test_failure(self, mock_run):
|
|
332
|
-
mock_run.side_effect = RunError('p4 sync -n failed')
|
|
333
|
-
with self.assertRaises(RunError):
|
|
334
|
-
get_file_count_to_sync(12345, '//myclient', '/ws')
|
|
335
|
-
|
|
336
|
-
|
|
337
319
|
class TestP4SyncOutputProcessor(unittest.TestCase):
|
|
338
320
|
def test_tracks_added_file(self):
|
|
339
|
-
processor = P4SyncOutputProcessor(
|
|
321
|
+
processor = P4SyncOutputProcessor()
|
|
340
322
|
processor('//depot/foo.txt#1 - added as /ws/foo.txt', sys.stdout)
|
|
341
323
|
self.assertEqual(processor.stats['add'], 1)
|
|
342
324
|
self.assertEqual(processor.synced_file_count, 1)
|
|
343
325
|
|
|
344
326
|
def test_tracks_deleted_file(self):
|
|
345
|
-
processor = P4SyncOutputProcessor(
|
|
327
|
+
processor = P4SyncOutputProcessor()
|
|
346
328
|
processor('//depot/foo.txt#2 - deleted as /ws/foo.txt', sys.stdout)
|
|
347
329
|
self.assertEqual(processor.stats['del'], 1)
|
|
348
330
|
|
|
349
331
|
def test_up_to_date_message(self):
|
|
350
|
-
processor = P4SyncOutputProcessor(
|
|
332
|
+
processor = P4SyncOutputProcessor()
|
|
351
333
|
processor('//...@12345 - file(s) up-to-date.', sys.stdout)
|
|
352
334
|
self.assertEqual(processor.synced_file_count, 0)
|
|
353
335
|
|
|
354
336
|
|
|
355
337
|
class TestP4Sync(unittest.TestCase):
|
|
356
338
|
@mock.patch('git_p4son.sync.run_with_output')
|
|
357
|
-
|
|
358
|
-
def test_success(self, mock_count, mock_rwo):
|
|
359
|
-
mock_count.return_value = 2
|
|
339
|
+
def test_success(self, mock_rwo):
|
|
360
340
|
mock_rwo.return_value = make_run_result()
|
|
361
341
|
result = p4_sync(12345, 'test', False, '//myclient', '/ws')
|
|
362
342
|
self.assertTrue(result)
|
|
363
343
|
|
|
364
|
-
@mock.patch('git_p4son.sync.get_file_count_to_sync')
|
|
365
|
-
def test_up_to_date(self, mock_count):
|
|
366
|
-
mock_count.return_value = 0
|
|
367
|
-
result = p4_sync(12345, 'test', False, '//myclient', '/ws')
|
|
368
|
-
self.assertTrue(result)
|
|
369
|
-
|
|
370
|
-
@mock.patch('git_p4son.sync.get_file_count_to_sync')
|
|
371
|
-
def test_count_failure(self, mock_count):
|
|
372
|
-
mock_count.side_effect = RunError('p4 sync -n failed')
|
|
373
|
-
with self.assertRaises(RunError):
|
|
374
|
-
p4_sync(12345, 'test', False, '//myclient', '/ws')
|
|
375
|
-
|
|
376
344
|
|
|
377
345
|
class TestSyncCommand(unittest.TestCase):
|
|
378
346
|
@mock.patch('git_p4son.sync.commit')
|
|
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
|