p4cmd 2.4.12__tar.gz → 2.5.0__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.
- {p4cmd-2.4.12 → p4cmd-2.5.0}/PKG-INFO +2 -2
- {p4cmd-2.4.12 → p4cmd-2.5.0}/README.md +1 -1
- {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/__init__.py +1 -1
- {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/p4cmd.py +26 -49
- p4cmd-2.5.0/p4cmd/utils.py +77 -0
- {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd.egg-info/PKG-INFO +2 -2
- {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd.egg-info/SOURCES.txt +1 -0
- {p4cmd-2.4.12 → p4cmd-2.5.0}/LICENSE +0 -0
- {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/p4errors.py +0 -0
- {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/p4file.py +0 -0
- {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/resources/__init__.py +0 -0
- {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/resources/perforce_DEPOT_ONLY.png +0 -0
- {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/resources/perforce_MOVED.png +0 -0
- {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/resources/perforce_NEED_SYNC.png +0 -0
- {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/resources/perforce_OPEN_FOR_ADD.png +0 -0
- {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/resources/perforce_OPEN_FOR_DELETE.png +0 -0
- {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/resources/perforce_OPEN_FOR_EDIT.png +0 -0
- {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/resources/perforce_UNKNOWN.png +0 -0
- {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/resources/perforce_UNTRACKED.png +0 -0
- {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd/resources/perforce_UP_TO_DATE.png +0 -0
- {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd.egg-info/dependency_links.txt +0 -0
- {p4cmd-2.4.12 → p4cmd-2.5.0}/p4cmd.egg-info/top_level.txt +0 -0
- {p4cmd-2.4.12 → p4cmd-2.5.0}/setup.cfg +0 -0
- {p4cmd-2.4.12 → p4cmd-2.5.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: p4cmd
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: Simple P4 python module
|
|
5
5
|
Home-page: https://github.com/nielsvaes/p4cmd
|
|
6
6
|
Author: Niels Vaes
|
|
@@ -50,7 +50,7 @@ os.environ["P4ROOT"] = "~/nisse/projects/raw"
|
|
|
50
50
|
client = p4cmd.P4Client.from_env()
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
Most of the functions are pretty self
|
|
53
|
+
Most of the functions are pretty self-explanatory and have docstrings about how they work.
|
|
54
54
|
|
|
55
55
|
There are 2 functions called `file_to_p4files` and `folder_to_p4files` that use the `P4File` class in `p4file`.
|
|
56
56
|
|
|
@@ -29,7 +29,7 @@ os.environ["P4ROOT"] = "~/nisse/projects/raw"
|
|
|
29
29
|
client = p4cmd.P4Client.from_env()
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
Most of the functions are pretty self
|
|
32
|
+
Most of the functions are pretty self-explanatory and have docstrings about how they work.
|
|
33
33
|
|
|
34
34
|
There are 2 functions called `file_to_p4files` and `folder_to_p4files` that use the `P4File` class in `p4file`.
|
|
35
35
|
|
|
@@ -9,6 +9,7 @@ import logging
|
|
|
9
9
|
|
|
10
10
|
from . import p4errors
|
|
11
11
|
from .p4file import P4File, Status
|
|
12
|
+
from .utils import split_list_into_strings_of_length, convert_to_list, decode_dictionaries, validate_not_empty
|
|
12
13
|
|
|
13
14
|
MAX_CMD_LEN = 8190
|
|
14
15
|
MAX_ARG_LEN = 8000 # max length of args string when combined, close to max, but leaving some extra margin
|
|
@@ -206,6 +207,7 @@ class P4Client(object):
|
|
|
206
207
|
else:
|
|
207
208
|
raise p4errors.WorkSpaceError("Tried to set a workspace/client({}) that did not exist".format(workspace))
|
|
208
209
|
|
|
210
|
+
@validate_not_empty
|
|
209
211
|
def files_to_p4files(self, file_list, allow_invalid_files=False):
|
|
210
212
|
"""
|
|
211
213
|
Turns a list of files into P4File objects. If the Perforce server can't be reached, returns a list of P4Files
|
|
@@ -232,6 +234,7 @@ class P4Client(object):
|
|
|
232
234
|
p4files.append(perforce_file)
|
|
233
235
|
return p4files
|
|
234
236
|
|
|
237
|
+
@validate_not_empty
|
|
235
238
|
def folder_to_p4files(self, folder, include_subfolders=True, allow_invalid_files=False):
|
|
236
239
|
"""
|
|
237
240
|
Returns all the files in a folder as a list of P4File objects. Uses the files_to_p4files function if the host
|
|
@@ -301,7 +304,8 @@ class P4Client(object):
|
|
|
301
304
|
if len(changelists):
|
|
302
305
|
return True
|
|
303
306
|
return False
|
|
304
|
-
|
|
307
|
+
|
|
308
|
+
@validate_not_empty
|
|
305
309
|
def move_files_to_changelist(self, file_list, changelist="default"):
|
|
306
310
|
"""
|
|
307
311
|
Moves the files in file_list to a changelist. Makes a new changelist if the given one doesn't exist.
|
|
@@ -371,6 +375,7 @@ class P4Client(object):
|
|
|
371
375
|
return True
|
|
372
376
|
return False
|
|
373
377
|
|
|
378
|
+
@validate_not_empty
|
|
374
379
|
def revert_files(self, file_list, unchanged_only=False):
|
|
375
380
|
"""
|
|
376
381
|
Reverts files in file_list
|
|
@@ -389,6 +394,8 @@ class P4Client(object):
|
|
|
389
394
|
|
|
390
395
|
return info_dicts
|
|
391
396
|
|
|
397
|
+
|
|
398
|
+
@validate_not_empty
|
|
392
399
|
def revert_folders(self, folder_list, unchanged_only=False):
|
|
393
400
|
"""
|
|
394
401
|
Recursively reverts complete folders
|
|
@@ -442,6 +449,7 @@ class P4Client(object):
|
|
|
442
449
|
info_dicts = self.run_cmd("submit", args=["-c", changelist, "--parallel", f"threads={self.max_parallel_connections}"])
|
|
443
450
|
return info_dicts
|
|
444
451
|
|
|
452
|
+
@validate_not_empty
|
|
445
453
|
def sync_folders(self, folder_list):
|
|
446
454
|
"""
|
|
447
455
|
Recursively syncs complete folders
|
|
@@ -463,6 +471,7 @@ class P4Client(object):
|
|
|
463
471
|
info_dicts = self.run_cmd("sync", args=["--parallel", f"threads={self.max_parallel_connections}"], file_list=cleaned_folder_list)
|
|
464
472
|
return info_dicts
|
|
465
473
|
|
|
474
|
+
@validate_not_empty
|
|
466
475
|
def sync_files(self, file_list, revision=-1, verify=True, force=False):
|
|
467
476
|
"""
|
|
468
477
|
Syncs files
|
|
@@ -493,6 +502,7 @@ class P4Client(object):
|
|
|
493
502
|
|
|
494
503
|
return info_dicts
|
|
495
504
|
|
|
505
|
+
@validate_not_empty
|
|
496
506
|
def reconcile_offline_files(self, file_list, changelist="default"):
|
|
497
507
|
"""
|
|
498
508
|
Adds, opens for edit or delete any files that were changed outside Perforce
|
|
@@ -508,6 +518,7 @@ class P4Client(object):
|
|
|
508
518
|
info_dicts = self.run_cmd("reconcile", args=["-c", changelist], file_list=file_list)
|
|
509
519
|
return info_dicts
|
|
510
520
|
|
|
521
|
+
@validate_not_empty
|
|
511
522
|
def reconcile_offline_folders(self, folder_list, changelist="default"):
|
|
512
523
|
"""
|
|
513
524
|
Adds, opens for edit or delete any files in the rootfolder + subfolders that were changed outside Perforce
|
|
@@ -532,6 +543,7 @@ class P4Client(object):
|
|
|
532
543
|
info_dicts = self.run_cmd("reconcile", args=["-c", changelist], file_list=cleaned_folder_list)
|
|
533
544
|
return info_dicts
|
|
534
545
|
|
|
546
|
+
@validate_not_empty
|
|
535
547
|
def delete_files(self, file_list, changelist="default"):
|
|
536
548
|
"""
|
|
537
549
|
Marks files for delete
|
|
@@ -582,6 +594,7 @@ class P4Client(object):
|
|
|
582
594
|
|
|
583
595
|
return files_and_cl
|
|
584
596
|
|
|
597
|
+
@validate_not_empty
|
|
585
598
|
def add_or_edit_folders(self, folders, include_subfolders=True, changelist="default"):
|
|
586
599
|
"""
|
|
587
600
|
Marks a folder for add or edit
|
|
@@ -605,6 +618,8 @@ class P4Client(object):
|
|
|
605
618
|
|
|
606
619
|
return self.add_or_edit_files(all_files, changelist=changelist)
|
|
607
620
|
|
|
621
|
+
|
|
622
|
+
@validate_not_empty
|
|
608
623
|
def add_or_edit_files(self, file_list, changelist="default"):
|
|
609
624
|
"""
|
|
610
625
|
Marks the files in file_list for add if they are new, or edit if they are already versioned
|
|
@@ -653,6 +668,7 @@ class P4Client(object):
|
|
|
653
668
|
return int(self.__get_dict_value(info_dict, "change"))
|
|
654
669
|
return -1
|
|
655
670
|
|
|
671
|
+
@validate_not_empty
|
|
656
672
|
def edit_files(self, file_list, changelist="default"):
|
|
657
673
|
"""
|
|
658
674
|
Marks the files in file_list for edit
|
|
@@ -673,6 +689,7 @@ class P4Client(object):
|
|
|
673
689
|
logging.error(self.__get_dict_value(info_dict, "data"))
|
|
674
690
|
return info_dicts
|
|
675
691
|
|
|
692
|
+
@validate_not_empty
|
|
676
693
|
def add_files(self, file_list, changelist="default"):
|
|
677
694
|
"""
|
|
678
695
|
Marks the files in file_list for add
|
|
@@ -738,7 +755,10 @@ class P4Client(object):
|
|
|
738
755
|
|
|
739
756
|
for info_dict in info_dicts:
|
|
740
757
|
description_filter = description_filter.rstrip("\n")
|
|
741
|
-
|
|
758
|
+
try:
|
|
759
|
+
cl_description = self.__get_dict_value(info_dict, "desc").rstrip("\n")
|
|
760
|
+
except AttributeError as err:
|
|
761
|
+
raise p4errors.P4cmdError(err)
|
|
742
762
|
|
|
743
763
|
if not cl_description:
|
|
744
764
|
logging.warning(f"The CL description is empty in this return object!\n{pformat(info_dict)}")
|
|
@@ -805,6 +825,7 @@ class P4Client(object):
|
|
|
805
825
|
|
|
806
826
|
return workspaces
|
|
807
827
|
|
|
828
|
+
@validate_not_empty
|
|
808
829
|
def get_depot_paths(self, paths):
|
|
809
830
|
"""
|
|
810
831
|
Returns the depot path of the given files or folders, based on the client
|
|
@@ -824,6 +845,8 @@ class P4Client(object):
|
|
|
824
845
|
depot_paths = [self.__get_dict_value(info, "depotFile").rstrip("/...") for info in info_dicts]
|
|
825
846
|
return depot_paths
|
|
826
847
|
|
|
848
|
+
|
|
849
|
+
@validate_not_empty
|
|
827
850
|
def get_local_paths(self, paths):
|
|
828
851
|
"""
|
|
829
852
|
Returns the local path of the given files or folders, based on the client
|
|
@@ -844,6 +867,7 @@ class P4Client(object):
|
|
|
844
867
|
local_paths = [self.__get_dict_value(info, "path") for info in info_dicts]
|
|
845
868
|
return local_paths
|
|
846
869
|
|
|
870
|
+
@validate_not_empty
|
|
847
871
|
def get_history(self, paths):
|
|
848
872
|
"""
|
|
849
873
|
Returns changes for the given paths
|
|
@@ -1018,50 +1042,3 @@ class P4Client(object):
|
|
|
1018
1042
|
if not f.lower().startswith(self.perforce_root.lower()):
|
|
1019
1043
|
raise Exception(f'{f} is not under perforce root: {self.perforce_root}')
|
|
1020
1044
|
|
|
1021
|
-
|
|
1022
|
-
def split_list_into_strings_of_length(input_list, max_length=100):
|
|
1023
|
-
"""
|
|
1024
|
-
From incoming list of strings, build joined together strings that are clamped to a max size
|
|
1025
|
-
|
|
1026
|
-
:param input_list:
|
|
1027
|
-
:param max_length: str clamp length
|
|
1028
|
-
"""
|
|
1029
|
-
full_list_str = " ".join([str(arg) for arg in input_list])
|
|
1030
|
-
|
|
1031
|
-
# if str is already below max length, just return it wrapped in a list
|
|
1032
|
-
if not len(full_list_str) > max_length:
|
|
1033
|
-
return [full_list_str]
|
|
1034
|
-
|
|
1035
|
-
# Keep appending on the last string until we hit the max length
|
|
1036
|
-
# then add a new entry to the list and keep appending
|
|
1037
|
-
clamped_str_list = [""]
|
|
1038
|
-
for arg in input_list:
|
|
1039
|
-
current_str = clamped_str_list[-1]
|
|
1040
|
-
new_str = "{}{} ".format(current_str, arg) # the space is deliberately placed, since the first item has no len
|
|
1041
|
-
|
|
1042
|
-
# if new string is too long, just append the arg as a new item and skip to next arg
|
|
1043
|
-
if len(new_str) >= max_length:
|
|
1044
|
-
clamped_str_list.append("{} ".format(arg)) #
|
|
1045
|
-
continue
|
|
1046
|
-
|
|
1047
|
-
# set latest to updated string
|
|
1048
|
-
clamped_str_list[-1] = new_str
|
|
1049
|
-
|
|
1050
|
-
return clamped_str_list
|
|
1051
|
-
|
|
1052
|
-
def decode_dictionaries(info_dicts):
|
|
1053
|
-
"""
|
|
1054
|
-
Decode list of dictionary keys and values into unicode from bytes
|
|
1055
|
-
"""
|
|
1056
|
-
decoded_dicts = []
|
|
1057
|
-
for info_dict in info_dicts:
|
|
1058
|
-
decoded_dict = {k.decode(): v.decode() for k, v in info_dict.items()}
|
|
1059
|
-
decoded_dicts.append(decoded_dict)
|
|
1060
|
-
return decoded_dicts
|
|
1061
|
-
|
|
1062
|
-
def convert_to_list(value):
|
|
1063
|
-
if isinstance(value, tuple):
|
|
1064
|
-
converted = [v for v in value]
|
|
1065
|
-
else:
|
|
1066
|
-
converted = [value]
|
|
1067
|
-
return converted
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
def split_list_into_strings_of_length(input_list, max_length=100):
|
|
4
|
+
"""
|
|
5
|
+
From incoming list of strings, build joined together strings that are clamped to a max size
|
|
6
|
+
|
|
7
|
+
:param input_list:
|
|
8
|
+
:param max_length: str clamp length
|
|
9
|
+
"""
|
|
10
|
+
full_list_str = " ".join([str(arg) for arg in input_list])
|
|
11
|
+
|
|
12
|
+
# if str is already below max length, just return it wrapped in a list
|
|
13
|
+
if not len(full_list_str) > max_length:
|
|
14
|
+
return [full_list_str]
|
|
15
|
+
|
|
16
|
+
# Keep appending on the last string until we hit the max length
|
|
17
|
+
# then add a new entry to the list and keep appending
|
|
18
|
+
clamped_str_list = [""]
|
|
19
|
+
for arg in input_list:
|
|
20
|
+
current_str = clamped_str_list[-1]
|
|
21
|
+
new_str = "{}{} ".format(current_str, arg) # the space is deliberately placed, since the first item has no len
|
|
22
|
+
|
|
23
|
+
# if new string is too long, just append the arg as a new item and skip to next arg
|
|
24
|
+
if len(new_str) >= max_length:
|
|
25
|
+
clamped_str_list.append("{} ".format(arg)) #
|
|
26
|
+
continue
|
|
27
|
+
|
|
28
|
+
# set latest to updated string
|
|
29
|
+
clamped_str_list[-1] = new_str
|
|
30
|
+
|
|
31
|
+
return clamped_str_list
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def decode_dictionaries(info_dicts):
|
|
35
|
+
"""
|
|
36
|
+
Decode list of dictionary keys and values into unicode from bytes
|
|
37
|
+
"""
|
|
38
|
+
decoded_dicts = []
|
|
39
|
+
for info_dict in info_dicts:
|
|
40
|
+
decoded_dict = {k.decode(): v.decode() for k, v in info_dict.items()}
|
|
41
|
+
decoded_dicts.append(decoded_dict)
|
|
42
|
+
return decoded_dicts
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def convert_to_list(value):
|
|
46
|
+
"""
|
|
47
|
+
Converts a value to a list if it isn't already one.
|
|
48
|
+
:param value: input value
|
|
49
|
+
:return: list
|
|
50
|
+
"""
|
|
51
|
+
if isinstance(value, tuple):
|
|
52
|
+
converted = [v for v in value]
|
|
53
|
+
else:
|
|
54
|
+
converted = [value]
|
|
55
|
+
return converted
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def validate_not_empty(func):
|
|
59
|
+
"""
|
|
60
|
+
Decorator to ensure file and folder lists are not empty before proceeding with Perforce operations
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def wrapper(*args, **kwargs):
|
|
64
|
+
file_or_folder_list = args[1] if len(args) > 1 else None
|
|
65
|
+
|
|
66
|
+
if file_or_folder_list is not None and not isinstance(file_or_folder_list, list):
|
|
67
|
+
file_or_folder_list = convert_to_list(file_or_folder_list)
|
|
68
|
+
|
|
69
|
+
if not file_or_folder_list:
|
|
70
|
+
logging.warning(f"Empty file list provided to {func.__name__}, operation skipped.")
|
|
71
|
+
logging.warning(f"args: {args}")
|
|
72
|
+
logging.warning(f"kwargs: {kwargs}")
|
|
73
|
+
return []
|
|
74
|
+
|
|
75
|
+
return func(*args, **kwargs)
|
|
76
|
+
|
|
77
|
+
return wrapper
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: p4cmd
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: Simple P4 python module
|
|
5
5
|
Home-page: https://github.com/nielsvaes/p4cmd
|
|
6
6
|
Author: Niels Vaes
|
|
@@ -50,7 +50,7 @@ os.environ["P4ROOT"] = "~/nisse/projects/raw"
|
|
|
50
50
|
client = p4cmd.P4Client.from_env()
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
Most of the functions are pretty self
|
|
53
|
+
Most of the functions are pretty self-explanatory and have docstrings about how they work.
|
|
54
54
|
|
|
55
55
|
There are 2 functions called `file_to_p4files` and `folder_to_p4files` that use the `P4File` class in `p4file`.
|
|
56
56
|
|
|
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
|