1c342db35SBrad Bishop# 292b42cb3SPatrick Williams# Copyright OpenEmbedded Contributors 392b42cb3SPatrick Williams# 4c342db35SBrad Bishop# SPDX-License-Identifier: GPL-2.0-only 5c342db35SBrad Bishop# 6c342db35SBrad Bishop 78e7b46e2SPatrick Williamsimport os 88e7b46e2SPatrick Williamsimport shlex 98e7b46e2SPatrick Williamsimport subprocess 10eb8dc403SDave Cobbleyimport oe.path 11eb8dc403SDave Cobbleyimport oe.types 12eb8dc403SDave Cobbley 13eb8dc403SDave Cobbleyclass NotFoundError(bb.BBHandledException): 14eb8dc403SDave Cobbley def __init__(self, path): 15eb8dc403SDave Cobbley self.path = path 16eb8dc403SDave Cobbley 17eb8dc403SDave Cobbley def __str__(self): 18eb8dc403SDave Cobbley return "Error: %s not found." % self.path 19eb8dc403SDave Cobbley 20eb8dc403SDave Cobbleyclass CmdError(bb.BBHandledException): 21eb8dc403SDave Cobbley def __init__(self, command, exitstatus, output): 22eb8dc403SDave Cobbley self.command = command 23eb8dc403SDave Cobbley self.status = exitstatus 24eb8dc403SDave Cobbley self.output = output 25eb8dc403SDave Cobbley 26eb8dc403SDave Cobbley def __str__(self): 27eb8dc403SDave Cobbley return "Command Error: '%s' exited with %d Output:\n%s" % \ 28eb8dc403SDave Cobbley (self.command, self.status, self.output) 29eb8dc403SDave Cobbley 30eb8dc403SDave Cobbley 31eb8dc403SDave Cobbleydef runcmd(args, dir = None): 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: 408e7b46e2SPatrick Williams args = [ shlex.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 464*da295319SPatrick Williams def extractPatches(tree, startcommits, outdir, paths=None): 465eb8dc403SDave Cobbley import tempfile 466eb8dc403SDave Cobbley import shutil 467eb8dc403SDave Cobbley tempdir = tempfile.mkdtemp(prefix='oepatch') 468eb8dc403SDave Cobbley try: 469*da295319SPatrick Williams for name, rev in startcommits.items(): 470*da295319SPatrick Williams shellcmd = ["git", "format-patch", "--no-signature", "--no-numbered", rev, "-o", tempdir] 471eb8dc403SDave Cobbley if paths: 472eb8dc403SDave Cobbley shellcmd.append('--') 473eb8dc403SDave Cobbley shellcmd.extend(paths) 474*da295319SPatrick Williams out = runcmd(["sh", "-c", " ".join(shellcmd)], os.path.join(tree, name)) 475eb8dc403SDave Cobbley if out: 476eb8dc403SDave Cobbley for srcfile in out.split(): 477eb8dc403SDave Cobbley for encoding in ['utf-8', 'latin-1']: 478eb8dc403SDave Cobbley patchlines = [] 479eb8dc403SDave Cobbley outfile = None 480eb8dc403SDave Cobbley try: 481eb8dc403SDave Cobbley with open(srcfile, 'r', encoding=encoding) as f: 482eb8dc403SDave Cobbley for line in f: 4834ed12e16SAndrew Geissler if line.startswith(GitApplyTree.patch_line_prefix): 484eb8dc403SDave Cobbley outfile = line.split()[-1].strip() 485eb8dc403SDave Cobbley continue 4864ed12e16SAndrew Geissler if line.startswith(GitApplyTree.ignore_commit_prefix): 487eb8dc403SDave Cobbley continue 488eb8dc403SDave Cobbley patchlines.append(line) 489eb8dc403SDave Cobbley except UnicodeDecodeError: 490eb8dc403SDave Cobbley continue 491eb8dc403SDave Cobbley break 492eb8dc403SDave Cobbley else: 493eb8dc403SDave Cobbley raise PatchError('Unable to find a character encoding to decode %s' % srcfile) 494eb8dc403SDave Cobbley 495eb8dc403SDave Cobbley if not outfile: 496eb8dc403SDave Cobbley outfile = os.path.basename(srcfile) 497*da295319SPatrick Williams bb.utils.mkdirhier(os.path.join(outdir, name)) 498*da295319SPatrick Williams with open(os.path.join(outdir, name, outfile), 'w') as of: 499eb8dc403SDave Cobbley for line in patchlines: 500eb8dc403SDave Cobbley of.write(line) 501eb8dc403SDave Cobbley finally: 502eb8dc403SDave Cobbley shutil.rmtree(tempdir) 503eb8dc403SDave Cobbley 5048e7b46e2SPatrick Williams def _need_dirty_check(self): 5058e7b46e2SPatrick Williams fetch = bb.fetch2.Fetch([], self.d) 5068e7b46e2SPatrick Williams check_dirtyness = False 5078e7b46e2SPatrick Williams for url in fetch.urls: 5088e7b46e2SPatrick Williams url_data = fetch.ud[url] 5098e7b46e2SPatrick Williams parm = url_data.parm 5108e7b46e2SPatrick Williams # a git url with subpath param will surely be dirty 5118e7b46e2SPatrick Williams # since the git tree from which we clone will be emptied 5128e7b46e2SPatrick Williams # from all files that are not in the subpath 5138e7b46e2SPatrick Williams if url_data.type == 'git' and parm.get('subpath'): 5148e7b46e2SPatrick Williams check_dirtyness = True 5158e7b46e2SPatrick Williams return check_dirtyness 5168e7b46e2SPatrick Williams 5178e7b46e2SPatrick Williams def _commitpatch(self, patch, patchfilevar): 5188e7b46e2SPatrick Williams output = "" 5198e7b46e2SPatrick Williams # Add all files 5208e7b46e2SPatrick Williams shellcmd = ["git", "add", "-f", "-A", "."] 5218e7b46e2SPatrick Williams output += runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) 5228e7b46e2SPatrick Williams # Exclude the patches directory 5238e7b46e2SPatrick Williams shellcmd = ["git", "reset", "HEAD", self.patchdir] 5248e7b46e2SPatrick Williams output += runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) 5258e7b46e2SPatrick Williams # Commit the result 5268e7b46e2SPatrick Williams (tmpfile, shellcmd) = self.prepareCommit(patch['file'], self.commituser, self.commitemail) 5278e7b46e2SPatrick Williams try: 5288e7b46e2SPatrick Williams shellcmd.insert(0, patchfilevar) 5298e7b46e2SPatrick Williams output += runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) 5308e7b46e2SPatrick Williams finally: 5318e7b46e2SPatrick Williams os.remove(tmpfile) 5328e7b46e2SPatrick Williams return output 5338e7b46e2SPatrick Williams 534eb8dc403SDave Cobbley def _applypatch(self, patch, force = False, reverse = False, run = True): 535eb8dc403SDave Cobbley import shutil 536eb8dc403SDave Cobbley 537eb8dc403SDave Cobbley def _applypatchhelper(shellcmd, patch, force = False, reverse = False, run = True): 538eb8dc403SDave Cobbley if reverse: 539eb8dc403SDave Cobbley shellcmd.append('-R') 540eb8dc403SDave Cobbley 541eb8dc403SDave Cobbley shellcmd.append(patch['file']) 542eb8dc403SDave Cobbley 543eb8dc403SDave Cobbley if not run: 544eb8dc403SDave Cobbley return "sh" + "-c" + " ".join(shellcmd) 545eb8dc403SDave Cobbley 546eb8dc403SDave Cobbley return runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) 547eb8dc403SDave Cobbley 548eb8dc403SDave Cobbley # Add hooks which add a pointer to the original patch file name in the commit message 549eb8dc403SDave Cobbley reporoot = (runcmd("git rev-parse --show-toplevel".split(), self.dir) or '').strip() 550eb8dc403SDave Cobbley if not reporoot: 551eb8dc403SDave Cobbley raise Exception("Cannot get repository root for directory %s" % self.dir) 5525082cc7fSAndrew Geissler gitdir = (runcmd("git rev-parse --absolute-git-dir".split(), self.dir) or '').strip() 5535082cc7fSAndrew Geissler if not gitdir: 5545082cc7fSAndrew Geissler raise Exception("Cannot get gitdir for directory %s" % self.dir) 5555082cc7fSAndrew Geissler hooks_dir = os.path.join(gitdir, 'hooks') 556eb8dc403SDave Cobbley hooks_dir_backup = hooks_dir + '.devtool-orig' 557eb8dc403SDave Cobbley if os.path.lexists(hooks_dir_backup): 558eb8dc403SDave Cobbley raise Exception("Git hooks backup directory already exists: %s" % hooks_dir_backup) 559eb8dc403SDave Cobbley if os.path.lexists(hooks_dir): 560eb8dc403SDave Cobbley shutil.move(hooks_dir, hooks_dir_backup) 561eb8dc403SDave Cobbley os.mkdir(hooks_dir) 562eb8dc403SDave Cobbley commithook = os.path.join(hooks_dir, 'commit-msg') 563eb8dc403SDave Cobbley applyhook = os.path.join(hooks_dir, 'applypatch-msg') 564eb8dc403SDave Cobbley with open(commithook, 'w') as f: 565eb8dc403SDave Cobbley # NOTE: the formatting here is significant; if you change it you'll also need to 566eb8dc403SDave Cobbley # change other places which read it back 5674ed12e16SAndrew Geissler f.write('echo "\n%s: $PATCHFILE" >> $1' % GitApplyTree.patch_line_prefix) 568eb8dc403SDave Cobbley os.chmod(commithook, 0o755) 569eb8dc403SDave Cobbley shutil.copy2(commithook, applyhook) 570eb8dc403SDave Cobbley try: 571eb8dc403SDave Cobbley patchfilevar = 'PATCHFILE="%s"' % os.path.basename(patch['file']) 5728e7b46e2SPatrick Williams if self._need_dirty_check(): 5738e7b46e2SPatrick Williams # Check dirtyness of the tree 5748e7b46e2SPatrick Williams try: 5758e7b46e2SPatrick Williams output = runcmd(["git", "--work-tree=%s" % reporoot, "status", "--short"]) 5768e7b46e2SPatrick Williams except CmdError: 5778e7b46e2SPatrick Williams pass 5788e7b46e2SPatrick Williams else: 5798e7b46e2SPatrick Williams if output: 5808e7b46e2SPatrick Williams # The tree is dirty, not need to try to apply patches with git anymore 5818e7b46e2SPatrick Williams # since they fail, fallback directly to patch 5828e7b46e2SPatrick Williams output = PatchTree._applypatch(self, patch, force, reverse, run) 5838e7b46e2SPatrick Williams output += self._commitpatch(patch, patchfilevar) 5848e7b46e2SPatrick Williams return output 585eb8dc403SDave Cobbley try: 586eb8dc403SDave Cobbley shellcmd = [patchfilevar, "git", "--work-tree=%s" % reporoot] 587eb8dc403SDave Cobbley self.gitCommandUserOptions(shellcmd, self.commituser, self.commitemail) 588d1e89497SAndrew Geissler shellcmd += ["am", "-3", "--keep-cr", "--no-scissors", "-p%s" % patch['strippath']] 589eb8dc403SDave Cobbley return _applypatchhelper(shellcmd, patch, force, reverse, run) 590eb8dc403SDave Cobbley except CmdError: 591eb8dc403SDave Cobbley # Need to abort the git am, or we'll still be within it at the end 592eb8dc403SDave Cobbley try: 593eb8dc403SDave Cobbley shellcmd = ["git", "--work-tree=%s" % reporoot, "am", "--abort"] 594eb8dc403SDave Cobbley runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) 595eb8dc403SDave Cobbley except CmdError: 596eb8dc403SDave Cobbley pass 597eb8dc403SDave Cobbley # git am won't always clean up after itself, sadly, so... 598eb8dc403SDave Cobbley shellcmd = ["git", "--work-tree=%s" % reporoot, "reset", "--hard", "HEAD"] 599eb8dc403SDave Cobbley runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) 600eb8dc403SDave Cobbley # Also need to take care of any stray untracked files 601eb8dc403SDave Cobbley shellcmd = ["git", "--work-tree=%s" % reporoot, "clean", "-f"] 602eb8dc403SDave Cobbley runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) 603eb8dc403SDave Cobbley 604eb8dc403SDave Cobbley # Fall back to git apply 605eb8dc403SDave Cobbley shellcmd = ["git", "--git-dir=%s" % reporoot, "apply", "-p%s" % patch['strippath']] 606eb8dc403SDave Cobbley try: 607eb8dc403SDave Cobbley output = _applypatchhelper(shellcmd, patch, force, reverse, run) 608eb8dc403SDave Cobbley except CmdError: 609eb8dc403SDave Cobbley # Fall back to patch 610eb8dc403SDave Cobbley output = PatchTree._applypatch(self, patch, force, reverse, run) 6118e7b46e2SPatrick Williams output += self._commitpatch(patch, patchfilevar) 612eb8dc403SDave Cobbley return output 613eb8dc403SDave Cobbley finally: 614eb8dc403SDave Cobbley shutil.rmtree(hooks_dir) 615eb8dc403SDave Cobbley if os.path.lexists(hooks_dir_backup): 616eb8dc403SDave Cobbley shutil.move(hooks_dir_backup, hooks_dir) 617eb8dc403SDave Cobbley 618eb8dc403SDave Cobbley 619eb8dc403SDave Cobbleyclass QuiltTree(PatchSet): 620eb8dc403SDave Cobbley def _runcmd(self, args, run = True): 621eb8dc403SDave Cobbley quiltrc = self.d.getVar('QUILTRCFILE') 622eb8dc403SDave Cobbley if not run: 623eb8dc403SDave Cobbley return ["quilt"] + ["--quiltrc"] + [quiltrc] + args 624eb8dc403SDave Cobbley runcmd(["quilt"] + ["--quiltrc"] + [quiltrc] + args, self.dir) 625eb8dc403SDave Cobbley 626eb8dc403SDave Cobbley def _quiltpatchpath(self, file): 627eb8dc403SDave Cobbley return os.path.join(self.dir, "patches", os.path.basename(file)) 628eb8dc403SDave Cobbley 629eb8dc403SDave Cobbley 630eb8dc403SDave Cobbley def __init__(self, dir, d): 631eb8dc403SDave Cobbley PatchSet.__init__(self, dir, d) 632eb8dc403SDave Cobbley self.initialized = False 633eb8dc403SDave Cobbley p = os.path.join(self.dir, 'patches') 634eb8dc403SDave Cobbley if not os.path.exists(p): 635eb8dc403SDave Cobbley os.makedirs(p) 636eb8dc403SDave Cobbley 637eb8dc403SDave Cobbley def Clean(self): 638eb8dc403SDave Cobbley try: 63978b72798SAndrew Geissler # make sure that patches/series file exists before quilt pop to keep quilt-0.67 happy 64078b72798SAndrew Geissler open(os.path.join(self.dir, "patches","series"), 'a').close() 641eb8dc403SDave Cobbley self._runcmd(["pop", "-a", "-f"]) 642eb8dc403SDave Cobbley oe.path.remove(os.path.join(self.dir, "patches","series")) 643eb8dc403SDave Cobbley except Exception: 644eb8dc403SDave Cobbley pass 645eb8dc403SDave Cobbley self.initialized = True 646eb8dc403SDave Cobbley 647eb8dc403SDave Cobbley def InitFromDir(self): 648eb8dc403SDave Cobbley # read series -> self.patches 649eb8dc403SDave Cobbley seriespath = os.path.join(self.dir, 'patches', 'series') 650eb8dc403SDave Cobbley if not os.path.exists(self.dir): 651eb8dc403SDave Cobbley raise NotFoundError(self.dir) 652eb8dc403SDave Cobbley if os.path.exists(seriespath): 653eb8dc403SDave Cobbley with open(seriespath, 'r') as f: 654eb8dc403SDave Cobbley for line in f.readlines(): 655eb8dc403SDave Cobbley patch = {} 656eb8dc403SDave Cobbley parts = line.strip().split() 657eb8dc403SDave Cobbley patch["quiltfile"] = self._quiltpatchpath(parts[0]) 658eb8dc403SDave Cobbley patch["quiltfilemd5"] = bb.utils.md5_file(patch["quiltfile"]) 659eb8dc403SDave Cobbley if len(parts) > 1: 660eb8dc403SDave Cobbley patch["strippath"] = parts[1][2:] 661eb8dc403SDave Cobbley self.patches.append(patch) 662eb8dc403SDave Cobbley 663eb8dc403SDave Cobbley # determine which patches are applied -> self._current 664eb8dc403SDave Cobbley try: 665eb8dc403SDave Cobbley output = runcmd(["quilt", "applied"], self.dir) 666eb8dc403SDave Cobbley except CmdError: 667eb8dc403SDave Cobbley import sys 668eb8dc403SDave Cobbley if sys.exc_value.output.strip() == "No patches applied": 669eb8dc403SDave Cobbley return 670eb8dc403SDave Cobbley else: 671eb8dc403SDave Cobbley raise 672eb8dc403SDave Cobbley output = [val for val in output.split('\n') if not val.startswith('#')] 673eb8dc403SDave Cobbley for patch in self.patches: 674eb8dc403SDave Cobbley if os.path.basename(patch["quiltfile"]) == output[-1]: 675eb8dc403SDave Cobbley self._current = self.patches.index(patch) 676eb8dc403SDave Cobbley self.initialized = True 677eb8dc403SDave Cobbley 678eb8dc403SDave Cobbley def Import(self, patch, force = None): 679eb8dc403SDave Cobbley if not self.initialized: 680eb8dc403SDave Cobbley self.InitFromDir() 681eb8dc403SDave Cobbley PatchSet.Import(self, patch, force) 682eb8dc403SDave Cobbley oe.path.symlink(patch["file"], self._quiltpatchpath(patch["file"]), force=True) 683eb8dc403SDave Cobbley with open(os.path.join(self.dir, "patches", "series"), "a") as f: 684eb8dc403SDave Cobbley f.write(os.path.basename(patch["file"]) + " -p" + patch["strippath"] + "\n") 685eb8dc403SDave Cobbley patch["quiltfile"] = self._quiltpatchpath(patch["file"]) 686eb8dc403SDave Cobbley patch["quiltfilemd5"] = bb.utils.md5_file(patch["quiltfile"]) 687eb8dc403SDave Cobbley 688eb8dc403SDave Cobbley # TODO: determine if the file being imported: 689eb8dc403SDave Cobbley # 1) is already imported, and is the same 690eb8dc403SDave Cobbley # 2) is already imported, but differs 691eb8dc403SDave Cobbley 692eb8dc403SDave Cobbley self.patches.insert(self._current or 0, patch) 693eb8dc403SDave Cobbley 694eb8dc403SDave Cobbley 695eb8dc403SDave Cobbley def Push(self, force = False, all = False, run = True): 696eb8dc403SDave Cobbley # quilt push [-f] 697eb8dc403SDave Cobbley 698eb8dc403SDave Cobbley args = ["push"] 699eb8dc403SDave Cobbley if force: 700eb8dc403SDave Cobbley args.append("-f") 701eb8dc403SDave Cobbley if all: 702eb8dc403SDave Cobbley args.append("-a") 703eb8dc403SDave Cobbley if not run: 704eb8dc403SDave Cobbley return self._runcmd(args, run) 705eb8dc403SDave Cobbley 706eb8dc403SDave Cobbley self._runcmd(args) 707eb8dc403SDave Cobbley 708eb8dc403SDave Cobbley if self._current is not None: 709eb8dc403SDave Cobbley self._current = self._current + 1 710eb8dc403SDave Cobbley else: 711eb8dc403SDave Cobbley self._current = 0 712eb8dc403SDave Cobbley 713eb8dc403SDave Cobbley def Pop(self, force = None, all = None): 714eb8dc403SDave Cobbley # quilt pop [-f] 715eb8dc403SDave Cobbley args = ["pop"] 716eb8dc403SDave Cobbley if force: 717eb8dc403SDave Cobbley args.append("-f") 718eb8dc403SDave Cobbley if all: 719eb8dc403SDave Cobbley args.append("-a") 720eb8dc403SDave Cobbley 721eb8dc403SDave Cobbley self._runcmd(args) 722eb8dc403SDave Cobbley 723eb8dc403SDave Cobbley if self._current == 0: 724eb8dc403SDave Cobbley self._current = None 725eb8dc403SDave Cobbley 726eb8dc403SDave Cobbley if self._current is not None: 727eb8dc403SDave Cobbley self._current = self._current - 1 728eb8dc403SDave Cobbley 729eb8dc403SDave Cobbley def Refresh(self, **kwargs): 730eb8dc403SDave Cobbley if kwargs.get("remote"): 731eb8dc403SDave Cobbley patch = self.patches[kwargs["patch"]] 732eb8dc403SDave Cobbley if not patch: 733eb8dc403SDave Cobbley raise PatchError("No patch found at index %s in patchset." % kwargs["patch"]) 734eb8dc403SDave Cobbley (type, host, path, user, pswd, parm) = bb.fetch.decodeurl(patch["remote"]) 735eb8dc403SDave Cobbley if type == "file": 736eb8dc403SDave Cobbley import shutil 737eb8dc403SDave Cobbley if not patch.get("file") and patch.get("remote"): 738eb8dc403SDave Cobbley patch["file"] = bb.fetch2.localpath(patch["remote"], self.d) 739eb8dc403SDave Cobbley 740eb8dc403SDave Cobbley shutil.copyfile(patch["quiltfile"], patch["file"]) 741eb8dc403SDave Cobbley else: 742eb8dc403SDave Cobbley raise PatchError("Unable to do a remote refresh of %s, unsupported remote url scheme %s." % (os.path.basename(patch["quiltfile"]), type)) 743eb8dc403SDave Cobbley else: 744eb8dc403SDave Cobbley # quilt refresh 745eb8dc403SDave Cobbley args = ["refresh"] 746eb8dc403SDave Cobbley if kwargs.get("quiltfile"): 747eb8dc403SDave Cobbley args.append(os.path.basename(kwargs["quiltfile"])) 748eb8dc403SDave Cobbley elif kwargs.get("patch"): 749eb8dc403SDave Cobbley args.append(os.path.basename(self.patches[kwargs["patch"]]["quiltfile"])) 750eb8dc403SDave Cobbley self._runcmd(args) 751eb8dc403SDave Cobbley 752eb8dc403SDave Cobbleyclass Resolver(object): 753eb8dc403SDave Cobbley def __init__(self, patchset, terminal): 754eb8dc403SDave Cobbley raise NotImplementedError() 755eb8dc403SDave Cobbley 756eb8dc403SDave Cobbley def Resolve(self): 757eb8dc403SDave Cobbley raise NotImplementedError() 758eb8dc403SDave Cobbley 759eb8dc403SDave Cobbley def Revert(self): 760eb8dc403SDave Cobbley raise NotImplementedError() 761eb8dc403SDave Cobbley 762eb8dc403SDave Cobbley def Finalize(self): 763eb8dc403SDave Cobbley raise NotImplementedError() 764eb8dc403SDave Cobbley 765eb8dc403SDave Cobbleyclass NOOPResolver(Resolver): 766eb8dc403SDave Cobbley def __init__(self, patchset, terminal): 767eb8dc403SDave Cobbley self.patchset = patchset 768eb8dc403SDave Cobbley self.terminal = terminal 769eb8dc403SDave Cobbley 770eb8dc403SDave Cobbley def Resolve(self): 771eb8dc403SDave Cobbley olddir = os.path.abspath(os.curdir) 772eb8dc403SDave Cobbley os.chdir(self.patchset.dir) 773eb8dc403SDave Cobbley try: 774eb8dc403SDave Cobbley self.patchset.Push() 775eb8dc403SDave Cobbley except Exception: 776eb8dc403SDave Cobbley import sys 777eb8dc403SDave Cobbley raise 778ac13d5f3SPatrick Williams finally: 779ac13d5f3SPatrick Williams os.chdir(olddir) 780eb8dc403SDave Cobbley 781eb8dc403SDave Cobbley# Patch resolver which relies on the user doing all the work involved in the 782eb8dc403SDave Cobbley# resolution, with the exception of refreshing the remote copy of the patch 783eb8dc403SDave Cobbley# files (the urls). 784eb8dc403SDave Cobbleyclass UserResolver(Resolver): 785eb8dc403SDave Cobbley def __init__(self, patchset, terminal): 786eb8dc403SDave Cobbley self.patchset = patchset 787eb8dc403SDave Cobbley self.terminal = terminal 788eb8dc403SDave Cobbley 789eb8dc403SDave Cobbley # Force a push in the patchset, then drop to a shell for the user to 790eb8dc403SDave Cobbley # resolve any rejected hunks 791eb8dc403SDave Cobbley def Resolve(self): 792eb8dc403SDave Cobbley olddir = os.path.abspath(os.curdir) 793eb8dc403SDave Cobbley os.chdir(self.patchset.dir) 794eb8dc403SDave Cobbley try: 795eb8dc403SDave Cobbley self.patchset.Push(False) 796eb8dc403SDave Cobbley except CmdError as v: 797eb8dc403SDave Cobbley # Patch application failed 798eb8dc403SDave Cobbley patchcmd = self.patchset.Push(True, False, False) 799eb8dc403SDave Cobbley 800eb8dc403SDave Cobbley t = self.patchset.d.getVar('T') 801eb8dc403SDave Cobbley if not t: 802eb8dc403SDave Cobbley bb.msg.fatal("Build", "T not set") 803eb8dc403SDave Cobbley bb.utils.mkdirhier(t) 804eb8dc403SDave Cobbley import random 805eb8dc403SDave Cobbley rcfile = "%s/bashrc.%s.%s" % (t, str(os.getpid()), random.random()) 806eb8dc403SDave Cobbley with open(rcfile, "w") as f: 807eb8dc403SDave Cobbley f.write("echo '*** Manual patch resolution mode ***'\n") 808eb8dc403SDave Cobbley f.write("echo 'Dropping to a shell, so patch rejects can be fixed manually.'\n") 809eb8dc403SDave Cobbley f.write("echo 'Run \"quilt refresh\" when patch is corrected, press CTRL+D to exit.'\n") 810eb8dc403SDave Cobbley f.write("echo ''\n") 811eb8dc403SDave Cobbley f.write(" ".join(patchcmd) + "\n") 812eb8dc403SDave Cobbley os.chmod(rcfile, 0o775) 813eb8dc403SDave Cobbley 814eb8dc403SDave Cobbley self.terminal("bash --rcfile " + rcfile, 'Patch Rejects: Please fix patch rejects manually', self.patchset.d) 815eb8dc403SDave Cobbley 816eb8dc403SDave Cobbley # Construct a new PatchSet after the user's changes, compare the 817eb8dc403SDave Cobbley # sets, checking patches for modifications, and doing a remote 818eb8dc403SDave Cobbley # refresh on each. 819eb8dc403SDave Cobbley oldpatchset = self.patchset 820eb8dc403SDave Cobbley self.patchset = oldpatchset.__class__(self.patchset.dir, self.patchset.d) 821eb8dc403SDave Cobbley 822eb8dc403SDave Cobbley for patch in self.patchset.patches: 823eb8dc403SDave Cobbley oldpatch = None 824eb8dc403SDave Cobbley for opatch in oldpatchset.patches: 825eb8dc403SDave Cobbley if opatch["quiltfile"] == patch["quiltfile"]: 826eb8dc403SDave Cobbley oldpatch = opatch 827eb8dc403SDave Cobbley 828eb8dc403SDave Cobbley if oldpatch: 829eb8dc403SDave Cobbley patch["remote"] = oldpatch["remote"] 830eb8dc403SDave Cobbley if patch["quiltfile"] == oldpatch["quiltfile"]: 831eb8dc403SDave Cobbley if patch["quiltfilemd5"] != oldpatch["quiltfilemd5"]: 832eb8dc403SDave Cobbley bb.note("Patch %s has changed, updating remote url %s" % (os.path.basename(patch["quiltfile"]), patch["remote"])) 833eb8dc403SDave Cobbley # user change? remote refresh 834eb8dc403SDave Cobbley self.patchset.Refresh(remote=True, patch=self.patchset.patches.index(patch)) 835eb8dc403SDave Cobbley else: 836eb8dc403SDave Cobbley # User did not fix the problem. Abort. 837eb8dc403SDave Cobbley raise PatchError("Patch application failed, and user did not fix and refresh the patch.") 838eb8dc403SDave Cobbley except Exception: 839eb8dc403SDave Cobbley raise 840ac13d5f3SPatrick Williams finally: 841eb8dc403SDave Cobbley os.chdir(olddir) 842eb8dc403SDave Cobbley 843eb8dc403SDave Cobbley 844eb8dc403SDave Cobbleydef patch_path(url, fetch, workdir, expand=True): 84519323693SBrad Bishop """Return the local path of a patch, or return nothing if this isn't a patch""" 846eb8dc403SDave Cobbley 847eb8dc403SDave Cobbley local = fetch.localpath(url) 84819323693SBrad Bishop if os.path.isdir(local): 84919323693SBrad Bishop return 850eb8dc403SDave Cobbley base, ext = os.path.splitext(os.path.basename(local)) 851eb8dc403SDave Cobbley if ext in ('.gz', '.bz2', '.xz', '.Z'): 852eb8dc403SDave Cobbley if expand: 853eb8dc403SDave Cobbley local = os.path.join(workdir, base) 854eb8dc403SDave Cobbley ext = os.path.splitext(base)[1] 855eb8dc403SDave Cobbley 856eb8dc403SDave Cobbley urldata = fetch.ud[url] 857eb8dc403SDave Cobbley if "apply" in urldata.parm: 858eb8dc403SDave Cobbley apply = oe.types.boolean(urldata.parm["apply"]) 859eb8dc403SDave Cobbley if not apply: 860eb8dc403SDave Cobbley return 861eb8dc403SDave Cobbley elif ext not in (".diff", ".patch"): 862eb8dc403SDave Cobbley return 863eb8dc403SDave Cobbley 864eb8dc403SDave Cobbley return local 865eb8dc403SDave Cobbley 866eb8dc403SDave Cobbleydef src_patches(d, all=False, expand=True): 867eb8dc403SDave Cobbley workdir = d.getVar('WORKDIR') 868eb8dc403SDave Cobbley fetch = bb.fetch2.Fetch([], d) 869eb8dc403SDave Cobbley patches = [] 870eb8dc403SDave Cobbley sources = [] 871eb8dc403SDave Cobbley for url in fetch.urls: 872eb8dc403SDave Cobbley local = patch_path(url, fetch, workdir, expand) 873eb8dc403SDave Cobbley if not local: 874eb8dc403SDave Cobbley if all: 875eb8dc403SDave Cobbley local = fetch.localpath(url) 876eb8dc403SDave Cobbley sources.append(local) 877eb8dc403SDave Cobbley continue 878eb8dc403SDave Cobbley 879eb8dc403SDave Cobbley urldata = fetch.ud[url] 880eb8dc403SDave Cobbley parm = urldata.parm 881eb8dc403SDave Cobbley patchname = parm.get('pname') or os.path.basename(local) 882eb8dc403SDave Cobbley 883eb8dc403SDave Cobbley apply, reason = should_apply(parm, d) 884eb8dc403SDave Cobbley if not apply: 885eb8dc403SDave Cobbley if reason: 886eb8dc403SDave Cobbley bb.note("Patch %s %s" % (patchname, reason)) 887eb8dc403SDave Cobbley continue 888eb8dc403SDave Cobbley 889eb8dc403SDave Cobbley patchparm = {'patchname': patchname} 890eb8dc403SDave Cobbley if "striplevel" in parm: 891eb8dc403SDave Cobbley striplevel = parm["striplevel"] 892eb8dc403SDave Cobbley elif "pnum" in parm: 893eb8dc403SDave Cobbley #bb.msg.warn(None, "Deprecated usage of 'pnum' url parameter in '%s', please use 'striplevel'" % url) 894eb8dc403SDave Cobbley striplevel = parm["pnum"] 895eb8dc403SDave Cobbley else: 896eb8dc403SDave Cobbley striplevel = '1' 897eb8dc403SDave Cobbley patchparm['striplevel'] = striplevel 898eb8dc403SDave Cobbley 899eb8dc403SDave Cobbley patchdir = parm.get('patchdir') 900eb8dc403SDave Cobbley if patchdir: 901eb8dc403SDave Cobbley patchparm['patchdir'] = patchdir 902eb8dc403SDave Cobbley 903eb8dc403SDave Cobbley localurl = bb.fetch.encodeurl(('file', '', local, '', '', patchparm)) 904eb8dc403SDave Cobbley patches.append(localurl) 905eb8dc403SDave Cobbley 906eb8dc403SDave Cobbley if all: 907eb8dc403SDave Cobbley return sources 908eb8dc403SDave Cobbley 909eb8dc403SDave Cobbley return patches 910eb8dc403SDave Cobbley 911eb8dc403SDave Cobbley 912eb8dc403SDave Cobbleydef should_apply(parm, d): 913c342db35SBrad Bishop import bb.utils 914eb8dc403SDave Cobbley if "mindate" in parm or "maxdate" in parm: 915eb8dc403SDave Cobbley pn = d.getVar('PN') 916eb8dc403SDave Cobbley srcdate = d.getVar('SRCDATE_%s' % pn) 917eb8dc403SDave Cobbley if not srcdate: 918eb8dc403SDave Cobbley srcdate = d.getVar('SRCDATE') 919eb8dc403SDave Cobbley 920eb8dc403SDave Cobbley if srcdate == "now": 921eb8dc403SDave Cobbley srcdate = d.getVar('DATE') 922eb8dc403SDave Cobbley 923eb8dc403SDave Cobbley if "maxdate" in parm and parm["maxdate"] < srcdate: 924eb8dc403SDave Cobbley return False, 'is outdated' 925eb8dc403SDave Cobbley 926eb8dc403SDave Cobbley if "mindate" in parm and parm["mindate"] > srcdate: 927eb8dc403SDave Cobbley return False, 'is predated' 928eb8dc403SDave Cobbley 929eb8dc403SDave Cobbley 930eb8dc403SDave Cobbley if "minrev" in parm: 931eb8dc403SDave Cobbley srcrev = d.getVar('SRCREV') 932eb8dc403SDave Cobbley if srcrev and srcrev < parm["minrev"]: 933eb8dc403SDave Cobbley return False, 'applies to later revisions' 934eb8dc403SDave Cobbley 935eb8dc403SDave Cobbley if "maxrev" in parm: 936eb8dc403SDave Cobbley srcrev = d.getVar('SRCREV') 937eb8dc403SDave Cobbley if srcrev and srcrev > parm["maxrev"]: 938eb8dc403SDave Cobbley return False, 'applies to earlier revisions' 939eb8dc403SDave Cobbley 940eb8dc403SDave Cobbley if "rev" in parm: 941eb8dc403SDave Cobbley srcrev = d.getVar('SRCREV') 942eb8dc403SDave Cobbley if srcrev and parm["rev"] not in srcrev: 943eb8dc403SDave Cobbley return False, "doesn't apply to revision" 944eb8dc403SDave Cobbley 945eb8dc403SDave Cobbley if "notrev" in parm: 946eb8dc403SDave Cobbley srcrev = d.getVar('SRCREV') 947eb8dc403SDave Cobbley if srcrev and parm["notrev"] in srcrev: 948eb8dc403SDave Cobbley return False, "doesn't apply to revision" 949eb8dc403SDave Cobbley 950c342db35SBrad Bishop if "maxver" in parm: 951c342db35SBrad Bishop pv = d.getVar('PV') 952c342db35SBrad Bishop if bb.utils.vercmp_string_op(pv, parm["maxver"], ">"): 953c342db35SBrad Bishop return False, "applies to earlier version" 954c342db35SBrad Bishop 955c342db35SBrad Bishop if "minver" in parm: 956c342db35SBrad Bishop pv = d.getVar('PV') 957c342db35SBrad Bishop if bb.utils.vercmp_string_op(pv, parm["minver"], "<"): 958c342db35SBrad Bishop return False, "applies to later version" 959c342db35SBrad Bishop 960eb8dc403SDave Cobbley return True, None 961