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