p4cmd 2.8.0__tar.gz → 2.8.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.
Files changed (24) hide show
  1. {p4cmd-2.8.0 → p4cmd-2.8.2}/PKG-INFO +1 -1
  2. {p4cmd-2.8.0 → p4cmd-2.8.2}/p4cmd/__init__.py +1 -1
  3. {p4cmd-2.8.0 → p4cmd-2.8.2}/p4cmd/p4cmd.py +94 -68
  4. {p4cmd-2.8.0 → p4cmd-2.8.2}/p4cmd.egg-info/PKG-INFO +1 -1
  5. {p4cmd-2.8.0 → p4cmd-2.8.2}/LICENSE +0 -0
  6. {p4cmd-2.8.0 → p4cmd-2.8.2}/README.md +0 -0
  7. {p4cmd-2.8.0 → p4cmd-2.8.2}/p4cmd/p4errors.py +0 -0
  8. {p4cmd-2.8.0 → p4cmd-2.8.2}/p4cmd/p4file.py +0 -0
  9. {p4cmd-2.8.0 → p4cmd-2.8.2}/p4cmd/resources/__init__.py +0 -0
  10. {p4cmd-2.8.0 → p4cmd-2.8.2}/p4cmd/resources/perforce_DEPOT_ONLY.png +0 -0
  11. {p4cmd-2.8.0 → p4cmd-2.8.2}/p4cmd/resources/perforce_MOVED.png +0 -0
  12. {p4cmd-2.8.0 → p4cmd-2.8.2}/p4cmd/resources/perforce_NEED_SYNC.png +0 -0
  13. {p4cmd-2.8.0 → p4cmd-2.8.2}/p4cmd/resources/perforce_OPEN_FOR_ADD.png +0 -0
  14. {p4cmd-2.8.0 → p4cmd-2.8.2}/p4cmd/resources/perforce_OPEN_FOR_DELETE.png +0 -0
  15. {p4cmd-2.8.0 → p4cmd-2.8.2}/p4cmd/resources/perforce_OPEN_FOR_EDIT.png +0 -0
  16. {p4cmd-2.8.0 → p4cmd-2.8.2}/p4cmd/resources/perforce_UNKNOWN.png +0 -0
  17. {p4cmd-2.8.0 → p4cmd-2.8.2}/p4cmd/resources/perforce_UNTRACKED.png +0 -0
  18. {p4cmd-2.8.0 → p4cmd-2.8.2}/p4cmd/resources/perforce_UP_TO_DATE.png +0 -0
  19. {p4cmd-2.8.0 → p4cmd-2.8.2}/p4cmd/utils.py +0 -0
  20. {p4cmd-2.8.0 → p4cmd-2.8.2}/p4cmd.egg-info/SOURCES.txt +0 -0
  21. {p4cmd-2.8.0 → p4cmd-2.8.2}/p4cmd.egg-info/dependency_links.txt +0 -0
  22. {p4cmd-2.8.0 → p4cmd-2.8.2}/p4cmd.egg-info/top_level.txt +0 -0
  23. {p4cmd-2.8.0 → p4cmd-2.8.2}/setup.cfg +0 -0
  24. {p4cmd-2.8.0 → p4cmd-2.8.2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: p4cmd
3
- Version: 2.8.0
3
+ Version: 2.8.2
4
4
  Summary: Simple P4 python module
5
5
  Home-page: https://github.com/nielsvaes/p4cmd
6
6
  Author: Niels Vaes
@@ -1,4 +1,4 @@
1
- VERSION = "2.8.0"
1
+ VERSION = "2.8.2"
2
2
 
3
3
  from .p4cmd import P4Client
4
4
  from .p4file import P4File
@@ -51,7 +51,7 @@ class P4Client(object):
51
51
  self.server = self.get_p4_setting("P4PORT")
52
52
  if self.server is None:
53
53
  raise p4errors.WorkSpaceError("Could not find P4PORT")
54
-
54
+
55
55
  self.depot_root = self.get_depot_paths([self.perforce_root])[0]
56
56
 
57
57
  @classmethod
@@ -73,6 +73,9 @@ class P4Client(object):
73
73
  """
74
74
  self.perforce_root = root
75
75
 
76
+ if not self.perforce_root:
77
+ raise p4errors.WorkSpaceError(f"self.perforce_root (value: {self.perforce_root}) is not set!")
78
+
76
79
  def set_max_parallel_connections(self, value):
77
80
  """
78
81
  Set the number of maximum parallel connections to use for sync/submit. Default class value is 4
@@ -94,12 +97,14 @@ class P4Client(object):
94
97
  :return: *list* of dictionaries with either the marshaled returns of the command or dictionaries with the
95
98
  raw output of the command
96
99
  """
100
+
101
+ if not self.perforce_root:
102
+ raise p4errors.WorkSpaceError(f"self.perforce_root (value: {self.perforce_root}) is not set!")
103
+
97
104
  if online_check:
98
- if not self.host_online():
99
- logging.warning("Can't connect to %s on port %s" % (self.__server_address(), self.__port_number()))
105
+ self.host_online()
100
106
 
101
- if self.perforce_root is not None:
102
- os.chdir(self.perforce_root)
107
+ os.chdir(self.perforce_root)
103
108
 
104
109
  file_list = [f'"{f}"' for f in file_list]
105
110
 
@@ -117,7 +122,8 @@ class P4Client(object):
117
122
 
118
123
  if len(command) > MAX_CMD_LEN:
119
124
  # This shouldn't happen, but just in case the command prefix end up really long
120
- logging.warning(f"Command length: {format(len(command))} exceeds MAX_CMD_LEN {MAX_CMD_LEN} on command: {MAX_CMD_LEN}")
125
+ logging.warning(
126
+ f"Command length: {format(len(command))} exceeds MAX_CMD_LEN {MAX_CMD_LEN} on command: {MAX_CMD_LEN}")
121
127
 
122
128
  with subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True) as pipe:
123
129
  output = pipe.stdout
@@ -268,7 +274,8 @@ class P4Client(object):
268
274
  if not complete_file_path in files:
269
275
  all_files.append(complete_file_path)
270
276
  else:
271
- all_files = [os.path.join(folder, file) for file in os.listdir(folder) if os.path.isfile(os.path.join(folder, file))]
277
+ all_files = [os.path.join(folder, file) for file in os.listdir(folder) if
278
+ os.path.isfile(os.path.join(folder, file))]
272
279
 
273
280
  return self.files_to_p4files(all_files)
274
281
 
@@ -281,12 +288,13 @@ class P4Client(object):
281
288
  """
282
289
  if not self.host_online():
283
290
  logging.warning("Can't connect to %s on port %s" % (self.__server_address(), self.__port_number()))
284
- #raise p4errors.ServerOffline("Can't connect to %s on port %s" % (self.__server_address(), self.__port_number()))
291
+ # raise p4errors.ServerOffline("Can't connect to %s on port %s" % (self.__server_address(), self.__port_number()))
285
292
  return
286
293
 
287
- output = subprocess.check_output('p4 --field "Description=%s" --field "Files=" change -o | p4 change -i' % description,
288
- stderr=subprocess.STDOUT,
289
- shell=True).decode()
294
+ output = subprocess.check_output(
295
+ 'p4 --field "Description=%s" --field "Files=" change -o | p4 change -i' % description,
296
+ stderr=subprocess.STDOUT,
297
+ shell=True).decode()
290
298
  changelist_number = output.split(" ")[1]
291
299
  return int(changelist_number)
292
300
 
@@ -302,7 +310,8 @@ class P4Client(object):
302
310
  return True
303
311
  else:
304
312
  changelist = str(changelist)
305
- changelists = self.get_pending_changelists(description_filter=changelist, perfect_match_only=True, case_sensitive=True)
313
+ changelists = self.get_pending_changelists(description_filter=changelist, perfect_match_only=True,
314
+ case_sensitive=True)
306
315
  if len(changelists):
307
316
  return True
308
317
  return False
@@ -315,24 +324,24 @@ class P4Client(object):
315
324
  :param new_description: *string* new description for the changelist
316
325
  :return: *bool* True if successful, False otherwise
317
326
  """
318
-
327
+
319
328
  # ... not super elegant, but it works
320
329
  try:
321
330
  changelist = self.__ensure_changelist(changelist)
322
-
331
+
323
332
  # do some formatting so that we have the proper indentation
324
333
  formatted_description = []
325
334
  for line in new_description.split('\n'):
326
335
  formatted_description.append(f"\t{line}")
327
336
  formatted_description = '\n'.join(formatted_description)
328
-
337
+
329
338
  output = subprocess.check_output(f'p4 change -o {changelist}', shell=True).decode()
330
-
339
+
331
340
  # Split the output into sections based on field names
332
341
  sections = []
333
342
  current_section = []
334
343
  current_field = None
335
-
344
+
336
345
  # take the output and start parsing it
337
346
  for line in output.split('\n'):
338
347
  # check if it's a new field
@@ -346,11 +355,11 @@ class P4Client(object):
346
355
  else:
347
356
  # if it's not a new field, add to the current section
348
357
  current_section.append(line)
349
-
358
+
350
359
  # add the last section
351
360
  if current_field:
352
361
  sections.append((current_field, current_section))
353
-
362
+
354
363
  # new remake the Description field with the update description
355
364
  new_output = []
356
365
  for field, section_lines in sections:
@@ -361,18 +370,18 @@ class P4Client(object):
361
370
  else:
362
371
  # keep everything else as-is
363
372
  new_output.extend(section_lines)
364
-
373
+
365
374
  updated_output = '\n'.join(new_output)
366
-
375
+
367
376
  # TODO: modify run_cmd to allow input so that commands like this can also run through it.
368
377
  result = subprocess.check_output(f'p4 change -i', input=updated_output.encode(), shell=True).decode()
369
-
378
+
370
379
  return True
371
380
  except Exception as e:
372
381
  if not self.silent:
373
382
  logging.error(f"Failed to update changelist description: {e}")
374
383
  return False
375
-
384
+
376
385
  @validate_not_empty
377
386
  def move_files_to_changelist(self, file_list, changelist="default"):
378
387
  """
@@ -410,7 +419,6 @@ class P4Client(object):
410
419
  result = self.move_files_to_changelist(files_to_move, target_changelist)
411
420
  return result
412
421
 
413
-
414
422
  def rename_file(self, old_file_path, new_file_path, changelist="default"):
415
423
  """
416
424
  P4 move-renames a file
@@ -438,7 +446,8 @@ class P4Client(object):
438
446
  """
439
447
  changelist = self.__ensure_changelist(changelist)
440
448
 
441
- info_dict = self.run_cmd("copy", args=["-c", str(changelist)], file_list=[original_file_path, copied_file_path])[0]
449
+ info_dict = \
450
+ self.run_cmd("copy", args=["-c", str(changelist)], file_list=[original_file_path, copied_file_path])[0]
442
451
  if self.__get_dict_value(info_dict, "code") != "error":
443
452
  return True
444
453
  return False
@@ -462,7 +471,6 @@ class P4Client(object):
462
471
 
463
472
  return info_dicts
464
473
 
465
-
466
474
  @validate_not_empty
467
475
  def revert_folders(self, folder_list, unchanged_only=False):
468
476
  """
@@ -514,7 +522,8 @@ class P4Client(object):
514
522
  if revert_unchanged_files:
515
523
  self.revert_changelist(unchanged_only=True, changelist=changelist)
516
524
 
517
- info_dicts = self.run_cmd("submit", args=["-c", changelist, "--parallel", f"threads={self.max_parallel_connections}"])
525
+ info_dicts = self.run_cmd("submit",
526
+ args=["-c", changelist, "--parallel", f"threads={self.max_parallel_connections}"])
518
527
  return info_dicts
519
528
 
520
529
  @validate_not_empty
@@ -536,7 +545,8 @@ class P4Client(object):
536
545
  folder += "/..."
537
546
  cleaned_folder_list.append(folder)
538
547
 
539
- info_dicts = self.run_cmd("sync", args=["--parallel", f"threads={self.max_parallel_connections}"], file_list=cleaned_folder_list)
548
+ info_dicts = self.run_cmd("sync", args=["--parallel", f"threads={self.max_parallel_connections}"],
549
+ file_list=cleaned_folder_list)
540
550
  return info_dicts
541
551
 
542
552
  @validate_not_empty
@@ -556,7 +566,8 @@ class P4Client(object):
556
566
  verify = False
557
567
  file_list = [f"{path}#{revision}" for path in file_list]
558
568
 
559
- initial_arg_list = ["-f", "--parallel", f"threads={self.max_parallel_connections}"] if force else ["--parallel", f"threads={self.max_parallel_connections}"]
569
+ initial_arg_list = ["-f", "--parallel", f"threads={self.max_parallel_connections}"] if force else ["--parallel",
570
+ f"threads={self.max_parallel_connections}"]
560
571
  if not self.silent:
561
572
  self.__validate_file_list(file_list)
562
573
 
@@ -629,7 +640,8 @@ class P4Client(object):
629
640
  info_dicts = self.run_cmd("delete", args=["-c", changelist], file_list=file_list)
630
641
  return info_dicts
631
642
 
632
- def delete_changelist(self, changelist, perfect_match_only=False, case_sensitive=False, delete_shelved_files=False, obliterate=False):
643
+ def delete_changelist(self, changelist, perfect_match_only=False, case_sensitive=False, delete_shelved_files=False,
644
+ obliterate=False):
633
645
  """
634
646
  Deletes a changelist via changelist number or description. Will fail if any files are checked out or shelved files exist.
635
647
  :param changelist: *string* or *int* change list number
@@ -645,10 +657,10 @@ class P4Client(object):
645
657
  if obliterate:
646
658
  info_dicts.append(self.revert_changelist(changelist=cl))
647
659
  delete_shelved_files = True
648
-
660
+
649
661
  if delete_shelved_files:
650
662
  info_dicts.append(self.delete_shelf(cl))
651
-
663
+
652
664
  info_dicts.append(self.run_cmd('change', args=['-d', cl]))
653
665
  # TODO: Break down info dicts and look for errors
654
666
  return info_dicts
@@ -684,7 +696,7 @@ class P4Client(object):
684
696
  :return: *list* of info dictionaries
685
697
  """
686
698
  changelist = self.__ensure_changelist(changelist)
687
-
699
+
688
700
  # if no files specified, get all files in the changelist
689
701
  if file_list is None:
690
702
  file_list = self.get_files_in_changelist(changelist)
@@ -692,34 +704,35 @@ class P4Client(object):
692
704
  file_list = convert_to_list(file_list) if not isinstance(file_list, list) else file_list
693
705
  if not self.silent:
694
706
  self.__validate_file_list(file_list)
695
-
707
+
696
708
  # if there are no files to shelve, return empty list
697
709
  if not file_list:
698
710
  if not self.silent:
699
711
  logging.warning(f"No files to shelve in changelist {changelist}")
700
712
  return []
701
-
713
+
702
714
  # iuild args list based on options
703
715
  args = ["-c", str(changelist)]
704
716
  if force:
705
717
  args.append("-f")
706
-
718
+
707
719
  # run the shelve command
708
720
  info_dicts = self.run_cmd("shelve", args=args, file_list=file_list)
709
-
721
+
710
722
  for info_dict in info_dicts:
711
723
  if self.__get_dict_value(info_dict, "code") == "error" and not self.silent:
712
724
  logging.error(self.__get_dict_value(info_dict, "data"))
713
-
725
+
714
726
  # revert files if requested
715
727
  if revert_after_shelve and file_list:
716
728
  revert_info_dicts = self.revert_files(file_list)
717
729
  # add revert info to the return data
718
730
  info_dicts.extend(revert_info_dicts)
719
-
731
+
720
732
  return info_dicts
721
733
 
722
- def unshelve_files(self, changelist, file_list=None, target_changelist="default", force=False, delete_shelved_files=False):
734
+ def unshelve_files(self, changelist, file_list=None, target_changelist="default", force=False,
735
+ delete_shelved_files=False):
723
736
  """
724
737
  Unshelves files from a shelved changelist. If no files are specified, unshelves all files.
725
738
 
@@ -732,21 +745,21 @@ class P4Client(object):
732
745
  """
733
746
  source_changelist = self.__ensure_changelist(changelist)
734
747
  target_changelist = self.__ensure_changelist(target_changelist)
735
-
748
+
736
749
  # build the arguments list
737
750
  args = []
738
-
751
+
739
752
  # add force flag if requested
740
753
  if force:
741
754
  args.append("-f")
742
-
755
+
743
756
  # add source changelist (required)
744
757
  args.extend(["-s", str(source_changelist)])
745
-
758
+
746
759
  # add target changelist if specified and not default
747
760
  if target_changelist != "default":
748
761
  args.extend(["-c", str(target_changelist)])
749
-
762
+
750
763
  # prepare file list if provided
751
764
  if file_list is not None:
752
765
  file_list = convert_to_list(file_list) if not isinstance(file_list, list) else file_list
@@ -754,33 +767,33 @@ class P4Client(object):
754
767
  self.__validate_file_list(file_list)
755
768
  else:
756
769
  file_list = []
757
-
770
+
758
771
  # run the unshelve command
759
772
  info_dicts = self.run_cmd("unshelve", args=args, file_list=file_list)
760
-
773
+
761
774
  # check for errors in the unshelve operation
762
775
  has_error = False
763
776
  for info_dict in info_dicts:
764
777
  if self.__get_dict_value(info_dict, "code") == "error" and not self.silent:
765
778
  logging.error(self.__get_dict_value(info_dict, "data"))
766
779
  has_error = True
767
-
780
+
768
781
  # delete shelved files if requested and unshelve was successful
769
782
  if delete_shelved_files and not has_error:
770
783
  # build the arguments for the shelve -d command
771
784
  delete_args = ["-d", "-c", str(source_changelist)]
772
-
785
+
773
786
  # if file_list provided, use it for the delete operation as well
774
787
  delete_info_dicts = self.run_cmd("shelve", args=delete_args, file_list=file_list)
775
-
788
+
776
789
  # check for errors in the delete operation
777
790
  for info_dict in delete_info_dicts:
778
791
  if self.__get_dict_value(info_dict, "code") == "error" and not self.silent:
779
792
  logging.error(self.__get_dict_value(info_dict, "data"))
780
-
793
+
781
794
  # add the delete info to the return data
782
795
  info_dicts.extend(delete_info_dicts)
783
-
796
+
784
797
  return info_dicts
785
798
 
786
799
  def delete_shelf(self, changelist):
@@ -793,13 +806,13 @@ class P4Client(object):
793
806
  try:
794
807
  # Ensure the changelist exists and is a number
795
808
  changelist = self.__ensure_changelist(changelist)
796
-
809
+
797
810
  # Don't allow deleting shelves from the default changelist
798
811
  if changelist == "default":
799
812
  if not self.silent:
800
813
  logging.error("Cannot delete shelved files from the default changelist")
801
814
  return [{"code": "error", "data": "Cannot delete shelved files from the default changelist"}]
802
-
815
+
803
816
  # Check if the changelist has shelved files
804
817
  has_shelved_files = False
805
818
  info_dicts = self.run_cmd("describe", args=["-S", str(changelist)])
@@ -810,16 +823,16 @@ class P4Client(object):
810
823
  break
811
824
  if has_shelved_files:
812
825
  break
813
-
826
+
814
827
  if not has_shelved_files:
815
828
  if not self.silent:
816
829
  logging.warning(f"No shelved files found in changelist {changelist}")
817
830
  return [{"code": "info", "data": f"No shelved files found in changelist {changelist}"}]
818
-
831
+
819
832
  # Delete all shelved files in the changelist
820
833
  info_dicts = self.run_cmd("shelve", args=["-d", "-c", str(changelist)])
821
834
  return info_dicts
822
-
835
+
823
836
  except Exception as e:
824
837
  if not self.silent:
825
838
  logging.error(f"Failed to delete shelved files: {e}")
@@ -845,11 +858,11 @@ class P4Client(object):
845
858
  if not complete_file_path in files:
846
859
  all_files.append(complete_file_path)
847
860
  else:
848
- all_files.extend([os.path.join(folder, file) for file in os.listdir(folder) if os.path.isfile(os.path.join(folder, file))])
861
+ all_files.extend([os.path.join(folder, file) for file in os.listdir(folder) if
862
+ os.path.isfile(os.path.join(folder, file))])
849
863
 
850
864
  return self.add_or_edit_files(all_files, changelist=changelist)
851
865
 
852
-
853
866
  @validate_not_empty
854
867
  def add_or_edit_files(self, file_list, changelist="default"):
855
868
  """
@@ -971,7 +984,8 @@ class P4Client(object):
971
984
 
972
985
  return depot_paths
973
986
 
974
- def get_pending_changelists(self, description_filter="", perfect_match_only=False, case_sensitive=False, descriptions=False):
987
+ def get_pending_changelists(self, description_filter="", perfect_match_only=False, case_sensitive=False,
988
+ descriptions=False):
975
989
  """
976
990
  Returns all the pending change lists, filtered on the changelist description
977
991
 
@@ -1038,7 +1052,7 @@ class P4Client(object):
1038
1052
  case_sensitive=case_sensitive,
1039
1053
  )
1040
1054
 
1041
- if len(changelists) > 1: # > 1 because "default" is always appended
1055
+ if len(changelists) > 1: # > 1 because "default" is always appended
1042
1056
  return changelists[0]
1043
1057
  else:
1044
1058
  return self.make_new_changelist(description=changelist_description)
@@ -1076,7 +1090,6 @@ class P4Client(object):
1076
1090
  depot_paths = [self.__get_dict_value(info, "depotFile").rstrip("/...") for info in info_dicts]
1077
1091
  return depot_paths
1078
1092
 
1079
-
1080
1093
  @validate_not_empty
1081
1094
  def get_local_paths(self, paths):
1082
1095
  """
@@ -1116,7 +1129,7 @@ class P4Client(object):
1116
1129
 
1117
1130
  return info_dicts
1118
1131
 
1119
- def host_online(self):
1132
+ def host_online(self, timeout=2.0):
1120
1133
  """
1121
1134
  Checks if the host for this client is online
1122
1135
 
@@ -1126,10 +1139,16 @@ class P4Client(object):
1126
1139
  host = self.__server_address()
1127
1140
 
1128
1141
  try:
1129
- sock = socket.create_connection((host, port), timeout=2)
1130
- sock.close()
1131
- return True
1132
- except:
1142
+ with socket.create_connection((host, port), timeout=timeout):
1143
+ return True
1144
+ except socket.timeout:
1145
+ logging.warning(f"Connection to {host} on port {port} timed out after {timeout} seconds, the running command might not complete correctly")
1146
+ return False
1147
+ except ConnectionRefusedError:
1148
+ logging.warning(f"Connection to {host} on port {port} refused to connect, is the server still up?")
1149
+ return False
1150
+ except OSError as e:
1151
+ logging.warning(f"oserror: {e.strerror or e}")
1133
1152
  return False
1134
1153
 
1135
1154
  def __server_address(self):
@@ -1243,6 +1262,9 @@ class P4Client(object):
1243
1262
 
1244
1263
  :return: *bool*
1245
1264
  """
1265
+ if not self.perforce_root:
1266
+ raise p4errors.WorkSpaceError(f"self.perforce_root (value: {self.perforce_root}) is not set!")
1267
+
1246
1268
  starting_dir = self.perforce_root
1247
1269
  last_dir = ""
1248
1270
  current_dir = starting_dir
@@ -1265,11 +1287,15 @@ class P4Client(object):
1265
1287
  :param file_list: List of files to iterate
1266
1288
  :return:
1267
1289
  """
1290
+ if not self.perforce_root:
1291
+ raise p4errors.WorkSpaceError(f"self.perforce_root (value: {self.perforce_root}) is not set!")
1292
+
1268
1293
  file_list = convert_to_list(file_list) if not isinstance(file_list, list) else file_list
1269
1294
 
1270
1295
  # Easy utility to check that the file is underneath the correct perforce root
1271
1296
  # Quicker than waiting for the result of a p4 fstat
1272
1297
  for f in file_list:
1273
- if not f.lower().startswith(self.perforce_root.lower()) and not f.lower().startswith(self.depot_root.lower()):
1298
+ if not f.lower().startswith(self.perforce_root.lower()) and not f.lower().startswith(
1299
+ self.depot_root.lower()):
1274
1300
  raise Exception(f'{f} is not under perforce root: {self.perforce_root}, {self.depot_root}')
1275
1301
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: p4cmd
3
- Version: 2.8.0
3
+ Version: 2.8.2
4
4
  Summary: Simple P4 python module
5
5
  Home-page: https://github.com/nielsvaes/p4cmd
6
6
  Author: Niels Vaes
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