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