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