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