xref: /openbmc/openbmc/poky/meta/lib/oe/patch.py (revision eb8dc403)
1*eb8dc403SDave Cobbleyimport oe.path
2*eb8dc403SDave Cobbleyimport oe.types
3*eb8dc403SDave Cobbley
4*eb8dc403SDave Cobbleyclass NotFoundError(bb.BBHandledException):
5*eb8dc403SDave Cobbley    def __init__(self, path):
6*eb8dc403SDave Cobbley        self.path = path
7*eb8dc403SDave Cobbley
8*eb8dc403SDave Cobbley    def __str__(self):
9*eb8dc403SDave Cobbley        return "Error: %s not found." % self.path
10*eb8dc403SDave Cobbley
11*eb8dc403SDave Cobbleyclass CmdError(bb.BBHandledException):
12*eb8dc403SDave Cobbley    def __init__(self, command, exitstatus, output):
13*eb8dc403SDave Cobbley        self.command = command
14*eb8dc403SDave Cobbley        self.status = exitstatus
15*eb8dc403SDave Cobbley        self.output = output
16*eb8dc403SDave Cobbley
17*eb8dc403SDave Cobbley    def __str__(self):
18*eb8dc403SDave Cobbley        return "Command Error: '%s' exited with %d  Output:\n%s" % \
19*eb8dc403SDave Cobbley                (self.command, self.status, self.output)
20*eb8dc403SDave Cobbley
21*eb8dc403SDave Cobbley
22*eb8dc403SDave Cobbleydef runcmd(args, dir = None):
23*eb8dc403SDave Cobbley    import pipes
24*eb8dc403SDave Cobbley
25*eb8dc403SDave Cobbley    if dir:
26*eb8dc403SDave Cobbley        olddir = os.path.abspath(os.curdir)
27*eb8dc403SDave Cobbley        if not os.path.exists(dir):
28*eb8dc403SDave Cobbley            raise NotFoundError(dir)
29*eb8dc403SDave Cobbley        os.chdir(dir)
30*eb8dc403SDave Cobbley        # print("cwd: %s -> %s" % (olddir, dir))
31*eb8dc403SDave Cobbley
32*eb8dc403SDave Cobbley    try:
33*eb8dc403SDave Cobbley        args = [ pipes.quote(str(arg)) for arg in args ]
34*eb8dc403SDave Cobbley        cmd = " ".join(args)
35*eb8dc403SDave Cobbley        # print("cmd: %s" % cmd)
36*eb8dc403SDave Cobbley        (exitstatus, output) = oe.utils.getstatusoutput(cmd)
37*eb8dc403SDave Cobbley        if exitstatus != 0:
38*eb8dc403SDave Cobbley            raise CmdError(cmd, exitstatus >> 8, output)
39*eb8dc403SDave Cobbley        if " fuzz " in output:
40*eb8dc403SDave Cobbley            bb.warn("""
41*eb8dc403SDave CobbleySome of the context lines in patches were ignored. This can lead to incorrectly applied patches.
42*eb8dc403SDave CobbleyThe context lines in the patches can be updated with devtool:
43*eb8dc403SDave Cobbley
44*eb8dc403SDave Cobbley    devtool modify <recipe>
45*eb8dc403SDave Cobbley    devtool finish --force-patch-refresh <recipe> <layer_path>
46*eb8dc403SDave Cobbley
47*eb8dc403SDave CobbleyThen the updated patches and the source tree (in devtool's workspace)
48*eb8dc403SDave Cobbleyshould be reviewed to make sure the patches apply in the correct place
49*eb8dc403SDave Cobbleyand don't introduce duplicate lines (which can, and does happen
50*eb8dc403SDave Cobbleywhen some of the context is ignored). Further information:
51*eb8dc403SDave Cobbleyhttp://lists.openembedded.org/pipermail/openembedded-core/2018-March/148675.html
52*eb8dc403SDave Cobbleyhttps://bugzilla.yoctoproject.org/show_bug.cgi?id=10450
53*eb8dc403SDave CobbleyDetails:
54*eb8dc403SDave Cobbley{}""".format(output))
55*eb8dc403SDave Cobbley        return output
56*eb8dc403SDave Cobbley
57*eb8dc403SDave Cobbley    finally:
58*eb8dc403SDave Cobbley        if dir:
59*eb8dc403SDave Cobbley            os.chdir(olddir)
60*eb8dc403SDave Cobbley
61*eb8dc403SDave Cobbleyclass PatchError(Exception):
62*eb8dc403SDave Cobbley    def __init__(self, msg):
63*eb8dc403SDave Cobbley        self.msg = msg
64*eb8dc403SDave Cobbley
65*eb8dc403SDave Cobbley    def __str__(self):
66*eb8dc403SDave Cobbley        return "Patch Error: %s" % self.msg
67*eb8dc403SDave Cobbley
68*eb8dc403SDave Cobbleyclass PatchSet(object):
69*eb8dc403SDave Cobbley    defaults = {
70*eb8dc403SDave Cobbley        "strippath": 1
71*eb8dc403SDave Cobbley    }
72*eb8dc403SDave Cobbley
73*eb8dc403SDave Cobbley    def __init__(self, dir, d):
74*eb8dc403SDave Cobbley        self.dir = dir
75*eb8dc403SDave Cobbley        self.d = d
76*eb8dc403SDave Cobbley        self.patches = []
77*eb8dc403SDave Cobbley        self._current = None
78*eb8dc403SDave Cobbley
79*eb8dc403SDave Cobbley    def current(self):
80*eb8dc403SDave Cobbley        return self._current
81*eb8dc403SDave Cobbley
82*eb8dc403SDave Cobbley    def Clean(self):
83*eb8dc403SDave Cobbley        """
84*eb8dc403SDave Cobbley        Clean out the patch set.  Generally includes unapplying all
85*eb8dc403SDave Cobbley        patches and wiping out all associated metadata.
86*eb8dc403SDave Cobbley        """
87*eb8dc403SDave Cobbley        raise NotImplementedError()
88*eb8dc403SDave Cobbley
89*eb8dc403SDave Cobbley    def Import(self, patch, force):
90*eb8dc403SDave Cobbley        if not patch.get("file"):
91*eb8dc403SDave Cobbley            if not patch.get("remote"):
92*eb8dc403SDave Cobbley                raise PatchError("Patch file must be specified in patch import.")
93*eb8dc403SDave Cobbley            else:
94*eb8dc403SDave Cobbley                patch["file"] = bb.fetch2.localpath(patch["remote"], self.d)
95*eb8dc403SDave Cobbley
96*eb8dc403SDave Cobbley        for param in PatchSet.defaults:
97*eb8dc403SDave Cobbley            if not patch.get(param):
98*eb8dc403SDave Cobbley                patch[param] = PatchSet.defaults[param]
99*eb8dc403SDave Cobbley
100*eb8dc403SDave Cobbley        if patch.get("remote"):
101*eb8dc403SDave Cobbley            patch["file"] = self.d.expand(bb.fetch2.localpath(patch["remote"], self.d))
102*eb8dc403SDave Cobbley
103*eb8dc403SDave Cobbley        patch["filemd5"] = bb.utils.md5_file(patch["file"])
104*eb8dc403SDave Cobbley
105*eb8dc403SDave Cobbley    def Push(self, force):
106*eb8dc403SDave Cobbley        raise NotImplementedError()
107*eb8dc403SDave Cobbley
108*eb8dc403SDave Cobbley    def Pop(self, force):
109*eb8dc403SDave Cobbley        raise NotImplementedError()
110*eb8dc403SDave Cobbley
111*eb8dc403SDave Cobbley    def Refresh(self, remote = None, all = None):
112*eb8dc403SDave Cobbley        raise NotImplementedError()
113*eb8dc403SDave Cobbley
114*eb8dc403SDave Cobbley    @staticmethod
115*eb8dc403SDave Cobbley    def getPatchedFiles(patchfile, striplevel, srcdir=None):
116*eb8dc403SDave Cobbley        """
117*eb8dc403SDave Cobbley        Read a patch file and determine which files it will modify.
118*eb8dc403SDave Cobbley        Params:
119*eb8dc403SDave Cobbley            patchfile: the patch file to read
120*eb8dc403SDave Cobbley            striplevel: the strip level at which the patch is going to be applied
121*eb8dc403SDave Cobbley            srcdir: optional path to join onto the patched file paths
122*eb8dc403SDave Cobbley        Returns:
123*eb8dc403SDave Cobbley            A list of tuples of file path and change mode ('A' for add,
124*eb8dc403SDave Cobbley            'D' for delete or 'M' for modify)
125*eb8dc403SDave Cobbley        """
126*eb8dc403SDave Cobbley
127*eb8dc403SDave Cobbley        def patchedpath(patchline):
128*eb8dc403SDave Cobbley            filepth = patchline.split()[1]
129*eb8dc403SDave Cobbley            if filepth.endswith('/dev/null'):
130*eb8dc403SDave Cobbley                return '/dev/null'
131*eb8dc403SDave Cobbley            filesplit = filepth.split(os.sep)
132*eb8dc403SDave Cobbley            if striplevel > len(filesplit):
133*eb8dc403SDave Cobbley                bb.error('Patch %s has invalid strip level %d' % (patchfile, striplevel))
134*eb8dc403SDave Cobbley                return None
135*eb8dc403SDave Cobbley            return os.sep.join(filesplit[striplevel:])
136*eb8dc403SDave Cobbley
137*eb8dc403SDave Cobbley        for encoding in ['utf-8', 'latin-1']:
138*eb8dc403SDave Cobbley            try:
139*eb8dc403SDave Cobbley                copiedmode = False
140*eb8dc403SDave Cobbley                filelist = []
141*eb8dc403SDave Cobbley                with open(patchfile) as f:
142*eb8dc403SDave Cobbley                    for line in f:
143*eb8dc403SDave Cobbley                        if line.startswith('--- '):
144*eb8dc403SDave Cobbley                            patchpth = patchedpath(line)
145*eb8dc403SDave Cobbley                            if not patchpth:
146*eb8dc403SDave Cobbley                                break
147*eb8dc403SDave Cobbley                            if copiedmode:
148*eb8dc403SDave Cobbley                                addedfile = patchpth
149*eb8dc403SDave Cobbley                            else:
150*eb8dc403SDave Cobbley                                removedfile = patchpth
151*eb8dc403SDave Cobbley                        elif line.startswith('+++ '):
152*eb8dc403SDave Cobbley                            addedfile = patchedpath(line)
153*eb8dc403SDave Cobbley                            if not addedfile:
154*eb8dc403SDave Cobbley                                break
155*eb8dc403SDave Cobbley                        elif line.startswith('*** '):
156*eb8dc403SDave Cobbley                            copiedmode = True
157*eb8dc403SDave Cobbley                            removedfile = patchedpath(line)
158*eb8dc403SDave Cobbley                            if not removedfile:
159*eb8dc403SDave Cobbley                                break
160*eb8dc403SDave Cobbley                        else:
161*eb8dc403SDave Cobbley                            removedfile = None
162*eb8dc403SDave Cobbley                            addedfile = None
163*eb8dc403SDave Cobbley
164*eb8dc403SDave Cobbley                        if addedfile and removedfile:
165*eb8dc403SDave Cobbley                            if removedfile == '/dev/null':
166*eb8dc403SDave Cobbley                                mode = 'A'
167*eb8dc403SDave Cobbley                            elif addedfile == '/dev/null':
168*eb8dc403SDave Cobbley                                mode = 'D'
169*eb8dc403SDave Cobbley                            else:
170*eb8dc403SDave Cobbley                                mode = 'M'
171*eb8dc403SDave Cobbley                            if srcdir:
172*eb8dc403SDave Cobbley                                fullpath = os.path.abspath(os.path.join(srcdir, addedfile))
173*eb8dc403SDave Cobbley                            else:
174*eb8dc403SDave Cobbley                                fullpath = addedfile
175*eb8dc403SDave Cobbley                            filelist.append((fullpath, mode))
176*eb8dc403SDave Cobbley            except UnicodeDecodeError:
177*eb8dc403SDave Cobbley                continue
178*eb8dc403SDave Cobbley            break
179*eb8dc403SDave Cobbley        else:
180*eb8dc403SDave Cobbley            raise PatchError('Unable to decode %s' % patchfile)
181*eb8dc403SDave Cobbley
182*eb8dc403SDave Cobbley        return filelist
183*eb8dc403SDave Cobbley
184*eb8dc403SDave Cobbley
185*eb8dc403SDave Cobbleyclass PatchTree(PatchSet):
186*eb8dc403SDave Cobbley    def __init__(self, dir, d):
187*eb8dc403SDave Cobbley        PatchSet.__init__(self, dir, d)
188*eb8dc403SDave Cobbley        self.patchdir = os.path.join(self.dir, 'patches')
189*eb8dc403SDave Cobbley        self.seriespath = os.path.join(self.dir, 'patches', 'series')
190*eb8dc403SDave Cobbley        bb.utils.mkdirhier(self.patchdir)
191*eb8dc403SDave Cobbley
192*eb8dc403SDave Cobbley    def _appendPatchFile(self, patch, strippath):
193*eb8dc403SDave Cobbley        with open(self.seriespath, 'a') as f:
194*eb8dc403SDave Cobbley            f.write(os.path.basename(patch) + "," + strippath + "\n")
195*eb8dc403SDave Cobbley        shellcmd = ["cat", patch, ">" , self.patchdir + "/" + os.path.basename(patch)]
196*eb8dc403SDave Cobbley        runcmd(["sh", "-c", " ".join(shellcmd)], self.dir)
197*eb8dc403SDave Cobbley
198*eb8dc403SDave Cobbley    def _removePatch(self, p):
199*eb8dc403SDave Cobbley        patch = {}
200*eb8dc403SDave Cobbley        patch['file'] = p.split(",")[0]
201*eb8dc403SDave Cobbley        patch['strippath'] = p.split(",")[1]
202*eb8dc403SDave Cobbley        self._applypatch(patch, False, True)
203*eb8dc403SDave Cobbley
204*eb8dc403SDave Cobbley    def _removePatchFile(self, all = False):
205*eb8dc403SDave Cobbley        if not os.path.exists(self.seriespath):
206*eb8dc403SDave Cobbley            return
207*eb8dc403SDave Cobbley        with open(self.seriespath, 'r+') as f:
208*eb8dc403SDave Cobbley            patches = f.readlines()
209*eb8dc403SDave Cobbley        if all:
210*eb8dc403SDave Cobbley            for p in reversed(patches):
211*eb8dc403SDave Cobbley                self._removePatch(os.path.join(self.patchdir, p.strip()))
212*eb8dc403SDave Cobbley            patches = []
213*eb8dc403SDave Cobbley        else:
214*eb8dc403SDave Cobbley            self._removePatch(os.path.join(self.patchdir, patches[-1].strip()))
215*eb8dc403SDave Cobbley            patches.pop()
216*eb8dc403SDave Cobbley        with open(self.seriespath, 'w') as f:
217*eb8dc403SDave Cobbley            for p in patches:
218*eb8dc403SDave Cobbley                f.write(p)
219*eb8dc403SDave Cobbley
220*eb8dc403SDave Cobbley    def Import(self, patch, force = None):
221*eb8dc403SDave Cobbley        """"""
222*eb8dc403SDave Cobbley        PatchSet.Import(self, patch, force)
223*eb8dc403SDave Cobbley
224*eb8dc403SDave Cobbley        if self._current is not None:
225*eb8dc403SDave Cobbley            i = self._current + 1
226*eb8dc403SDave Cobbley        else:
227*eb8dc403SDave Cobbley            i = 0
228*eb8dc403SDave Cobbley        self.patches.insert(i, patch)
229*eb8dc403SDave Cobbley
230*eb8dc403SDave Cobbley    def _applypatch(self, patch, force = False, reverse = False, run = True):
231*eb8dc403SDave Cobbley        shellcmd = ["cat", patch['file'], "|", "patch", "--no-backup-if-mismatch", "-p", patch['strippath']]
232*eb8dc403SDave Cobbley        if reverse:
233*eb8dc403SDave Cobbley            shellcmd.append('-R')
234*eb8dc403SDave Cobbley
235*eb8dc403SDave Cobbley        if not run:
236*eb8dc403SDave Cobbley            return "sh" + "-c" + " ".join(shellcmd)
237*eb8dc403SDave Cobbley
238*eb8dc403SDave Cobbley        if not force:
239*eb8dc403SDave Cobbley            shellcmd.append('--dry-run')
240*eb8dc403SDave Cobbley
241*eb8dc403SDave Cobbley        try:
242*eb8dc403SDave Cobbley            output = runcmd(["sh", "-c", " ".join(shellcmd)], self.dir)
243*eb8dc403SDave Cobbley
244*eb8dc403SDave Cobbley            if force:
245*eb8dc403SDave Cobbley                return
246*eb8dc403SDave Cobbley
247*eb8dc403SDave Cobbley            shellcmd.pop(len(shellcmd) - 1)
248*eb8dc403SDave Cobbley            output = runcmd(["sh", "-c", " ".join(shellcmd)], self.dir)
249*eb8dc403SDave Cobbley        except CmdError as err:
250*eb8dc403SDave Cobbley            raise bb.BBHandledException("Applying '%s' failed:\n%s" %
251*eb8dc403SDave Cobbley                                        (os.path.basename(patch['file']), err.output))
252*eb8dc403SDave Cobbley
253*eb8dc403SDave Cobbley        if not reverse:
254*eb8dc403SDave Cobbley            self._appendPatchFile(patch['file'], patch['strippath'])
255*eb8dc403SDave Cobbley
256*eb8dc403SDave Cobbley        return output
257*eb8dc403SDave Cobbley
258*eb8dc403SDave Cobbley    def Push(self, force = False, all = False, run = True):
259*eb8dc403SDave Cobbley        bb.note("self._current is %s" % self._current)
260*eb8dc403SDave Cobbley        bb.note("patches is %s" % self.patches)
261*eb8dc403SDave Cobbley        if all:
262*eb8dc403SDave Cobbley            for i in self.patches:
263*eb8dc403SDave Cobbley                bb.note("applying patch %s" % i)
264*eb8dc403SDave Cobbley                self._applypatch(i, force)
265*eb8dc403SDave Cobbley                self._current = i
266*eb8dc403SDave Cobbley        else:
267*eb8dc403SDave Cobbley            if self._current is not None:
268*eb8dc403SDave Cobbley                next = self._current + 1
269*eb8dc403SDave Cobbley            else:
270*eb8dc403SDave Cobbley                next = 0
271*eb8dc403SDave Cobbley
272*eb8dc403SDave Cobbley            bb.note("applying patch %s" % self.patches[next])
273*eb8dc403SDave Cobbley            ret = self._applypatch(self.patches[next], force)
274*eb8dc403SDave Cobbley
275*eb8dc403SDave Cobbley            self._current = next
276*eb8dc403SDave Cobbley            return ret
277*eb8dc403SDave Cobbley
278*eb8dc403SDave Cobbley    def Pop(self, force = None, all = None):
279*eb8dc403SDave Cobbley        if all:
280*eb8dc403SDave Cobbley            self._removePatchFile(True)
281*eb8dc403SDave Cobbley            self._current = None
282*eb8dc403SDave Cobbley        else:
283*eb8dc403SDave Cobbley            self._removePatchFile(False)
284*eb8dc403SDave Cobbley
285*eb8dc403SDave Cobbley        if self._current == 0:
286*eb8dc403SDave Cobbley            self._current = None
287*eb8dc403SDave Cobbley
288*eb8dc403SDave Cobbley        if self._current is not None:
289*eb8dc403SDave Cobbley            self._current = self._current - 1
290*eb8dc403SDave Cobbley
291*eb8dc403SDave Cobbley    def Clean(self):
292*eb8dc403SDave Cobbley        """"""
293*eb8dc403SDave Cobbley        self.Pop(all=True)
294*eb8dc403SDave Cobbley
295*eb8dc403SDave Cobbleyclass GitApplyTree(PatchTree):
296*eb8dc403SDave Cobbley    patch_line_prefix = '%% original patch'
297*eb8dc403SDave Cobbley    ignore_commit_prefix = '%% ignore'
298*eb8dc403SDave Cobbley
299*eb8dc403SDave Cobbley    def __init__(self, dir, d):
300*eb8dc403SDave Cobbley        PatchTree.__init__(self, dir, d)
301*eb8dc403SDave Cobbley        self.commituser = d.getVar('PATCH_GIT_USER_NAME')
302*eb8dc403SDave Cobbley        self.commitemail = d.getVar('PATCH_GIT_USER_EMAIL')
303*eb8dc403SDave Cobbley
304*eb8dc403SDave Cobbley    @staticmethod
305*eb8dc403SDave Cobbley    def extractPatchHeader(patchfile):
306*eb8dc403SDave Cobbley        """
307*eb8dc403SDave Cobbley        Extract just the header lines from the top of a patch file
308*eb8dc403SDave Cobbley        """
309*eb8dc403SDave Cobbley        for encoding in ['utf-8', 'latin-1']:
310*eb8dc403SDave Cobbley            lines = []
311*eb8dc403SDave Cobbley            try:
312*eb8dc403SDave Cobbley                with open(patchfile, 'r', encoding=encoding) as f:
313*eb8dc403SDave Cobbley                    for line in f:
314*eb8dc403SDave Cobbley                        if line.startswith('Index: ') or line.startswith('diff -') or line.startswith('---'):
315*eb8dc403SDave Cobbley                            break
316*eb8dc403SDave Cobbley                        lines.append(line)
317*eb8dc403SDave Cobbley            except UnicodeDecodeError:
318*eb8dc403SDave Cobbley                continue
319*eb8dc403SDave Cobbley            break
320*eb8dc403SDave Cobbley        else:
321*eb8dc403SDave Cobbley            raise PatchError('Unable to find a character encoding to decode %s' % patchfile)
322*eb8dc403SDave Cobbley        return lines
323*eb8dc403SDave Cobbley
324*eb8dc403SDave Cobbley    @staticmethod
325*eb8dc403SDave Cobbley    def decodeAuthor(line):
326*eb8dc403SDave Cobbley        from email.header import decode_header
327*eb8dc403SDave Cobbley        authorval = line.split(':', 1)[1].strip().replace('"', '')
328*eb8dc403SDave Cobbley        result =  decode_header(authorval)[0][0]
329*eb8dc403SDave Cobbley        if hasattr(result, 'decode'):
330*eb8dc403SDave Cobbley            result = result.decode('utf-8')
331*eb8dc403SDave Cobbley        return result
332*eb8dc403SDave Cobbley
333*eb8dc403SDave Cobbley    @staticmethod
334*eb8dc403SDave Cobbley    def interpretPatchHeader(headerlines):
335*eb8dc403SDave Cobbley        import re
336*eb8dc403SDave Cobbley        author_re = re.compile('[\S ]+ <\S+@\S+\.\S+>')
337*eb8dc403SDave Cobbley        from_commit_re = re.compile('^From [a-z0-9]{40} .*')
338*eb8dc403SDave Cobbley        outlines = []
339*eb8dc403SDave Cobbley        author = None
340*eb8dc403SDave Cobbley        date = None
341*eb8dc403SDave Cobbley        subject = None
342*eb8dc403SDave Cobbley        for line in headerlines:
343*eb8dc403SDave Cobbley            if line.startswith('Subject: '):
344*eb8dc403SDave Cobbley                subject = line.split(':', 1)[1]
345*eb8dc403SDave Cobbley                # Remove any [PATCH][oe-core] etc.
346*eb8dc403SDave Cobbley                subject = re.sub(r'\[.+?\]\s*', '', subject)
347*eb8dc403SDave Cobbley                continue
348*eb8dc403SDave Cobbley            elif line.startswith('From: ') or line.startswith('Author: '):
349*eb8dc403SDave Cobbley                authorval = GitApplyTree.decodeAuthor(line)
350*eb8dc403SDave Cobbley                # git is fussy about author formatting i.e. it must be Name <email@domain>
351*eb8dc403SDave Cobbley                if author_re.match(authorval):
352*eb8dc403SDave Cobbley                    author = authorval
353*eb8dc403SDave Cobbley                    continue
354*eb8dc403SDave Cobbley            elif line.startswith('Date: '):
355*eb8dc403SDave Cobbley                if date is None:
356*eb8dc403SDave Cobbley                    dateval = line.split(':', 1)[1].strip()
357*eb8dc403SDave Cobbley                    # Very crude check for date format, since git will blow up if it's not in the right
358*eb8dc403SDave Cobbley                    # format. Without e.g. a python-dateutils dependency we can't do a whole lot more
359*eb8dc403SDave Cobbley                    if len(dateval) > 12:
360*eb8dc403SDave Cobbley                        date = dateval
361*eb8dc403SDave Cobbley                continue
362*eb8dc403SDave Cobbley            elif not author and line.lower().startswith('signed-off-by: '):
363*eb8dc403SDave Cobbley                authorval = GitApplyTree.decodeAuthor(line)
364*eb8dc403SDave Cobbley                # git is fussy about author formatting i.e. it must be Name <email@domain>
365*eb8dc403SDave Cobbley                if author_re.match(authorval):
366*eb8dc403SDave Cobbley                    author = authorval
367*eb8dc403SDave Cobbley            elif from_commit_re.match(line):
368*eb8dc403SDave Cobbley                # We don't want the From <commit> line - if it's present it will break rebasing
369*eb8dc403SDave Cobbley                continue
370*eb8dc403SDave Cobbley            outlines.append(line)
371*eb8dc403SDave Cobbley
372*eb8dc403SDave Cobbley        if not subject:
373*eb8dc403SDave Cobbley            firstline = None
374*eb8dc403SDave Cobbley            for line in headerlines:
375*eb8dc403SDave Cobbley                line = line.strip()
376*eb8dc403SDave Cobbley                if firstline:
377*eb8dc403SDave Cobbley                    if line:
378*eb8dc403SDave Cobbley                        # Second line is not blank, the first line probably isn't usable
379*eb8dc403SDave Cobbley                        firstline = None
380*eb8dc403SDave Cobbley                    break
381*eb8dc403SDave Cobbley                elif line:
382*eb8dc403SDave Cobbley                    firstline = line
383*eb8dc403SDave Cobbley            if firstline and not firstline.startswith(('#', 'Index:', 'Upstream-Status:')) and len(firstline) < 100:
384*eb8dc403SDave Cobbley                subject = firstline
385*eb8dc403SDave Cobbley
386*eb8dc403SDave Cobbley        return outlines, author, date, subject
387*eb8dc403SDave Cobbley
388*eb8dc403SDave Cobbley    @staticmethod
389*eb8dc403SDave Cobbley    def gitCommandUserOptions(cmd, commituser=None, commitemail=None, d=None):
390*eb8dc403SDave Cobbley        if d:
391*eb8dc403SDave Cobbley            commituser = d.getVar('PATCH_GIT_USER_NAME')
392*eb8dc403SDave Cobbley            commitemail = d.getVar('PATCH_GIT_USER_EMAIL')
393*eb8dc403SDave Cobbley        if commituser:
394*eb8dc403SDave Cobbley            cmd += ['-c', 'user.name="%s"' % commituser]
395*eb8dc403SDave Cobbley        if commitemail:
396*eb8dc403SDave Cobbley            cmd += ['-c', 'user.email="%s"' % commitemail]
397*eb8dc403SDave Cobbley
398*eb8dc403SDave Cobbley    @staticmethod
399*eb8dc403SDave Cobbley    def prepareCommit(patchfile, commituser=None, commitemail=None):
400*eb8dc403SDave Cobbley        """
401*eb8dc403SDave Cobbley        Prepare a git commit command line based on the header from a patch file
402*eb8dc403SDave Cobbley        (typically this is useful for patches that cannot be applied with "git am" due to formatting)
403*eb8dc403SDave Cobbley        """
404*eb8dc403SDave Cobbley        import tempfile
405*eb8dc403SDave Cobbley        # Process patch header and extract useful information
406*eb8dc403SDave Cobbley        lines = GitApplyTree.extractPatchHeader(patchfile)
407*eb8dc403SDave Cobbley        outlines, author, date, subject = GitApplyTree.interpretPatchHeader(lines)
408*eb8dc403SDave Cobbley        if not author or not subject or not date:
409*eb8dc403SDave Cobbley            try:
410*eb8dc403SDave Cobbley                shellcmd = ["git", "log", "--format=email", "--follow", "--diff-filter=A", "--", patchfile]
411*eb8dc403SDave Cobbley                out = runcmd(["sh", "-c", " ".join(shellcmd)], os.path.dirname(patchfile))
412*eb8dc403SDave Cobbley            except CmdError:
413*eb8dc403SDave Cobbley                out = None
414*eb8dc403SDave Cobbley            if out:
415*eb8dc403SDave Cobbley                _, newauthor, newdate, newsubject = GitApplyTree.interpretPatchHeader(out.splitlines())
416*eb8dc403SDave Cobbley                if not author:
417*eb8dc403SDave Cobbley                    # If we're setting the author then the date should be set as well
418*eb8dc403SDave Cobbley                    author = newauthor
419*eb8dc403SDave Cobbley                    date = newdate
420*eb8dc403SDave Cobbley                elif not date:
421*eb8dc403SDave Cobbley                    # If we don't do this we'll get the current date, at least this will be closer
422*eb8dc403SDave Cobbley                    date = newdate
423*eb8dc403SDave Cobbley                if not subject:
424*eb8dc403SDave Cobbley                    subject = newsubject
425*eb8dc403SDave Cobbley        if subject and outlines and not outlines[0].strip() == subject:
426*eb8dc403SDave Cobbley            outlines.insert(0, '%s\n\n' % subject.strip())
427*eb8dc403SDave Cobbley
428*eb8dc403SDave Cobbley        # Write out commit message to a file
429*eb8dc403SDave Cobbley        with tempfile.NamedTemporaryFile('w', delete=False) as tf:
430*eb8dc403SDave Cobbley            tmpfile = tf.name
431*eb8dc403SDave Cobbley            for line in outlines:
432*eb8dc403SDave Cobbley                tf.write(line)
433*eb8dc403SDave Cobbley        # Prepare git command
434*eb8dc403SDave Cobbley        cmd = ["git"]
435*eb8dc403SDave Cobbley        GitApplyTree.gitCommandUserOptions(cmd, commituser, commitemail)
436*eb8dc403SDave Cobbley        cmd += ["commit", "-F", tmpfile]
437*eb8dc403SDave Cobbley        # git doesn't like plain email addresses as authors
438*eb8dc403SDave Cobbley        if author and '<' in author:
439*eb8dc403SDave Cobbley            cmd.append('--author="%s"' % author)
440*eb8dc403SDave Cobbley        if date:
441*eb8dc403SDave Cobbley            cmd.append('--date="%s"' % date)
442*eb8dc403SDave Cobbley        return (tmpfile, cmd)
443*eb8dc403SDave Cobbley
444*eb8dc403SDave Cobbley    @staticmethod
445*eb8dc403SDave Cobbley    def extractPatches(tree, startcommit, outdir, paths=None):
446*eb8dc403SDave Cobbley        import tempfile
447*eb8dc403SDave Cobbley        import shutil
448*eb8dc403SDave Cobbley        import re
449*eb8dc403SDave Cobbley        tempdir = tempfile.mkdtemp(prefix='oepatch')
450*eb8dc403SDave Cobbley        try:
451*eb8dc403SDave Cobbley            shellcmd = ["git", "format-patch", "--no-signature", "--no-numbered", startcommit, "-o", tempdir]
452*eb8dc403SDave Cobbley            if paths:
453*eb8dc403SDave Cobbley                shellcmd.append('--')
454*eb8dc403SDave Cobbley                shellcmd.extend(paths)
455*eb8dc403SDave Cobbley            out = runcmd(["sh", "-c", " ".join(shellcmd)], tree)
456*eb8dc403SDave Cobbley            if out:
457*eb8dc403SDave Cobbley                for srcfile in out.split():
458*eb8dc403SDave Cobbley                    for encoding in ['utf-8', 'latin-1']:
459*eb8dc403SDave Cobbley                        patchlines = []
460*eb8dc403SDave Cobbley                        outfile = None
461*eb8dc403SDave Cobbley                        try:
462*eb8dc403SDave Cobbley                            with open(srcfile, 'r', encoding=encoding) as f:
463*eb8dc403SDave Cobbley                                for line in f:
464*eb8dc403SDave Cobbley                                    checkline = line
465*eb8dc403SDave Cobbley                                    if checkline.startswith('Subject: '):
466*eb8dc403SDave Cobbley                                        checkline = re.sub(r'\[.+?\]\s*', '', checkline[9:])
467*eb8dc403SDave Cobbley                                    if checkline.startswith(GitApplyTree.patch_line_prefix):
468*eb8dc403SDave Cobbley                                        outfile = line.split()[-1].strip()
469*eb8dc403SDave Cobbley                                        continue
470*eb8dc403SDave Cobbley                                    if checkline.startswith(GitApplyTree.ignore_commit_prefix):
471*eb8dc403SDave Cobbley                                        continue
472*eb8dc403SDave Cobbley                                    patchlines.append(line)
473*eb8dc403SDave Cobbley                        except UnicodeDecodeError:
474*eb8dc403SDave Cobbley                            continue
475*eb8dc403SDave Cobbley                        break
476*eb8dc403SDave Cobbley                    else:
477*eb8dc403SDave Cobbley                        raise PatchError('Unable to find a character encoding to decode %s' % srcfile)
478*eb8dc403SDave Cobbley
479*eb8dc403SDave Cobbley                    if not outfile:
480*eb8dc403SDave Cobbley                        outfile = os.path.basename(srcfile)
481*eb8dc403SDave Cobbley                    with open(os.path.join(outdir, outfile), 'w') as of:
482*eb8dc403SDave Cobbley                        for line in patchlines:
483*eb8dc403SDave Cobbley                            of.write(line)
484*eb8dc403SDave Cobbley        finally:
485*eb8dc403SDave Cobbley            shutil.rmtree(tempdir)
486*eb8dc403SDave Cobbley
487*eb8dc403SDave Cobbley    def _applypatch(self, patch, force = False, reverse = False, run = True):
488*eb8dc403SDave Cobbley        import shutil
489*eb8dc403SDave Cobbley
490*eb8dc403SDave Cobbley        def _applypatchhelper(shellcmd, patch, force = False, reverse = False, run = True):
491*eb8dc403SDave Cobbley            if reverse:
492*eb8dc403SDave Cobbley                shellcmd.append('-R')
493*eb8dc403SDave Cobbley
494*eb8dc403SDave Cobbley            shellcmd.append(patch['file'])
495*eb8dc403SDave Cobbley
496*eb8dc403SDave Cobbley            if not run:
497*eb8dc403SDave Cobbley                return "sh" + "-c" + " ".join(shellcmd)
498*eb8dc403SDave Cobbley
499*eb8dc403SDave Cobbley            return runcmd(["sh", "-c", " ".join(shellcmd)], self.dir)
500*eb8dc403SDave Cobbley
501*eb8dc403SDave Cobbley        # Add hooks which add a pointer to the original patch file name in the commit message
502*eb8dc403SDave Cobbley        reporoot = (runcmd("git rev-parse --show-toplevel".split(), self.dir) or '').strip()
503*eb8dc403SDave Cobbley        if not reporoot:
504*eb8dc403SDave Cobbley            raise Exception("Cannot get repository root for directory %s" % self.dir)
505*eb8dc403SDave Cobbley        hooks_dir = os.path.join(reporoot, '.git', 'hooks')
506*eb8dc403SDave Cobbley        hooks_dir_backup = hooks_dir + '.devtool-orig'
507*eb8dc403SDave Cobbley        if os.path.lexists(hooks_dir_backup):
508*eb8dc403SDave Cobbley            raise Exception("Git hooks backup directory already exists: %s" % hooks_dir_backup)
509*eb8dc403SDave Cobbley        if os.path.lexists(hooks_dir):
510*eb8dc403SDave Cobbley            shutil.move(hooks_dir, hooks_dir_backup)
511*eb8dc403SDave Cobbley        os.mkdir(hooks_dir)
512*eb8dc403SDave Cobbley        commithook = os.path.join(hooks_dir, 'commit-msg')
513*eb8dc403SDave Cobbley        applyhook = os.path.join(hooks_dir, 'applypatch-msg')
514*eb8dc403SDave Cobbley        with open(commithook, 'w') as f:
515*eb8dc403SDave Cobbley            # NOTE: the formatting here is significant; if you change it you'll also need to
516*eb8dc403SDave Cobbley            # change other places which read it back
517*eb8dc403SDave Cobbley            f.write('echo >> $1\n')
518*eb8dc403SDave Cobbley            f.write('echo "%s: $PATCHFILE" >> $1\n' % GitApplyTree.patch_line_prefix)
519*eb8dc403SDave Cobbley        os.chmod(commithook, 0o755)
520*eb8dc403SDave Cobbley        shutil.copy2(commithook, applyhook)
521*eb8dc403SDave Cobbley        try:
522*eb8dc403SDave Cobbley            patchfilevar = 'PATCHFILE="%s"' % os.path.basename(patch['file'])
523*eb8dc403SDave Cobbley            try:
524*eb8dc403SDave Cobbley                shellcmd = [patchfilevar, "git", "--work-tree=%s" % reporoot]
525*eb8dc403SDave Cobbley                self.gitCommandUserOptions(shellcmd, self.commituser, self.commitemail)
526*eb8dc403SDave Cobbley                shellcmd += ["am", "-3", "--keep-cr", "-p%s" % patch['strippath']]
527*eb8dc403SDave Cobbley                return _applypatchhelper(shellcmd, patch, force, reverse, run)
528*eb8dc403SDave Cobbley            except CmdError:
529*eb8dc403SDave Cobbley                # Need to abort the git am, or we'll still be within it at the end
530*eb8dc403SDave Cobbley                try:
531*eb8dc403SDave Cobbley                    shellcmd = ["git", "--work-tree=%s" % reporoot, "am", "--abort"]
532*eb8dc403SDave Cobbley                    runcmd(["sh", "-c", " ".join(shellcmd)], self.dir)
533*eb8dc403SDave Cobbley                except CmdError:
534*eb8dc403SDave Cobbley                    pass
535*eb8dc403SDave Cobbley                # git am won't always clean up after itself, sadly, so...
536*eb8dc403SDave Cobbley                shellcmd = ["git", "--work-tree=%s" % reporoot, "reset", "--hard", "HEAD"]
537*eb8dc403SDave Cobbley                runcmd(["sh", "-c", " ".join(shellcmd)], self.dir)
538*eb8dc403SDave Cobbley                # Also need to take care of any stray untracked files
539*eb8dc403SDave Cobbley                shellcmd = ["git", "--work-tree=%s" % reporoot, "clean", "-f"]
540*eb8dc403SDave Cobbley                runcmd(["sh", "-c", " ".join(shellcmd)], self.dir)
541*eb8dc403SDave Cobbley
542*eb8dc403SDave Cobbley                # Fall back to git apply
543*eb8dc403SDave Cobbley                shellcmd = ["git", "--git-dir=%s" % reporoot, "apply", "-p%s" % patch['strippath']]
544*eb8dc403SDave Cobbley                try:
545*eb8dc403SDave Cobbley                    output = _applypatchhelper(shellcmd, patch, force, reverse, run)
546*eb8dc403SDave Cobbley                except CmdError:
547*eb8dc403SDave Cobbley                    # Fall back to patch
548*eb8dc403SDave Cobbley                    output = PatchTree._applypatch(self, patch, force, reverse, run)
549*eb8dc403SDave Cobbley                # Add all files
550*eb8dc403SDave Cobbley                shellcmd = ["git", "add", "-f", "-A", "."]
551*eb8dc403SDave Cobbley                output += runcmd(["sh", "-c", " ".join(shellcmd)], self.dir)
552*eb8dc403SDave Cobbley                # Exclude the patches directory
553*eb8dc403SDave Cobbley                shellcmd = ["git", "reset", "HEAD", self.patchdir]
554*eb8dc403SDave Cobbley                output += runcmd(["sh", "-c", " ".join(shellcmd)], self.dir)
555*eb8dc403SDave Cobbley                # Commit the result
556*eb8dc403SDave Cobbley                (tmpfile, shellcmd) = self.prepareCommit(patch['file'], self.commituser, self.commitemail)
557*eb8dc403SDave Cobbley                try:
558*eb8dc403SDave Cobbley                    shellcmd.insert(0, patchfilevar)
559*eb8dc403SDave Cobbley                    output += runcmd(["sh", "-c", " ".join(shellcmd)], self.dir)
560*eb8dc403SDave Cobbley                finally:
561*eb8dc403SDave Cobbley                    os.remove(tmpfile)
562*eb8dc403SDave Cobbley                return output
563*eb8dc403SDave Cobbley        finally:
564*eb8dc403SDave Cobbley            shutil.rmtree(hooks_dir)
565*eb8dc403SDave Cobbley            if os.path.lexists(hooks_dir_backup):
566*eb8dc403SDave Cobbley                shutil.move(hooks_dir_backup, hooks_dir)
567*eb8dc403SDave Cobbley
568*eb8dc403SDave Cobbley
569*eb8dc403SDave Cobbleyclass QuiltTree(PatchSet):
570*eb8dc403SDave Cobbley    def _runcmd(self, args, run = True):
571*eb8dc403SDave Cobbley        quiltrc = self.d.getVar('QUILTRCFILE')
572*eb8dc403SDave Cobbley        if not run:
573*eb8dc403SDave Cobbley            return ["quilt"] + ["--quiltrc"] + [quiltrc] + args
574*eb8dc403SDave Cobbley        runcmd(["quilt"] + ["--quiltrc"] + [quiltrc] + args, self.dir)
575*eb8dc403SDave Cobbley
576*eb8dc403SDave Cobbley    def _quiltpatchpath(self, file):
577*eb8dc403SDave Cobbley        return os.path.join(self.dir, "patches", os.path.basename(file))
578*eb8dc403SDave Cobbley
579*eb8dc403SDave Cobbley
580*eb8dc403SDave Cobbley    def __init__(self, dir, d):
581*eb8dc403SDave Cobbley        PatchSet.__init__(self, dir, d)
582*eb8dc403SDave Cobbley        self.initialized = False
583*eb8dc403SDave Cobbley        p = os.path.join(self.dir, 'patches')
584*eb8dc403SDave Cobbley        if not os.path.exists(p):
585*eb8dc403SDave Cobbley            os.makedirs(p)
586*eb8dc403SDave Cobbley
587*eb8dc403SDave Cobbley    def Clean(self):
588*eb8dc403SDave Cobbley        try:
589*eb8dc403SDave Cobbley            self._runcmd(["pop", "-a", "-f"])
590*eb8dc403SDave Cobbley            oe.path.remove(os.path.join(self.dir, "patches","series"))
591*eb8dc403SDave Cobbley        except Exception:
592*eb8dc403SDave Cobbley            pass
593*eb8dc403SDave Cobbley        self.initialized = True
594*eb8dc403SDave Cobbley
595*eb8dc403SDave Cobbley    def InitFromDir(self):
596*eb8dc403SDave Cobbley        # read series -> self.patches
597*eb8dc403SDave Cobbley        seriespath = os.path.join(self.dir, 'patches', 'series')
598*eb8dc403SDave Cobbley        if not os.path.exists(self.dir):
599*eb8dc403SDave Cobbley            raise NotFoundError(self.dir)
600*eb8dc403SDave Cobbley        if os.path.exists(seriespath):
601*eb8dc403SDave Cobbley            with open(seriespath, 'r') as f:
602*eb8dc403SDave Cobbley                for line in f.readlines():
603*eb8dc403SDave Cobbley                    patch = {}
604*eb8dc403SDave Cobbley                    parts = line.strip().split()
605*eb8dc403SDave Cobbley                    patch["quiltfile"] = self._quiltpatchpath(parts[0])
606*eb8dc403SDave Cobbley                    patch["quiltfilemd5"] = bb.utils.md5_file(patch["quiltfile"])
607*eb8dc403SDave Cobbley                    if len(parts) > 1:
608*eb8dc403SDave Cobbley                        patch["strippath"] = parts[1][2:]
609*eb8dc403SDave Cobbley                    self.patches.append(patch)
610*eb8dc403SDave Cobbley
611*eb8dc403SDave Cobbley            # determine which patches are applied -> self._current
612*eb8dc403SDave Cobbley            try:
613*eb8dc403SDave Cobbley                output = runcmd(["quilt", "applied"], self.dir)
614*eb8dc403SDave Cobbley            except CmdError:
615*eb8dc403SDave Cobbley                import sys
616*eb8dc403SDave Cobbley                if sys.exc_value.output.strip() == "No patches applied":
617*eb8dc403SDave Cobbley                    return
618*eb8dc403SDave Cobbley                else:
619*eb8dc403SDave Cobbley                    raise
620*eb8dc403SDave Cobbley            output = [val for val in output.split('\n') if not val.startswith('#')]
621*eb8dc403SDave Cobbley            for patch in self.patches:
622*eb8dc403SDave Cobbley                if os.path.basename(patch["quiltfile"]) == output[-1]:
623*eb8dc403SDave Cobbley                    self._current = self.patches.index(patch)
624*eb8dc403SDave Cobbley        self.initialized = True
625*eb8dc403SDave Cobbley
626*eb8dc403SDave Cobbley    def Import(self, patch, force = None):
627*eb8dc403SDave Cobbley        if not self.initialized:
628*eb8dc403SDave Cobbley            self.InitFromDir()
629*eb8dc403SDave Cobbley        PatchSet.Import(self, patch, force)
630*eb8dc403SDave Cobbley        oe.path.symlink(patch["file"], self._quiltpatchpath(patch["file"]), force=True)
631*eb8dc403SDave Cobbley        with open(os.path.join(self.dir, "patches", "series"), "a") as f:
632*eb8dc403SDave Cobbley            f.write(os.path.basename(patch["file"]) + " -p" + patch["strippath"] + "\n")
633*eb8dc403SDave Cobbley        patch["quiltfile"] = self._quiltpatchpath(patch["file"])
634*eb8dc403SDave Cobbley        patch["quiltfilemd5"] = bb.utils.md5_file(patch["quiltfile"])
635*eb8dc403SDave Cobbley
636*eb8dc403SDave Cobbley        # TODO: determine if the file being imported:
637*eb8dc403SDave Cobbley        #      1) is already imported, and is the same
638*eb8dc403SDave Cobbley        #      2) is already imported, but differs
639*eb8dc403SDave Cobbley
640*eb8dc403SDave Cobbley        self.patches.insert(self._current or 0, patch)
641*eb8dc403SDave Cobbley
642*eb8dc403SDave Cobbley
643*eb8dc403SDave Cobbley    def Push(self, force = False, all = False, run = True):
644*eb8dc403SDave Cobbley        # quilt push [-f]
645*eb8dc403SDave Cobbley
646*eb8dc403SDave Cobbley        args = ["push"]
647*eb8dc403SDave Cobbley        if force:
648*eb8dc403SDave Cobbley            args.append("-f")
649*eb8dc403SDave Cobbley        if all:
650*eb8dc403SDave Cobbley            args.append("-a")
651*eb8dc403SDave Cobbley        if not run:
652*eb8dc403SDave Cobbley            return self._runcmd(args, run)
653*eb8dc403SDave Cobbley
654*eb8dc403SDave Cobbley        self._runcmd(args)
655*eb8dc403SDave Cobbley
656*eb8dc403SDave Cobbley        if self._current is not None:
657*eb8dc403SDave Cobbley            self._current = self._current + 1
658*eb8dc403SDave Cobbley        else:
659*eb8dc403SDave Cobbley            self._current = 0
660*eb8dc403SDave Cobbley
661*eb8dc403SDave Cobbley    def Pop(self, force = None, all = None):
662*eb8dc403SDave Cobbley        # quilt pop [-f]
663*eb8dc403SDave Cobbley        args = ["pop"]
664*eb8dc403SDave Cobbley        if force:
665*eb8dc403SDave Cobbley            args.append("-f")
666*eb8dc403SDave Cobbley        if all:
667*eb8dc403SDave Cobbley            args.append("-a")
668*eb8dc403SDave Cobbley
669*eb8dc403SDave Cobbley        self._runcmd(args)
670*eb8dc403SDave Cobbley
671*eb8dc403SDave Cobbley        if self._current == 0:
672*eb8dc403SDave Cobbley            self._current = None
673*eb8dc403SDave Cobbley
674*eb8dc403SDave Cobbley        if self._current is not None:
675*eb8dc403SDave Cobbley            self._current = self._current - 1
676*eb8dc403SDave Cobbley
677*eb8dc403SDave Cobbley    def Refresh(self, **kwargs):
678*eb8dc403SDave Cobbley        if kwargs.get("remote"):
679*eb8dc403SDave Cobbley            patch = self.patches[kwargs["patch"]]
680*eb8dc403SDave Cobbley            if not patch:
681*eb8dc403SDave Cobbley                raise PatchError("No patch found at index %s in patchset." % kwargs["patch"])
682*eb8dc403SDave Cobbley            (type, host, path, user, pswd, parm) = bb.fetch.decodeurl(patch["remote"])
683*eb8dc403SDave Cobbley            if type == "file":
684*eb8dc403SDave Cobbley                import shutil
685*eb8dc403SDave Cobbley                if not patch.get("file") and patch.get("remote"):
686*eb8dc403SDave Cobbley                    patch["file"] = bb.fetch2.localpath(patch["remote"], self.d)
687*eb8dc403SDave Cobbley
688*eb8dc403SDave Cobbley                shutil.copyfile(patch["quiltfile"], patch["file"])
689*eb8dc403SDave Cobbley            else:
690*eb8dc403SDave Cobbley                raise PatchError("Unable to do a remote refresh of %s, unsupported remote url scheme %s." % (os.path.basename(patch["quiltfile"]), type))
691*eb8dc403SDave Cobbley        else:
692*eb8dc403SDave Cobbley            # quilt refresh
693*eb8dc403SDave Cobbley            args = ["refresh"]
694*eb8dc403SDave Cobbley            if kwargs.get("quiltfile"):
695*eb8dc403SDave Cobbley                args.append(os.path.basename(kwargs["quiltfile"]))
696*eb8dc403SDave Cobbley            elif kwargs.get("patch"):
697*eb8dc403SDave Cobbley                args.append(os.path.basename(self.patches[kwargs["patch"]]["quiltfile"]))
698*eb8dc403SDave Cobbley            self._runcmd(args)
699*eb8dc403SDave Cobbley
700*eb8dc403SDave Cobbleyclass Resolver(object):
701*eb8dc403SDave Cobbley    def __init__(self, patchset, terminal):
702*eb8dc403SDave Cobbley        raise NotImplementedError()
703*eb8dc403SDave Cobbley
704*eb8dc403SDave Cobbley    def Resolve(self):
705*eb8dc403SDave Cobbley        raise NotImplementedError()
706*eb8dc403SDave Cobbley
707*eb8dc403SDave Cobbley    def Revert(self):
708*eb8dc403SDave Cobbley        raise NotImplementedError()
709*eb8dc403SDave Cobbley
710*eb8dc403SDave Cobbley    def Finalize(self):
711*eb8dc403SDave Cobbley        raise NotImplementedError()
712*eb8dc403SDave Cobbley
713*eb8dc403SDave Cobbleyclass NOOPResolver(Resolver):
714*eb8dc403SDave Cobbley    def __init__(self, patchset, terminal):
715*eb8dc403SDave Cobbley        self.patchset = patchset
716*eb8dc403SDave Cobbley        self.terminal = terminal
717*eb8dc403SDave Cobbley
718*eb8dc403SDave Cobbley    def Resolve(self):
719*eb8dc403SDave Cobbley        olddir = os.path.abspath(os.curdir)
720*eb8dc403SDave Cobbley        os.chdir(self.patchset.dir)
721*eb8dc403SDave Cobbley        try:
722*eb8dc403SDave Cobbley            self.patchset.Push()
723*eb8dc403SDave Cobbley        except Exception:
724*eb8dc403SDave Cobbley            import sys
725*eb8dc403SDave Cobbley            os.chdir(olddir)
726*eb8dc403SDave Cobbley            raise
727*eb8dc403SDave Cobbley
728*eb8dc403SDave Cobbley# Patch resolver which relies on the user doing all the work involved in the
729*eb8dc403SDave Cobbley# resolution, with the exception of refreshing the remote copy of the patch
730*eb8dc403SDave Cobbley# files (the urls).
731*eb8dc403SDave Cobbleyclass UserResolver(Resolver):
732*eb8dc403SDave Cobbley    def __init__(self, patchset, terminal):
733*eb8dc403SDave Cobbley        self.patchset = patchset
734*eb8dc403SDave Cobbley        self.terminal = terminal
735*eb8dc403SDave Cobbley
736*eb8dc403SDave Cobbley    # Force a push in the patchset, then drop to a shell for the user to
737*eb8dc403SDave Cobbley    # resolve any rejected hunks
738*eb8dc403SDave Cobbley    def Resolve(self):
739*eb8dc403SDave Cobbley        olddir = os.path.abspath(os.curdir)
740*eb8dc403SDave Cobbley        os.chdir(self.patchset.dir)
741*eb8dc403SDave Cobbley        try:
742*eb8dc403SDave Cobbley            self.patchset.Push(False)
743*eb8dc403SDave Cobbley        except CmdError as v:
744*eb8dc403SDave Cobbley            # Patch application failed
745*eb8dc403SDave Cobbley            patchcmd = self.patchset.Push(True, False, False)
746*eb8dc403SDave Cobbley
747*eb8dc403SDave Cobbley            t = self.patchset.d.getVar('T')
748*eb8dc403SDave Cobbley            if not t:
749*eb8dc403SDave Cobbley                bb.msg.fatal("Build", "T not set")
750*eb8dc403SDave Cobbley            bb.utils.mkdirhier(t)
751*eb8dc403SDave Cobbley            import random
752*eb8dc403SDave Cobbley            rcfile = "%s/bashrc.%s.%s" % (t, str(os.getpid()), random.random())
753*eb8dc403SDave Cobbley            with open(rcfile, "w") as f:
754*eb8dc403SDave Cobbley                f.write("echo '*** Manual patch resolution mode ***'\n")
755*eb8dc403SDave Cobbley                f.write("echo 'Dropping to a shell, so patch rejects can be fixed manually.'\n")
756*eb8dc403SDave Cobbley                f.write("echo 'Run \"quilt refresh\" when patch is corrected, press CTRL+D to exit.'\n")
757*eb8dc403SDave Cobbley                f.write("echo ''\n")
758*eb8dc403SDave Cobbley                f.write(" ".join(patchcmd) + "\n")
759*eb8dc403SDave Cobbley            os.chmod(rcfile, 0o775)
760*eb8dc403SDave Cobbley
761*eb8dc403SDave Cobbley            self.terminal("bash --rcfile " + rcfile, 'Patch Rejects: Please fix patch rejects manually', self.patchset.d)
762*eb8dc403SDave Cobbley
763*eb8dc403SDave Cobbley            # Construct a new PatchSet after the user's changes, compare the
764*eb8dc403SDave Cobbley            # sets, checking patches for modifications, and doing a remote
765*eb8dc403SDave Cobbley            # refresh on each.
766*eb8dc403SDave Cobbley            oldpatchset = self.patchset
767*eb8dc403SDave Cobbley            self.patchset = oldpatchset.__class__(self.patchset.dir, self.patchset.d)
768*eb8dc403SDave Cobbley
769*eb8dc403SDave Cobbley            for patch in self.patchset.patches:
770*eb8dc403SDave Cobbley                oldpatch = None
771*eb8dc403SDave Cobbley                for opatch in oldpatchset.patches:
772*eb8dc403SDave Cobbley                    if opatch["quiltfile"] == patch["quiltfile"]:
773*eb8dc403SDave Cobbley                        oldpatch = opatch
774*eb8dc403SDave Cobbley
775*eb8dc403SDave Cobbley                if oldpatch:
776*eb8dc403SDave Cobbley                    patch["remote"] = oldpatch["remote"]
777*eb8dc403SDave Cobbley                    if patch["quiltfile"] == oldpatch["quiltfile"]:
778*eb8dc403SDave Cobbley                        if patch["quiltfilemd5"] != oldpatch["quiltfilemd5"]:
779*eb8dc403SDave Cobbley                            bb.note("Patch %s has changed, updating remote url %s" % (os.path.basename(patch["quiltfile"]), patch["remote"]))
780*eb8dc403SDave Cobbley                            # user change?  remote refresh
781*eb8dc403SDave Cobbley                            self.patchset.Refresh(remote=True, patch=self.patchset.patches.index(patch))
782*eb8dc403SDave Cobbley                        else:
783*eb8dc403SDave Cobbley                            # User did not fix the problem.  Abort.
784*eb8dc403SDave Cobbley                            raise PatchError("Patch application failed, and user did not fix and refresh the patch.")
785*eb8dc403SDave Cobbley        except Exception:
786*eb8dc403SDave Cobbley            os.chdir(olddir)
787*eb8dc403SDave Cobbley            raise
788*eb8dc403SDave Cobbley        os.chdir(olddir)
789*eb8dc403SDave Cobbley
790*eb8dc403SDave Cobbley
791*eb8dc403SDave Cobbleydef patch_path(url, fetch, workdir, expand=True):
792*eb8dc403SDave Cobbley    """Return the local path of a patch, or None if this isn't a patch"""
793*eb8dc403SDave Cobbley
794*eb8dc403SDave Cobbley    local = fetch.localpath(url)
795*eb8dc403SDave Cobbley    base, ext = os.path.splitext(os.path.basename(local))
796*eb8dc403SDave Cobbley    if ext in ('.gz', '.bz2', '.xz', '.Z'):
797*eb8dc403SDave Cobbley        if expand:
798*eb8dc403SDave Cobbley            local = os.path.join(workdir, base)
799*eb8dc403SDave Cobbley        ext = os.path.splitext(base)[1]
800*eb8dc403SDave Cobbley
801*eb8dc403SDave Cobbley    urldata = fetch.ud[url]
802*eb8dc403SDave Cobbley    if "apply" in urldata.parm:
803*eb8dc403SDave Cobbley        apply = oe.types.boolean(urldata.parm["apply"])
804*eb8dc403SDave Cobbley        if not apply:
805*eb8dc403SDave Cobbley            return
806*eb8dc403SDave Cobbley    elif ext not in (".diff", ".patch"):
807*eb8dc403SDave Cobbley        return
808*eb8dc403SDave Cobbley
809*eb8dc403SDave Cobbley    return local
810*eb8dc403SDave Cobbley
811*eb8dc403SDave Cobbleydef src_patches(d, all=False, expand=True):
812*eb8dc403SDave Cobbley    workdir = d.getVar('WORKDIR')
813*eb8dc403SDave Cobbley    fetch = bb.fetch2.Fetch([], d)
814*eb8dc403SDave Cobbley    patches = []
815*eb8dc403SDave Cobbley    sources = []
816*eb8dc403SDave Cobbley    for url in fetch.urls:
817*eb8dc403SDave Cobbley        local = patch_path(url, fetch, workdir, expand)
818*eb8dc403SDave Cobbley        if not local:
819*eb8dc403SDave Cobbley            if all:
820*eb8dc403SDave Cobbley                local = fetch.localpath(url)
821*eb8dc403SDave Cobbley                sources.append(local)
822*eb8dc403SDave Cobbley            continue
823*eb8dc403SDave Cobbley
824*eb8dc403SDave Cobbley        urldata = fetch.ud[url]
825*eb8dc403SDave Cobbley        parm = urldata.parm
826*eb8dc403SDave Cobbley        patchname = parm.get('pname') or os.path.basename(local)
827*eb8dc403SDave Cobbley
828*eb8dc403SDave Cobbley        apply, reason = should_apply(parm, d)
829*eb8dc403SDave Cobbley        if not apply:
830*eb8dc403SDave Cobbley            if reason:
831*eb8dc403SDave Cobbley                bb.note("Patch %s %s" % (patchname, reason))
832*eb8dc403SDave Cobbley            continue
833*eb8dc403SDave Cobbley
834*eb8dc403SDave Cobbley        patchparm = {'patchname': patchname}
835*eb8dc403SDave Cobbley        if "striplevel" in parm:
836*eb8dc403SDave Cobbley            striplevel = parm["striplevel"]
837*eb8dc403SDave Cobbley        elif "pnum" in parm:
838*eb8dc403SDave Cobbley            #bb.msg.warn(None, "Deprecated usage of 'pnum' url parameter in '%s', please use 'striplevel'" % url)
839*eb8dc403SDave Cobbley            striplevel = parm["pnum"]
840*eb8dc403SDave Cobbley        else:
841*eb8dc403SDave Cobbley            striplevel = '1'
842*eb8dc403SDave Cobbley        patchparm['striplevel'] = striplevel
843*eb8dc403SDave Cobbley
844*eb8dc403SDave Cobbley        patchdir = parm.get('patchdir')
845*eb8dc403SDave Cobbley        if patchdir:
846*eb8dc403SDave Cobbley            patchparm['patchdir'] = patchdir
847*eb8dc403SDave Cobbley
848*eb8dc403SDave Cobbley        localurl = bb.fetch.encodeurl(('file', '', local, '', '', patchparm))
849*eb8dc403SDave Cobbley        patches.append(localurl)
850*eb8dc403SDave Cobbley
851*eb8dc403SDave Cobbley    if all:
852*eb8dc403SDave Cobbley        return sources
853*eb8dc403SDave Cobbley
854*eb8dc403SDave Cobbley    return patches
855*eb8dc403SDave Cobbley
856*eb8dc403SDave Cobbley
857*eb8dc403SDave Cobbleydef should_apply(parm, d):
858*eb8dc403SDave Cobbley    if "mindate" in parm or "maxdate" in parm:
859*eb8dc403SDave Cobbley        pn = d.getVar('PN')
860*eb8dc403SDave Cobbley        srcdate = d.getVar('SRCDATE_%s' % pn)
861*eb8dc403SDave Cobbley        if not srcdate:
862*eb8dc403SDave Cobbley            srcdate = d.getVar('SRCDATE')
863*eb8dc403SDave Cobbley
864*eb8dc403SDave Cobbley        if srcdate == "now":
865*eb8dc403SDave Cobbley            srcdate = d.getVar('DATE')
866*eb8dc403SDave Cobbley
867*eb8dc403SDave Cobbley        if "maxdate" in parm and parm["maxdate"] < srcdate:
868*eb8dc403SDave Cobbley            return False, 'is outdated'
869*eb8dc403SDave Cobbley
870*eb8dc403SDave Cobbley        if "mindate" in parm and parm["mindate"] > srcdate:
871*eb8dc403SDave Cobbley            return False, 'is predated'
872*eb8dc403SDave Cobbley
873*eb8dc403SDave Cobbley
874*eb8dc403SDave Cobbley    if "minrev" in parm:
875*eb8dc403SDave Cobbley        srcrev = d.getVar('SRCREV')
876*eb8dc403SDave Cobbley        if srcrev and srcrev < parm["minrev"]:
877*eb8dc403SDave Cobbley            return False, 'applies to later revisions'
878*eb8dc403SDave Cobbley
879*eb8dc403SDave Cobbley    if "maxrev" in parm:
880*eb8dc403SDave Cobbley        srcrev = d.getVar('SRCREV')
881*eb8dc403SDave Cobbley        if srcrev and srcrev > parm["maxrev"]:
882*eb8dc403SDave Cobbley            return False, 'applies to earlier revisions'
883*eb8dc403SDave Cobbley
884*eb8dc403SDave Cobbley    if "rev" in parm:
885*eb8dc403SDave Cobbley        srcrev = d.getVar('SRCREV')
886*eb8dc403SDave Cobbley        if srcrev and parm["rev"] not in srcrev:
887*eb8dc403SDave Cobbley            return False, "doesn't apply to revision"
888*eb8dc403SDave Cobbley
889*eb8dc403SDave Cobbley    if "notrev" in parm:
890*eb8dc403SDave Cobbley        srcrev = d.getVar('SRCREV')
891*eb8dc403SDave Cobbley        if srcrev and parm["notrev"] in srcrev:
892*eb8dc403SDave Cobbley            return False, "doesn't apply to revision"
893*eb8dc403SDave Cobbley
894*eb8dc403SDave Cobbley    return True, None
895*eb8dc403SDave Cobbley
896