source: tailor/vcpx/repository/p4/source.py @ 1655

Revision 1655, 6.7 KB checked in by vincent.legoll@…, 5 years ago (diff)

fix revnotes? being empty stacktrace

Fix "IndexError?: list index out of range" because there was no 'notes' in the filelog

Line 
1# -*- mode: python; coding: utf-8 -*-
2# :Progetto: vcpx -- p4 source
3# :Creato:   Fri Mar 16 23:06:43 PDT 2007
4# :Autore:   Dustin Sallings <dustin@spy.net>
5# :Licenza:  GNU General Public License
6#
7
8"""
9This module implements the source backend for p4.
10"""
11
12__docformat__ = 'reStructuredText'
13
14from vcpx.shwrap import ExternalCommand, PIPE
15from vcpx.config import ConfigurationError
16from vcpx.source import UpdatableSourceWorkingDir, GetUpstreamChangesetsFailure
17from vcpx.source import ChangesetApplicationFailure
18from vcpx.changes import Changeset
19from vcpx.tzinfo import UTC
20
21from datetime import datetime
22import exceptions
23import string
24import time
25import os
26import re
27
28import p4lib
29
30P4_DATE_FMT="%Y/%m/%d %H:%M:%S"
31
32class P4SourceWorkingDir(UpdatableSourceWorkingDir):
33    branchRE = re.compile(r'^branch from (?P<path>//.*?)#')
34
35    def __getP4(self):
36        p4=self.repository.EXECUTABLE
37        args={}
38        if self.repository.p4client is not None:
39            args['client']=self.repository.p4client
40        if self.repository.p4port is not None:
41            args['port']=self.repository.p4port
42        return p4lib.P4(p4=p4, **args)
43
44    def __getNativeChanges(self, sincerev):
45        changes=self.__getP4().changes(self.repository.depot_path + "...")
46        changes.reverse()
47        # Get rid of changes that are too low
48        sincerev = int(sincerev)
49        changes = [c for c in changes if int(c['change']) > sincerev]
50        return changes
51
52    def __parseDate(self, d):
53        return datetime.fromtimestamp(time.mktime(
54            time.strptime(d, P4_DATE_FMT)), UTC)
55
56    def __adaptChanges(self, changes):
57        # most of the info about a changeset is filled in later
58        return [Changeset(str(c['change']), None, c['user'], None)
59                for c in changes]
60
61    def _getUpstreamChangesets(self, sincerev):
62        return self.__adaptChanges(self.__getNativeChanges(sincerev))
63
64    def _localFilename(self, f, dp=None):
65        if dp is None:
66            dp=self.repository.depot_path
67        trans=string.maketrans(" ", "_")
68        fn=f['depotFile']
69        rv=fn
70        if not fn.startswith(dp): return None
71
72        rv=fn[len(dp):]
73        if rv[0]=='/':
74            rv=rv[1:]
75        return rv
76
77    def _applyChangeset(self, changeset):
78        p4 = self.__getP4()
79        desc = p4.describe(changeset.revision, shortForm=True)
80
81        changeset.author = desc['user']
82        changeset.date = self.__parseDate(desc['date'])
83        changeset.log = desc['description']
84
85        desc['files'] = [f for f in desc['files']
86                         if self._localFilename(f) is not None]
87
88        # check for added dirs
89        for f in desc['files']:
90            if f['action'] in ['add', 'branch']:
91                name = self._localFilename(f)
92                self._addParents(name, changeset)
93
94        p4.sync(self.repository.depot_path + '...@' + str(changeset.revision))
95
96        # dict of {path:str -> e:ChangesetEntry}
97        branched = dict()
98        for f in desc['files']:
99            name = self._localFilename(f)
100            path = f['depotFile']
101            act = f['action']
102
103            if act == 'branch':
104                e = changeset.addEntry(name, changeset.revision)
105                e.action_kind = e.ADDED
106
107                log = p4.filelog(path+'#'+str(f['rev']), maxRevs=1)
108
109                # rev['notes'] may be empty
110                notes = log[0]['revs'][0]['notes']
111                if len(notes) > 0:
112                    m = self.branchRE.match(notes[0])
113                    if m:
114                        old = m.group('path')
115                        branched[old] = e
116                        self.log.info('Branch %r to %r' % (old, name))
117
118        for f in desc['files']:
119            name = self._localFilename(f)
120            path = f['depotFile']
121            act = f['action']
122
123            # branches were already handled
124            if act == 'branch':
125                continue
126
127            # deletes might be renames
128            if act == 'delete' and path in branched:
129                e = branched[path]
130                e.action_kind = e.RENAMED
131                e.old_name = name
132                self.log.info('Rename %r to %r' % (name, e.name))
133                continue
134
135            e = changeset.addEntry(name, changeset.revision)
136            if act == 'add':
137                e.action_kind = e.ADDED
138            elif act == 'delete':
139                e.action_kind = e.DELETED
140            elif act in ['edit', 'integrate']:
141                e.action_kind = e.UPDATED
142            else:
143                assert False
144
145        # check for removed dirs
146        for f in desc['files']:
147            if f['action'] == 'delete':
148                name = self._localFilename(f)
149                self._delParents(name, changeset)
150
151        changes = ','.join([repr(e.name) for e in changeset.entries])
152        self.log.info('Updated %s', changes)
153
154        return []
155
156    # Perforce doesn't track directories, so we have to notice
157    # when a file add implies a directory add.  Otherwise targets
158    # like svn will barf.
159    # xxx This is a little fragile, because it depends on having
160    # a clean p4 workdir with sequential updates.  It might make
161    # more sense for the svn target to notice missing dir adds.
162    def _addParents(self, name, changeset):
163        parent = os.path.dirname(name)
164        if parent == '': return
165
166        path = os.path.join(self.repository.basedir, parent)
167        if os.path.exists(path): return
168
169        self._addParents(parent, changeset)
170
171        self.log.info('Adding dir %r' % parent)
172        e = changeset.addEntry(parent, changeset.revision)
173        e.action_kind = e.ADDED
174        e.is_directory = True
175        os.mkdir(path)
176
177    # Try to guess when a directory should be removed.
178    # xxx This is also kind of fragile
179    def _delParents(self, name, changeset):
180        parent = os.path.dirname(name)
181        if parent == '': return
182
183        path = os.path.join(self.repository.basedir, parent)
184        if not os.path.exists(path): return
185        if len(os.listdir(path)) > 0: return
186
187        self.log.info('Removing dir %r' % parent)
188        e = changeset.addEntry(parent, changeset.revision)
189        e.action_kind = e.DELETED
190        e.is_directory = True
191        os.rmdir(path)
192
193        self._delParents(parent, changeset)
194
195
196    def _checkoutUpstreamRevision(self, revision):
197        force=False
198        if revision == 'INITIAL':
199            revision = self.__getNativeChanges(-1)[0]['change']
200            force=True
201        p4=self.__getP4()
202        desc=p4.describe(revision, shortForm=True)
203
204        p4.sync(self.repository.depot_path + '...@' + str(revision), force=force)
205
206        ts=self.__parseDate(desc['date'])
207
208        return Changeset(revision, ts, desc['user'], desc['description'])
Note: See TracBrowser for help on using the repository browser.