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): 297*73bd93f1SPatrick Williams notes_ref = "refs/notes/devtool" 298*73bd93f1SPatrick Williams original_patch = 'original patch' 299*73bd93f1SPatrick Williams ignore_commit = 'ignore' 300eb8dc403SDave Cobbley 301eb8dc403SDave Cobbley def __init__(self, dir, d): 302eb8dc403SDave Cobbley PatchTree.__init__(self, dir, d) 303eb8dc403SDave Cobbley self.commituser = d.getVar('PATCH_GIT_USER_NAME') 304eb8dc403SDave Cobbley self.commitemail = d.getVar('PATCH_GIT_USER_EMAIL') 305615f2f11SAndrew Geissler if not self._isInitialized(d): 306595f6308SAndrew Geissler self._initRepo() 307595f6308SAndrew Geissler 308615f2f11SAndrew Geissler def _isInitialized(self, d): 309595f6308SAndrew Geissler cmd = "git rev-parse --show-toplevel" 3107e0e3c0cSAndrew Geissler try: 3117e0e3c0cSAndrew Geissler output = runcmd(cmd.split(), self.dir).strip() 3127e0e3c0cSAndrew Geissler except CmdError as err: 3137e0e3c0cSAndrew Geissler ## runcmd returned non-zero which most likely means 128 3147e0e3c0cSAndrew Geissler ## Not a git directory 3157e0e3c0cSAndrew Geissler return False 316615f2f11SAndrew Geissler ## Make sure repo is in builddir to not break top-level git repos, or under workdir 317615f2f11SAndrew Geissler return os.path.samefile(output, self.dir) or oe.path.is_path_parent(d.getVar('WORKDIR'), output) 318595f6308SAndrew Geissler 319595f6308SAndrew Geissler def _initRepo(self): 320595f6308SAndrew Geissler runcmd("git init".split(), self.dir) 321595f6308SAndrew Geissler runcmd("git add .".split(), self.dir) 3227e0e3c0cSAndrew Geissler runcmd("git commit -a --allow-empty -m bitbake_patching_started".split(), self.dir) 323eb8dc403SDave Cobbley 324eb8dc403SDave Cobbley @staticmethod 325eb8dc403SDave Cobbley def extractPatchHeader(patchfile): 326eb8dc403SDave Cobbley """ 327eb8dc403SDave Cobbley Extract just the header lines from the top of a patch file 328eb8dc403SDave Cobbley """ 329eb8dc403SDave Cobbley for encoding in ['utf-8', 'latin-1']: 330eb8dc403SDave Cobbley lines = [] 331eb8dc403SDave Cobbley try: 332eb8dc403SDave Cobbley with open(patchfile, 'r', encoding=encoding) as f: 333eb8dc403SDave Cobbley for line in f: 334eb8dc403SDave Cobbley if line.startswith('Index: ') or line.startswith('diff -') or line.startswith('---'): 335eb8dc403SDave Cobbley break 336eb8dc403SDave Cobbley lines.append(line) 337eb8dc403SDave Cobbley except UnicodeDecodeError: 338eb8dc403SDave Cobbley continue 339eb8dc403SDave Cobbley break 340eb8dc403SDave Cobbley else: 341eb8dc403SDave Cobbley raise PatchError('Unable to find a character encoding to decode %s' % patchfile) 342eb8dc403SDave Cobbley return lines 343eb8dc403SDave Cobbley 344eb8dc403SDave Cobbley @staticmethod 345eb8dc403SDave Cobbley def decodeAuthor(line): 346eb8dc403SDave Cobbley from email.header import decode_header 347eb8dc403SDave Cobbley authorval = line.split(':', 1)[1].strip().replace('"', '') 348eb8dc403SDave Cobbley result = decode_header(authorval)[0][0] 349eb8dc403SDave Cobbley if hasattr(result, 'decode'): 350eb8dc403SDave Cobbley result = result.decode('utf-8') 351eb8dc403SDave Cobbley return result 352eb8dc403SDave Cobbley 353eb8dc403SDave Cobbley @staticmethod 354eb8dc403SDave Cobbley def interpretPatchHeader(headerlines): 355eb8dc403SDave Cobbley import re 35619323693SBrad Bishop author_re = re.compile(r'[\S ]+ <\S+@\S+\.\S+>') 35719323693SBrad Bishop from_commit_re = re.compile(r'^From [a-z0-9]{40} .*') 358eb8dc403SDave Cobbley outlines = [] 359eb8dc403SDave Cobbley author = None 360eb8dc403SDave Cobbley date = None 361eb8dc403SDave Cobbley subject = None 362eb8dc403SDave Cobbley for line in headerlines: 363eb8dc403SDave Cobbley if line.startswith('Subject: '): 364eb8dc403SDave Cobbley subject = line.split(':', 1)[1] 365eb8dc403SDave Cobbley # Remove any [PATCH][oe-core] etc. 366eb8dc403SDave Cobbley subject = re.sub(r'\[.+?\]\s*', '', subject) 367eb8dc403SDave Cobbley continue 368eb8dc403SDave Cobbley elif line.startswith('From: ') or line.startswith('Author: '): 369eb8dc403SDave Cobbley authorval = GitApplyTree.decodeAuthor(line) 370eb8dc403SDave Cobbley # git is fussy about author formatting i.e. it must be Name <email@domain> 371eb8dc403SDave Cobbley if author_re.match(authorval): 372eb8dc403SDave Cobbley author = authorval 373eb8dc403SDave Cobbley continue 374eb8dc403SDave Cobbley elif line.startswith('Date: '): 375eb8dc403SDave Cobbley if date is None: 376eb8dc403SDave Cobbley dateval = line.split(':', 1)[1].strip() 377eb8dc403SDave Cobbley # Very crude check for date format, since git will blow up if it's not in the right 378eb8dc403SDave Cobbley # format. Without e.g. a python-dateutils dependency we can't do a whole lot more 379eb8dc403SDave Cobbley if len(dateval) > 12: 380eb8dc403SDave Cobbley date = dateval 381eb8dc403SDave Cobbley continue 382eb8dc403SDave Cobbley elif not author and line.lower().startswith('signed-off-by: '): 383eb8dc403SDave Cobbley authorval = GitApplyTree.decodeAuthor(line) 384eb8dc403SDave Cobbley # git is fussy about author formatting i.e. it must be Name <email@domain> 385eb8dc403SDave Cobbley if author_re.match(authorval): 386eb8dc403SDave Cobbley author = authorval 387eb8dc403SDave Cobbley elif from_commit_re.match(line): 388eb8dc403SDave Cobbley # We don't want the From <commit> line - if it's present it will break rebasing 389eb8dc403SDave Cobbley continue 390eb8dc403SDave Cobbley outlines.append(line) 391eb8dc403SDave Cobbley 392eb8dc403SDave Cobbley if not subject: 393eb8dc403SDave Cobbley firstline = None 394eb8dc403SDave Cobbley for line in headerlines: 395eb8dc403SDave Cobbley line = line.strip() 396eb8dc403SDave Cobbley if firstline: 397eb8dc403SDave Cobbley if line: 398eb8dc403SDave Cobbley # Second line is not blank, the first line probably isn't usable 399eb8dc403SDave Cobbley firstline = None 400eb8dc403SDave Cobbley break 401eb8dc403SDave Cobbley elif line: 402eb8dc403SDave Cobbley firstline = line 403eb8dc403SDave Cobbley if firstline and not firstline.startswith(('#', 'Index:', 'Upstream-Status:')) and len(firstline) < 100: 404eb8dc403SDave Cobbley subject = firstline 405eb8dc403SDave Cobbley 406eb8dc403SDave Cobbley return outlines, author, date, subject 407eb8dc403SDave Cobbley 408eb8dc403SDave Cobbley @staticmethod 409eb8dc403SDave Cobbley def gitCommandUserOptions(cmd, commituser=None, commitemail=None, d=None): 410eb8dc403SDave Cobbley if d: 411eb8dc403SDave Cobbley commituser = d.getVar('PATCH_GIT_USER_NAME') 412eb8dc403SDave Cobbley commitemail = d.getVar('PATCH_GIT_USER_EMAIL') 413eb8dc403SDave Cobbley if commituser: 414eb8dc403SDave Cobbley cmd += ['-c', 'user.name="%s"' % commituser] 415eb8dc403SDave Cobbley if commitemail: 416eb8dc403SDave Cobbley cmd += ['-c', 'user.email="%s"' % commitemail] 417eb8dc403SDave Cobbley 418eb8dc403SDave Cobbley @staticmethod 419eb8dc403SDave Cobbley def prepareCommit(patchfile, commituser=None, commitemail=None): 420eb8dc403SDave Cobbley """ 421eb8dc403SDave Cobbley Prepare a git commit command line based on the header from a patch file 422eb8dc403SDave Cobbley (typically this is useful for patches that cannot be applied with "git am" due to formatting) 423eb8dc403SDave Cobbley """ 424eb8dc403SDave Cobbley import tempfile 425eb8dc403SDave Cobbley # Process patch header and extract useful information 426eb8dc403SDave Cobbley lines = GitApplyTree.extractPatchHeader(patchfile) 427eb8dc403SDave Cobbley outlines, author, date, subject = GitApplyTree.interpretPatchHeader(lines) 428eb8dc403SDave Cobbley if not author or not subject or not date: 429eb8dc403SDave Cobbley try: 430eb8dc403SDave Cobbley shellcmd = ["git", "log", "--format=email", "--follow", "--diff-filter=A", "--", patchfile] 431eb8dc403SDave Cobbley out = runcmd(["sh", "-c", " ".join(shellcmd)], os.path.dirname(patchfile)) 432eb8dc403SDave Cobbley except CmdError: 433eb8dc403SDave Cobbley out = None 434eb8dc403SDave Cobbley if out: 435eb8dc403SDave Cobbley _, newauthor, newdate, newsubject = GitApplyTree.interpretPatchHeader(out.splitlines()) 436eb8dc403SDave Cobbley if not author: 437eb8dc403SDave Cobbley # If we're setting the author then the date should be set as well 438eb8dc403SDave Cobbley author = newauthor 439eb8dc403SDave Cobbley date = newdate 440eb8dc403SDave Cobbley elif not date: 441eb8dc403SDave Cobbley # If we don't do this we'll get the current date, at least this will be closer 442eb8dc403SDave Cobbley date = newdate 443eb8dc403SDave Cobbley if not subject: 444eb8dc403SDave Cobbley subject = newsubject 4454ed12e16SAndrew Geissler if subject and not (outlines and outlines[0].strip() == subject): 446eb8dc403SDave Cobbley outlines.insert(0, '%s\n\n' % subject.strip()) 447eb8dc403SDave Cobbley 448eb8dc403SDave Cobbley # Write out commit message to a file 449eb8dc403SDave Cobbley with tempfile.NamedTemporaryFile('w', delete=False) as tf: 450eb8dc403SDave Cobbley tmpfile = tf.name 451eb8dc403SDave Cobbley for line in outlines: 452eb8dc403SDave Cobbley tf.write(line) 453eb8dc403SDave Cobbley # Prepare git command 454eb8dc403SDave Cobbley cmd = ["git"] 455eb8dc403SDave Cobbley GitApplyTree.gitCommandUserOptions(cmd, commituser, commitemail) 456*73bd93f1SPatrick Williams cmd += ["commit", "-F", tmpfile, "--no-verify"] 457eb8dc403SDave Cobbley # git doesn't like plain email addresses as authors 458eb8dc403SDave Cobbley if author and '<' in author: 459eb8dc403SDave Cobbley cmd.append('--author="%s"' % author) 460eb8dc403SDave Cobbley if date: 461eb8dc403SDave Cobbley cmd.append('--date="%s"' % date) 462eb8dc403SDave Cobbley return (tmpfile, cmd) 463eb8dc403SDave Cobbley 464eb8dc403SDave Cobbley @staticmethod 465*73bd93f1SPatrick Williams def addNote(repo, ref, key, value=None): 466*73bd93f1SPatrick Williams note = key + (": %s" % value if value else "") 467*73bd93f1SPatrick Williams notes_ref = GitApplyTree.notes_ref 468*73bd93f1SPatrick Williams runcmd(["git", "config", "notes.rewriteMode", "ignore"], repo) 469*73bd93f1SPatrick Williams runcmd(["git", "config", "notes.displayRef", notes_ref, notes_ref], repo) 470*73bd93f1SPatrick Williams runcmd(["git", "config", "notes.rewriteRef", notes_ref, notes_ref], repo) 471*73bd93f1SPatrick Williams runcmd(["git", "notes", "--ref", notes_ref, "append", "-m", note, ref], repo) 472*73bd93f1SPatrick Williams 473*73bd93f1SPatrick Williams @staticmethod 474*73bd93f1SPatrick Williams def removeNote(repo, ref, key): 475*73bd93f1SPatrick Williams notes = GitApplyTree.getNotes(repo, ref) 476*73bd93f1SPatrick Williams notes = {k: v for k, v in notes.items() if k != key and not k.startswith(key + ":")} 477*73bd93f1SPatrick Williams runcmd(["git", "notes", "--ref", GitApplyTree.notes_ref, "remove", "--ignore-missing", ref], repo) 478*73bd93f1SPatrick Williams for note, value in notes.items(): 479*73bd93f1SPatrick Williams GitApplyTree.addNote(repo, ref, note, value) 480*73bd93f1SPatrick Williams 481*73bd93f1SPatrick Williams @staticmethod 482*73bd93f1SPatrick Williams def getNotes(repo, ref): 483*73bd93f1SPatrick Williams import re 484*73bd93f1SPatrick Williams 485*73bd93f1SPatrick Williams note = None 486*73bd93f1SPatrick Williams try: 487*73bd93f1SPatrick Williams note = runcmd(["git", "notes", "--ref", GitApplyTree.notes_ref, "show", ref], repo) 488*73bd93f1SPatrick Williams prefix = "" 489*73bd93f1SPatrick Williams except CmdError: 490*73bd93f1SPatrick Williams note = runcmd(['git', 'show', '-s', '--format=%B', ref], repo) 491*73bd93f1SPatrick Williams prefix = "%% " 492*73bd93f1SPatrick Williams 493*73bd93f1SPatrick Williams note_re = re.compile(r'^%s(.*?)(?::\s*(.*))?$' % prefix) 494*73bd93f1SPatrick Williams notes = dict() 495*73bd93f1SPatrick Williams for line in note.splitlines(): 496*73bd93f1SPatrick Williams m = note_re.match(line) 497*73bd93f1SPatrick Williams if m: 498*73bd93f1SPatrick Williams notes[m.group(1)] = m.group(2) 499*73bd93f1SPatrick Williams 500*73bd93f1SPatrick Williams return notes 501*73bd93f1SPatrick Williams 502*73bd93f1SPatrick Williams @staticmethod 503*73bd93f1SPatrick Williams def commitIgnored(subject, dir=None, files=None, d=None): 504*73bd93f1SPatrick Williams if files: 505*73bd93f1SPatrick Williams runcmd(['git', 'add'] + files, dir) 506*73bd93f1SPatrick Williams cmd = ["git"] 507*73bd93f1SPatrick Williams GitApplyTree.gitCommandUserOptions(cmd, d=d) 508*73bd93f1SPatrick Williams cmd += ["commit", "-m", subject, "--no-verify"] 509*73bd93f1SPatrick Williams runcmd(cmd, dir) 510*73bd93f1SPatrick Williams GitApplyTree.addNote(dir, "HEAD", GitApplyTree.ignore_commit) 511*73bd93f1SPatrick Williams 512*73bd93f1SPatrick Williams @staticmethod 513da295319SPatrick Williams def extractPatches(tree, startcommits, outdir, paths=None): 514eb8dc403SDave Cobbley import tempfile 515eb8dc403SDave Cobbley import shutil 516eb8dc403SDave Cobbley tempdir = tempfile.mkdtemp(prefix='oepatch') 517eb8dc403SDave Cobbley try: 518da295319SPatrick Williams for name, rev in startcommits.items(): 519da295319SPatrick Williams shellcmd = ["git", "format-patch", "--no-signature", "--no-numbered", rev, "-o", tempdir] 520eb8dc403SDave Cobbley if paths: 521eb8dc403SDave Cobbley shellcmd.append('--') 522eb8dc403SDave Cobbley shellcmd.extend(paths) 523da295319SPatrick Williams out = runcmd(["sh", "-c", " ".join(shellcmd)], os.path.join(tree, name)) 524eb8dc403SDave Cobbley if out: 525eb8dc403SDave Cobbley for srcfile in out.split(): 526*73bd93f1SPatrick Williams # This loop, which is used to remove any line that 527*73bd93f1SPatrick Williams # starts with "%% original patch", is kept for backwards 528*73bd93f1SPatrick Williams # compatibility. If/when that compatibility is dropped, 529*73bd93f1SPatrick Williams # it can be replaced with code to just read the first 530*73bd93f1SPatrick Williams # line of the patch file to get the SHA-1, and the code 531*73bd93f1SPatrick Williams # below that writes the modified patch file can be 532*73bd93f1SPatrick Williams # replaced with a simple file move. 533eb8dc403SDave Cobbley for encoding in ['utf-8', 'latin-1']: 534eb8dc403SDave Cobbley patchlines = [] 535eb8dc403SDave Cobbley try: 536169d7bccSPatrick Williams with open(srcfile, 'r', encoding=encoding, newline='') as f: 537eb8dc403SDave Cobbley for line in f: 538*73bd93f1SPatrick Williams if line.startswith("%% " + GitApplyTree.original_patch): 539eb8dc403SDave Cobbley continue 540eb8dc403SDave Cobbley patchlines.append(line) 541eb8dc403SDave Cobbley except UnicodeDecodeError: 542eb8dc403SDave Cobbley continue 543eb8dc403SDave Cobbley break 544eb8dc403SDave Cobbley else: 545eb8dc403SDave Cobbley raise PatchError('Unable to find a character encoding to decode %s' % srcfile) 546eb8dc403SDave Cobbley 547*73bd93f1SPatrick Williams sha1 = patchlines[0].split()[1] 548*73bd93f1SPatrick Williams notes = GitApplyTree.getNotes(os.path.join(tree, name), sha1) 549*73bd93f1SPatrick Williams if GitApplyTree.ignore_commit in notes: 550*73bd93f1SPatrick Williams continue 551*73bd93f1SPatrick Williams outfile = notes.get(GitApplyTree.original_patch, os.path.basename(srcfile)) 552*73bd93f1SPatrick Williams 553da295319SPatrick Williams bb.utils.mkdirhier(os.path.join(outdir, name)) 554da295319SPatrick Williams with open(os.path.join(outdir, name, outfile), 'w') as of: 555eb8dc403SDave Cobbley for line in patchlines: 556eb8dc403SDave Cobbley of.write(line) 557eb8dc403SDave Cobbley finally: 558eb8dc403SDave Cobbley shutil.rmtree(tempdir) 559eb8dc403SDave Cobbley 5608e7b46e2SPatrick Williams def _need_dirty_check(self): 5618e7b46e2SPatrick Williams fetch = bb.fetch2.Fetch([], self.d) 5628e7b46e2SPatrick Williams check_dirtyness = False 5638e7b46e2SPatrick Williams for url in fetch.urls: 5648e7b46e2SPatrick Williams url_data = fetch.ud[url] 5658e7b46e2SPatrick Williams parm = url_data.parm 5668e7b46e2SPatrick Williams # a git url with subpath param will surely be dirty 5678e7b46e2SPatrick Williams # since the git tree from which we clone will be emptied 5688e7b46e2SPatrick Williams # from all files that are not in the subpath 5698e7b46e2SPatrick Williams if url_data.type == 'git' and parm.get('subpath'): 5708e7b46e2SPatrick Williams check_dirtyness = True 5718e7b46e2SPatrick Williams return check_dirtyness 5728e7b46e2SPatrick Williams 5738e7b46e2SPatrick Williams def _commitpatch(self, patch, patchfilevar): 5748e7b46e2SPatrick Williams output = "" 5758e7b46e2SPatrick Williams # Add all files 5768e7b46e2SPatrick Williams shellcmd = ["git", "add", "-f", "-A", "."] 5778e7b46e2SPatrick Williams output += runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) 5788e7b46e2SPatrick Williams # Exclude the patches directory 5798e7b46e2SPatrick Williams shellcmd = ["git", "reset", "HEAD", self.patchdir] 5808e7b46e2SPatrick Williams output += runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) 5818e7b46e2SPatrick Williams # Commit the result 5828e7b46e2SPatrick Williams (tmpfile, shellcmd) = self.prepareCommit(patch['file'], self.commituser, self.commitemail) 5838e7b46e2SPatrick Williams try: 5848e7b46e2SPatrick Williams shellcmd.insert(0, patchfilevar) 5858e7b46e2SPatrick Williams output += runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) 5868e7b46e2SPatrick Williams finally: 5878e7b46e2SPatrick Williams os.remove(tmpfile) 5888e7b46e2SPatrick Williams return output 5898e7b46e2SPatrick Williams 590eb8dc403SDave Cobbley def _applypatch(self, patch, force = False, reverse = False, run = True): 591eb8dc403SDave Cobbley import shutil 592eb8dc403SDave Cobbley 593eb8dc403SDave Cobbley def _applypatchhelper(shellcmd, patch, force = False, reverse = False, run = True): 594eb8dc403SDave Cobbley if reverse: 595eb8dc403SDave Cobbley shellcmd.append('-R') 596eb8dc403SDave Cobbley 597eb8dc403SDave Cobbley shellcmd.append(patch['file']) 598eb8dc403SDave Cobbley 599eb8dc403SDave Cobbley if not run: 600eb8dc403SDave Cobbley return "sh" + "-c" + " ".join(shellcmd) 601eb8dc403SDave Cobbley 602eb8dc403SDave Cobbley return runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) 603eb8dc403SDave Cobbley 604eb8dc403SDave Cobbley reporoot = (runcmd("git rev-parse --show-toplevel".split(), self.dir) or '').strip() 605eb8dc403SDave Cobbley if not reporoot: 606eb8dc403SDave Cobbley raise Exception("Cannot get repository root for directory %s" % self.dir) 607*73bd93f1SPatrick Williams 608*73bd93f1SPatrick Williams patch_applied = True 609eb8dc403SDave Cobbley try: 610eb8dc403SDave Cobbley patchfilevar = 'PATCHFILE="%s"' % os.path.basename(patch['file']) 6118e7b46e2SPatrick Williams if self._need_dirty_check(): 6128e7b46e2SPatrick Williams # Check dirtyness of the tree 6138e7b46e2SPatrick Williams try: 6148e7b46e2SPatrick Williams output = runcmd(["git", "--work-tree=%s" % reporoot, "status", "--short"]) 6158e7b46e2SPatrick Williams except CmdError: 6168e7b46e2SPatrick Williams pass 6178e7b46e2SPatrick Williams else: 6188e7b46e2SPatrick Williams if output: 619*73bd93f1SPatrick Williams # The tree is dirty, no need to try to apply patches with git anymore 6208e7b46e2SPatrick Williams # since they fail, fallback directly to patch 6218e7b46e2SPatrick Williams output = PatchTree._applypatch(self, patch, force, reverse, run) 6228e7b46e2SPatrick Williams output += self._commitpatch(patch, patchfilevar) 6238e7b46e2SPatrick Williams return output 624eb8dc403SDave Cobbley try: 625eb8dc403SDave Cobbley shellcmd = [patchfilevar, "git", "--work-tree=%s" % reporoot] 626eb8dc403SDave Cobbley self.gitCommandUserOptions(shellcmd, self.commituser, self.commitemail) 627d1e89497SAndrew Geissler shellcmd += ["am", "-3", "--keep-cr", "--no-scissors", "-p%s" % patch['strippath']] 628eb8dc403SDave Cobbley return _applypatchhelper(shellcmd, patch, force, reverse, run) 629eb8dc403SDave Cobbley except CmdError: 630eb8dc403SDave Cobbley # Need to abort the git am, or we'll still be within it at the end 631eb8dc403SDave Cobbley try: 632eb8dc403SDave Cobbley shellcmd = ["git", "--work-tree=%s" % reporoot, "am", "--abort"] 633eb8dc403SDave Cobbley runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) 634eb8dc403SDave Cobbley except CmdError: 635eb8dc403SDave Cobbley pass 636eb8dc403SDave Cobbley # git am won't always clean up after itself, sadly, so... 637eb8dc403SDave Cobbley shellcmd = ["git", "--work-tree=%s" % reporoot, "reset", "--hard", "HEAD"] 638eb8dc403SDave Cobbley runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) 639eb8dc403SDave Cobbley # Also need to take care of any stray untracked files 640eb8dc403SDave Cobbley shellcmd = ["git", "--work-tree=%s" % reporoot, "clean", "-f"] 641eb8dc403SDave Cobbley runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) 642eb8dc403SDave Cobbley 643eb8dc403SDave Cobbley # Fall back to git apply 644eb8dc403SDave Cobbley shellcmd = ["git", "--git-dir=%s" % reporoot, "apply", "-p%s" % patch['strippath']] 645eb8dc403SDave Cobbley try: 646eb8dc403SDave Cobbley output = _applypatchhelper(shellcmd, patch, force, reverse, run) 647eb8dc403SDave Cobbley except CmdError: 648eb8dc403SDave Cobbley # Fall back to patch 649eb8dc403SDave Cobbley output = PatchTree._applypatch(self, patch, force, reverse, run) 6508e7b46e2SPatrick Williams output += self._commitpatch(patch, patchfilevar) 651eb8dc403SDave Cobbley return output 652*73bd93f1SPatrick Williams except: 653*73bd93f1SPatrick Williams patch_applied = False 654*73bd93f1SPatrick Williams raise 655eb8dc403SDave Cobbley finally: 656*73bd93f1SPatrick Williams if patch_applied: 657*73bd93f1SPatrick Williams GitApplyTree.addNote(self.dir, "HEAD", GitApplyTree.original_patch, os.path.basename(patch['file'])) 658eb8dc403SDave Cobbley 659eb8dc403SDave Cobbley 660eb8dc403SDave Cobbleyclass QuiltTree(PatchSet): 661eb8dc403SDave Cobbley def _runcmd(self, args, run = True): 662eb8dc403SDave Cobbley quiltrc = self.d.getVar('QUILTRCFILE') 663eb8dc403SDave Cobbley if not run: 664eb8dc403SDave Cobbley return ["quilt"] + ["--quiltrc"] + [quiltrc] + args 665eb8dc403SDave Cobbley runcmd(["quilt"] + ["--quiltrc"] + [quiltrc] + args, self.dir) 666eb8dc403SDave Cobbley 667eb8dc403SDave Cobbley def _quiltpatchpath(self, file): 668eb8dc403SDave Cobbley return os.path.join(self.dir, "patches", os.path.basename(file)) 669eb8dc403SDave Cobbley 670eb8dc403SDave Cobbley 671eb8dc403SDave Cobbley def __init__(self, dir, d): 672eb8dc403SDave Cobbley PatchSet.__init__(self, dir, d) 673eb8dc403SDave Cobbley self.initialized = False 674eb8dc403SDave Cobbley p = os.path.join(self.dir, 'patches') 675eb8dc403SDave Cobbley if not os.path.exists(p): 676eb8dc403SDave Cobbley os.makedirs(p) 677eb8dc403SDave Cobbley 678eb8dc403SDave Cobbley def Clean(self): 679eb8dc403SDave Cobbley try: 68078b72798SAndrew Geissler # make sure that patches/series file exists before quilt pop to keep quilt-0.67 happy 68178b72798SAndrew Geissler open(os.path.join(self.dir, "patches","series"), 'a').close() 682eb8dc403SDave Cobbley self._runcmd(["pop", "-a", "-f"]) 683eb8dc403SDave Cobbley oe.path.remove(os.path.join(self.dir, "patches","series")) 684eb8dc403SDave Cobbley except Exception: 685eb8dc403SDave Cobbley pass 686eb8dc403SDave Cobbley self.initialized = True 687eb8dc403SDave Cobbley 688eb8dc403SDave Cobbley def InitFromDir(self): 689eb8dc403SDave Cobbley # read series -> self.patches 690eb8dc403SDave Cobbley seriespath = os.path.join(self.dir, 'patches', 'series') 691eb8dc403SDave Cobbley if not os.path.exists(self.dir): 692eb8dc403SDave Cobbley raise NotFoundError(self.dir) 693eb8dc403SDave Cobbley if os.path.exists(seriespath): 694eb8dc403SDave Cobbley with open(seriespath, 'r') as f: 695eb8dc403SDave Cobbley for line in f.readlines(): 696eb8dc403SDave Cobbley patch = {} 697eb8dc403SDave Cobbley parts = line.strip().split() 698eb8dc403SDave Cobbley patch["quiltfile"] = self._quiltpatchpath(parts[0]) 699eb8dc403SDave Cobbley patch["quiltfilemd5"] = bb.utils.md5_file(patch["quiltfile"]) 700eb8dc403SDave Cobbley if len(parts) > 1: 701eb8dc403SDave Cobbley patch["strippath"] = parts[1][2:] 702eb8dc403SDave Cobbley self.patches.append(patch) 703eb8dc403SDave Cobbley 704eb8dc403SDave Cobbley # determine which patches are applied -> self._current 705eb8dc403SDave Cobbley try: 706eb8dc403SDave Cobbley output = runcmd(["quilt", "applied"], self.dir) 707eb8dc403SDave Cobbley except CmdError: 708eb8dc403SDave Cobbley import sys 709eb8dc403SDave Cobbley if sys.exc_value.output.strip() == "No patches applied": 710eb8dc403SDave Cobbley return 711eb8dc403SDave Cobbley else: 712eb8dc403SDave Cobbley raise 713eb8dc403SDave Cobbley output = [val for val in output.split('\n') if not val.startswith('#')] 714eb8dc403SDave Cobbley for patch in self.patches: 715eb8dc403SDave Cobbley if os.path.basename(patch["quiltfile"]) == output[-1]: 716eb8dc403SDave Cobbley self._current = self.patches.index(patch) 717eb8dc403SDave Cobbley self.initialized = True 718eb8dc403SDave Cobbley 719eb8dc403SDave Cobbley def Import(self, patch, force = None): 720eb8dc403SDave Cobbley if not self.initialized: 721eb8dc403SDave Cobbley self.InitFromDir() 722eb8dc403SDave Cobbley PatchSet.Import(self, patch, force) 723eb8dc403SDave Cobbley oe.path.symlink(patch["file"], self._quiltpatchpath(patch["file"]), force=True) 724eb8dc403SDave Cobbley with open(os.path.join(self.dir, "patches", "series"), "a") as f: 725eb8dc403SDave Cobbley f.write(os.path.basename(patch["file"]) + " -p" + patch["strippath"] + "\n") 726eb8dc403SDave Cobbley patch["quiltfile"] = self._quiltpatchpath(patch["file"]) 727eb8dc403SDave Cobbley patch["quiltfilemd5"] = bb.utils.md5_file(patch["quiltfile"]) 728eb8dc403SDave Cobbley 729eb8dc403SDave Cobbley # TODO: determine if the file being imported: 730eb8dc403SDave Cobbley # 1) is already imported, and is the same 731eb8dc403SDave Cobbley # 2) is already imported, but differs 732eb8dc403SDave Cobbley 733eb8dc403SDave Cobbley self.patches.insert(self._current or 0, patch) 734eb8dc403SDave Cobbley 735eb8dc403SDave Cobbley 736eb8dc403SDave Cobbley def Push(self, force = False, all = False, run = True): 737eb8dc403SDave Cobbley # quilt push [-f] 738eb8dc403SDave Cobbley 739eb8dc403SDave Cobbley args = ["push"] 740eb8dc403SDave Cobbley if force: 741eb8dc403SDave Cobbley args.append("-f") 742eb8dc403SDave Cobbley if all: 743eb8dc403SDave Cobbley args.append("-a") 744eb8dc403SDave Cobbley if not run: 745eb8dc403SDave Cobbley return self._runcmd(args, run) 746eb8dc403SDave Cobbley 747eb8dc403SDave Cobbley self._runcmd(args) 748eb8dc403SDave Cobbley 749eb8dc403SDave Cobbley if self._current is not None: 750eb8dc403SDave Cobbley self._current = self._current + 1 751eb8dc403SDave Cobbley else: 752eb8dc403SDave Cobbley self._current = 0 753eb8dc403SDave Cobbley 754eb8dc403SDave Cobbley def Pop(self, force = None, all = None): 755eb8dc403SDave Cobbley # quilt pop [-f] 756eb8dc403SDave Cobbley args = ["pop"] 757eb8dc403SDave Cobbley if force: 758eb8dc403SDave Cobbley args.append("-f") 759eb8dc403SDave Cobbley if all: 760eb8dc403SDave Cobbley args.append("-a") 761eb8dc403SDave Cobbley 762eb8dc403SDave Cobbley self._runcmd(args) 763eb8dc403SDave Cobbley 764eb8dc403SDave Cobbley if self._current == 0: 765eb8dc403SDave Cobbley self._current = None 766eb8dc403SDave Cobbley 767eb8dc403SDave Cobbley if self._current is not None: 768eb8dc403SDave Cobbley self._current = self._current - 1 769eb8dc403SDave Cobbley 770eb8dc403SDave Cobbley def Refresh(self, **kwargs): 771eb8dc403SDave Cobbley if kwargs.get("remote"): 772eb8dc403SDave Cobbley patch = self.patches[kwargs["patch"]] 773eb8dc403SDave Cobbley if not patch: 774eb8dc403SDave Cobbley raise PatchError("No patch found at index %s in patchset." % kwargs["patch"]) 775eb8dc403SDave Cobbley (type, host, path, user, pswd, parm) = bb.fetch.decodeurl(patch["remote"]) 776eb8dc403SDave Cobbley if type == "file": 777eb8dc403SDave Cobbley import shutil 778eb8dc403SDave Cobbley if not patch.get("file") and patch.get("remote"): 779eb8dc403SDave Cobbley patch["file"] = bb.fetch2.localpath(patch["remote"], self.d) 780eb8dc403SDave Cobbley 781eb8dc403SDave Cobbley shutil.copyfile(patch["quiltfile"], patch["file"]) 782eb8dc403SDave Cobbley else: 783eb8dc403SDave Cobbley raise PatchError("Unable to do a remote refresh of %s, unsupported remote url scheme %s." % (os.path.basename(patch["quiltfile"]), type)) 784eb8dc403SDave Cobbley else: 785eb8dc403SDave Cobbley # quilt refresh 786eb8dc403SDave Cobbley args = ["refresh"] 787eb8dc403SDave Cobbley if kwargs.get("quiltfile"): 788eb8dc403SDave Cobbley args.append(os.path.basename(kwargs["quiltfile"])) 789eb8dc403SDave Cobbley elif kwargs.get("patch"): 790eb8dc403SDave Cobbley args.append(os.path.basename(self.patches[kwargs["patch"]]["quiltfile"])) 791eb8dc403SDave Cobbley self._runcmd(args) 792eb8dc403SDave Cobbley 793eb8dc403SDave Cobbleyclass Resolver(object): 794eb8dc403SDave Cobbley def __init__(self, patchset, terminal): 795eb8dc403SDave Cobbley raise NotImplementedError() 796eb8dc403SDave Cobbley 797eb8dc403SDave Cobbley def Resolve(self): 798eb8dc403SDave Cobbley raise NotImplementedError() 799eb8dc403SDave Cobbley 800eb8dc403SDave Cobbley def Revert(self): 801eb8dc403SDave Cobbley raise NotImplementedError() 802eb8dc403SDave Cobbley 803eb8dc403SDave Cobbley def Finalize(self): 804eb8dc403SDave Cobbley raise NotImplementedError() 805eb8dc403SDave Cobbley 806eb8dc403SDave Cobbleyclass NOOPResolver(Resolver): 807eb8dc403SDave Cobbley def __init__(self, patchset, terminal): 808eb8dc403SDave Cobbley self.patchset = patchset 809eb8dc403SDave Cobbley self.terminal = terminal 810eb8dc403SDave Cobbley 811eb8dc403SDave Cobbley def Resolve(self): 812eb8dc403SDave Cobbley olddir = os.path.abspath(os.curdir) 813eb8dc403SDave Cobbley os.chdir(self.patchset.dir) 814eb8dc403SDave Cobbley try: 815eb8dc403SDave Cobbley self.patchset.Push() 816eb8dc403SDave Cobbley except Exception: 817eb8dc403SDave Cobbley import sys 818eb8dc403SDave Cobbley raise 819ac13d5f3SPatrick Williams finally: 820ac13d5f3SPatrick Williams os.chdir(olddir) 821eb8dc403SDave Cobbley 822eb8dc403SDave Cobbley# Patch resolver which relies on the user doing all the work involved in the 823eb8dc403SDave Cobbley# resolution, with the exception of refreshing the remote copy of the patch 824eb8dc403SDave Cobbley# files (the urls). 825eb8dc403SDave Cobbleyclass UserResolver(Resolver): 826eb8dc403SDave Cobbley def __init__(self, patchset, terminal): 827eb8dc403SDave Cobbley self.patchset = patchset 828eb8dc403SDave Cobbley self.terminal = terminal 829eb8dc403SDave Cobbley 830eb8dc403SDave Cobbley # Force a push in the patchset, then drop to a shell for the user to 831eb8dc403SDave Cobbley # resolve any rejected hunks 832eb8dc403SDave Cobbley def Resolve(self): 833eb8dc403SDave Cobbley olddir = os.path.abspath(os.curdir) 834eb8dc403SDave Cobbley os.chdir(self.patchset.dir) 835eb8dc403SDave Cobbley try: 836eb8dc403SDave Cobbley self.patchset.Push(False) 837eb8dc403SDave Cobbley except CmdError as v: 838eb8dc403SDave Cobbley # Patch application failed 839eb8dc403SDave Cobbley patchcmd = self.patchset.Push(True, False, False) 840eb8dc403SDave Cobbley 841eb8dc403SDave Cobbley t = self.patchset.d.getVar('T') 842eb8dc403SDave Cobbley if not t: 843eb8dc403SDave Cobbley bb.msg.fatal("Build", "T not set") 844eb8dc403SDave Cobbley bb.utils.mkdirhier(t) 845eb8dc403SDave Cobbley import random 846eb8dc403SDave Cobbley rcfile = "%s/bashrc.%s.%s" % (t, str(os.getpid()), random.random()) 847eb8dc403SDave Cobbley with open(rcfile, "w") as f: 848eb8dc403SDave Cobbley f.write("echo '*** Manual patch resolution mode ***'\n") 849eb8dc403SDave Cobbley f.write("echo 'Dropping to a shell, so patch rejects can be fixed manually.'\n") 850eb8dc403SDave Cobbley f.write("echo 'Run \"quilt refresh\" when patch is corrected, press CTRL+D to exit.'\n") 851eb8dc403SDave Cobbley f.write("echo ''\n") 852eb8dc403SDave Cobbley f.write(" ".join(patchcmd) + "\n") 853eb8dc403SDave Cobbley os.chmod(rcfile, 0o775) 854eb8dc403SDave Cobbley 855eb8dc403SDave Cobbley self.terminal("bash --rcfile " + rcfile, 'Patch Rejects: Please fix patch rejects manually', self.patchset.d) 856eb8dc403SDave Cobbley 857eb8dc403SDave Cobbley # Construct a new PatchSet after the user's changes, compare the 858eb8dc403SDave Cobbley # sets, checking patches for modifications, and doing a remote 859eb8dc403SDave Cobbley # refresh on each. 860eb8dc403SDave Cobbley oldpatchset = self.patchset 861eb8dc403SDave Cobbley self.patchset = oldpatchset.__class__(self.patchset.dir, self.patchset.d) 862eb8dc403SDave Cobbley 863eb8dc403SDave Cobbley for patch in self.patchset.patches: 864eb8dc403SDave Cobbley oldpatch = None 865eb8dc403SDave Cobbley for opatch in oldpatchset.patches: 866eb8dc403SDave Cobbley if opatch["quiltfile"] == patch["quiltfile"]: 867eb8dc403SDave Cobbley oldpatch = opatch 868eb8dc403SDave Cobbley 869eb8dc403SDave Cobbley if oldpatch: 870eb8dc403SDave Cobbley patch["remote"] = oldpatch["remote"] 871eb8dc403SDave Cobbley if patch["quiltfile"] == oldpatch["quiltfile"]: 872eb8dc403SDave Cobbley if patch["quiltfilemd5"] != oldpatch["quiltfilemd5"]: 873eb8dc403SDave Cobbley bb.note("Patch %s has changed, updating remote url %s" % (os.path.basename(patch["quiltfile"]), patch["remote"])) 874eb8dc403SDave Cobbley # user change? remote refresh 875eb8dc403SDave Cobbley self.patchset.Refresh(remote=True, patch=self.patchset.patches.index(patch)) 876eb8dc403SDave Cobbley else: 877eb8dc403SDave Cobbley # User did not fix the problem. Abort. 878eb8dc403SDave Cobbley raise PatchError("Patch application failed, and user did not fix and refresh the patch.") 879eb8dc403SDave Cobbley except Exception: 880eb8dc403SDave Cobbley raise 881ac13d5f3SPatrick Williams finally: 882eb8dc403SDave Cobbley os.chdir(olddir) 883eb8dc403SDave Cobbley 884eb8dc403SDave Cobbley 885eb8dc403SDave Cobbleydef patch_path(url, fetch, workdir, expand=True): 88619323693SBrad Bishop """Return the local path of a patch, or return nothing if this isn't a patch""" 887eb8dc403SDave Cobbley 888eb8dc403SDave Cobbley local = fetch.localpath(url) 88919323693SBrad Bishop if os.path.isdir(local): 89019323693SBrad Bishop return 891eb8dc403SDave Cobbley base, ext = os.path.splitext(os.path.basename(local)) 892eb8dc403SDave Cobbley if ext in ('.gz', '.bz2', '.xz', '.Z'): 893eb8dc403SDave Cobbley if expand: 894eb8dc403SDave Cobbley local = os.path.join(workdir, base) 895eb8dc403SDave Cobbley ext = os.path.splitext(base)[1] 896eb8dc403SDave Cobbley 897eb8dc403SDave Cobbley urldata = fetch.ud[url] 898eb8dc403SDave Cobbley if "apply" in urldata.parm: 899eb8dc403SDave Cobbley apply = oe.types.boolean(urldata.parm["apply"]) 900eb8dc403SDave Cobbley if not apply: 901eb8dc403SDave Cobbley return 902eb8dc403SDave Cobbley elif ext not in (".diff", ".patch"): 903eb8dc403SDave Cobbley return 904eb8dc403SDave Cobbley 905eb8dc403SDave Cobbley return local 906eb8dc403SDave Cobbley 907eb8dc403SDave Cobbleydef src_patches(d, all=False, expand=True): 908eb8dc403SDave Cobbley workdir = d.getVar('WORKDIR') 909eb8dc403SDave Cobbley fetch = bb.fetch2.Fetch([], d) 910eb8dc403SDave Cobbley patches = [] 911eb8dc403SDave Cobbley sources = [] 912eb8dc403SDave Cobbley for url in fetch.urls: 913eb8dc403SDave Cobbley local = patch_path(url, fetch, workdir, expand) 914eb8dc403SDave Cobbley if not local: 915eb8dc403SDave Cobbley if all: 916eb8dc403SDave Cobbley local = fetch.localpath(url) 917eb8dc403SDave Cobbley sources.append(local) 918eb8dc403SDave Cobbley continue 919eb8dc403SDave Cobbley 920eb8dc403SDave Cobbley urldata = fetch.ud[url] 921eb8dc403SDave Cobbley parm = urldata.parm 922eb8dc403SDave Cobbley patchname = parm.get('pname') or os.path.basename(local) 923eb8dc403SDave Cobbley 924eb8dc403SDave Cobbley apply, reason = should_apply(parm, d) 925eb8dc403SDave Cobbley if not apply: 926eb8dc403SDave Cobbley if reason: 927eb8dc403SDave Cobbley bb.note("Patch %s %s" % (patchname, reason)) 928eb8dc403SDave Cobbley continue 929eb8dc403SDave Cobbley 930eb8dc403SDave Cobbley patchparm = {'patchname': patchname} 931eb8dc403SDave Cobbley if "striplevel" in parm: 932eb8dc403SDave Cobbley striplevel = parm["striplevel"] 933eb8dc403SDave Cobbley elif "pnum" in parm: 934eb8dc403SDave Cobbley #bb.msg.warn(None, "Deprecated usage of 'pnum' url parameter in '%s', please use 'striplevel'" % url) 935eb8dc403SDave Cobbley striplevel = parm["pnum"] 936eb8dc403SDave Cobbley else: 937eb8dc403SDave Cobbley striplevel = '1' 938eb8dc403SDave Cobbley patchparm['striplevel'] = striplevel 939eb8dc403SDave Cobbley 940eb8dc403SDave Cobbley patchdir = parm.get('patchdir') 941eb8dc403SDave Cobbley if patchdir: 942eb8dc403SDave Cobbley patchparm['patchdir'] = patchdir 943eb8dc403SDave Cobbley 944eb8dc403SDave Cobbley localurl = bb.fetch.encodeurl(('file', '', local, '', '', patchparm)) 945eb8dc403SDave Cobbley patches.append(localurl) 946eb8dc403SDave Cobbley 947eb8dc403SDave Cobbley if all: 948eb8dc403SDave Cobbley return sources 949eb8dc403SDave Cobbley 950eb8dc403SDave Cobbley return patches 951eb8dc403SDave Cobbley 952eb8dc403SDave Cobbley 953eb8dc403SDave Cobbleydef should_apply(parm, d): 954c342db35SBrad Bishop import bb.utils 955eb8dc403SDave Cobbley if "mindate" in parm or "maxdate" in parm: 956eb8dc403SDave Cobbley pn = d.getVar('PN') 957eb8dc403SDave Cobbley srcdate = d.getVar('SRCDATE_%s' % pn) 958eb8dc403SDave Cobbley if not srcdate: 959eb8dc403SDave Cobbley srcdate = d.getVar('SRCDATE') 960eb8dc403SDave Cobbley 961eb8dc403SDave Cobbley if srcdate == "now": 962eb8dc403SDave Cobbley srcdate = d.getVar('DATE') 963eb8dc403SDave Cobbley 964eb8dc403SDave Cobbley if "maxdate" in parm and parm["maxdate"] < srcdate: 965eb8dc403SDave Cobbley return False, 'is outdated' 966eb8dc403SDave Cobbley 967eb8dc403SDave Cobbley if "mindate" in parm and parm["mindate"] > srcdate: 968eb8dc403SDave Cobbley return False, 'is predated' 969eb8dc403SDave Cobbley 970eb8dc403SDave Cobbley 971eb8dc403SDave Cobbley if "minrev" in parm: 972eb8dc403SDave Cobbley srcrev = d.getVar('SRCREV') 973eb8dc403SDave Cobbley if srcrev and srcrev < parm["minrev"]: 974eb8dc403SDave Cobbley return False, 'applies to later revisions' 975eb8dc403SDave Cobbley 976eb8dc403SDave Cobbley if "maxrev" in parm: 977eb8dc403SDave Cobbley srcrev = d.getVar('SRCREV') 978eb8dc403SDave Cobbley if srcrev and srcrev > parm["maxrev"]: 979eb8dc403SDave Cobbley return False, 'applies to earlier revisions' 980eb8dc403SDave Cobbley 981eb8dc403SDave Cobbley if "rev" in parm: 982eb8dc403SDave Cobbley srcrev = d.getVar('SRCREV') 983eb8dc403SDave Cobbley if srcrev and parm["rev"] not in srcrev: 984eb8dc403SDave Cobbley return False, "doesn't apply to revision" 985eb8dc403SDave Cobbley 986eb8dc403SDave Cobbley if "notrev" in parm: 987eb8dc403SDave Cobbley srcrev = d.getVar('SRCREV') 988eb8dc403SDave Cobbley if srcrev and parm["notrev"] in srcrev: 989eb8dc403SDave Cobbley return False, "doesn't apply to revision" 990eb8dc403SDave Cobbley 991c342db35SBrad Bishop if "maxver" in parm: 992c342db35SBrad Bishop pv = d.getVar('PV') 993c342db35SBrad Bishop if bb.utils.vercmp_string_op(pv, parm["maxver"], ">"): 994c342db35SBrad Bishop return False, "applies to earlier version" 995c342db35SBrad Bishop 996c342db35SBrad Bishop if "minver" in parm: 997c342db35SBrad Bishop pv = d.getVar('PV') 998c342db35SBrad Bishop if bb.utils.vercmp_string_op(pv, parm["minver"], "<"): 999c342db35SBrad Bishop return False, "applies to later version" 1000c342db35SBrad Bishop 1001eb8dc403SDave Cobbley return True, None 1002