Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 72 additions & 8 deletions nimp/utils/p4.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
''' Perforce utilities '''

import argparse
import json
import logging
import os
import os.path
Expand Down Expand Up @@ -228,6 +229,23 @@ def reconcile(self, cl_number, *files):

return ret

def reconcile_workspace(self, cl_number=None, workspace_path_to_reconcile=None, dry_run=False):
''' Reconciles given workspace '''
p4_reconcile_args = ['reconcile', '-f', '-e', '-a', '-d']
if dry_run:
p4_reconcile_args.append('-n')
if cl_number:
p4_reconcile_args.extend(['-c', cl_number])
if workspace_path_to_reconcile:
if not workspace_path_to_reconcile.endswith('...'):
workspace_path_to_reconcile = os.path.join(workspace_path_to_reconcile, '...')
workspace_path_to_reconcile = nimp.system.sanitize_path(workspace_path_to_reconcile)
p4_reconcile_args.append(workspace_path_to_reconcile)

if self._run(*p4_reconcile_args) is None:
return False
return True

def get_changelist_description(self, cl_number):
''' Returns description of given changelist '''
desc, = next(self._parse_command_output(["describe", cl_number], r"\.\.\. desc (.*)"))
Expand All @@ -248,6 +266,26 @@ def get_last_synced_changelist(self):

return cl_number

def get_file_workspace_current_revision(self, file):
''' Returns the file revision currently synced in the workspace '''
revision, = next(self._parse_command_output(['have', file], r'\.\.\. haveRev (\d+)'))
return revision

def print_file_by_revision(self, file, revision_number):
''' Prints the file revision for a given revision number '''
output = self._run('print', f'{file}#{revision_number}', use_json_format=True)
return self.parse_json_output_for_data(output)

@staticmethod
def parse_json_output_for_data(output):
data = ''
output = [json_element for json_element in output.split('\n') if json_element]
for output_chunk in output:
output_chunk = json.loads(output_chunk)
if 'data' in output_chunk.keys():
data += output_chunk['data']
return data

def get_or_create_changelist(self, description):
''' Creates or returns changelist number if it's not already created '''
pending_changelists = self.get_pending_changelists()
Expand Down Expand Up @@ -335,6 +373,31 @@ def submit(self, cl_number):

return True

def submit_default_changelist(self, description=None, dry_run=False):
''' Submits given changelist '''
assert description is not None
logging.info("Submitting default changelist...")
command = self._get_p4_command('submit', '-f', 'revertunchanged')
# descriptions could be too long for perforce limitations
command_length = len(' '.join(command))
perforce_cmd_max_characters_limit = 4096
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a real Perforce limitation or a windows commandline one ?
If it's a windows one, you can look into the p4 -x global option which allow providing arg in a file to by-pass this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the -x option can help us there.
My understanding from reading the docs it taht the p4 -x file_with_stuff.txt submit -m submit -d message would submit the list of files in file_with_stuff. This provides no workarround to the commandline length since we cannot pass the submit description as a file containing the desc.

4096 seems to be a bogus number (got it from pergit), 8191 seems to be the number for a cmd env.
I found no mention of a limiation to the submit description in the perforce documentation however.

What do you think?
Should we try and not limit the message length?
Or maybe expose it as an optionnal param for users of this function to handle?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just tried with a p4 -x test.txt print
with test.txt:

-q
//path/to/file

and it worked as expected.
I think it's worth looking into and always using a file for submit to avoid escape hell to handle quotes and such in the description text.

if description:
d_flag = 4 # accounts for ' -d ' string
description_max_characters_limit = perforce_cmd_max_characters_limit - command_length - d_flag
description = description[:description_max_characters_limit]
command.extend(['-d', description])
if dry_run:
logging.info(command)
return True
else:
_, _, error = nimp.sys.process.call(command, capture_output=True)

if error is not None and error != "":
logging.error("%s", error)
return False

return True

def sync(self, *files, cl_number = None):
''' Udpate given file '''
command = ["sync"]
Expand All @@ -357,8 +420,7 @@ def get_modified_files(self, *cl_numbers, root = '//...'):
for cl_number in cl_numbers:
for filename, action in self._parse_command_output(["fstat", "-e", cl_number , root],
r"^\.\.\. depotFile(.*)$",
r"^\.\.\. headAction(.*)",
hide_output=True):
r"^\.\.\. headAction(.*)"):
filename = os.path.normpath(filename) if filename is not None else ''
yield filename, action

Expand All @@ -370,8 +432,10 @@ def _escape_filename(name):
.replace('#', '%23') \
.replace('*', '%2A')

def _get_p4_command(self, *args):
def _get_p4_command(self, *args, use_json_format=False):
command = ['p4', '-z', 'tag']
if use_json_format:
command.append('-Mj')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's recommended to always use -ztag with -Mj

if self._port is not None:
command += ['-p', self._port]
if self._user is not None:
Expand All @@ -384,11 +448,11 @@ def _get_p4_command(self, *args):
command += list(args)
return command

def _run(self, *args, stdin=None, hide_output=False):
command = self._get_p4_command(*args)
def _run(self, *args, stdin=None, use_json_format=False):
command = self._get_p4_command(*args, use_json_format=use_json_format)

for _ in range(5):
result, output, error = nimp.sys.process.call(command, stdin=stdin, encoding='cp437', capture_output=True, hide_output=hide_output)
result, output, error = nimp.sys.process.call(command, stdin=stdin, encoding='cp437', capture_output=True)

if 'Operation took too long ' in error:
continue
Expand All @@ -405,8 +469,8 @@ def _run(self, *args, stdin=None, hide_output=False):

return output

def _parse_command_output(self, command, *patterns, stdin = None, hide_output = False):
output = self._run(*command, stdin = stdin, hide_output = hide_output)
def _parse_command_output(self, command, *patterns, stdin = None):
output = self._run(*command, stdin = stdin)

if output is not None:
match_list = []
Expand Down