Update perforce SCMTool to use p4python 08.1 API.
Updated 5 months, 2 weeks ago
| David Trowbridge | Reviewers | ||
| trunk | reviewboard | ||
| None | Review Board SVN | ||
As of the 08.1 API release, the p4python wrapper is now an officially supported product from Perforce. This is awesome, but they changed things up a bit (entirely for the better -- parsing changesets is quite a bit simpler now). This change updates our perforce tool to use the new API.
This code has been running on reviewboard.eng since they upgraded our main perforce server a couple months ago.
| /trunk/reviewboard/manage.py | |||
|---|---|---|---|
| Revision 1408 | New Change | ||
| ... | 81 lines hidden [Expand] | ||
| 82 | imp.find_module('pysvn') |
82 | imp.find_module('pysvn') |
| 83 | except ImportError: |
83 | except ImportError: |
| 84 | dependency_warning('pysvn not found. SVN integration will not work.') |
84 | dependency_warning('pysvn not found. SVN integration will not work.') |
| 85 | 85 | ||
| 86 | try: |
86 | try: |
| 87 | imp.find_module('p4') |
87 | imp.find_module('P4') |
| 88 | subprocess.call(['p4', '-h'], |
88 | subprocess.call(['p4', '-h'], |
| 89 | stdin=subprocess.PIPE, stdout=subprocess.PIPE) |
89 | stdin=subprocess.PIPE, stdout=subprocess.PIPE) |
| 90 | except ImportError: |
90 | except ImportError: |
| 91 | dependency_warning('p4python not found. Perforce integration will not work.') |
91 | dependency_warning('p4python (>=08.1) not found. Perforce integration will not work.') |
| 92 | except OSError: |
92 | except OSError: |
| 93 | dependency_error('p4 command not found. Perforce integration will not work.') |
93 | dependency_error('p4 command not found. Perforce integration will not work.') |
| 94 | 94 | ||
| 95 | try: |
95 | try: |
| 96 | imp.find_module('mercurial') |
96 | imp.find_module('mercurial') |
| ... | 32 lines hidden [Expand] | ||
| /trunk/reviewboard/scmtools/perforce.py | |||
|---|---|---|---|
| Revision 1408 | New Change | ||
| 1 |
|
1 |
|
| 2 | import subprocess |
2 | import subprocess |
| 3 | 3 | ||
| 4 | try: |
4 | try: |
| 5 | from p4 import P4Error |
5 | from P4 import P4Error |
| 6 | except ImportError: |
6 | except ImportError: |
| 7 | pass |
7 | pass |
| 8 | 8 | ||
| 9 | from reviewboard.diffviewer.parser import DiffParser |
9 | from reviewboard.diffviewer.parser import DiffParser |
| 10 | from reviewboard.scmtools.core import SCMTool, ChangeSet, HEAD, PRE_CREATION |
10 | from reviewboard.scmtools.core import SCMTool, ChangeSet, HEAD, PRE_CREATION |
| 11 | 11 | ||
| 12 | class PerforceTool(SCMTool): |
12 | class PerforceTool(SCMTool): |
| 13 | def __init__(self, repository): |
13 | def __init__(self, repository): |
| 14 | SCMTool.__init__(self, repository) |
14 | SCMTool.__init__(self, repository) |
| 15 | 15 | ||
| 16 | import p4 |
16 | import P4 |
| 17 | self.p4 = p4.P4() |
17 | self.p4 = P4.P4() |
| 18 | self.p4.port = str(repository.mirror_path or repository.path) |
18 | self.p4.port = str(repository.mirror_path or repository.path) |
| 19 | self.p4.user = str(repository.username) |
19 | self.p4.user = str(repository.username) |
| 20 | self.p4.password = str(repository.password) |
20 | self.p4.password = str(repository.password) |
| 21 | self.p4.exception_level = 1 |
||
| 21 | 22 | ||
| 22 | # We defer actually connecting until just before we do some operation |
23 | # We defer actually connecting until just before we do some operation |
| 23 | # that requires an active connection to the perforce depot. This |
24 | # that requires an active connection to the perforce depot. This |
| 24 | # connection is then left open as long as possible. |
25 | # connection is then left open as long as possible. |
| 25 | 26 | ||
| ... | 7 lines hidden [Expand] | ||
| 33 | # no internet connection, we'll get a P4Error from disconnect(). |
34 | # no internet connection, we'll get a P4Error from disconnect(). |
| 34 | # This is totally safe to ignore. |
35 | # This is totally safe to ignore. |
| 35 | pass |
36 | pass |
| 36 | 37 | ||
| 37 | def _connect(self): |
38 | def _connect(self): |
| 38 | if not self.p4.connected: |
39 | if not self.p4.connected(): |
| 39 | self.p4.connect() |
40 | self.p4.connect() |
| 40 | 41 | ||
| 41 | def _disconnect(self): |
42 | def _disconnect(self): |
| 42 | try: |
43 | try: |
| 43 | if self.p4.connected: |
44 | if self.p4.connected(): |
| 44 | self.p4.disconnect() |
45 | self.p4.disconnect() |
| 45 | except AttributeError: |
46 | except AttributeError: |
| 46 | pass |
47 | pass |
| 47 | 48 | ||
| 48 | def get_pending_changesets(self, userid): |
49 | def get_pending_changesets(self, userid): |
| ... | 4 lines hidden [Expand] | ||
| 53 | 54 | ||
| 54 | def get_changeset(self, changesetid): |
55 | def get_changeset(self, changesetid): |
| 55 | self._connect() |
56 | self._connect() |
| 56 | changeset = self.p4.run_describe('-s', str(changesetid)) |
57 | changeset = self.p4.run_describe('-s', str(changesetid)) |
| 57 | self._disconnect() |
58 | self._disconnect() |
| 58 | return self.parse_change_desc(changeset, changesetid) |
||
| 59 | 59 | ||
| 60 | if changeset: |
||
| 61 | return self.parse_change_desc(changeset[0], changesetid) |
||
| 62 | return None |
||
| 63 | |||
| 60 | def get_diffs_use_absolute_paths(self): |
64 | def get_diffs_use_absolute_paths(self): |
| 61 | return True |
65 | return True |
| 62 | 66 | ||
| 63 | def get_file(self, path, revision=HEAD): |
67 | def get_file(self, path, revision=HEAD): |
| 64 | if revision == PRE_CREATION: |
68 | if revision == PRE_CREATION: |
| ... | 71 lines hidden [Expand] | ||
| 136 | # |
140 | # |
| 137 | # We parse the username out of the first line to check that one user |
141 | # We parse the username out of the first line to check that one user |
| 138 | # isn't attempting to "claim" another's changelist. We then split |
142 | # isn't attempting to "claim" another's changelist. We then split |
| 139 | # everything around the 'Affected files ...' line, and process the |
143 | # everything around the 'Affected files ...' line, and process the |
| 140 | # results. |
144 | # results. |
| 141 | changeset.username = changedesc[0].split(' ')[3].split('@')[0] |
145 | changeset.username = changedesc['user'] |
| 142 | 146 | changeset.description = changedesc['desc'] |
|
| 143 | description = '\n'.join(changedesc[1:]) |
147 | changeset.files = changedesc['depotFile'] |
| 144 | file_header = re.search('Affected files ...', description) |
||
| 145 | |||
| 146 | desc = None |
||
| 147 | for line in description[:file_header.start()].split('\n'): |
||
| 148 | if line.startswith('\t'): |
||
| 149 | line = line[1:] |
||
| 150 | if desc: |
||
| 151 | desc += '\n' + line.rstrip() |
||
| 152 | else: |
||
| 153 | desc = line.rstrip() |
||
| 154 | changeset.description = desc.rstrip() |
||
| 155 | changeset.files = filter(lambda x: len(x), |
||
| 156 | [x.strip().split('#', 1)[0] for x in |
||
| 157 | description[file_header.end():].split('\n')]) |
||
| 158 | 148 | ||
| 159 | split = changeset.description.find('\n\n') |
149 | split = changeset.description.find('\n\n') |
| 160 | if split >= 0 and split < 100: |
150 | if split >= 0 and split < 100: |
| 161 | changeset.summary = \ |
151 | changeset.summary = \ |
| 162 | changeset.description.split('\n\n', 1)[0].replace('\n', ' ') |
152 | changeset.description.split('\n\n', 1)[0].replace('\n', ' ') |
| ... | 39 lines hidden [Expand] | ||
Other reviews