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