p4cmd 2.4.13__tar.gz → 2.6.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.13 → p4cmd-2.6.0}/PKG-INFO +2 -2
- {p4cmd-2.4.13 → p4cmd-2.6.0}/README.md +1 -1
- {p4cmd-2.4.13 → p4cmd-2.6.0}/p4cmd/__init__.py +1 -1
- {p4cmd-2.4.13 → p4cmd-2.6.0}/p4cmd/p4cmd.py +138 -51
- p4cmd-2.6.0/p4cmd/utils.py +77 -0
- {p4cmd-2.4.13 → p4cmd-2.6.0}/p4cmd.egg-info/PKG-INFO +2 -2
- {p4cmd-2.4.13 → p4cmd-2.6.0}/p4cmd.egg-info/SOURCES.txt +1 -0
- {p4cmd-2.4.13 → p4cmd-2.6.0}/LICENSE +0 -0
- {p4cmd-2.4.13 → p4cmd-2.6.0}/p4cmd/p4errors.py +0 -0
- {p4cmd-2.4.13 → p4cmd-2.6.0}/p4cmd/p4file.py +0 -0
- {p4cmd-2.4.13 → p4cmd-2.6.0}/p4cmd/resources/__init__.py +0 -0
- {p4cmd-2.4.13 → p4cmd-2.6.0}/p4cmd/resources/perforce_DEPOT_ONLY.png +0 -0
- {p4cmd-2.4.13 → p4cmd-2.6.0}/p4cmd/resources/perforce_MOVED.png +0 -0
- {p4cmd-2.4.13 → p4cmd-2.6.0}/p4cmd/resources/perforce_NEED_SYNC.png +0 -0
- {p4cmd-2.4.13 → p4cmd-2.6.0}/p4cmd/resources/perforce_OPEN_FOR_ADD.png +0 -0
- {p4cmd-2.4.13 → p4cmd-2.6.0}/p4cmd/resources/perforce_OPEN_FOR_DELETE.png +0 -0
- {p4cmd-2.4.13 → p4cmd-2.6.0}/p4cmd/resources/perforce_OPEN_FOR_EDIT.png +0 -0
- {p4cmd-2.4.13 → p4cmd-2.6.0}/p4cmd/resources/perforce_UNKNOWN.png +0 -0
- {p4cmd-2.4.13 → p4cmd-2.6.0}/p4cmd/resources/perforce_UNTRACKED.png +0 -0
- {p4cmd-2.4.13 → p4cmd-2.6.0}/p4cmd/resources/perforce_UP_TO_DATE.png +0 -0
- {p4cmd-2.4.13 → p4cmd-2.6.0}/p4cmd.egg-info/dependency_links.txt +0 -0
- {p4cmd-2.4.13 → p4cmd-2.6.0}/p4cmd.egg-info/top_level.txt +0 -0
- {p4cmd-2.4.13 → p4cmd-2.6.0}/setup.cfg +0 -0
- {p4cmd-2.4.13 → p4cmd-2.6.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.6.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
|
|
@@ -50,6 +51,8 @@ class P4Client(object):
|
|
|
50
51
|
self.server = self.get_p4_setting("P4PORT")
|
|
51
52
|
if self.server is None:
|
|
52
53
|
raise p4errors.WorkSpaceError("Could not find P4PORT")
|
|
54
|
+
|
|
55
|
+
self.depot_root = self.get_depot_paths([self.perforce_root])[0]
|
|
53
56
|
|
|
54
57
|
@classmethod
|
|
55
58
|
def from_env(cls, *args, **kwargs):
|
|
@@ -206,6 +209,7 @@ class P4Client(object):
|
|
|
206
209
|
else:
|
|
207
210
|
raise p4errors.WorkSpaceError("Tried to set a workspace/client({}) that did not exist".format(workspace))
|
|
208
211
|
|
|
212
|
+
@validate_not_empty
|
|
209
213
|
def files_to_p4files(self, file_list, allow_invalid_files=False):
|
|
210
214
|
"""
|
|
211
215
|
Turns a list of files into P4File objects. If the Perforce server can't be reached, returns a list of P4Files
|
|
@@ -232,6 +236,7 @@ class P4Client(object):
|
|
|
232
236
|
p4files.append(perforce_file)
|
|
233
237
|
return p4files
|
|
234
238
|
|
|
239
|
+
@validate_not_empty
|
|
235
240
|
def folder_to_p4files(self, folder, include_subfolders=True, allow_invalid_files=False):
|
|
236
241
|
"""
|
|
237
242
|
Returns all the files in a folder as a list of P4File objects. Uses the files_to_p4files function if the host
|
|
@@ -301,7 +306,8 @@ class P4Client(object):
|
|
|
301
306
|
if len(changelists):
|
|
302
307
|
return True
|
|
303
308
|
return False
|
|
304
|
-
|
|
309
|
+
|
|
310
|
+
@validate_not_empty
|
|
305
311
|
def move_files_to_changelist(self, file_list, changelist="default"):
|
|
306
312
|
"""
|
|
307
313
|
Moves the files in file_list to a changelist. Makes a new changelist if the given one doesn't exist.
|
|
@@ -371,6 +377,7 @@ class P4Client(object):
|
|
|
371
377
|
return True
|
|
372
378
|
return False
|
|
373
379
|
|
|
380
|
+
@validate_not_empty
|
|
374
381
|
def revert_files(self, file_list, unchanged_only=False):
|
|
375
382
|
"""
|
|
376
383
|
Reverts files in file_list
|
|
@@ -389,6 +396,8 @@ class P4Client(object):
|
|
|
389
396
|
|
|
390
397
|
return info_dicts
|
|
391
398
|
|
|
399
|
+
|
|
400
|
+
@validate_not_empty
|
|
392
401
|
def revert_folders(self, folder_list, unchanged_only=False):
|
|
393
402
|
"""
|
|
394
403
|
Recursively reverts complete folders
|
|
@@ -442,6 +451,7 @@ class P4Client(object):
|
|
|
442
451
|
info_dicts = self.run_cmd("submit", args=["-c", changelist, "--parallel", f"threads={self.max_parallel_connections}"])
|
|
443
452
|
return info_dicts
|
|
444
453
|
|
|
454
|
+
@validate_not_empty
|
|
445
455
|
def sync_folders(self, folder_list):
|
|
446
456
|
"""
|
|
447
457
|
Recursively syncs complete folders
|
|
@@ -463,6 +473,7 @@ class P4Client(object):
|
|
|
463
473
|
info_dicts = self.run_cmd("sync", args=["--parallel", f"threads={self.max_parallel_connections}"], file_list=cleaned_folder_list)
|
|
464
474
|
return info_dicts
|
|
465
475
|
|
|
476
|
+
@validate_not_empty
|
|
466
477
|
def sync_files(self, file_list, revision=-1, verify=True, force=False):
|
|
467
478
|
"""
|
|
468
479
|
Syncs files
|
|
@@ -493,6 +504,7 @@ class P4Client(object):
|
|
|
493
504
|
|
|
494
505
|
return info_dicts
|
|
495
506
|
|
|
507
|
+
@validate_not_empty
|
|
496
508
|
def reconcile_offline_files(self, file_list, changelist="default"):
|
|
497
509
|
"""
|
|
498
510
|
Adds, opens for edit or delete any files that were changed outside Perforce
|
|
@@ -508,6 +520,7 @@ class P4Client(object):
|
|
|
508
520
|
info_dicts = self.run_cmd("reconcile", args=["-c", changelist], file_list=file_list)
|
|
509
521
|
return info_dicts
|
|
510
522
|
|
|
523
|
+
@validate_not_empty
|
|
511
524
|
def reconcile_offline_folders(self, folder_list, changelist="default"):
|
|
512
525
|
"""
|
|
513
526
|
Adds, opens for edit or delete any files in the rootfolder + subfolders that were changed outside Perforce
|
|
@@ -532,6 +545,7 @@ class P4Client(object):
|
|
|
532
545
|
info_dicts = self.run_cmd("reconcile", args=["-c", changelist], file_list=cleaned_folder_list)
|
|
533
546
|
return info_dicts
|
|
534
547
|
|
|
548
|
+
@validate_not_empty
|
|
535
549
|
def delete_files(self, file_list, changelist="default"):
|
|
536
550
|
"""
|
|
537
551
|
Marks files for delete
|
|
@@ -582,6 +596,118 @@ class P4Client(object):
|
|
|
582
596
|
|
|
583
597
|
return files_and_cl
|
|
584
598
|
|
|
599
|
+
@validate_not_empty
|
|
600
|
+
def shelve_files(self, changelist, file_list=None, revert_after_shelve=False, force=False):
|
|
601
|
+
"""
|
|
602
|
+
Shelves files in a changelist. If no files are specified, shelves all files in the changelist.
|
|
603
|
+
|
|
604
|
+
:param changelist: *string* or *int* changelist number or description
|
|
605
|
+
:param file_list: *list* or *None* list of files to shelve. If None, shelves all files in the changelist
|
|
606
|
+
:param revert_after_shelve: *bool* if True, reverts the files after shelving them
|
|
607
|
+
:param force: *bool* if True, forces the shelving operation even if files are already shelved
|
|
608
|
+
:return: *list* of info dictionaries
|
|
609
|
+
"""
|
|
610
|
+
changelist = self.__ensure_changelist(changelist)
|
|
611
|
+
|
|
612
|
+
# if no files specified, get all files in the changelist
|
|
613
|
+
if file_list is None:
|
|
614
|
+
file_list = self.get_files_in_changelist(changelist)
|
|
615
|
+
else:
|
|
616
|
+
file_list = convert_to_list(file_list) if not isinstance(file_list, list) else file_list
|
|
617
|
+
if not self.silent:
|
|
618
|
+
self.__validate_file_list(file_list)
|
|
619
|
+
|
|
620
|
+
# if there are no files to shelve, return empty list
|
|
621
|
+
if not file_list:
|
|
622
|
+
if not self.silent:
|
|
623
|
+
logging.warning(f"No files to shelve in changelist {changelist}")
|
|
624
|
+
return []
|
|
625
|
+
|
|
626
|
+
# iuild args list based on options
|
|
627
|
+
args = ["-c", str(changelist)]
|
|
628
|
+
if force:
|
|
629
|
+
args.append("-f")
|
|
630
|
+
|
|
631
|
+
# run the shelve command
|
|
632
|
+
info_dicts = self.run_cmd("shelve", args=args, file_list=file_list)
|
|
633
|
+
|
|
634
|
+
for info_dict in info_dicts:
|
|
635
|
+
if self.__get_dict_value(info_dict, "code") == "error" and not self.silent:
|
|
636
|
+
logging.error(self.__get_dict_value(info_dict, "data"))
|
|
637
|
+
|
|
638
|
+
# revert files if requested
|
|
639
|
+
if revert_after_shelve and file_list:
|
|
640
|
+
revert_info_dicts = self.revert_files(file_list)
|
|
641
|
+
# add revert info to the return data
|
|
642
|
+
info_dicts.extend(revert_info_dicts)
|
|
643
|
+
|
|
644
|
+
return info_dicts
|
|
645
|
+
|
|
646
|
+
def unshelve_files(self, changelist, file_list=None, target_changelist="default", force=False, delete_shelved_files=False):
|
|
647
|
+
"""
|
|
648
|
+
Unshelves files from a shelved changelist. If no files are specified, unshelves all files.
|
|
649
|
+
|
|
650
|
+
:param changelist: *string* or *int* source changelist number containing shelved files
|
|
651
|
+
:param file_list: *list* or *None* list of files to unshelve. If None, unshelves all files
|
|
652
|
+
:param target_changelist: *string* or *int* target changelist to unshelve files into
|
|
653
|
+
:param force: *bool* if True, forces the unshelve operation even if files are already opened
|
|
654
|
+
:param delete_shelved_files: *bool* if True, deletes the shelved files after unshelving them
|
|
655
|
+
:return: *list* of info dictionaries
|
|
656
|
+
"""
|
|
657
|
+
source_changelist = self.__ensure_changelist(changelist)
|
|
658
|
+
target_changelist = self.__ensure_changelist(target_changelist)
|
|
659
|
+
|
|
660
|
+
# build the arguments list
|
|
661
|
+
args = []
|
|
662
|
+
|
|
663
|
+
# add force flag if requested
|
|
664
|
+
if force:
|
|
665
|
+
args.append("-f")
|
|
666
|
+
|
|
667
|
+
# add source changelist (required)
|
|
668
|
+
args.extend(["-s", str(source_changelist)])
|
|
669
|
+
|
|
670
|
+
# add target changelist if specified and not default
|
|
671
|
+
if target_changelist != "default":
|
|
672
|
+
args.extend(["-c", str(target_changelist)])
|
|
673
|
+
|
|
674
|
+
# prepare file list if provided
|
|
675
|
+
if file_list is not None:
|
|
676
|
+
file_list = convert_to_list(file_list) if not isinstance(file_list, list) else file_list
|
|
677
|
+
if not self.silent:
|
|
678
|
+
self.__validate_file_list(file_list)
|
|
679
|
+
else:
|
|
680
|
+
file_list = []
|
|
681
|
+
|
|
682
|
+
# run the unshelve command
|
|
683
|
+
info_dicts = self.run_cmd("unshelve", args=args, file_list=file_list)
|
|
684
|
+
|
|
685
|
+
# check for errors in the unshelve operation
|
|
686
|
+
has_error = False
|
|
687
|
+
for info_dict in info_dicts:
|
|
688
|
+
if self.__get_dict_value(info_dict, "code") == "error" and not self.silent:
|
|
689
|
+
logging.error(self.__get_dict_value(info_dict, "data"))
|
|
690
|
+
has_error = True
|
|
691
|
+
|
|
692
|
+
# delete shelved files if requested and unshelve was successful
|
|
693
|
+
if delete_shelved_files and not has_error:
|
|
694
|
+
# build the arguments for the shelve -d command
|
|
695
|
+
delete_args = ["-d", "-c", str(source_changelist)]
|
|
696
|
+
|
|
697
|
+
# if file_list provided, use it for the delete operation as well
|
|
698
|
+
delete_info_dicts = self.run_cmd("shelve", args=delete_args, file_list=file_list)
|
|
699
|
+
|
|
700
|
+
# check for errors in the delete operation
|
|
701
|
+
for info_dict in delete_info_dicts:
|
|
702
|
+
if self.__get_dict_value(info_dict, "code") == "error" and not self.silent:
|
|
703
|
+
logging.error(self.__get_dict_value(info_dict, "data"))
|
|
704
|
+
|
|
705
|
+
# add the delete info to the return data
|
|
706
|
+
info_dicts.extend(delete_info_dicts)
|
|
707
|
+
|
|
708
|
+
return info_dicts
|
|
709
|
+
|
|
710
|
+
@validate_not_empty
|
|
585
711
|
def add_or_edit_folders(self, folders, include_subfolders=True, changelist="default"):
|
|
586
712
|
"""
|
|
587
713
|
Marks a folder for add or edit
|
|
@@ -605,6 +731,8 @@ class P4Client(object):
|
|
|
605
731
|
|
|
606
732
|
return self.add_or_edit_files(all_files, changelist=changelist)
|
|
607
733
|
|
|
734
|
+
|
|
735
|
+
@validate_not_empty
|
|
608
736
|
def add_or_edit_files(self, file_list, changelist="default"):
|
|
609
737
|
"""
|
|
610
738
|
Marks the files in file_list for add if they are new, or edit if they are already versioned
|
|
@@ -653,6 +781,7 @@ class P4Client(object):
|
|
|
653
781
|
return int(self.__get_dict_value(info_dict, "change"))
|
|
654
782
|
return -1
|
|
655
783
|
|
|
784
|
+
@validate_not_empty
|
|
656
785
|
def edit_files(self, file_list, changelist="default"):
|
|
657
786
|
"""
|
|
658
787
|
Marks the files in file_list for edit
|
|
@@ -673,6 +802,7 @@ class P4Client(object):
|
|
|
673
802
|
logging.error(self.__get_dict_value(info_dict, "data"))
|
|
674
803
|
return info_dicts
|
|
675
804
|
|
|
805
|
+
@validate_not_empty
|
|
676
806
|
def add_files(self, file_list, changelist="default"):
|
|
677
807
|
"""
|
|
678
808
|
Marks the files in file_list for add
|
|
@@ -808,6 +938,7 @@ class P4Client(object):
|
|
|
808
938
|
|
|
809
939
|
return workspaces
|
|
810
940
|
|
|
941
|
+
@validate_not_empty
|
|
811
942
|
def get_depot_paths(self, paths):
|
|
812
943
|
"""
|
|
813
944
|
Returns the depot path of the given files or folders, based on the client
|
|
@@ -827,6 +958,8 @@ class P4Client(object):
|
|
|
827
958
|
depot_paths = [self.__get_dict_value(info, "depotFile").rstrip("/...") for info in info_dicts]
|
|
828
959
|
return depot_paths
|
|
829
960
|
|
|
961
|
+
|
|
962
|
+
@validate_not_empty
|
|
830
963
|
def get_local_paths(self, paths):
|
|
831
964
|
"""
|
|
832
965
|
Returns the local path of the given files or folders, based on the client
|
|
@@ -847,6 +980,7 @@ class P4Client(object):
|
|
|
847
980
|
local_paths = [self.__get_dict_value(info, "path") for info in info_dicts]
|
|
848
981
|
return local_paths
|
|
849
982
|
|
|
983
|
+
@validate_not_empty
|
|
850
984
|
def get_history(self, paths):
|
|
851
985
|
"""
|
|
852
986
|
Returns changes for the given paths
|
|
@@ -1018,53 +1152,6 @@ class P4Client(object):
|
|
|
1018
1152
|
# Easy utility to check that the file is underneath the correct perforce root
|
|
1019
1153
|
# Quicker than waiting for the result of a p4 fstat
|
|
1020
1154
|
for f in file_list:
|
|
1021
|
-
if not f.lower().startswith(self.perforce_root.lower()):
|
|
1022
|
-
raise Exception(f'{f} is not under perforce root: {self.perforce_root}')
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
def split_list_into_strings_of_length(input_list, max_length=100):
|
|
1026
|
-
"""
|
|
1027
|
-
From incoming list of strings, build joined together strings that are clamped to a max size
|
|
1028
|
-
|
|
1029
|
-
:param input_list:
|
|
1030
|
-
:param max_length: str clamp length
|
|
1031
|
-
"""
|
|
1032
|
-
full_list_str = " ".join([str(arg) for arg in input_list])
|
|
1033
|
-
|
|
1034
|
-
# if str is already below max length, just return it wrapped in a list
|
|
1035
|
-
if not len(full_list_str) > max_length:
|
|
1036
|
-
return [full_list_str]
|
|
1037
|
-
|
|
1038
|
-
# Keep appending on the last string until we hit the max length
|
|
1039
|
-
# then add a new entry to the list and keep appending
|
|
1040
|
-
clamped_str_list = [""]
|
|
1041
|
-
for arg in input_list:
|
|
1042
|
-
current_str = clamped_str_list[-1]
|
|
1043
|
-
new_str = "{}{} ".format(current_str, arg) # the space is deliberately placed, since the first item has no len
|
|
1044
|
-
|
|
1045
|
-
# if new string is too long, just append the arg as a new item and skip to next arg
|
|
1046
|
-
if len(new_str) >= max_length:
|
|
1047
|
-
clamped_str_list.append("{} ".format(arg)) #
|
|
1048
|
-
continue
|
|
1049
|
-
|
|
1050
|
-
# set latest to updated string
|
|
1051
|
-
clamped_str_list[-1] = new_str
|
|
1052
|
-
|
|
1053
|
-
return clamped_str_list
|
|
1054
|
-
|
|
1055
|
-
def decode_dictionaries(info_dicts):
|
|
1056
|
-
"""
|
|
1057
|
-
Decode list of dictionary keys and values into unicode from bytes
|
|
1058
|
-
"""
|
|
1059
|
-
decoded_dicts = []
|
|
1060
|
-
for info_dict in info_dicts:
|
|
1061
|
-
decoded_dict = {k.decode(): v.decode() for k, v in info_dict.items()}
|
|
1062
|
-
decoded_dicts.append(decoded_dict)
|
|
1063
|
-
return decoded_dicts
|
|
1064
|
-
|
|
1065
|
-
def convert_to_list(value):
|
|
1066
|
-
if isinstance(value, tuple):
|
|
1067
|
-
converted = [v for v in value]
|
|
1068
|
-
else:
|
|
1069
|
-
converted = [value]
|
|
1070
|
-
return converted
|
|
1155
|
+
if not f.lower().startswith(self.perforce_root.lower()) and not f.lower().startswith(self.depot_root.lower()):
|
|
1156
|
+
raise Exception(f'{f} is not under perforce root: {self.perforce_root}, {self.depot_root}')
|
|
1157
|
+
|
|
@@ -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.6.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
|