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