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