git-p4son 0.2.9__tar.gz → 0.2.11__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.9 → git_p4son-0.2.11}/PKG-INFO +46 -5
- {git_p4son-0.2.9 → git_p4son-0.2.11}/README.md +45 -4
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son/__init__.py +1 -1
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son/alias.py +54 -21
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son/changelist_store.py +36 -6
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son/cli.py +14 -7
- git_p4son-0.2.11/git_p4son/common.py +319 -0
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son/complete.py +5 -0
- git_p4son-0.2.11/git_p4son/config.py +89 -0
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son/git.py +53 -7
- git_p4son-0.2.11/git_p4son/hooks.py +121 -0
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son/init.py +16 -2
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son/lib.py +67 -17
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son/log.py +84 -13
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son/new.py +4 -2
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son/perforce.py +83 -26
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son/review.py +6 -0
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son/sync.py +54 -86
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son.egg-info/PKG-INFO +46 -5
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son.egg-info/SOURCES.txt +4 -0
- {git_p4son-0.2.9 → git_p4son-0.2.11}/pyproject.toml +1 -1
- git_p4son-0.2.11/tests/test_alias.py +106 -0
- {git_p4son-0.2.9 → git_p4son-0.2.11}/tests/test_changelist_store.py +59 -1
- {git_p4son-0.2.9 → git_p4son-0.2.11}/tests/test_cli.py +8 -1
- {git_p4son-0.2.9 → git_p4son-0.2.11}/tests/test_common.py +101 -6
- {git_p4son-0.2.9 → git_p4son-0.2.11}/tests/test_complete.py +40 -0
- {git_p4son-0.2.9 → git_p4son-0.2.11}/tests/test_config.py +47 -0
- git_p4son-0.2.11/tests/test_hooks.py +197 -0
- {git_p4son-0.2.9 → git_p4son-0.2.11}/tests/test_init.py +42 -5
- {git_p4son-0.2.9 → git_p4son-0.2.11}/tests/test_lib_changelist.py +82 -21
- {git_p4son-0.2.9 → git_p4son-0.2.11}/tests/test_lib_edit.py +81 -0
- {git_p4son-0.2.9 → git_p4son-0.2.11}/tests/test_lib_review.py +27 -11
- {git_p4son-0.2.9 → git_p4son-0.2.11}/tests/test_list_changes.py +2 -1
- git_p4son-0.2.11/tests/test_log.py +69 -0
- git_p4son-0.2.11/tests/test_new.py +56 -0
- {git_p4son-0.2.9 → git_p4son-0.2.11}/tests/test_perforce.py +21 -2
- {git_p4son-0.2.9 → git_p4son-0.2.11}/tests/test_review.py +16 -1
- {git_p4son-0.2.9 → git_p4son-0.2.11}/tests/test_sync.py +96 -50
- git_p4son-0.2.9/git_p4son/common.py +0 -235
- git_p4son-0.2.9/git_p4son/config.py +0 -42
- git_p4son-0.2.9/tests/test_log.py +0 -257
- {git_p4son-0.2.9 → git_p4son-0.2.11}/LICENSE +0 -0
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son/__main__.py +0 -0
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son/completions/_git-p4son +0 -0
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son/completions/git-p4son.bash +0 -0
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son/completions/git-p4son.ps1 +0 -0
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son/list_changes.py +0 -0
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son/update.py +0 -0
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son.egg-info/dependency_links.txt +0 -0
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son.egg-info/entry_points.txt +0 -0
- {git_p4son-0.2.9 → git_p4son-0.2.11}/git_p4son.egg-info/top_level.txt +0 -0
- {git_p4son-0.2.9 → git_p4son-0.2.11}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-p4son
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.11
|
|
4
4
|
Summary: Utility for keeping a Perforce workspace and local git repo in sync
|
|
5
5
|
Author-email: Andreas Andersson <andreas@neoboid.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -276,6 +276,11 @@ git p4son sync last-synced
|
|
|
276
276
|
git p4son sync 12345 --force
|
|
277
277
|
```
|
|
278
278
|
|
|
279
|
+
#### post-sync hook
|
|
280
|
+
|
|
281
|
+
After a successful `git-p4son sync` that actually performs sync work, git-p4son runs [hooks](#hooks)
|
|
282
|
+
from `.git-p4son/hooks/post-sync/`.
|
|
283
|
+
|
|
279
284
|
### New Command
|
|
280
285
|
|
|
281
286
|
Create a new Perforce changelist and add changed files to it. Description will contain an enumerated list of git commits since the base branch.
|
|
@@ -312,7 +317,11 @@ git p4son new -m "New feature" --review -b main
|
|
|
312
317
|
|
|
313
318
|
### Update Command
|
|
314
319
|
|
|
315
|
-
Update an existing Perforce changelist description
|
|
320
|
+
Update an existing Perforce changelist description. Commits since the base branch replace their existing
|
|
321
|
+
entries in the enumerated commit list (matched by subject) and new ones are appended; entries outside the
|
|
322
|
+
range are kept and the list is renumbered. So `update -b main` rebuilds the whole list without duplicating
|
|
323
|
+
it, while `update -b HEAD~3` only refreshes the last three entries. By default also opens changed files for
|
|
324
|
+
edit.
|
|
316
325
|
|
|
317
326
|
```sh
|
|
318
327
|
git p4son update [changelist] [--base-branch BASE_BRANCH] [--dry-run] [--no-desc] [--no-edit] [--shelve]
|
|
@@ -459,16 +468,21 @@ git p4son alias delete myfeature
|
|
|
459
468
|
|
|
460
469
|
#### alias clean
|
|
461
470
|
|
|
462
|
-
|
|
471
|
+
Clean up changelist aliases:
|
|
463
472
|
|
|
464
473
|
```sh
|
|
465
474
|
git p4son alias clean
|
|
466
475
|
```
|
|
467
476
|
|
|
468
|
-
|
|
477
|
+
The command lists all aliases and asks how to proceed:
|
|
478
|
+
- `a` (all): Delete all aliases
|
|
479
|
+
- `i` (interactive): Review each alias one by one
|
|
480
|
+
- `q` (quit): Exit without deleting anything
|
|
481
|
+
|
|
482
|
+
In interactive mode, each alias is displayed in turn with a prompt:
|
|
469
483
|
- `y` (yes): Delete this alias
|
|
470
484
|
- `n` (no): Keep this alias
|
|
471
|
-
- `a` (all): Delete this and all remaining aliases
|
|
485
|
+
- `a` (all): Delete this and all remaining aliases (handy for keeping the first few and sweeping the rest)
|
|
472
486
|
- `q` (quit): Stop and keep remaining aliases
|
|
473
487
|
|
|
474
488
|
**Examples:**
|
|
@@ -500,6 +514,32 @@ Most commands that accept an alias or changelist argument default to the current
|
|
|
500
514
|
The keyword cannot be used in a detached HEAD state. Use `--no-alias` (on `new` and `review`) or supply an
|
|
501
515
|
explicit alias name instead.
|
|
502
516
|
|
|
517
|
+
## Hooks
|
|
518
|
+
|
|
519
|
+
Hooks work similarly to git hooks. They are executables (unix)/scripts (windows) that you place in
|
|
520
|
+
the `.git-p4son/hooks/[hook-name]/` directory. These scripts will be called to add or modify behavior
|
|
521
|
+
at defined times during git-p4son operations. Scripts get run in filename (lexicographically) sorted
|
|
522
|
+
order.
|
|
523
|
+
|
|
524
|
+
The hook process CWD is the directory where git-p4son was invoked, and `GIT_P4SON_REPO_ROOT_DIR` is
|
|
525
|
+
set to the git-p4son repository root.
|
|
526
|
+
|
|
527
|
+
On Windows, `.ps1`, `.nu`, `.sh`, and `.py` files are run with `powershell.exe`, `nushell.exe`, `bash.exe`, and
|
|
528
|
+
`python.exe` respectively. Customize or add Windows script associations in `.git-p4son/config.toml`:
|
|
529
|
+
|
|
530
|
+
```toml
|
|
531
|
+
[hooks.extension-associations]
|
|
532
|
+
".ps1" = ["pwsh.exe", "-NoProfile", "-File"]
|
|
533
|
+
".py" = "python.exe"
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
Non-executable or unrecognized hook files are skipped with a warning.
|
|
537
|
+
|
|
538
|
+
### post-sync hook
|
|
539
|
+
|
|
540
|
+
After a successful `git-p4son sync` that actually performs sync work, git-p4son runs executable hooks from
|
|
541
|
+
`.git-p4son/hooks/post-sync/`.
|
|
542
|
+
|
|
503
543
|
## Shell Completions
|
|
504
544
|
|
|
505
545
|
Tab completion is available for bash, zsh, and PowerShell, including commands, flags, and dynamic alias names.
|
|
@@ -530,3 +570,4 @@ Add the following to your PowerShell profile (`$PROFILE`):
|
|
|
530
570
|
```
|
|
531
571
|
|
|
532
572
|
All three enable completion for `git p4son <TAB>` and `git-p4son <TAB>`.
|
|
573
|
+
|
|
@@ -253,6 +253,11 @@ git p4son sync last-synced
|
|
|
253
253
|
git p4son sync 12345 --force
|
|
254
254
|
```
|
|
255
255
|
|
|
256
|
+
#### post-sync hook
|
|
257
|
+
|
|
258
|
+
After a successful `git-p4son sync` that actually performs sync work, git-p4son runs [hooks](#hooks)
|
|
259
|
+
from `.git-p4son/hooks/post-sync/`.
|
|
260
|
+
|
|
256
261
|
### New Command
|
|
257
262
|
|
|
258
263
|
Create a new Perforce changelist and add changed files to it. Description will contain an enumerated list of git commits since the base branch.
|
|
@@ -289,7 +294,11 @@ git p4son new -m "New feature" --review -b main
|
|
|
289
294
|
|
|
290
295
|
### Update Command
|
|
291
296
|
|
|
292
|
-
Update an existing Perforce changelist description
|
|
297
|
+
Update an existing Perforce changelist description. Commits since the base branch replace their existing
|
|
298
|
+
entries in the enumerated commit list (matched by subject) and new ones are appended; entries outside the
|
|
299
|
+
range are kept and the list is renumbered. So `update -b main` rebuilds the whole list without duplicating
|
|
300
|
+
it, while `update -b HEAD~3` only refreshes the last three entries. By default also opens changed files for
|
|
301
|
+
edit.
|
|
293
302
|
|
|
294
303
|
```sh
|
|
295
304
|
git p4son update [changelist] [--base-branch BASE_BRANCH] [--dry-run] [--no-desc] [--no-edit] [--shelve]
|
|
@@ -436,16 +445,21 @@ git p4son alias delete myfeature
|
|
|
436
445
|
|
|
437
446
|
#### alias clean
|
|
438
447
|
|
|
439
|
-
|
|
448
|
+
Clean up changelist aliases:
|
|
440
449
|
|
|
441
450
|
```sh
|
|
442
451
|
git p4son alias clean
|
|
443
452
|
```
|
|
444
453
|
|
|
445
|
-
|
|
454
|
+
The command lists all aliases and asks how to proceed:
|
|
455
|
+
- `a` (all): Delete all aliases
|
|
456
|
+
- `i` (interactive): Review each alias one by one
|
|
457
|
+
- `q` (quit): Exit without deleting anything
|
|
458
|
+
|
|
459
|
+
In interactive mode, each alias is displayed in turn with a prompt:
|
|
446
460
|
- `y` (yes): Delete this alias
|
|
447
461
|
- `n` (no): Keep this alias
|
|
448
|
-
- `a` (all): Delete this and all remaining aliases
|
|
462
|
+
- `a` (all): Delete this and all remaining aliases (handy for keeping the first few and sweeping the rest)
|
|
449
463
|
- `q` (quit): Stop and keep remaining aliases
|
|
450
464
|
|
|
451
465
|
**Examples:**
|
|
@@ -477,6 +491,32 @@ Most commands that accept an alias or changelist argument default to the current
|
|
|
477
491
|
The keyword cannot be used in a detached HEAD state. Use `--no-alias` (on `new` and `review`) or supply an
|
|
478
492
|
explicit alias name instead.
|
|
479
493
|
|
|
494
|
+
## Hooks
|
|
495
|
+
|
|
496
|
+
Hooks work similarly to git hooks. They are executables (unix)/scripts (windows) that you place in
|
|
497
|
+
the `.git-p4son/hooks/[hook-name]/` directory. These scripts will be called to add or modify behavior
|
|
498
|
+
at defined times during git-p4son operations. Scripts get run in filename (lexicographically) sorted
|
|
499
|
+
order.
|
|
500
|
+
|
|
501
|
+
The hook process CWD is the directory where git-p4son was invoked, and `GIT_P4SON_REPO_ROOT_DIR` is
|
|
502
|
+
set to the git-p4son repository root.
|
|
503
|
+
|
|
504
|
+
On Windows, `.ps1`, `.nu`, `.sh`, and `.py` files are run with `powershell.exe`, `nushell.exe`, `bash.exe`, and
|
|
505
|
+
`python.exe` respectively. Customize or add Windows script associations in `.git-p4son/config.toml`:
|
|
506
|
+
|
|
507
|
+
```toml
|
|
508
|
+
[hooks.extension-associations]
|
|
509
|
+
".ps1" = ["pwsh.exe", "-NoProfile", "-File"]
|
|
510
|
+
".py" = "python.exe"
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
Non-executable or unrecognized hook files are skipped with a warning.
|
|
514
|
+
|
|
515
|
+
### post-sync hook
|
|
516
|
+
|
|
517
|
+
After a successful `git-p4son sync` that actually performs sync work, git-p4son runs executable hooks from
|
|
518
|
+
`.git-p4son/hooks/post-sync/`.
|
|
519
|
+
|
|
480
520
|
## Shell Completions
|
|
481
521
|
|
|
482
522
|
Tab completion is available for bash, zsh, and PowerShell, including commands, flags, and dynamic alias names.
|
|
@@ -507,3 +547,4 @@ Add the following to your PowerShell profile (`$PROFILE`):
|
|
|
507
547
|
```
|
|
508
548
|
|
|
509
549
|
All three enable completion for `git p4son <TAB>` and `git-p4son <TAB>`.
|
|
550
|
+
|
|
@@ -54,43 +54,52 @@ def alias_delete_command(args: argparse.Namespace) -> int:
|
|
|
54
54
|
return 0
|
|
55
55
|
|
|
56
56
|
|
|
57
|
-
def
|
|
58
|
-
"""Prompt until
|
|
57
|
+
def _prompt_choice(prefix: str, options: list[str]) -> str | None:
|
|
58
|
+
"""Prompt until the response matches an option or its first letter.
|
|
59
|
+
|
|
60
|
+
The choices are rendered from the options, e.g. "[y]es / [n]o", so the
|
|
61
|
+
prompt and the accepted keys cannot drift apart. Options must have
|
|
62
|
+
unique first letters. Returns the chosen option, or None on EOF.
|
|
63
|
+
"""
|
|
64
|
+
shorthands = {option[0]: option for option in options}
|
|
65
|
+
rendered = ' / '.join(f'[{option[0]}]{option[1:]}' for option in options)
|
|
66
|
+
keys = list(shorthands)
|
|
59
67
|
while True:
|
|
60
68
|
try:
|
|
61
|
-
response = input(
|
|
69
|
+
response = input(f'{prefix} {rendered}: ').strip().lower()
|
|
62
70
|
except EOFError:
|
|
63
71
|
print()
|
|
64
72
|
return None
|
|
65
73
|
|
|
66
|
-
if response in
|
|
67
|
-
return
|
|
68
|
-
elif response in
|
|
69
|
-
return
|
|
70
|
-
elif response in ('a', 'all'):
|
|
71
|
-
return 'all'
|
|
72
|
-
elif response in ('q', 'quit'):
|
|
73
|
-
return 'quit'
|
|
74
|
+
if response in shorthands:
|
|
75
|
+
return shorthands[response]
|
|
76
|
+
elif response in options:
|
|
77
|
+
return response
|
|
74
78
|
else:
|
|
75
|
-
print('Please enter
|
|
79
|
+
print('Please enter ' + ', '.join(keys[:-1]) + f' or {keys[-1]}')
|
|
76
80
|
|
|
77
81
|
|
|
78
|
-
def
|
|
79
|
-
"""
|
|
80
|
-
|
|
82
|
+
def _clean_all(aliases: list[tuple[str, str]], workspace_dir: str) -> None:
|
|
83
|
+
"""Delete every alias without further prompting."""
|
|
84
|
+
for name, _changelist in aliases:
|
|
85
|
+
if delete_changelist_alias(name, workspace_dir):
|
|
86
|
+
log.success(f'Deleted "{name}"')
|
|
81
87
|
|
|
82
|
-
aliases = list_changelist_aliases(workspace_dir)
|
|
83
|
-
if not aliases:
|
|
84
|
-
log.success('No changelist aliases to clean')
|
|
85
|
-
return 0
|
|
86
88
|
|
|
89
|
+
def _clean_interactive(aliases: list[tuple[str, str]],
|
|
90
|
+
workspace_dir: str) -> None:
|
|
91
|
+
"""Review each alias in turn with yes/no/all/quit prompts.
|
|
92
|
+
|
|
93
|
+
The all option lets the user keep the first aliases and sweep the rest:
|
|
94
|
+
answer no until the last one worth keeping, then a deletes the
|
|
95
|
+
remainder without further prompts."""
|
|
87
96
|
response = None
|
|
88
97
|
for name, changelist in aliases:
|
|
89
98
|
log.heading(f'{name} -> CL {changelist}')
|
|
90
99
|
|
|
91
100
|
if response != 'all':
|
|
92
|
-
response =
|
|
93
|
-
'Delete? [
|
|
101
|
+
response = _prompt_choice(
|
|
102
|
+
'Delete?', ['yes', 'no', 'all', 'quit'])
|
|
94
103
|
|
|
95
104
|
if response is None or response == 'quit':
|
|
96
105
|
log.info('Aborting')
|
|
@@ -102,6 +111,30 @@ def alias_clean_command(args: argparse.Namespace) -> int:
|
|
|
102
111
|
if delete_changelist_alias(name, workspace_dir):
|
|
103
112
|
log.success('Deleted')
|
|
104
113
|
|
|
114
|
+
|
|
115
|
+
def alias_clean_command(args: argparse.Namespace) -> int:
|
|
116
|
+
"""Execute the 'alias clean' command."""
|
|
117
|
+
workspace_dir = args.workspace_dir
|
|
118
|
+
|
|
119
|
+
aliases = list_changelist_aliases(workspace_dir)
|
|
120
|
+
if not aliases:
|
|
121
|
+
log.success('No changelist aliases to clean')
|
|
122
|
+
return 0
|
|
123
|
+
|
|
124
|
+
log.info(f'Found {len(aliases)}')
|
|
125
|
+
for name, changelist in aliases:
|
|
126
|
+
log.info(f'{name} -> {changelist}')
|
|
127
|
+
|
|
128
|
+
mode = _prompt_choice('Delete?', ['all', 'interactive', 'quit'])
|
|
129
|
+
if mode is None or mode == 'quit':
|
|
130
|
+
log.info('Aborting')
|
|
131
|
+
return 0
|
|
132
|
+
|
|
133
|
+
if mode == 'all':
|
|
134
|
+
_clean_all(aliases, workspace_dir)
|
|
135
|
+
else:
|
|
136
|
+
_clean_interactive(aliases, workspace_dir)
|
|
137
|
+
|
|
105
138
|
return 0
|
|
106
139
|
|
|
107
140
|
|
|
@@ -19,6 +19,13 @@ RESERVED_KEYWORDS = frozenset({'last-synced', 'branch'})
|
|
|
19
19
|
# (Windows disallows trailing dots).
|
|
20
20
|
_ALIAS_NAME_RE = re.compile(r'^[A-Za-z0-9_-]([A-Za-z0-9._-]*[A-Za-z0-9_-])?$')
|
|
21
21
|
|
|
22
|
+
# Windows device names; opening such a path hits the device namespace
|
|
23
|
+
# instead of creating a file, on any drive and regardless of extension.
|
|
24
|
+
_WINDOWS_RESERVED_NAMES = frozenset(
|
|
25
|
+
{'con', 'prn', 'aux', 'nul'}
|
|
26
|
+
| {f'com{i}' for i in range(1, 10)}
|
|
27
|
+
| {f'lpt{i}' for i in range(1, 10)})
|
|
28
|
+
|
|
22
29
|
|
|
23
30
|
def validate_alias_name(name: str) -> str | None:
|
|
24
31
|
"""Return an error message if alias name is invalid, else None."""
|
|
@@ -30,6 +37,15 @@ def validate_alias_name(name: str) -> str | None:
|
|
|
30
37
|
return (
|
|
31
38
|
f'Invalid alias name "{name}": must contain only letters, digits, '
|
|
32
39
|
'hyphens, underscores, and dots, and must not start or end with a dot')
|
|
40
|
+
if name.isdigit():
|
|
41
|
+
# Digit strings are always interpreted as changelist numbers, so
|
|
42
|
+
# such an alias could be created but never referenced.
|
|
43
|
+
return (
|
|
44
|
+
f'Invalid alias name "{name}": an all-digit name would be '
|
|
45
|
+
'indistinguishable from a changelist number')
|
|
46
|
+
if name.split('.', 1)[0].lower() in _WINDOWS_RESERVED_NAMES:
|
|
47
|
+
return (
|
|
48
|
+
f'Invalid alias name "{name}": reserved device name on Windows')
|
|
33
49
|
return None
|
|
34
50
|
|
|
35
51
|
|
|
@@ -38,6 +54,18 @@ def _changelists_dir(workspace_dir: str) -> str:
|
|
|
38
54
|
return os.path.join(workspace_dir, CONFIG_DIR, 'changelists')
|
|
39
55
|
|
|
40
56
|
|
|
57
|
+
def _alias_path(name: str, workspace_dir: str) -> str | None:
|
|
58
|
+
"""Validate name and return its store path, or None if invalid.
|
|
59
|
+
|
|
60
|
+
Validating on every lookup, not just on save, keeps raw user input
|
|
61
|
+
(e.g. "../../somefile") from escaping the store directory."""
|
|
62
|
+
error = validate_alias_name(name)
|
|
63
|
+
if error:
|
|
64
|
+
log.error(error)
|
|
65
|
+
return None
|
|
66
|
+
return os.path.join(_changelists_dir(workspace_dir), name)
|
|
67
|
+
|
|
68
|
+
|
|
41
69
|
def alias_exists(name: str, workspace_dir: str) -> bool:
|
|
42
70
|
"""Check whether a changelist alias exists."""
|
|
43
71
|
alias_path = os.path.join(_changelists_dir(workspace_dir), name)
|
|
@@ -46,13 +74,11 @@ def alias_exists(name: str, workspace_dir: str) -> bool:
|
|
|
46
74
|
|
|
47
75
|
def save_changelist_alias(name: str, changelist: str, workspace_dir: str, force: bool = False) -> bool:
|
|
48
76
|
"""Save a changelist number under a named alias."""
|
|
49
|
-
|
|
50
|
-
if
|
|
51
|
-
log.error(error)
|
|
77
|
+
alias_path = _alias_path(name, workspace_dir)
|
|
78
|
+
if alias_path is None:
|
|
52
79
|
return False
|
|
53
80
|
|
|
54
81
|
changelists_dir = _changelists_dir(workspace_dir)
|
|
55
|
-
alias_path = os.path.join(changelists_dir, name)
|
|
56
82
|
|
|
57
83
|
if os.path.exists(alias_path) and not force:
|
|
58
84
|
log.error(
|
|
@@ -71,7 +97,9 @@ def save_changelist_alias(name: str, changelist: str, workspace_dir: str, force:
|
|
|
71
97
|
|
|
72
98
|
def load_changelist_alias(name: str, workspace_dir: str) -> str | None:
|
|
73
99
|
"""Load a changelist number from a named alias, or None if not found."""
|
|
74
|
-
alias_path =
|
|
100
|
+
alias_path = _alias_path(name, workspace_dir)
|
|
101
|
+
if alias_path is None:
|
|
102
|
+
return None
|
|
75
103
|
|
|
76
104
|
if not os.path.exists(alias_path):
|
|
77
105
|
log.error(f'No changelist alias found: {name}')
|
|
@@ -108,7 +136,9 @@ def list_changelist_aliases(workspace_dir: str) -> list[tuple[str, str]]:
|
|
|
108
136
|
|
|
109
137
|
def delete_changelist_alias(name: str, workspace_dir: str) -> bool:
|
|
110
138
|
"""Delete a changelist alias file."""
|
|
111
|
-
alias_path =
|
|
139
|
+
alias_path = _alias_path(name, workspace_dir)
|
|
140
|
+
if alias_path is None:
|
|
141
|
+
return False
|
|
112
142
|
|
|
113
143
|
if not os.path.exists(alias_path):
|
|
114
144
|
log.error(f'No changelist alias found: {name}')
|
|
@@ -3,6 +3,7 @@ Main CLI entry point for git-p4son.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
|
+
import os
|
|
6
7
|
import sys
|
|
7
8
|
import time
|
|
8
9
|
from importlib.resources import files
|
|
@@ -148,6 +149,7 @@ Examples:
|
|
|
148
149
|
)
|
|
149
150
|
new_parser.add_argument(
|
|
150
151
|
'-s', '--sleep',
|
|
152
|
+
type=int,
|
|
151
153
|
help='Sleep for the specified number of seconds after the command is done'
|
|
152
154
|
)
|
|
153
155
|
|
|
@@ -155,9 +157,11 @@ Examples:
|
|
|
155
157
|
update_parser = subparsers.add_parser(
|
|
156
158
|
'update',
|
|
157
159
|
help='Update an existing changelist description and open files for edit',
|
|
158
|
-
description='Update an existing Perforce changelist description
|
|
159
|
-
'
|
|
160
|
-
'
|
|
160
|
+
description='Update an existing Perforce changelist description: '
|
|
161
|
+
'commits since the base branch replace their existing entries in '
|
|
162
|
+
'the enumerated commit list and new ones are appended; entries '
|
|
163
|
+
'outside the range are kept. By default also opens changed files '
|
|
164
|
+
'for edit.'
|
|
161
165
|
)
|
|
162
166
|
update_parser.add_argument(
|
|
163
167
|
'changelist',
|
|
@@ -193,6 +197,7 @@ Examples:
|
|
|
193
197
|
)
|
|
194
198
|
update_parser.add_argument(
|
|
195
199
|
'-s', '--sleep',
|
|
200
|
+
type=int,
|
|
196
201
|
help='Sleep for the specified number of seconds after the command is done'
|
|
197
202
|
)
|
|
198
203
|
|
|
@@ -267,9 +272,9 @@ Examples:
|
|
|
267
272
|
# alias clean
|
|
268
273
|
alias_subparsers.add_parser(
|
|
269
274
|
'clean',
|
|
270
|
-
help='
|
|
271
|
-
description='
|
|
272
|
-
'yes/no/all/quit prompts'
|
|
275
|
+
help='Clean up changelist aliases',
|
|
276
|
+
description='List all changelist aliases, then delete all of them '
|
|
277
|
+
'or review each one interactively with yes/no/all/quit prompts'
|
|
273
278
|
)
|
|
274
279
|
|
|
275
280
|
# Review subcommand
|
|
@@ -378,6 +383,8 @@ def completion_command(args: argparse.Namespace) -> int:
|
|
|
378
383
|
|
|
379
384
|
|
|
380
385
|
def run_command(args: argparse.Namespace) -> int:
|
|
386
|
+
args.invocation_dir = os.getcwd()
|
|
387
|
+
|
|
381
388
|
log.heading('Finding workspace directory')
|
|
382
389
|
args.workspace_dir = get_workspace_dir()
|
|
383
390
|
if not args.workspace_dir:
|
|
@@ -465,7 +472,7 @@ def main() -> int:
|
|
|
465
472
|
exit_code = run_command(args)
|
|
466
473
|
|
|
467
474
|
if exit_code == 0 and getattr(args, 'sleep', None) is not None:
|
|
468
|
-
seconds =
|
|
475
|
+
seconds = args.sleep
|
|
469
476
|
log.heading(f'Sleeping for {seconds} seconds')
|
|
470
477
|
time.sleep(seconds)
|
|
471
478
|
log.success('awake again')
|