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