git-p4son 0.4.1__tar.gz → 0.4.2__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.4.1 → git_p4son-0.4.2}/PKG-INFO +29 -51
- {git_p4son-0.4.1 → git_p4son-0.4.2}/README.md +28 -50
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/__init__.py +1 -1
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/cli.py +10 -0
- git_p4son-0.4.2/git_p4son/processes.py +98 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/sync.py +17 -6
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/sync_split.py +3 -1
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son.egg-info/PKG-INFO +29 -51
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son.egg-info/SOURCES.txt +2 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/pyproject.toml +1 -1
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_cli.py +8 -0
- git_p4son-0.4.2/tests/test_processes.py +153 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_sync_split.py +46 -6
- {git_p4son-0.4.1 → git_p4son-0.4.2}/LICENSE +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/__main__.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/alias.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/changelist_store.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/common.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/complete.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/completions/_git-p4son +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/completions/git-p4son.bash +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/completions/git-p4son.ps1 +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/config.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/depot.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/git.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/hooks.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/init.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/lib.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/list_changes.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/log.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/new.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/perforce.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/review.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/state.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/update.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son/writable.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son.egg-info/dependency_links.txt +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son.egg-info/entry_points.txt +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/git_p4son.egg-info/top_level.txt +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/setup.cfg +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_alias.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_changelist_store.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_common.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_complete.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_config.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_depot.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_git.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_hooks.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_init.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_lib_changelist.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_lib_edit.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_lib_revert.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_lib_review.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_list_changes.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_log.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_new.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_perforce.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_review.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_state.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_sync.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_update.py +0 -0
- {git_p4son-0.4.1 → git_p4son-0.4.2}/tests/test_writable.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-p4son
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
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
|
|
@@ -298,7 +298,7 @@ saves the answer; run `git p4son writable apply` once your files are committed t
|
|
|
298
298
|
Sync local git repository with a Perforce workspace:
|
|
299
299
|
|
|
300
300
|
```sh
|
|
301
|
-
git p4son sync [changelist ...] [--force] [--dry-run]
|
|
301
|
+
git p4son sync [changelist ...] [--force] [--dry-run] [--ignore-blocking-processes]
|
|
302
302
|
```
|
|
303
303
|
|
|
304
304
|
**Arguments:**
|
|
@@ -311,6 +311,7 @@ git p4son sync [changelist ...] [--force] [--dry-run]
|
|
|
311
311
|
- `-f, --force`: Allow syncing to changelists older than the current one.
|
|
312
312
|
- `-n, --dry-run`: Print the resolved sync sequence without syncing. The arguments are validated as for a
|
|
313
313
|
real sync, but nothing else runs: no clean-workspace checks, no hooks, and no prompts.
|
|
314
|
+
- `--ignore-blocking-processes`: Sync even while a [blocking process](#blocking-processes) is running.
|
|
314
315
|
|
|
315
316
|
**Examples:**
|
|
316
317
|
```sh
|
|
@@ -324,6 +325,24 @@ git p4son sync 12345 --force
|
|
|
324
325
|
git p4son sync --dry-run # show the changelists a sync to latest would visit
|
|
325
326
|
```
|
|
326
327
|
|
|
328
|
+
#### Blocking processes
|
|
329
|
+
|
|
330
|
+
Syncing while e.g. the Unreal editor is open lets p4 replace assets the editor still has loaded, so the editor
|
|
331
|
+
keeps working against files that no longer match what is on disk. List such processes in `.git-p4son/config.toml`
|
|
332
|
+
and `sync` refuses to start while any of them is running:
|
|
333
|
+
|
|
334
|
+
```toml
|
|
335
|
+
[sync]
|
|
336
|
+
blocking-processes = ["UnrealEditor", "UnrealLightmass"]
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Names are matched with any `.exe` suffix stripped and case ignored, so a single entry covers `UnrealEditor.exe` on
|
|
340
|
+
Windows and `UnrealEditor` on macOS. The check runs before the clean-workspace checks and the pre-sync hooks. A
|
|
341
|
+
setting that is present but malformed, or a process list that cannot be read, aborts the sync rather than silently
|
|
342
|
+
skipping the check.
|
|
343
|
+
|
|
344
|
+
Pass `--ignore-blocking-processes` to sync once without the check, or remove the setting to turn it off for good.
|
|
345
|
+
|
|
327
346
|
#### pre-sync hook
|
|
328
347
|
|
|
329
348
|
Before `git-p4son sync` syncs any changelist, git-p4son runs [hooks](#hooks) from
|
|
@@ -340,7 +359,7 @@ from `.git-p4son/hooks/post-sync/`.
|
|
|
340
359
|
Sync forward from the last synced changelist, splitting selected users' changelists into their own commits:
|
|
341
360
|
|
|
342
361
|
```sh
|
|
343
|
-
git p4son sync-split [changelist] [--user NAME ...] [--dry-run]
|
|
362
|
+
git p4son sync-split [changelist] [--user NAME ...] [--dry-run] [--ignore-blocking-processes]
|
|
344
363
|
```
|
|
345
364
|
|
|
346
365
|
Syncing straight to the latest changelist lumps your own submitted changelists together with everyone
|
|
@@ -354,11 +373,12 @@ own submits one at a time. Repeat `--user` to split out several people's changel
|
|
|
354
373
|
|
|
355
374
|
It is equivalent to working out the numbers by hand and running
|
|
356
375
|
`git p4son sync <before-theirs> <theirs> ... head`, and it delegates to `sync` once the sequence is
|
|
357
|
-
resolved, so [pre-sync and post-sync hooks](#hooks),
|
|
358
|
-
all behave exactly as they do for `sync`. The
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
376
|
+
resolved, so [pre-sync and post-sync hooks](#hooks), [blocking processes](#blocking-processes), writable-file
|
|
377
|
+
merging, and the clean-workspace checks all behave exactly as they do for `sync`. The blocking processes check,
|
|
378
|
+
the clean-workspace checks and the pre-sync hooks are the one thing that runs earlier: resolving the sequence
|
|
379
|
+
costs several Perforce queries, so they go first, and a running blocking process, a dirty workspace or a hook
|
|
380
|
+
that aborts the sync says so before that work. They run once for the whole sync, and not at all for a
|
|
381
|
+
`--dry-run` or when there is nothing to sync.
|
|
362
382
|
|
|
363
383
|
**Arguments:**
|
|
364
384
|
- `changelist` (optional): Changelist number to sync up to, or `head` for the latest. Omit to sync to the
|
|
@@ -368,6 +388,7 @@ and not at all for a `--dry-run` or when there is nothing to sync.
|
|
|
368
388
|
- `-u, --user NAME`: Perforce user whose changelists to split into their own commits. Repeat the flag to
|
|
369
389
|
select several users. Defaults to the current p4 user (from `p4 info`).
|
|
370
390
|
- `-n, --dry-run`: Print the resolved sync sequence without syncing.
|
|
391
|
+
- `--ignore-blocking-processes`: Sync even while a [blocking process](#blocking-processes) is running.
|
|
371
392
|
|
|
372
393
|
**Examples:**
|
|
373
394
|
```sh
|
|
@@ -714,49 +735,6 @@ before the first sync.
|
|
|
714
735
|
After a successful `git-p4son sync` that actually performs sync work, git-p4son runs executable hooks from
|
|
715
736
|
`.git-p4son/hooks/post-sync/`.
|
|
716
737
|
|
|
717
|
-
### Example hooks
|
|
718
|
-
|
|
719
|
-
The [`examples/hooks/`](examples/hooks/) directory holds ready-made hooks. Nothing installs them for you:
|
|
720
|
-
symlink or copy the one you want into the matching hook directory in your workspace. Symlinking keeps the hook
|
|
721
|
-
up to date with the clone it points at, and on macOS and Linux the executable bit comes from the file in the
|
|
722
|
-
clone, so no `chmod` is needed.
|
|
723
|
-
|
|
724
|
-
#### block-while-running (pre-sync)
|
|
725
|
-
|
|
726
|
-
Aborts the sync while a named process is running, so a sync cannot swap out assets that a running editor still
|
|
727
|
-
has loaded. Link it into your workspace's `pre-sync` hook directory - on macOS and Linux:
|
|
728
|
-
|
|
729
|
-
```sh
|
|
730
|
-
ln -s /path/to/git-p4son/examples/hooks/pre-sync/block-while-running.py .git-p4son/hooks/pre-sync/
|
|
731
|
-
```
|
|
732
|
-
|
|
733
|
-
On Windows, from PowerShell. Note that `-Path` is the link to create and `-Target` the existing file, the
|
|
734
|
-
reverse of the `ln -s` argument order, and that `-Target` should be absolute: a relative path is resolved
|
|
735
|
-
against the current directory and quietly produces a broken link.
|
|
736
|
-
|
|
737
|
-
```powershell
|
|
738
|
-
New-Item -ItemType SymbolicLink `
|
|
739
|
-
-Path "C:\p4\my-workspace\.git-p4son\hooks\pre-sync\block-while-running.py" `
|
|
740
|
-
-Target "C:\src\git-p4son\examples\hooks\pre-sync\block-while-running.py"
|
|
741
|
-
```
|
|
742
|
-
|
|
743
|
-
Creating a symlink on Windows needs either an elevated shell or Developer Mode enabled (Settings > System >
|
|
744
|
-
For developers). Without one of those, copy the file instead and re-copy it whenever the clone updates.
|
|
745
|
-
|
|
746
|
-
Which processes block a sync is read from your workspace's own `.git-p4son/config.toml`, so a symlinked hook
|
|
747
|
-
stays per-project:
|
|
748
|
-
|
|
749
|
-
```toml
|
|
750
|
-
[hooks.block-while-running]
|
|
751
|
-
processes = ["UnrealEditor", "UnrealLightmass"]
|
|
752
|
-
```
|
|
753
|
-
|
|
754
|
-
Names are matched with any `.exe` suffix stripped and case ignored, so a single entry covers
|
|
755
|
-
`UnrealEditor.exe` on Windows and `UnrealEditor` on macOS. Without that table the hook checks nothing and the
|
|
756
|
-
sync proceeds; a table that is present but malformed aborts the sync rather than silently skipping the check.
|
|
757
|
-
|
|
758
|
-
Set `GIT_P4SON_SKIP_PROCESS_CHECK=1` to run one sync without the check.
|
|
759
|
-
|
|
760
738
|
## Shell Completions
|
|
761
739
|
|
|
762
740
|
Tab completion is available for bash, zsh, and PowerShell, including commands, flags, and dynamic alias names.
|
|
@@ -275,7 +275,7 @@ saves the answer; run `git p4son writable apply` once your files are committed t
|
|
|
275
275
|
Sync local git repository with a Perforce workspace:
|
|
276
276
|
|
|
277
277
|
```sh
|
|
278
|
-
git p4son sync [changelist ...] [--force] [--dry-run]
|
|
278
|
+
git p4son sync [changelist ...] [--force] [--dry-run] [--ignore-blocking-processes]
|
|
279
279
|
```
|
|
280
280
|
|
|
281
281
|
**Arguments:**
|
|
@@ -288,6 +288,7 @@ git p4son sync [changelist ...] [--force] [--dry-run]
|
|
|
288
288
|
- `-f, --force`: Allow syncing to changelists older than the current one.
|
|
289
289
|
- `-n, --dry-run`: Print the resolved sync sequence without syncing. The arguments are validated as for a
|
|
290
290
|
real sync, but nothing else runs: no clean-workspace checks, no hooks, and no prompts.
|
|
291
|
+
- `--ignore-blocking-processes`: Sync even while a [blocking process](#blocking-processes) is running.
|
|
291
292
|
|
|
292
293
|
**Examples:**
|
|
293
294
|
```sh
|
|
@@ -301,6 +302,24 @@ git p4son sync 12345 --force
|
|
|
301
302
|
git p4son sync --dry-run # show the changelists a sync to latest would visit
|
|
302
303
|
```
|
|
303
304
|
|
|
305
|
+
#### Blocking processes
|
|
306
|
+
|
|
307
|
+
Syncing while e.g. the Unreal editor is open lets p4 replace assets the editor still has loaded, so the editor
|
|
308
|
+
keeps working against files that no longer match what is on disk. List such processes in `.git-p4son/config.toml`
|
|
309
|
+
and `sync` refuses to start while any of them is running:
|
|
310
|
+
|
|
311
|
+
```toml
|
|
312
|
+
[sync]
|
|
313
|
+
blocking-processes = ["UnrealEditor", "UnrealLightmass"]
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Names are matched with any `.exe` suffix stripped and case ignored, so a single entry covers `UnrealEditor.exe` on
|
|
317
|
+
Windows and `UnrealEditor` on macOS. The check runs before the clean-workspace checks and the pre-sync hooks. A
|
|
318
|
+
setting that is present but malformed, or a process list that cannot be read, aborts the sync rather than silently
|
|
319
|
+
skipping the check.
|
|
320
|
+
|
|
321
|
+
Pass `--ignore-blocking-processes` to sync once without the check, or remove the setting to turn it off for good.
|
|
322
|
+
|
|
304
323
|
#### pre-sync hook
|
|
305
324
|
|
|
306
325
|
Before `git-p4son sync` syncs any changelist, git-p4son runs [hooks](#hooks) from
|
|
@@ -317,7 +336,7 @@ from `.git-p4son/hooks/post-sync/`.
|
|
|
317
336
|
Sync forward from the last synced changelist, splitting selected users' changelists into their own commits:
|
|
318
337
|
|
|
319
338
|
```sh
|
|
320
|
-
git p4son sync-split [changelist] [--user NAME ...] [--dry-run]
|
|
339
|
+
git p4son sync-split [changelist] [--user NAME ...] [--dry-run] [--ignore-blocking-processes]
|
|
321
340
|
```
|
|
322
341
|
|
|
323
342
|
Syncing straight to the latest changelist lumps your own submitted changelists together with everyone
|
|
@@ -331,11 +350,12 @@ own submits one at a time. Repeat `--user` to split out several people's changel
|
|
|
331
350
|
|
|
332
351
|
It is equivalent to working out the numbers by hand and running
|
|
333
352
|
`git p4son sync <before-theirs> <theirs> ... head`, and it delegates to `sync` once the sequence is
|
|
334
|
-
resolved, so [pre-sync and post-sync hooks](#hooks),
|
|
335
|
-
all behave exactly as they do for `sync`. The
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
353
|
+
resolved, so [pre-sync and post-sync hooks](#hooks), [blocking processes](#blocking-processes), writable-file
|
|
354
|
+
merging, and the clean-workspace checks all behave exactly as they do for `sync`. The blocking processes check,
|
|
355
|
+
the clean-workspace checks and the pre-sync hooks are the one thing that runs earlier: resolving the sequence
|
|
356
|
+
costs several Perforce queries, so they go first, and a running blocking process, a dirty workspace or a hook
|
|
357
|
+
that aborts the sync says so before that work. They run once for the whole sync, and not at all for a
|
|
358
|
+
`--dry-run` or when there is nothing to sync.
|
|
339
359
|
|
|
340
360
|
**Arguments:**
|
|
341
361
|
- `changelist` (optional): Changelist number to sync up to, or `head` for the latest. Omit to sync to the
|
|
@@ -345,6 +365,7 @@ and not at all for a `--dry-run` or when there is nothing to sync.
|
|
|
345
365
|
- `-u, --user NAME`: Perforce user whose changelists to split into their own commits. Repeat the flag to
|
|
346
366
|
select several users. Defaults to the current p4 user (from `p4 info`).
|
|
347
367
|
- `-n, --dry-run`: Print the resolved sync sequence without syncing.
|
|
368
|
+
- `--ignore-blocking-processes`: Sync even while a [blocking process](#blocking-processes) is running.
|
|
348
369
|
|
|
349
370
|
**Examples:**
|
|
350
371
|
```sh
|
|
@@ -691,49 +712,6 @@ before the first sync.
|
|
|
691
712
|
After a successful `git-p4son sync` that actually performs sync work, git-p4son runs executable hooks from
|
|
692
713
|
`.git-p4son/hooks/post-sync/`.
|
|
693
714
|
|
|
694
|
-
### Example hooks
|
|
695
|
-
|
|
696
|
-
The [`examples/hooks/`](examples/hooks/) directory holds ready-made hooks. Nothing installs them for you:
|
|
697
|
-
symlink or copy the one you want into the matching hook directory in your workspace. Symlinking keeps the hook
|
|
698
|
-
up to date with the clone it points at, and on macOS and Linux the executable bit comes from the file in the
|
|
699
|
-
clone, so no `chmod` is needed.
|
|
700
|
-
|
|
701
|
-
#### block-while-running (pre-sync)
|
|
702
|
-
|
|
703
|
-
Aborts the sync while a named process is running, so a sync cannot swap out assets that a running editor still
|
|
704
|
-
has loaded. Link it into your workspace's `pre-sync` hook directory - on macOS and Linux:
|
|
705
|
-
|
|
706
|
-
```sh
|
|
707
|
-
ln -s /path/to/git-p4son/examples/hooks/pre-sync/block-while-running.py .git-p4son/hooks/pre-sync/
|
|
708
|
-
```
|
|
709
|
-
|
|
710
|
-
On Windows, from PowerShell. Note that `-Path` is the link to create and `-Target` the existing file, the
|
|
711
|
-
reverse of the `ln -s` argument order, and that `-Target` should be absolute: a relative path is resolved
|
|
712
|
-
against the current directory and quietly produces a broken link.
|
|
713
|
-
|
|
714
|
-
```powershell
|
|
715
|
-
New-Item -ItemType SymbolicLink `
|
|
716
|
-
-Path "C:\p4\my-workspace\.git-p4son\hooks\pre-sync\block-while-running.py" `
|
|
717
|
-
-Target "C:\src\git-p4son\examples\hooks\pre-sync\block-while-running.py"
|
|
718
|
-
```
|
|
719
|
-
|
|
720
|
-
Creating a symlink on Windows needs either an elevated shell or Developer Mode enabled (Settings > System >
|
|
721
|
-
For developers). Without one of those, copy the file instead and re-copy it whenever the clone updates.
|
|
722
|
-
|
|
723
|
-
Which processes block a sync is read from your workspace's own `.git-p4son/config.toml`, so a symlinked hook
|
|
724
|
-
stays per-project:
|
|
725
|
-
|
|
726
|
-
```toml
|
|
727
|
-
[hooks.block-while-running]
|
|
728
|
-
processes = ["UnrealEditor", "UnrealLightmass"]
|
|
729
|
-
```
|
|
730
|
-
|
|
731
|
-
Names are matched with any `.exe` suffix stripped and case ignored, so a single entry covers
|
|
732
|
-
`UnrealEditor.exe` on Windows and `UnrealEditor` on macOS. Without that table the hook checks nothing and the
|
|
733
|
-
sync proceeds; a table that is present but malformed aborts the sync rather than silently skipping the check.
|
|
734
|
-
|
|
735
|
-
Set `GIT_P4SON_SKIP_PROCESS_CHECK=1` to run one sync without the check.
|
|
736
|
-
|
|
737
715
|
## Shell Completions
|
|
738
716
|
|
|
739
717
|
Tab completion is available for bash, zsh, and PowerShell, including commands, flags, and dynamic alias names.
|
|
@@ -109,6 +109,11 @@ Examples:
|
|
|
109
109
|
action='store_true',
|
|
110
110
|
help='Print the resolved sync sequence without syncing'
|
|
111
111
|
)
|
|
112
|
+
sync_parser.add_argument(
|
|
113
|
+
'--ignore-blocking-processes',
|
|
114
|
+
action='store_true',
|
|
115
|
+
help='Sync even while a configured blocking process is running'
|
|
116
|
+
)
|
|
112
117
|
|
|
113
118
|
# Sync-split subcommand
|
|
114
119
|
sync_split_parser = subparsers.add_parser(
|
|
@@ -141,6 +146,11 @@ Examples:
|
|
|
141
146
|
action='store_true',
|
|
142
147
|
help='Print the resolved sync sequence without syncing'
|
|
143
148
|
)
|
|
149
|
+
sync_split_parser.add_argument(
|
|
150
|
+
'--ignore-blocking-processes',
|
|
151
|
+
action='store_true',
|
|
152
|
+
help='Sync even while a configured blocking process is running'
|
|
153
|
+
)
|
|
144
154
|
|
|
145
155
|
# New subcommand
|
|
146
156
|
new_parser = subparsers.add_parser(
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Blocking processes for git-p4son.
|
|
3
|
+
|
|
4
|
+
A sync is refused while any of the configured blocking processes is running.
|
|
5
|
+
Syncing while e.g. the Unreal editor is open lets p4 replace assets the editor
|
|
6
|
+
still has loaded, so the editor keeps working against files that no longer
|
|
7
|
+
match what is on disk.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import csv
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
from .common import RunError, run
|
|
14
|
+
from .config import load_config
|
|
15
|
+
from .log import log
|
|
16
|
+
|
|
17
|
+
IGNORE_FLAG = '--ignore-blocking-processes'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_blocking_processes(workspace_dir: str) -> list[str]:
|
|
21
|
+
"""Return the configured blocking process names; raise ValueError if malformed."""
|
|
22
|
+
sync = load_config(workspace_dir).get('sync', {})
|
|
23
|
+
if not isinstance(sync, dict):
|
|
24
|
+
raise ValueError('sync in config.toml is not a table')
|
|
25
|
+
names = sync.get('blocking-processes', [])
|
|
26
|
+
if (not isinstance(names, list)
|
|
27
|
+
or not all(isinstance(name, str) for name in names)):
|
|
28
|
+
raise ValueError(
|
|
29
|
+
'sync.blocking-processes in config.toml must be a list of strings')
|
|
30
|
+
return names
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def normalize_process_name(name: str) -> str:
|
|
34
|
+
"""Reduce a process name to a form comparable across platforms.
|
|
35
|
+
|
|
36
|
+
Any directory and .exe suffix are dropped and case is ignored, so
|
|
37
|
+
"UnrealEditor" matches "UnrealEditor.exe" on Windows and a full path in
|
|
38
|
+
ps output."""
|
|
39
|
+
name = os.path.basename(name.strip())
|
|
40
|
+
if name.lower().endswith('.exe'):
|
|
41
|
+
name = name[:-len('.exe')]
|
|
42
|
+
return name.lower()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_running_processes() -> set[str]:
|
|
46
|
+
"""Return the normalized names of all currently running processes."""
|
|
47
|
+
if os.name == 'nt':
|
|
48
|
+
# /NH drops the header row. CSV output keeps an image name that
|
|
49
|
+
# contains spaces or commas inside one quoted field.
|
|
50
|
+
result = run(['tasklist', '/NH', '/FO', 'CSV'])
|
|
51
|
+
lines = [line for line in result.stdout if line.strip()]
|
|
52
|
+
return {normalize_process_name(row[0])
|
|
53
|
+
for row in csv.reader(lines) if row}
|
|
54
|
+
# -ww stops ps truncating output to the terminal width, which would
|
|
55
|
+
# otherwise cut long process names short.
|
|
56
|
+
result = run(['ps', '-A', '-ww', '-o', 'comm='])
|
|
57
|
+
return {normalize_process_name(line)
|
|
58
|
+
for line in result.stdout if line.strip()}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def check_no_blocking_processes(workspace_dir: str, ignore: bool) -> bool:
|
|
62
|
+
"""Report whether the sync may go ahead as far as running processes go.
|
|
63
|
+
|
|
64
|
+
Nothing configured means nothing to check, so no process listing is
|
|
65
|
+
made. A malformed config or a process list that cannot be read fails
|
|
66
|
+
the check: going ahead would silently drop the protection. ignore skips
|
|
67
|
+
all of it, config included."""
|
|
68
|
+
if ignore:
|
|
69
|
+
log.heading('Checking for blocking processes')
|
|
70
|
+
log.warning(f'Skipped ({IGNORE_FLAG})')
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
names = get_blocking_processes(workspace_dir)
|
|
75
|
+
except ValueError as error:
|
|
76
|
+
log.error(f'Could not read the blocking processes: {error}')
|
|
77
|
+
return False
|
|
78
|
+
if not names:
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
log.heading('Checking for blocking processes')
|
|
82
|
+
try:
|
|
83
|
+
running = get_running_processes()
|
|
84
|
+
except (OSError, RunError) as error:
|
|
85
|
+
log.error(f'Could not list running processes: {error}')
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
blocking = sorted(name for name in names
|
|
89
|
+
if normalize_process_name(name) in running)
|
|
90
|
+
if not blocking:
|
|
91
|
+
log.success('None running')
|
|
92
|
+
return True
|
|
93
|
+
|
|
94
|
+
for name in blocking:
|
|
95
|
+
log.info(name)
|
|
96
|
+
log.error('Refusing to sync while these blocking processes are running. '
|
|
97
|
+
f'Close them and sync again, or pass {IGNORE_FLAG}')
|
|
98
|
+
return False
|
|
@@ -23,6 +23,7 @@ from .hooks import run_hooks
|
|
|
23
23
|
from .lib import check_git_workspace_clean
|
|
24
24
|
from .depot import resolve_depot_root
|
|
25
25
|
from .log import log
|
|
26
|
+
from .processes import check_no_blocking_processes
|
|
26
27
|
from .writable import is_writable_mode, make_writable
|
|
27
28
|
from .perforce import (
|
|
28
29
|
get_latest_changelist,
|
|
@@ -645,8 +646,10 @@ def _run_pre_sync_hooks(workspace_dir: str, invocation_dir: str) -> bool:
|
|
|
645
646
|
|
|
646
647
|
|
|
647
648
|
def sync_preflight(depot_root: str, workspace_dir: str, invocation_dir: str,
|
|
648
|
-
already_done: bool = False
|
|
649
|
-
|
|
649
|
+
already_done: bool = False,
|
|
650
|
+
ignore_blocking_processes: bool = False) -> bool:
|
|
651
|
+
"""Gate a sync: no blocking processes, both workspaces clean, then the
|
|
652
|
+
pre-sync hooks.
|
|
650
653
|
|
|
651
654
|
Returns False when the sync must not go ahead. Callers run this once they
|
|
652
655
|
know a sync will actually be attempted, so that a run with nothing to sync
|
|
@@ -657,6 +660,11 @@ def sync_preflight(depot_root: str, workspace_dir: str, invocation_dir: str,
|
|
|
657
660
|
"""
|
|
658
661
|
if already_done:
|
|
659
662
|
return True
|
|
663
|
+
# First: it is the cheapest check, and an editor left open is the most
|
|
664
|
+
# common reason not to sync.
|
|
665
|
+
if not check_no_blocking_processes(workspace_dir,
|
|
666
|
+
ignore_blocking_processes):
|
|
667
|
+
return False
|
|
660
668
|
if not check_git_workspace_clean(workspace_dir):
|
|
661
669
|
return False
|
|
662
670
|
if not _check_p4_workspace_clean(depot_root, workspace_dir):
|
|
@@ -776,6 +784,8 @@ def sync_command(args: argparse.Namespace) -> int:
|
|
|
776
784
|
workspace_dir = args.workspace_dir
|
|
777
785
|
invocation_dir = vars(args).get('invocation_dir', workspace_dir)
|
|
778
786
|
preflight_done = vars(args).get('preflight_done', False)
|
|
787
|
+
ignore_blocking_processes = vars(args).get('ignore_blocking_processes',
|
|
788
|
+
False)
|
|
779
789
|
|
|
780
790
|
# sync-split resolves the depot root before handing over, so it passes
|
|
781
791
|
# the result on rather than having the client spec queried twice.
|
|
@@ -846,11 +856,12 @@ def sync_command(args: argparse.Namespace) -> int:
|
|
|
846
856
|
if not _handle_clobber_warning(clobber, workspace_dir):
|
|
847
857
|
return 1
|
|
848
858
|
|
|
849
|
-
# The single gate: both workspaces clean, then
|
|
850
|
-
# once for the whole sync, covering the
|
|
851
|
-
# skipped outright when the caller
|
|
859
|
+
# The single gate: no blocking processes, both workspaces clean, then
|
|
860
|
+
# the pre-sync hooks. Runs once for the whole sync, covering the
|
|
861
|
+
# catch-up pass as well, and is skipped outright when the caller
|
|
862
|
+
# (sync-split) already ran it.
|
|
852
863
|
if not sync_preflight(depot_root, workspace_dir, invocation_dir,
|
|
853
|
-
preflight_done):
|
|
864
|
+
preflight_done, ignore_blocking_processes):
|
|
854
865
|
return 1
|
|
855
866
|
|
|
856
867
|
log.heading('Finding HEAD commit')
|
|
@@ -117,7 +117,9 @@ def sync_split_command(args: argparse.Namespace) -> int:
|
|
|
117
117
|
# sync_command is told it is done so none of it happens twice, and a dry
|
|
118
118
|
# run syncs nothing, so it skips the gate entirely.
|
|
119
119
|
if not args.dry_run:
|
|
120
|
-
if not sync_preflight(
|
|
120
|
+
if not sync_preflight(
|
|
121
|
+
depot_root, workspace_dir, invocation_dir,
|
|
122
|
+
ignore_blocking_processes=args.ignore_blocking_processes):
|
|
121
123
|
return 1
|
|
122
124
|
args.preflight_done = True
|
|
123
125
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-p4son
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
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
|
|
@@ -298,7 +298,7 @@ saves the answer; run `git p4son writable apply` once your files are committed t
|
|
|
298
298
|
Sync local git repository with a Perforce workspace:
|
|
299
299
|
|
|
300
300
|
```sh
|
|
301
|
-
git p4son sync [changelist ...] [--force] [--dry-run]
|
|
301
|
+
git p4son sync [changelist ...] [--force] [--dry-run] [--ignore-blocking-processes]
|
|
302
302
|
```
|
|
303
303
|
|
|
304
304
|
**Arguments:**
|
|
@@ -311,6 +311,7 @@ git p4son sync [changelist ...] [--force] [--dry-run]
|
|
|
311
311
|
- `-f, --force`: Allow syncing to changelists older than the current one.
|
|
312
312
|
- `-n, --dry-run`: Print the resolved sync sequence without syncing. The arguments are validated as for a
|
|
313
313
|
real sync, but nothing else runs: no clean-workspace checks, no hooks, and no prompts.
|
|
314
|
+
- `--ignore-blocking-processes`: Sync even while a [blocking process](#blocking-processes) is running.
|
|
314
315
|
|
|
315
316
|
**Examples:**
|
|
316
317
|
```sh
|
|
@@ -324,6 +325,24 @@ git p4son sync 12345 --force
|
|
|
324
325
|
git p4son sync --dry-run # show the changelists a sync to latest would visit
|
|
325
326
|
```
|
|
326
327
|
|
|
328
|
+
#### Blocking processes
|
|
329
|
+
|
|
330
|
+
Syncing while e.g. the Unreal editor is open lets p4 replace assets the editor still has loaded, so the editor
|
|
331
|
+
keeps working against files that no longer match what is on disk. List such processes in `.git-p4son/config.toml`
|
|
332
|
+
and `sync` refuses to start while any of them is running:
|
|
333
|
+
|
|
334
|
+
```toml
|
|
335
|
+
[sync]
|
|
336
|
+
blocking-processes = ["UnrealEditor", "UnrealLightmass"]
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Names are matched with any `.exe` suffix stripped and case ignored, so a single entry covers `UnrealEditor.exe` on
|
|
340
|
+
Windows and `UnrealEditor` on macOS. The check runs before the clean-workspace checks and the pre-sync hooks. A
|
|
341
|
+
setting that is present but malformed, or a process list that cannot be read, aborts the sync rather than silently
|
|
342
|
+
skipping the check.
|
|
343
|
+
|
|
344
|
+
Pass `--ignore-blocking-processes` to sync once without the check, or remove the setting to turn it off for good.
|
|
345
|
+
|
|
327
346
|
#### pre-sync hook
|
|
328
347
|
|
|
329
348
|
Before `git-p4son sync` syncs any changelist, git-p4son runs [hooks](#hooks) from
|
|
@@ -340,7 +359,7 @@ from `.git-p4son/hooks/post-sync/`.
|
|
|
340
359
|
Sync forward from the last synced changelist, splitting selected users' changelists into their own commits:
|
|
341
360
|
|
|
342
361
|
```sh
|
|
343
|
-
git p4son sync-split [changelist] [--user NAME ...] [--dry-run]
|
|
362
|
+
git p4son sync-split [changelist] [--user NAME ...] [--dry-run] [--ignore-blocking-processes]
|
|
344
363
|
```
|
|
345
364
|
|
|
346
365
|
Syncing straight to the latest changelist lumps your own submitted changelists together with everyone
|
|
@@ -354,11 +373,12 @@ own submits one at a time. Repeat `--user` to split out several people's changel
|
|
|
354
373
|
|
|
355
374
|
It is equivalent to working out the numbers by hand and running
|
|
356
375
|
`git p4son sync <before-theirs> <theirs> ... head`, and it delegates to `sync` once the sequence is
|
|
357
|
-
resolved, so [pre-sync and post-sync hooks](#hooks),
|
|
358
|
-
all behave exactly as they do for `sync`. The
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
376
|
+
resolved, so [pre-sync and post-sync hooks](#hooks), [blocking processes](#blocking-processes), writable-file
|
|
377
|
+
merging, and the clean-workspace checks all behave exactly as they do for `sync`. The blocking processes check,
|
|
378
|
+
the clean-workspace checks and the pre-sync hooks are the one thing that runs earlier: resolving the sequence
|
|
379
|
+
costs several Perforce queries, so they go first, and a running blocking process, a dirty workspace or a hook
|
|
380
|
+
that aborts the sync says so before that work. They run once for the whole sync, and not at all for a
|
|
381
|
+
`--dry-run` or when there is nothing to sync.
|
|
362
382
|
|
|
363
383
|
**Arguments:**
|
|
364
384
|
- `changelist` (optional): Changelist number to sync up to, or `head` for the latest. Omit to sync to the
|
|
@@ -368,6 +388,7 @@ and not at all for a `--dry-run` or when there is nothing to sync.
|
|
|
368
388
|
- `-u, --user NAME`: Perforce user whose changelists to split into their own commits. Repeat the flag to
|
|
369
389
|
select several users. Defaults to the current p4 user (from `p4 info`).
|
|
370
390
|
- `-n, --dry-run`: Print the resolved sync sequence without syncing.
|
|
391
|
+
- `--ignore-blocking-processes`: Sync even while a [blocking process](#blocking-processes) is running.
|
|
371
392
|
|
|
372
393
|
**Examples:**
|
|
373
394
|
```sh
|
|
@@ -714,49 +735,6 @@ before the first sync.
|
|
|
714
735
|
After a successful `git-p4son sync` that actually performs sync work, git-p4son runs executable hooks from
|
|
715
736
|
`.git-p4son/hooks/post-sync/`.
|
|
716
737
|
|
|
717
|
-
### Example hooks
|
|
718
|
-
|
|
719
|
-
The [`examples/hooks/`](examples/hooks/) directory holds ready-made hooks. Nothing installs them for you:
|
|
720
|
-
symlink or copy the one you want into the matching hook directory in your workspace. Symlinking keeps the hook
|
|
721
|
-
up to date with the clone it points at, and on macOS and Linux the executable bit comes from the file in the
|
|
722
|
-
clone, so no `chmod` is needed.
|
|
723
|
-
|
|
724
|
-
#### block-while-running (pre-sync)
|
|
725
|
-
|
|
726
|
-
Aborts the sync while a named process is running, so a sync cannot swap out assets that a running editor still
|
|
727
|
-
has loaded. Link it into your workspace's `pre-sync` hook directory - on macOS and Linux:
|
|
728
|
-
|
|
729
|
-
```sh
|
|
730
|
-
ln -s /path/to/git-p4son/examples/hooks/pre-sync/block-while-running.py .git-p4son/hooks/pre-sync/
|
|
731
|
-
```
|
|
732
|
-
|
|
733
|
-
On Windows, from PowerShell. Note that `-Path` is the link to create and `-Target` the existing file, the
|
|
734
|
-
reverse of the `ln -s` argument order, and that `-Target` should be absolute: a relative path is resolved
|
|
735
|
-
against the current directory and quietly produces a broken link.
|
|
736
|
-
|
|
737
|
-
```powershell
|
|
738
|
-
New-Item -ItemType SymbolicLink `
|
|
739
|
-
-Path "C:\p4\my-workspace\.git-p4son\hooks\pre-sync\block-while-running.py" `
|
|
740
|
-
-Target "C:\src\git-p4son\examples\hooks\pre-sync\block-while-running.py"
|
|
741
|
-
```
|
|
742
|
-
|
|
743
|
-
Creating a symlink on Windows needs either an elevated shell or Developer Mode enabled (Settings > System >
|
|
744
|
-
For developers). Without one of those, copy the file instead and re-copy it whenever the clone updates.
|
|
745
|
-
|
|
746
|
-
Which processes block a sync is read from your workspace's own `.git-p4son/config.toml`, so a symlinked hook
|
|
747
|
-
stays per-project:
|
|
748
|
-
|
|
749
|
-
```toml
|
|
750
|
-
[hooks.block-while-running]
|
|
751
|
-
processes = ["UnrealEditor", "UnrealLightmass"]
|
|
752
|
-
```
|
|
753
|
-
|
|
754
|
-
Names are matched with any `.exe` suffix stripped and case ignored, so a single entry covers
|
|
755
|
-
`UnrealEditor.exe` on Windows and `UnrealEditor` on macOS. Without that table the hook checks nothing and the
|
|
756
|
-
sync proceeds; a table that is present but malformed aborts the sync rather than silently skipping the check.
|
|
757
|
-
|
|
758
|
-
Set `GIT_P4SON_SKIP_PROCESS_CHECK=1` to run one sync without the check.
|
|
759
|
-
|
|
760
738
|
## Shell Completions
|
|
761
739
|
|
|
762
740
|
Tab completion is available for bash, zsh, and PowerShell, including commands, flags, and dynamic alias names.
|
|
@@ -18,6 +18,7 @@ git_p4son/list_changes.py
|
|
|
18
18
|
git_p4son/log.py
|
|
19
19
|
git_p4son/new.py
|
|
20
20
|
git_p4son/perforce.py
|
|
21
|
+
git_p4son/processes.py
|
|
21
22
|
git_p4son/review.py
|
|
22
23
|
git_p4son/state.py
|
|
23
24
|
git_p4son/sync.py
|
|
@@ -50,6 +51,7 @@ tests/test_list_changes.py
|
|
|
50
51
|
tests/test_log.py
|
|
51
52
|
tests/test_new.py
|
|
52
53
|
tests/test_perforce.py
|
|
54
|
+
tests/test_processes.py
|
|
53
55
|
tests/test_review.py
|
|
54
56
|
tests/test_state.py
|
|
55
57
|
tests/test_sync.py
|
|
@@ -53,6 +53,14 @@ class TestCreateParser(unittest.TestCase):
|
|
|
53
53
|
args = self.parser.parse_args(['sync', flag])
|
|
54
54
|
self.assertTrue(args.dry_run)
|
|
55
55
|
|
|
56
|
+
def test_ignore_blocking_processes(self):
|
|
57
|
+
for command in ('sync', 'sync-split'):
|
|
58
|
+
self.assertFalse(self.parser.parse_args(
|
|
59
|
+
[command]).ignore_blocking_processes)
|
|
60
|
+
self.assertTrue(self.parser.parse_args(
|
|
61
|
+
[command, '--ignore-blocking-processes'])
|
|
62
|
+
.ignore_blocking_processes)
|
|
63
|
+
|
|
56
64
|
def test_new_command_no_message(self):
|
|
57
65
|
args = self.parser.parse_args(['new'])
|
|
58
66
|
self.assertEqual(args.command, 'new')
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Tests for git_p4son.processes module."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import tempfile
|
|
5
|
+
import unittest
|
|
6
|
+
from unittest import mock
|
|
7
|
+
|
|
8
|
+
from git_p4son.common import RunError
|
|
9
|
+
from git_p4son.processes import (
|
|
10
|
+
check_no_blocking_processes, get_blocking_processes,
|
|
11
|
+
get_running_processes, normalize_process_name,
|
|
12
|
+
)
|
|
13
|
+
from tests.helpers import make_run_result
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _WorkspaceTestCase(unittest.TestCase):
|
|
17
|
+
|
|
18
|
+
def setUp(self):
|
|
19
|
+
self._tmp = tempfile.TemporaryDirectory()
|
|
20
|
+
self.addCleanup(self._tmp.cleanup)
|
|
21
|
+
self.workspace = self._tmp.name
|
|
22
|
+
|
|
23
|
+
def write_config(self, text):
|
|
24
|
+
config_dir = os.path.join(self.workspace, '.git-p4son')
|
|
25
|
+
os.makedirs(config_dir, exist_ok=True)
|
|
26
|
+
with open(os.path.join(config_dir, 'config.toml'), 'w',
|
|
27
|
+
encoding='utf-8') as f:
|
|
28
|
+
f.write(text)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class TestGetBlockingProcesses(_WorkspaceTestCase):
|
|
32
|
+
|
|
33
|
+
def test_no_config_file(self):
|
|
34
|
+
self.assertEqual(get_blocking_processes(self.workspace), [])
|
|
35
|
+
|
|
36
|
+
def test_no_sync_table(self):
|
|
37
|
+
self.write_config('[depot]\nroot = "//ws"\n')
|
|
38
|
+
self.assertEqual(get_blocking_processes(self.workspace), [])
|
|
39
|
+
|
|
40
|
+
def test_no_key(self):
|
|
41
|
+
self.write_config('[sync]\nother = 1\n')
|
|
42
|
+
self.assertEqual(get_blocking_processes(self.workspace), [])
|
|
43
|
+
|
|
44
|
+
def test_names(self):
|
|
45
|
+
self.write_config(
|
|
46
|
+
'[sync]\nblocking-processes = ["UnrealEditor", "Lightmass"]\n')
|
|
47
|
+
self.assertEqual(get_blocking_processes(self.workspace),
|
|
48
|
+
['UnrealEditor', 'Lightmass'])
|
|
49
|
+
|
|
50
|
+
def test_not_a_list(self):
|
|
51
|
+
self.write_config('[sync]\nblocking-processes = "UnrealEditor"\n')
|
|
52
|
+
with self.assertRaises(ValueError):
|
|
53
|
+
get_blocking_processes(self.workspace)
|
|
54
|
+
|
|
55
|
+
def test_non_string_entry(self):
|
|
56
|
+
self.write_config('[sync]\nblocking-processes = ["a", 1]\n')
|
|
57
|
+
with self.assertRaises(ValueError):
|
|
58
|
+
get_blocking_processes(self.workspace)
|
|
59
|
+
|
|
60
|
+
def test_sync_not_a_table(self):
|
|
61
|
+
self.write_config('sync = 1\n')
|
|
62
|
+
with self.assertRaises(ValueError):
|
|
63
|
+
get_blocking_processes(self.workspace)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class TestNormalizeProcessName(unittest.TestCase):
|
|
67
|
+
|
|
68
|
+
def test_strips_exe_and_case(self):
|
|
69
|
+
self.assertEqual(normalize_process_name('UnrealEditor.EXE'),
|
|
70
|
+
'unrealeditor')
|
|
71
|
+
|
|
72
|
+
def test_strips_directory_and_whitespace(self):
|
|
73
|
+
self.assertEqual(
|
|
74
|
+
normalize_process_name(
|
|
75
|
+
' /Applications/Foo.app/Contents/MacOS/Foo \n'),
|
|
76
|
+
'foo')
|
|
77
|
+
|
|
78
|
+
def test_exe_only_stripped_as_suffix(self):
|
|
79
|
+
self.assertEqual(normalize_process_name('exeditor'), 'exeditor')
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class TestGetRunningProcesses(unittest.TestCase):
|
|
83
|
+
|
|
84
|
+
@mock.patch('git_p4son.processes.os.name', 'posix')
|
|
85
|
+
@mock.patch('git_p4son.processes.run')
|
|
86
|
+
def test_ps(self, mock_run):
|
|
87
|
+
mock_run.return_value = make_run_result(
|
|
88
|
+
stdout=['/usr/bin/zsh', '', 'UnrealEditor'])
|
|
89
|
+
self.assertEqual(get_running_processes(), {'zsh', 'unrealeditor'})
|
|
90
|
+
self.assertEqual(mock_run.call_args.args[0][0], 'ps')
|
|
91
|
+
|
|
92
|
+
@mock.patch('git_p4son.processes.os.name', 'nt')
|
|
93
|
+
@mock.patch('git_p4son.processes.run')
|
|
94
|
+
def test_tasklist(self, mock_run):
|
|
95
|
+
mock_run.return_value = make_run_result(stdout=[
|
|
96
|
+
'',
|
|
97
|
+
'"UnrealEditor.exe","1234","Console","1","2,000 K"',
|
|
98
|
+
'"My, App.exe","42","Console","1","100 K"',
|
|
99
|
+
])
|
|
100
|
+
self.assertEqual(get_running_processes(),
|
|
101
|
+
{'unrealeditor', 'my, app'})
|
|
102
|
+
self.assertEqual(mock_run.call_args.args[0][0], 'tasklist')
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class TestCheckNoBlockingProcesses(_WorkspaceTestCase):
|
|
106
|
+
|
|
107
|
+
def setUp(self):
|
|
108
|
+
super().setUp()
|
|
109
|
+
patcher = mock.patch('git_p4son.processes.log')
|
|
110
|
+
self.mock_log = patcher.start()
|
|
111
|
+
self.addCleanup(patcher.stop)
|
|
112
|
+
patcher = mock.patch('git_p4son.processes.get_running_processes',
|
|
113
|
+
return_value={'zsh', 'unrealeditor'})
|
|
114
|
+
self.mock_running = patcher.start()
|
|
115
|
+
self.addCleanup(patcher.stop)
|
|
116
|
+
|
|
117
|
+
def test_nothing_configured_lists_nothing(self):
|
|
118
|
+
self.assertTrue(check_no_blocking_processes(self.workspace, False))
|
|
119
|
+
self.mock_running.assert_not_called()
|
|
120
|
+
|
|
121
|
+
def test_none_running(self):
|
|
122
|
+
self.write_config('[sync]\nblocking-processes = ["Lightmass"]\n')
|
|
123
|
+
self.assertTrue(check_no_blocking_processes(self.workspace, False))
|
|
124
|
+
|
|
125
|
+
def test_running_blocks(self):
|
|
126
|
+
self.write_config(
|
|
127
|
+
'[sync]\nblocking-processes = ["UnrealEditor.exe", "Lightmass"]\n')
|
|
128
|
+
self.assertFalse(check_no_blocking_processes(self.workspace, False))
|
|
129
|
+
self.mock_log.info.assert_called_once_with('UnrealEditor.exe')
|
|
130
|
+
|
|
131
|
+
def test_ignore_skips_listing(self):
|
|
132
|
+
self.write_config('[sync]\nblocking-processes = ["UnrealEditor"]\n')
|
|
133
|
+
self.assertTrue(check_no_blocking_processes(self.workspace, True))
|
|
134
|
+
self.mock_running.assert_not_called()
|
|
135
|
+
|
|
136
|
+
def test_ignore_skips_malformed_config(self):
|
|
137
|
+
self.write_config('[sync]\nblocking-processes = "UnrealEditor"\n')
|
|
138
|
+
self.assertTrue(check_no_blocking_processes(self.workspace, True))
|
|
139
|
+
|
|
140
|
+
def test_malformed_config_fails(self):
|
|
141
|
+
self.write_config('[sync]\nblocking-processes = "UnrealEditor"\n')
|
|
142
|
+
self.assertFalse(check_no_blocking_processes(self.workspace, False))
|
|
143
|
+
|
|
144
|
+
def test_invalid_toml_fails(self):
|
|
145
|
+
self.write_config('[sync\n')
|
|
146
|
+
self.assertFalse(check_no_blocking_processes(self.workspace, False))
|
|
147
|
+
|
|
148
|
+
def test_listing_failure_fails(self):
|
|
149
|
+
self.write_config('[sync]\nblocking-processes = ["UnrealEditor"]\n')
|
|
150
|
+
for error in (OSError('no ps'), RunError('ps failed')):
|
|
151
|
+
self.mock_running.side_effect = error
|
|
152
|
+
self.assertFalse(
|
|
153
|
+
check_no_blocking_processes(self.workspace, False))
|
|
@@ -117,7 +117,7 @@ class TestSyncSplitCommand(unittest.TestCase):
|
|
|
117
117
|
|
|
118
118
|
def _args(self, **overrides):
|
|
119
119
|
values = dict(changelist=None, user=None, dry_run=False,
|
|
120
|
-
workspace_dir='/ws')
|
|
120
|
+
ignore_blocking_processes=False, workspace_dir='/ws')
|
|
121
121
|
values.update(overrides)
|
|
122
122
|
return mock.Mock(**values)
|
|
123
123
|
|
|
@@ -244,7 +244,7 @@ class TestSyncSplitPreSyncHooks(unittest.TestCase):
|
|
|
244
244
|
|
|
245
245
|
def _args(self, **overrides):
|
|
246
246
|
values = dict(changelist=None, user=None, dry_run=False,
|
|
247
|
-
workspace_dir='/ws', invocation_dir='/invoked')
|
|
247
|
+
ignore_blocking_processes=False, workspace_dir='/ws', invocation_dir='/invoked')
|
|
248
248
|
values.update(overrides)
|
|
249
249
|
return argparse.Namespace(**values)
|
|
250
250
|
|
|
@@ -281,6 +281,19 @@ class TestSyncSplitPreSyncHooks(unittest.TestCase):
|
|
|
281
281
|
sync_split_command(args)
|
|
282
282
|
self.assertTrue(args.preflight_done)
|
|
283
283
|
|
|
284
|
+
@mock.patch('git_p4son.sync_split.sync_command', return_value=0)
|
|
285
|
+
@mock.patch('git_p4son.sync_split.get_submitted_changes', return_value=[])
|
|
286
|
+
@mock.patch('git_p4son.sync_split.get_p4_user', return_value='me')
|
|
287
|
+
@mock.patch('git_p4son.sync_split.get_latest_changelist', return_value=103)
|
|
288
|
+
@mock.patch('git_p4son.sync_split.git_last_sync',
|
|
289
|
+
return_value=LastSync(changelist=100, commit='abc123'))
|
|
290
|
+
@mock.patch('git_p4son.sync_split.sync_preflight', return_value=True)
|
|
291
|
+
def test_ignore_blocking_processes_reaches_the_gate(
|
|
292
|
+
self, mock_gate, _last_sync, _latest, _user, _changes, _sync):
|
|
293
|
+
sync_split_command(self._args(ignore_blocking_processes=True))
|
|
294
|
+
self.assertTrue(
|
|
295
|
+
mock_gate.call_args.kwargs['ignore_blocking_processes'])
|
|
296
|
+
|
|
284
297
|
@mock.patch('git_p4son.sync_split.sync_command', return_value=0)
|
|
285
298
|
@mock.patch('git_p4son.sync_split.get_submitted_changes', return_value=[])
|
|
286
299
|
@mock.patch('git_p4son.sync_split.get_p4_user', return_value='me')
|
|
@@ -348,15 +361,42 @@ class TestSyncSplitPreSyncHooks(unittest.TestCase):
|
|
|
348
361
|
|
|
349
362
|
|
|
350
363
|
class TestSyncPreflight(unittest.TestCase):
|
|
351
|
-
"""The one gate both commands share:
|
|
352
|
-
that order, and skipped wholesale when a caller
|
|
364
|
+
"""The one gate both commands share: blocking processes, workspace
|
|
365
|
+
checks then hooks, in that order, and skipped wholesale when a caller
|
|
366
|
+
already ran it."""
|
|
367
|
+
|
|
368
|
+
@mock.patch('git_p4son.sync.run_hooks')
|
|
369
|
+
@mock.patch('git_p4son.sync.p4_get_opened_files')
|
|
370
|
+
@mock.patch('git_p4son.lib.get_dirty_files')
|
|
371
|
+
@mock.patch('git_p4son.sync.check_no_blocking_processes',
|
|
372
|
+
return_value=False)
|
|
373
|
+
def test_blocking_process_stops_before_everything_else(
|
|
374
|
+
self, mock_blocking, mock_dirty, mock_opened, mock_run_hooks):
|
|
375
|
+
self.assertFalse(sync_preflight('//ws', '/ws', '/invoked'))
|
|
376
|
+
mock_blocking.assert_called_once_with('/ws', False)
|
|
377
|
+
mock_dirty.assert_not_called()
|
|
378
|
+
mock_opened.assert_not_called()
|
|
379
|
+
mock_run_hooks.assert_not_called()
|
|
380
|
+
|
|
381
|
+
@mock.patch('git_p4son.sync.run_hooks', return_value=[])
|
|
382
|
+
@mock.patch('git_p4son.sync.p4_get_opened_files', return_value=[])
|
|
383
|
+
@mock.patch('git_p4son.lib.get_dirty_files', return_value=[])
|
|
384
|
+
@mock.patch('git_p4son.sync.check_no_blocking_processes',
|
|
385
|
+
return_value=True)
|
|
386
|
+
def test_ignore_blocking_processes_is_passed_on(
|
|
387
|
+
self, mock_blocking, _dirty, _opened, _hooks):
|
|
388
|
+
self.assertTrue(sync_preflight('//ws', '/ws', '/invoked',
|
|
389
|
+
ignore_blocking_processes=True))
|
|
390
|
+
mock_blocking.assert_called_once_with('/ws', True)
|
|
353
391
|
|
|
354
392
|
@mock.patch('git_p4son.sync.run_hooks')
|
|
355
393
|
@mock.patch('git_p4son.sync.p4_get_opened_files')
|
|
356
394
|
@mock.patch('git_p4son.lib.get_dirty_files')
|
|
395
|
+
@mock.patch('git_p4son.sync.check_no_blocking_processes')
|
|
357
396
|
def test_already_done_skips_everything(
|
|
358
|
-
self, mock_dirty, mock_opened, mock_run_hooks):
|
|
397
|
+
self, mock_blocking, mock_dirty, mock_opened, mock_run_hooks):
|
|
359
398
|
self.assertTrue(sync_preflight('//ws', '/ws', '/invoked', True))
|
|
399
|
+
mock_blocking.assert_not_called()
|
|
360
400
|
mock_dirty.assert_not_called()
|
|
361
401
|
mock_opened.assert_not_called()
|
|
362
402
|
mock_run_hooks.assert_not_called()
|
|
@@ -414,7 +454,7 @@ class TestSyncSplitCommandMultiUser(unittest.TestCase):
|
|
|
414
454
|
|
|
415
455
|
def _args(self, **overrides):
|
|
416
456
|
values = dict(changelist=None, user=None, dry_run=False,
|
|
417
|
-
workspace_dir='/ws')
|
|
457
|
+
ignore_blocking_processes=False, workspace_dir='/ws')
|
|
418
458
|
values.update(overrides)
|
|
419
459
|
return mock.Mock(**values)
|
|
420
460
|
|
|
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
|