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