git-p4son 0.3.0__tar.gz → 0.3.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.3.0 → git_p4son-0.3.2}/PKG-INFO +107 -6
- {git_p4son-0.3.0 → git_p4son-0.3.2}/README.md +106 -5
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/__init__.py +1 -1
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/changelist_store.py +1 -1
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/cli.py +49 -4
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/complete.py +14 -5
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/config.py +11 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/init.py +31 -17
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/perforce.py +27 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/sync.py +170 -70
- git_p4son-0.3.2/git_p4son/sync_split.py +148 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son.egg-info/PKG-INFO +107 -6
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son.egg-info/SOURCES.txt +3 -1
- {git_p4son-0.3.0 → git_p4son-0.3.2}/pyproject.toml +1 -1
- {git_p4son-0.3.0 → git_p4son-0.3.2}/tests/test_changelist_store.py +5 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/tests/test_cli.py +7 -3
- {git_p4son-0.3.0 → git_p4son-0.3.2}/tests/test_complete.py +18 -2
- {git_p4son-0.3.0 → git_p4son-0.3.2}/tests/test_config.py +22 -1
- {git_p4son-0.3.0 → git_p4son-0.3.2}/tests/test_init.py +37 -5
- {git_p4son-0.3.0 → git_p4son-0.3.2}/tests/test_perforce.py +48 -2
- {git_p4son-0.3.0 → git_p4son-0.3.2}/tests/test_sync.py +382 -14
- git_p4son-0.3.2/tests/test_sync_split.py +260 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/LICENSE +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/__main__.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/alias.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/common.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/completions/_git-p4son +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/completions/git-p4son.bash +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/completions/git-p4son.ps1 +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/git.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/hooks.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/lib.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/list_changes.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/log.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/new.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/review.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/state.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son/update.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son.egg-info/dependency_links.txt +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son.egg-info/entry_points.txt +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/git_p4son.egg-info/top_level.txt +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/setup.cfg +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/tests/test_alias.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/tests/test_common.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/tests/test_git.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/tests/test_hooks.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/tests/test_lib_changelist.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/tests/test_lib_edit.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/tests/test_lib_review.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/tests/test_list_changes.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/tests/test_log.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/tests/test_new.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/tests/test_review.py +0 -0
- {git_p4son-0.3.0 → git_p4son-0.3.2}/tests/test_state.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-p4son
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.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
|
|
@@ -233,8 +233,8 @@ commit any files that need attention.
|
|
|
233
233
|
|
|
234
234
|
## Usage
|
|
235
235
|
|
|
236
|
-
git-p4son provides
|
|
237
|
-
and `completion`.
|
|
236
|
+
git-p4son provides nine commands: `init`, `sync`, `sync-split`, `new`, `update`, `review`, `list-changes`,
|
|
237
|
+
`alias`, and `completion`.
|
|
238
238
|
|
|
239
239
|
To see help for any command, use `-h`:
|
|
240
240
|
|
|
@@ -266,6 +266,11 @@ The depot root determines which part of the Perforce workspace git-p4son syncs.
|
|
|
266
266
|
workspace or just the directory's subtree where the git root is placed. The selection is saved in
|
|
267
267
|
`.git-p4son/config.toml` and used by all subsequent commands.
|
|
268
268
|
|
|
269
|
+
The saved root uses a `$(workspace)` placeholder for the workspace (client) name, e.g.
|
|
270
|
+
`root = "//$(workspace)/Engine"`. git-p4son substitutes the live workspace name each time it runs, so renaming
|
|
271
|
+
the Perforce workspace does not break the config. You can still store a concrete name (e.g. `//my-workspace/Engine`)
|
|
272
|
+
by hand-editing `config.toml`; both forms work.
|
|
273
|
+
|
|
269
274
|
The `.gitignore` is set up using this priority:
|
|
270
275
|
- If `.gitignore` already exists, it is left as is
|
|
271
276
|
- If `.p4ignore` exists, it is copied to `.gitignore` as a starting point
|
|
@@ -276,12 +281,14 @@ The `.gitignore` is set up using this priority:
|
|
|
276
281
|
Sync local git repository with a Perforce workspace:
|
|
277
282
|
|
|
278
283
|
```sh
|
|
279
|
-
git p4son sync [changelist] [--force]
|
|
284
|
+
git p4son sync [changelist ...] [--force]
|
|
280
285
|
```
|
|
281
286
|
|
|
282
287
|
**Arguments:**
|
|
283
|
-
- `changelist` (optional): Changelist number,
|
|
284
|
-
sync to the latest changelist affecting the workspace.
|
|
288
|
+
- `changelist` (optional): Changelist number, `last-synced` to re-sync the last synced changelist, or `head`
|
|
289
|
+
to sync to the latest changelist. Omit to sync to the latest changelist affecting the workspace. Several
|
|
290
|
+
strictly increasing changelist numbers may be given to sync each in sequence, producing one commit per
|
|
291
|
+
changelist. A trailing `head` may close out such a sequence to finish at the latest changelist.
|
|
285
292
|
|
|
286
293
|
**Options:**
|
|
287
294
|
- `-f, --force`: Allow syncing to changelists older than the current one.
|
|
@@ -289,16 +296,73 @@ git p4son sync [changelist] [--force]
|
|
|
289
296
|
**Examples:**
|
|
290
297
|
```sh
|
|
291
298
|
git p4son sync # sync to latest
|
|
299
|
+
git p4son sync head # sync to latest (explicit)
|
|
292
300
|
git p4son sync 12345
|
|
301
|
+
git p4son sync 123 156 178 # sync each changelist in sequence, one commit each
|
|
302
|
+
git p4son sync 123 156 head # sync 123, 156, then the latest changelist
|
|
293
303
|
git p4son sync last-synced
|
|
294
304
|
git p4son sync 12345 --force
|
|
295
305
|
```
|
|
296
306
|
|
|
307
|
+
#### pre-sync hook
|
|
308
|
+
|
|
309
|
+
Before `git-p4son sync` syncs any changelist, git-p4son runs [hooks](#hooks) from
|
|
310
|
+
`.git-p4son/hooks/pre-sync/`. If any hook exits non-zero the sync is aborted. When a sequence of
|
|
311
|
+
changelists is given, the pre-sync hooks run once, before the first sync.
|
|
312
|
+
|
|
297
313
|
#### post-sync hook
|
|
298
314
|
|
|
299
315
|
After a successful `git-p4son sync` that actually performs sync work, git-p4son runs [hooks](#hooks)
|
|
300
316
|
from `.git-p4son/hooks/post-sync/`.
|
|
301
317
|
|
|
318
|
+
### Sync-Split Command
|
|
319
|
+
|
|
320
|
+
Sync forward from the last synced changelist, splitting selected users' changelists into their own commits:
|
|
321
|
+
|
|
322
|
+
```sh
|
|
323
|
+
git p4son sync-split [changelist] [--user NAME ...] [--dry-run]
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Syncing straight to the latest changelist lumps your own submitted changelists together with everyone
|
|
327
|
+
else's in a single commit. `sync-split` resolves the sequence for you: it looks up the last synced
|
|
328
|
+
changelist in the git log, asks Perforce which changelists the selected users submitted since then, and
|
|
329
|
+
syncs the changelist submitted immediately before each of those first. The result is a git history where
|
|
330
|
+
every changelist those users submitted is a commit containing nothing but their own change.
|
|
331
|
+
|
|
332
|
+
By default the selected user is you (the current p4 user), which covers the common case of reviewing your
|
|
333
|
+
own submits one at a time. Repeat `--user` to split out several people's changelists in the same run.
|
|
334
|
+
|
|
335
|
+
It is equivalent to working out the numbers by hand and running
|
|
336
|
+
`git p4son sync <before-theirs> <theirs> ... head`, and it delegates to `sync` once the sequence is
|
|
337
|
+
resolved, so [pre-sync and post-sync hooks](#hooks), writable-file merging, and the clean-workspace checks
|
|
338
|
+
all behave exactly as they do for `sync`.
|
|
339
|
+
|
|
340
|
+
**Arguments:**
|
|
341
|
+
- `changelist` (optional): Changelist number to sync up to, or `head` for the latest. Omit to sync to the
|
|
342
|
+
latest changelist affecting the workspace.
|
|
343
|
+
|
|
344
|
+
**Options:**
|
|
345
|
+
- `-u, --user NAME`: Perforce user whose changelists to split into their own commits. Repeat the flag to
|
|
346
|
+
select several users. Defaults to the current p4 user (from `p4 info`).
|
|
347
|
+
- `-n, --dry-run`: Print the resolved sync sequence without syncing.
|
|
348
|
+
|
|
349
|
+
**Examples:**
|
|
350
|
+
```sh
|
|
351
|
+
git p4son sync-split # sync to latest, your own changelists split out
|
|
352
|
+
git p4son sync-split head # same, explicit
|
|
353
|
+
git p4son sync-split 12345 # stop at changelist 12345
|
|
354
|
+
git p4son sync-split --dry-run # show the sequence that would be synced
|
|
355
|
+
git p4son sync-split -u alice # split out alice's changelists instead of yours
|
|
356
|
+
git p4son sync-split -u alice -u bob # split out both alice's and bob's changelists
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
For example, with the last sync at CL 100 and your own changelists at 102 and 105, `sync-split` resolves
|
|
360
|
+
to `git p4son sync 101 102 104 105 106`, producing five commits where CL 102 and CL 105 each hold only
|
|
361
|
+
your change.
|
|
362
|
+
|
|
363
|
+
`sync-split` only moves forward: it requires a previous sync to start from, and refuses a target older
|
|
364
|
+
than the last synced changelist (use `git p4son sync --force` for that).
|
|
365
|
+
|
|
302
366
|
### New Command
|
|
303
367
|
|
|
304
368
|
Create a new Perforce changelist and add changed files to it. Description will contain an enumerated list of git commits since the base branch.
|
|
@@ -553,11 +617,48 @@ On Windows, `.ps1`, `.nu`, `.sh`, and `.py` files are run with `powershell.exe`,
|
|
|
553
617
|
|
|
554
618
|
Non-executable or unrecognized hook files are skipped with a warning.
|
|
555
619
|
|
|
620
|
+
### pre-sync hook
|
|
621
|
+
|
|
622
|
+
Before `git-p4son sync` syncs any changelist, git-p4son runs executable hooks from
|
|
623
|
+
`.git-p4son/hooks/pre-sync/`. If any hook exits with a non-zero code the sync is aborted before any
|
|
624
|
+
changelist is synced. When a sequence of changelists is synced, the pre-sync hooks run only once,
|
|
625
|
+
before the first sync.
|
|
626
|
+
|
|
556
627
|
### post-sync hook
|
|
557
628
|
|
|
558
629
|
After a successful `git-p4son sync` that actually performs sync work, git-p4son runs executable hooks from
|
|
559
630
|
`.git-p4son/hooks/post-sync/`.
|
|
560
631
|
|
|
632
|
+
### Example hooks
|
|
633
|
+
|
|
634
|
+
The [`examples/hooks/`](examples/hooks/) directory holds ready-made hooks. Nothing installs them for you:
|
|
635
|
+
symlink or copy the one you want into the matching hook directory in your workspace. Symlinking keeps the hook
|
|
636
|
+
up to date with the clone it points at, and on macOS and Linux the executable bit comes from the file in the
|
|
637
|
+
clone, so no `chmod` is needed.
|
|
638
|
+
|
|
639
|
+
#### block-while-running (pre-sync)
|
|
640
|
+
|
|
641
|
+
Aborts the sync while a named process is running, so a sync cannot swap out assets that a running editor still
|
|
642
|
+
has loaded:
|
|
643
|
+
|
|
644
|
+
```sh
|
|
645
|
+
ln -s /path/to/git-p4son/examples/hooks/pre-sync/block-while-running.py .git-p4son/hooks/pre-sync/
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
Which processes block a sync is read from your workspace's own `.git-p4son/config.toml`, so a symlinked hook
|
|
649
|
+
stays per-project:
|
|
650
|
+
|
|
651
|
+
```toml
|
|
652
|
+
[hooks.block-while-running]
|
|
653
|
+
processes = ["UnrealEditor", "UnrealLightmass"]
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
Names are matched with any `.exe` suffix stripped and case ignored, so a single entry covers
|
|
657
|
+
`UnrealEditor.exe` on Windows and `UnrealEditor` on macOS. Without that table the hook checks nothing and the
|
|
658
|
+
sync proceeds; a table that is present but malformed aborts the sync rather than silently skipping the check.
|
|
659
|
+
|
|
660
|
+
Set `GIT_P4SON_SKIP_PROCESS_CHECK=1` to run one sync without the check.
|
|
661
|
+
|
|
561
662
|
## Shell Completions
|
|
562
663
|
|
|
563
664
|
Tab completion is available for bash, zsh, and PowerShell, including commands, flags, and dynamic alias names.
|
|
@@ -210,8 +210,8 @@ commit any files that need attention.
|
|
|
210
210
|
|
|
211
211
|
## Usage
|
|
212
212
|
|
|
213
|
-
git-p4son provides
|
|
214
|
-
and `completion`.
|
|
213
|
+
git-p4son provides nine commands: `init`, `sync`, `sync-split`, `new`, `update`, `review`, `list-changes`,
|
|
214
|
+
`alias`, and `completion`.
|
|
215
215
|
|
|
216
216
|
To see help for any command, use `-h`:
|
|
217
217
|
|
|
@@ -243,6 +243,11 @@ The depot root determines which part of the Perforce workspace git-p4son syncs.
|
|
|
243
243
|
workspace or just the directory's subtree where the git root is placed. The selection is saved in
|
|
244
244
|
`.git-p4son/config.toml` and used by all subsequent commands.
|
|
245
245
|
|
|
246
|
+
The saved root uses a `$(workspace)` placeholder for the workspace (client) name, e.g.
|
|
247
|
+
`root = "//$(workspace)/Engine"`. git-p4son substitutes the live workspace name each time it runs, so renaming
|
|
248
|
+
the Perforce workspace does not break the config. You can still store a concrete name (e.g. `//my-workspace/Engine`)
|
|
249
|
+
by hand-editing `config.toml`; both forms work.
|
|
250
|
+
|
|
246
251
|
The `.gitignore` is set up using this priority:
|
|
247
252
|
- If `.gitignore` already exists, it is left as is
|
|
248
253
|
- If `.p4ignore` exists, it is copied to `.gitignore` as a starting point
|
|
@@ -253,12 +258,14 @@ The `.gitignore` is set up using this priority:
|
|
|
253
258
|
Sync local git repository with a Perforce workspace:
|
|
254
259
|
|
|
255
260
|
```sh
|
|
256
|
-
git p4son sync [changelist] [--force]
|
|
261
|
+
git p4son sync [changelist ...] [--force]
|
|
257
262
|
```
|
|
258
263
|
|
|
259
264
|
**Arguments:**
|
|
260
|
-
- `changelist` (optional): Changelist number,
|
|
261
|
-
sync to the latest changelist affecting the workspace.
|
|
265
|
+
- `changelist` (optional): Changelist number, `last-synced` to re-sync the last synced changelist, or `head`
|
|
266
|
+
to sync to the latest changelist. Omit to sync to the latest changelist affecting the workspace. Several
|
|
267
|
+
strictly increasing changelist numbers may be given to sync each in sequence, producing one commit per
|
|
268
|
+
changelist. A trailing `head` may close out such a sequence to finish at the latest changelist.
|
|
262
269
|
|
|
263
270
|
**Options:**
|
|
264
271
|
- `-f, --force`: Allow syncing to changelists older than the current one.
|
|
@@ -266,16 +273,73 @@ git p4son sync [changelist] [--force]
|
|
|
266
273
|
**Examples:**
|
|
267
274
|
```sh
|
|
268
275
|
git p4son sync # sync to latest
|
|
276
|
+
git p4son sync head # sync to latest (explicit)
|
|
269
277
|
git p4son sync 12345
|
|
278
|
+
git p4son sync 123 156 178 # sync each changelist in sequence, one commit each
|
|
279
|
+
git p4son sync 123 156 head # sync 123, 156, then the latest changelist
|
|
270
280
|
git p4son sync last-synced
|
|
271
281
|
git p4son sync 12345 --force
|
|
272
282
|
```
|
|
273
283
|
|
|
284
|
+
#### pre-sync hook
|
|
285
|
+
|
|
286
|
+
Before `git-p4son sync` syncs any changelist, git-p4son runs [hooks](#hooks) from
|
|
287
|
+
`.git-p4son/hooks/pre-sync/`. If any hook exits non-zero the sync is aborted. When a sequence of
|
|
288
|
+
changelists is given, the pre-sync hooks run once, before the first sync.
|
|
289
|
+
|
|
274
290
|
#### post-sync hook
|
|
275
291
|
|
|
276
292
|
After a successful `git-p4son sync` that actually performs sync work, git-p4son runs [hooks](#hooks)
|
|
277
293
|
from `.git-p4son/hooks/post-sync/`.
|
|
278
294
|
|
|
295
|
+
### Sync-Split Command
|
|
296
|
+
|
|
297
|
+
Sync forward from the last synced changelist, splitting selected users' changelists into their own commits:
|
|
298
|
+
|
|
299
|
+
```sh
|
|
300
|
+
git p4son sync-split [changelist] [--user NAME ...] [--dry-run]
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Syncing straight to the latest changelist lumps your own submitted changelists together with everyone
|
|
304
|
+
else's in a single commit. `sync-split` resolves the sequence for you: it looks up the last synced
|
|
305
|
+
changelist in the git log, asks Perforce which changelists the selected users submitted since then, and
|
|
306
|
+
syncs the changelist submitted immediately before each of those first. The result is a git history where
|
|
307
|
+
every changelist those users submitted is a commit containing nothing but their own change.
|
|
308
|
+
|
|
309
|
+
By default the selected user is you (the current p4 user), which covers the common case of reviewing your
|
|
310
|
+
own submits one at a time. Repeat `--user` to split out several people's changelists in the same run.
|
|
311
|
+
|
|
312
|
+
It is equivalent to working out the numbers by hand and running
|
|
313
|
+
`git p4son sync <before-theirs> <theirs> ... head`, and it delegates to `sync` once the sequence is
|
|
314
|
+
resolved, so [pre-sync and post-sync hooks](#hooks), writable-file merging, and the clean-workspace checks
|
|
315
|
+
all behave exactly as they do for `sync`.
|
|
316
|
+
|
|
317
|
+
**Arguments:**
|
|
318
|
+
- `changelist` (optional): Changelist number to sync up to, or `head` for the latest. Omit to sync to the
|
|
319
|
+
latest changelist affecting the workspace.
|
|
320
|
+
|
|
321
|
+
**Options:**
|
|
322
|
+
- `-u, --user NAME`: Perforce user whose changelists to split into their own commits. Repeat the flag to
|
|
323
|
+
select several users. Defaults to the current p4 user (from `p4 info`).
|
|
324
|
+
- `-n, --dry-run`: Print the resolved sync sequence without syncing.
|
|
325
|
+
|
|
326
|
+
**Examples:**
|
|
327
|
+
```sh
|
|
328
|
+
git p4son sync-split # sync to latest, your own changelists split out
|
|
329
|
+
git p4son sync-split head # same, explicit
|
|
330
|
+
git p4son sync-split 12345 # stop at changelist 12345
|
|
331
|
+
git p4son sync-split --dry-run # show the sequence that would be synced
|
|
332
|
+
git p4son sync-split -u alice # split out alice's changelists instead of yours
|
|
333
|
+
git p4son sync-split -u alice -u bob # split out both alice's and bob's changelists
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
For example, with the last sync at CL 100 and your own changelists at 102 and 105, `sync-split` resolves
|
|
337
|
+
to `git p4son sync 101 102 104 105 106`, producing five commits where CL 102 and CL 105 each hold only
|
|
338
|
+
your change.
|
|
339
|
+
|
|
340
|
+
`sync-split` only moves forward: it requires a previous sync to start from, and refuses a target older
|
|
341
|
+
than the last synced changelist (use `git p4son sync --force` for that).
|
|
342
|
+
|
|
279
343
|
### New Command
|
|
280
344
|
|
|
281
345
|
Create a new Perforce changelist and add changed files to it. Description will contain an enumerated list of git commits since the base branch.
|
|
@@ -530,11 +594,48 @@ On Windows, `.ps1`, `.nu`, `.sh`, and `.py` files are run with `powershell.exe`,
|
|
|
530
594
|
|
|
531
595
|
Non-executable or unrecognized hook files are skipped with a warning.
|
|
532
596
|
|
|
597
|
+
### pre-sync hook
|
|
598
|
+
|
|
599
|
+
Before `git-p4son sync` syncs any changelist, git-p4son runs executable hooks from
|
|
600
|
+
`.git-p4son/hooks/pre-sync/`. If any hook exits with a non-zero code the sync is aborted before any
|
|
601
|
+
changelist is synced. When a sequence of changelists is synced, the pre-sync hooks run only once,
|
|
602
|
+
before the first sync.
|
|
603
|
+
|
|
533
604
|
### post-sync hook
|
|
534
605
|
|
|
535
606
|
After a successful `git-p4son sync` that actually performs sync work, git-p4son runs executable hooks from
|
|
536
607
|
`.git-p4son/hooks/post-sync/`.
|
|
537
608
|
|
|
609
|
+
### Example hooks
|
|
610
|
+
|
|
611
|
+
The [`examples/hooks/`](examples/hooks/) directory holds ready-made hooks. Nothing installs them for you:
|
|
612
|
+
symlink or copy the one you want into the matching hook directory in your workspace. Symlinking keeps the hook
|
|
613
|
+
up to date with the clone it points at, and on macOS and Linux the executable bit comes from the file in the
|
|
614
|
+
clone, so no `chmod` is needed.
|
|
615
|
+
|
|
616
|
+
#### block-while-running (pre-sync)
|
|
617
|
+
|
|
618
|
+
Aborts the sync while a named process is running, so a sync cannot swap out assets that a running editor still
|
|
619
|
+
has loaded:
|
|
620
|
+
|
|
621
|
+
```sh
|
|
622
|
+
ln -s /path/to/git-p4son/examples/hooks/pre-sync/block-while-running.py .git-p4son/hooks/pre-sync/
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
Which processes block a sync is read from your workspace's own `.git-p4son/config.toml`, so a symlinked hook
|
|
626
|
+
stays per-project:
|
|
627
|
+
|
|
628
|
+
```toml
|
|
629
|
+
[hooks.block-while-running]
|
|
630
|
+
processes = ["UnrealEditor", "UnrealLightmass"]
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
Names are matched with any `.exe` suffix stripped and case ignored, so a single entry covers
|
|
634
|
+
`UnrealEditor.exe` on Windows and `UnrealEditor` on macOS. Without that table the hook checks nothing and the
|
|
635
|
+
sync proceeds; a table that is present but malformed aborts the sync rather than silently skipping the check.
|
|
636
|
+
|
|
637
|
+
Set `GIT_P4SON_SKIP_PROCESS_CHECK=1` to run one sync without the check.
|
|
638
|
+
|
|
538
639
|
## Shell Completions
|
|
539
640
|
|
|
540
641
|
Tab completion is available for bash, zsh, and PowerShell, including commands, flags, and dynamic alias names.
|
|
@@ -11,7 +11,7 @@ from . import CONFIG_DIR
|
|
|
11
11
|
from .log import log
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
RESERVED_KEYWORDS = frozenset({'last-synced', 'branch'})
|
|
14
|
+
RESERVED_KEYWORDS = frozenset({'last-synced', 'head', 'branch'})
|
|
15
15
|
|
|
16
16
|
# Allowed characters: ASCII letters, digits, hyphen, underscore, dot.
|
|
17
17
|
# Must not start or end with a dot, so "." and ".." are rejected and the
|
|
@@ -9,6 +9,7 @@ import time
|
|
|
9
9
|
from importlib.resources import files
|
|
10
10
|
from . import __version__
|
|
11
11
|
from .sync import sync_command
|
|
12
|
+
from .sync_split import sync_split_command
|
|
12
13
|
from .new import new_command
|
|
13
14
|
from .update import update_command
|
|
14
15
|
from .list_changes import list_changes_command
|
|
@@ -31,8 +32,14 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
31
32
|
epilog="""
|
|
32
33
|
Examples:
|
|
33
34
|
git-p4son sync # Sync to the latest changelist
|
|
35
|
+
git-p4son sync head # Sync to the latest changelist (explicit)
|
|
34
36
|
git-p4son sync 12345 # Sync to changelist 12345
|
|
37
|
+
git-p4son sync 123 156 178 # Sync each changelist in sequence, one commit each
|
|
38
|
+
git-p4son sync 123 156 head # Sync 123, 156, then the latest changelist
|
|
35
39
|
git-p4son sync last-synced # Re-sync the last synced changelist
|
|
40
|
+
git-p4son sync-split # Sync to latest, your own changelists split out
|
|
41
|
+
git-p4son sync-split 12345 # Same, but stop at changelist 12345
|
|
42
|
+
git-p4son sync-split -u alice -u bob # Split out alice's and bob's changelists
|
|
36
43
|
git-p4son new -m "Fix bug" # Create changelist, alias defaults to branch name
|
|
37
44
|
git-p4son new -m "Fix bug" --review # Create changelist, create Swarm review
|
|
38
45
|
git-p4son new -m "Fix bug" --no-alias # Create changelist without saving an alias
|
|
@@ -78,10 +85,14 @@ Examples:
|
|
|
78
85
|
)
|
|
79
86
|
sync_parser.add_argument(
|
|
80
87
|
'changelist',
|
|
81
|
-
nargs='
|
|
82
|
-
default=
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
nargs='*',
|
|
89
|
+
default=[],
|
|
90
|
+
metavar='CHANGELIST',
|
|
91
|
+
help='Changelist number(s) to sync, "last-synced" to re-sync the last synced changelist, '
|
|
92
|
+
'or "head" to sync to the latest changelist. Give several strictly increasing '
|
|
93
|
+
'changelist numbers to sync each in sequence, one commit per changelist; "head" may '
|
|
94
|
+
'close out such a sequence as a trailing target. Omit to sync to the latest '
|
|
95
|
+
'changelist affecting the workspace'
|
|
85
96
|
)
|
|
86
97
|
sync_parser.add_argument(
|
|
87
98
|
'-f', '--force',
|
|
@@ -89,6 +100,38 @@ Examples:
|
|
|
89
100
|
help='Allow syncing to changelists older than the current one.'
|
|
90
101
|
)
|
|
91
102
|
|
|
103
|
+
# Sync-split subcommand
|
|
104
|
+
sync_split_parser = subparsers.add_parser(
|
|
105
|
+
'sync-split',
|
|
106
|
+
help='Sync forward, splitting a user\'s changelists into own commits',
|
|
107
|
+
description='Sync from the last synced changelist up to a target '
|
|
108
|
+
'changelist (the latest by default), syncing the changelist submitted '
|
|
109
|
+
'just before each of the selected users\' submits first so that every '
|
|
110
|
+
'changelist they submitted lands in a git commit of its own.'
|
|
111
|
+
)
|
|
112
|
+
sync_split_parser.add_argument(
|
|
113
|
+
'changelist',
|
|
114
|
+
nargs='?',
|
|
115
|
+
default=None,
|
|
116
|
+
metavar='CHANGELIST',
|
|
117
|
+
help='Changelist number to sync up to, or "head" for the latest. '
|
|
118
|
+
'Omit to sync to the latest changelist affecting the workspace'
|
|
119
|
+
)
|
|
120
|
+
sync_split_parser.add_argument(
|
|
121
|
+
'-u', '--user',
|
|
122
|
+
action='append',
|
|
123
|
+
default=None,
|
|
124
|
+
metavar='NAME',
|
|
125
|
+
help='Perforce user whose changelists to split into their own '
|
|
126
|
+
'commits. Repeat to select several users. '
|
|
127
|
+
'Defaults to the current p4 user'
|
|
128
|
+
)
|
|
129
|
+
sync_split_parser.add_argument(
|
|
130
|
+
'-n', '--dry-run',
|
|
131
|
+
action='store_true',
|
|
132
|
+
help='Print the resolved sync sequence without syncing'
|
|
133
|
+
)
|
|
134
|
+
|
|
92
135
|
# New subcommand
|
|
93
136
|
new_parser = subparsers.add_parser(
|
|
94
137
|
'new',
|
|
@@ -422,6 +465,8 @@ def run_command(args: argparse.Namespace) -> int:
|
|
|
422
465
|
|
|
423
466
|
if args.command == 'sync':
|
|
424
467
|
return sync_command(args)
|
|
468
|
+
elif args.command == 'sync-split':
|
|
469
|
+
return sync_split_command(args)
|
|
425
470
|
elif args.command == 'new':
|
|
426
471
|
return new_command(args)
|
|
427
472
|
elif args.command == 'update':
|
|
@@ -106,11 +106,20 @@ def _complete_positional(command, subcommand, positional_count,
|
|
|
106
106
|
"""Complete a positional argument."""
|
|
107
107
|
aliases = _get_alias_names(workspace_dir)
|
|
108
108
|
|
|
109
|
-
if command == 'sync'
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
109
|
+
if command == 'sync':
|
|
110
|
+
if positional_count == 0:
|
|
111
|
+
candidates = [
|
|
112
|
+
('head', 'Sync to the latest changelist'),
|
|
113
|
+
('last-synced', 'Re-sync last synced changelist'),
|
|
114
|
+
]
|
|
115
|
+
return _filter(candidates, prefix)
|
|
116
|
+
# "head" may also close out a sequence of changelist numbers.
|
|
117
|
+
return _filter(
|
|
118
|
+
[('head', 'Sync to the latest changelist')], prefix)
|
|
119
|
+
|
|
120
|
+
if command == 'sync-split' and positional_count == 0:
|
|
121
|
+
return _filter(
|
|
122
|
+
[('head', 'Sync to the latest changelist')], prefix)
|
|
114
123
|
|
|
115
124
|
if command == 'update' and positional_count == 0:
|
|
116
125
|
branch_candidates = _get_branch_candidates(prefix, workspace_dir)
|
|
@@ -12,6 +12,12 @@ from . import CONFIG_DIR
|
|
|
12
12
|
|
|
13
13
|
_BARE_KEY_RE = re.compile(r'^[A-Za-z0-9_-]+$')
|
|
14
14
|
|
|
15
|
+
# Placeholder allowed in a stored depot root, substituted with the live
|
|
16
|
+
# Perforce client (workspace) name each time the root is used. Storing e.g.
|
|
17
|
+
# root = "//$(workspace)/Engine" keeps the config working after the workspace
|
|
18
|
+
# is renamed, at the cost of one client-name lookup per command.
|
|
19
|
+
WORKSPACE_PLACEHOLDER = '$(workspace)'
|
|
20
|
+
|
|
15
21
|
|
|
16
22
|
def config_path(workspace_dir: str) -> str:
|
|
17
23
|
"""Return the path to the config file."""
|
|
@@ -94,3 +100,8 @@ def get_depot_root(workspace_dir: str) -> str | None:
|
|
|
94
100
|
"""Get the depot root from config, or None if not configured."""
|
|
95
101
|
config = load_config(workspace_dir)
|
|
96
102
|
return config.get('depot', {}).get('root')
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def expand_depot_root(depot_root: str, workspace_name: str) -> str:
|
|
106
|
+
"""Substitute the live workspace name for the $(workspace) placeholder."""
|
|
107
|
+
return depot_root.replace(WORKSPACE_PLACEHOLDER, workspace_name)
|
|
@@ -10,7 +10,12 @@ import os
|
|
|
10
10
|
import shutil
|
|
11
11
|
|
|
12
12
|
from .common import CommandError, run, run_with_output
|
|
13
|
-
from .config import
|
|
13
|
+
from .config import (
|
|
14
|
+
WORKSPACE_PLACEHOLDER,
|
|
15
|
+
expand_depot_root,
|
|
16
|
+
get_depot_root,
|
|
17
|
+
save_config,
|
|
18
|
+
)
|
|
14
19
|
from .log import log
|
|
15
20
|
from .perforce import get_client_spec
|
|
16
21
|
from .git import resolve_editor
|
|
@@ -26,31 +31,38 @@ def _validate_depot_root(depot_root: str, cwd: str) -> bool:
|
|
|
26
31
|
return False
|
|
27
32
|
|
|
28
33
|
|
|
29
|
-
def _compute_cwd_depot_root(
|
|
30
|
-
|
|
31
|
-
"""Compute depot root for cwd relative to workspace root.
|
|
34
|
+
def _compute_cwd_depot_root(cwd: str, p4_workspace_root: str) -> str | None:
|
|
35
|
+
"""Compute the depot root template for cwd relative to workspace root.
|
|
32
36
|
|
|
33
|
-
|
|
37
|
+
Uses the $(workspace) placeholder in place of the client name so the stored
|
|
38
|
+
root survives a workspace rename. Returns None if cwd is the workspace root
|
|
39
|
+
(identical to the entire workspace).
|
|
34
40
|
"""
|
|
35
41
|
rel = os.path.relpath(cwd, p4_workspace_root)
|
|
36
42
|
if rel == '.':
|
|
37
43
|
return None
|
|
38
44
|
rel_posix = rel.replace(os.sep, '/')
|
|
39
|
-
return f'//{
|
|
45
|
+
return f'//{WORKSPACE_PLACEHOLDER}/{rel_posix}'
|
|
40
46
|
|
|
41
47
|
|
|
42
48
|
def _select_depot_root(client_name: str, cwd: str,
|
|
43
49
|
p4_workspace_root: str) -> str | None:
|
|
44
|
-
"""Interactive prompt for depot root selection.
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
"""Interactive prompt for depot root selection.
|
|
51
|
+
|
|
52
|
+
Returns a depot root template (with the $(workspace) placeholder) to store,
|
|
53
|
+
or None to abort. Menu entries and validation use the resolved path.
|
|
54
|
+
"""
|
|
55
|
+
entire_root = f'//{WORKSPACE_PLACEHOLDER}'
|
|
56
|
+
cwd_root = _compute_cwd_depot_root(cwd, p4_workspace_root)
|
|
47
57
|
|
|
48
58
|
while True:
|
|
49
59
|
print()
|
|
50
60
|
print('Select the depot path that git-p4son should sync:')
|
|
51
|
-
print(f' 1. Entire workspace:
|
|
61
|
+
print(f' 1. Entire workspace: '
|
|
62
|
+
f'{expand_depot_root(entire_root, client_name)}/...')
|
|
52
63
|
if cwd_root:
|
|
53
|
-
print(f' 2. Current directory:
|
|
64
|
+
print(f' 2. Current directory: '
|
|
65
|
+
f'{expand_depot_root(cwd_root, client_name)}/...')
|
|
54
66
|
abort_num = '3'
|
|
55
67
|
else:
|
|
56
68
|
abort_num = '2'
|
|
@@ -72,10 +84,11 @@ def _select_depot_root(client_name: str, cwd: str,
|
|
|
72
84
|
print(f'Invalid choice: {choice}')
|
|
73
85
|
continue
|
|
74
86
|
|
|
75
|
-
|
|
87
|
+
resolved = expand_depot_root(depot_root, client_name)
|
|
88
|
+
if _validate_depot_root(resolved, cwd):
|
|
76
89
|
return depot_root
|
|
77
90
|
|
|
78
|
-
log.error(f'Depot root {
|
|
91
|
+
log.error(f'Depot root {resolved}/... is not valid')
|
|
79
92
|
|
|
80
93
|
|
|
81
94
|
def _configure_depot_root(client_name: str, cwd: str,
|
|
@@ -84,17 +97,18 @@ def _configure_depot_root(client_name: str, cwd: str,
|
|
|
84
97
|
log.heading('Finding git-p4son depot root')
|
|
85
98
|
depot_root = get_depot_root(cwd)
|
|
86
99
|
if depot_root:
|
|
87
|
-
log.success(f'{depot_root}/...')
|
|
100
|
+
log.success(f'{expand_depot_root(depot_root, client_name)}/...')
|
|
88
101
|
else:
|
|
89
102
|
log.warning('no root configured')
|
|
90
103
|
|
|
91
104
|
if depot_root:
|
|
92
105
|
log.heading('Validating depot root')
|
|
93
|
-
|
|
106
|
+
resolved = expand_depot_root(depot_root, client_name)
|
|
107
|
+
if _validate_depot_root(resolved, cwd):
|
|
94
108
|
log.success('all good')
|
|
95
109
|
return True
|
|
96
110
|
else:
|
|
97
|
-
log.error(f'{
|
|
111
|
+
log.error(f'{resolved}/... is not valid')
|
|
98
112
|
depot_root = None
|
|
99
113
|
|
|
100
114
|
if not depot_root:
|
|
@@ -104,7 +118,7 @@ def _configure_depot_root(client_name: str, cwd: str,
|
|
|
104
118
|
log.error('aborting')
|
|
105
119
|
return False
|
|
106
120
|
|
|
107
|
-
log.success(f'{depot_root}/...')
|
|
121
|
+
log.success(f'{expand_depot_root(depot_root, client_name)}/...')
|
|
108
122
|
save_config(cwd, {'depot': {'root': depot_root}})
|
|
109
123
|
return True
|
|
110
124
|
|
|
@@ -204,6 +204,33 @@ def get_latest_changelist(depot_root: str, workspace_dir: str) -> int:
|
|
|
204
204
|
return int(fields['change'])
|
|
205
205
|
|
|
206
206
|
|
|
207
|
+
@dataclass
|
|
208
|
+
class P4Change:
|
|
209
|
+
"""A submitted changelist affecting the depot root."""
|
|
210
|
+
|
|
211
|
+
change: int
|
|
212
|
+
user: str
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def get_submitted_changes(depot_root: str, start: int, end: int,
|
|
216
|
+
workspace_dir: str) -> list[P4Change]:
|
|
217
|
+
"""List changelists submitted to the depot root in [start, end], oldest first."""
|
|
218
|
+
res = run(['p4', '-ztag', 'changes', '-s', 'submitted',
|
|
219
|
+
f'{depot_root}/...@{start},@{end}'], cwd=workspace_dir)
|
|
220
|
+
records = parse_ztag_multi_output(res.stdout)
|
|
221
|
+
changes = [P4Change(change=int(r['change']), user=r.get('user', ''))
|
|
222
|
+
for r in records if 'change' in r]
|
|
223
|
+
return sorted(changes, key=lambda c: c.change)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def get_p4_user(workspace_dir: str) -> str | None:
|
|
227
|
+
"""Get the Perforce user name of the current connection."""
|
|
228
|
+
res = run(['p4', '-ztag', 'info'], cwd=workspace_dir)
|
|
229
|
+
fields = parse_ztag_output(res.stdout)
|
|
230
|
+
user = fields.get('userName')
|
|
231
|
+
return user or None
|
|
232
|
+
|
|
233
|
+
|
|
207
234
|
# --- file operations ---
|
|
208
235
|
|
|
209
236
|
def get_changelist_for_file(filename: str, workspace_dir: str) -> tuple[str, str] | None:
|