diff options
| -rw-r--r-- | meta/lib/oe/patch.py | 109 | ||||
| -rw-r--r-- | meta/lib/oeqa/selftest/cases/devtool.py | 9 | ||||
| -rw-r--r-- | scripts/lib/devtool/standard.py | 15 | ||||
| -rw-r--r-- | scripts/lib/devtool/upgrade.py | 24 |
4 files changed, 103 insertions, 54 deletions
diff --git a/meta/lib/oe/patch.py b/meta/lib/oe/patch.py index 3ded5f3601..60a0cc8291 100644 --- a/meta/lib/oe/patch.py +++ b/meta/lib/oe/patch.py | |||
| @@ -294,8 +294,9 @@ class PatchTree(PatchSet): | |||
| 294 | self.Pop(all=True) | 294 | self.Pop(all=True) |
| 295 | 295 | ||
| 296 | class GitApplyTree(PatchTree): | 296 | class GitApplyTree(PatchTree): |
| 297 | patch_line_prefix = '%% original patch' | 297 | notes_ref = "refs/notes/devtool" |
| 298 | ignore_commit_prefix = '%% ignore' | 298 | original_patch = 'original patch' |
| 299 | ignore_commit = 'ignore' | ||
| 299 | 300 | ||
| 300 | def __init__(self, dir, d): | 301 | def __init__(self, dir, d): |
| 301 | PatchTree.__init__(self, dir, d) | 302 | PatchTree.__init__(self, dir, d) |
| @@ -452,7 +453,7 @@ class GitApplyTree(PatchTree): | |||
| 452 | # Prepare git command | 453 | # Prepare git command |
| 453 | cmd = ["git"] | 454 | cmd = ["git"] |
| 454 | GitApplyTree.gitCommandUserOptions(cmd, commituser, commitemail) | 455 | GitApplyTree.gitCommandUserOptions(cmd, commituser, commitemail) |
| 455 | cmd += ["commit", "-F", tmpfile] | 456 | cmd += ["commit", "-F", tmpfile, "--no-verify"] |
| 456 | # git doesn't like plain email addresses as authors | 457 | # git doesn't like plain email addresses as authors |
| 457 | if author and '<' in author: | 458 | if author and '<' in author: |
| 458 | cmd.append('--author="%s"' % author) | 459 | cmd.append('--author="%s"' % author) |
| @@ -461,14 +462,52 @@ class GitApplyTree(PatchTree): | |||
| 461 | return (tmpfile, cmd) | 462 | return (tmpfile, cmd) |
| 462 | 463 | ||
| 463 | @staticmethod | 464 | @staticmethod |
| 465 | def addNote(repo, ref, key, value=None): | ||
| 466 | note = key + (": %s" % value if value else "") | ||
| 467 | notes_ref = GitApplyTree.notes_ref | ||
| 468 | runcmd(["git", "config", "notes.rewriteMode", "ignore"], repo) | ||
| 469 | runcmd(["git", "config", "notes.displayRef", notes_ref, notes_ref], repo) | ||
| 470 | runcmd(["git", "config", "notes.rewriteRef", notes_ref, notes_ref], repo) | ||
| 471 | runcmd(["git", "notes", "--ref", notes_ref, "append", "-m", note, ref], repo) | ||
| 472 | |||
| 473 | @staticmethod | ||
| 474 | def removeNote(repo, ref, key): | ||
| 475 | notes = GitApplyTree.getNotes(repo, ref) | ||
| 476 | notes = {k: v for k, v in notes.items() if k != key and not k.startswith(key + ":")} | ||
| 477 | runcmd(["git", "notes", "--ref", GitApplyTree.notes_ref, "remove", "--ignore-missing", ref], repo) | ||
| 478 | for note, value in notes.items(): | ||
| 479 | GitApplyTree.addNote(repo, ref, note, value) | ||
| 480 | |||
| 481 | @staticmethod | ||
| 482 | def getNotes(repo, ref): | ||
| 483 | import re | ||
| 484 | |||
| 485 | note = None | ||
| 486 | try: | ||
| 487 | note = runcmd(["git", "notes", "--ref", GitApplyTree.notes_ref, "show", ref], repo) | ||
| 488 | prefix = "" | ||
| 489 | except CmdError: | ||
| 490 | note = runcmd(['git', 'show', '-s', '--format=%B', ref], repo) | ||
| 491 | prefix = "%% " | ||
| 492 | |||
| 493 | note_re = re.compile(r'^%s(.*?)(?::\s*(.*))?$' % prefix) | ||
| 494 | notes = dict() | ||
| 495 | for line in note.splitlines(): | ||
| 496 | m = note_re.match(line) | ||
| 497 | if m: | ||
| 498 | notes[m.group(1)] = m.group(2) | ||
| 499 | |||
| 500 | return notes | ||
| 501 | |||
| 502 | @staticmethod | ||
| 464 | def commitIgnored(subject, dir=None, files=None, d=None): | 503 | def commitIgnored(subject, dir=None, files=None, d=None): |
| 465 | if files: | 504 | if files: |
| 466 | runcmd(['git', 'add'] + files, dir) | 505 | runcmd(['git', 'add'] + files, dir) |
| 467 | message = "%s\n\n%s" % (subject, GitApplyTree.ignore_commit_prefix) | ||
| 468 | cmd = ["git"] | 506 | cmd = ["git"] |
| 469 | GitApplyTree.gitCommandUserOptions(cmd, d=d) | 507 | GitApplyTree.gitCommandUserOptions(cmd, d=d) |
| 470 | cmd += ["commit", "-m", message, "--no-verify"] | 508 | cmd += ["commit", "-m", subject, "--no-verify"] |
| 471 | runcmd(cmd, dir) | 509 | runcmd(cmd, dir) |
| 510 | GitApplyTree.addNote(dir, "HEAD", GitApplyTree.ignore_commit) | ||
| 472 | 511 | ||
| 473 | @staticmethod | 512 | @staticmethod |
| 474 | def extractPatches(tree, startcommits, outdir, paths=None): | 513 | def extractPatches(tree, startcommits, outdir, paths=None): |
| @@ -484,18 +523,20 @@ class GitApplyTree(PatchTree): | |||
| 484 | out = runcmd(["sh", "-c", " ".join(shellcmd)], os.path.join(tree, name)) | 523 | out = runcmd(["sh", "-c", " ".join(shellcmd)], os.path.join(tree, name)) |
| 485 | if out: | 524 | if out: |
| 486 | for srcfile in out.split(): | 525 | for srcfile in out.split(): |
| 487 | outfile = os.path.basename(srcfile) | 526 | # This loop, which is used to remove any line that |
| 527 | # starts with "%% original patch", is kept for backwards | ||
| 528 | # compatibility. If/when that compatibility is dropped, | ||
| 529 | # it can be replaced with code to just read the first | ||
| 530 | # line of the patch file to get the SHA-1, and the code | ||
| 531 | # below that writes the modified patch file can be | ||
| 532 | # replaced with a simple file move. | ||
| 488 | for encoding in ['utf-8', 'latin-1']: | 533 | for encoding in ['utf-8', 'latin-1']: |
| 489 | patchlines = [] | 534 | patchlines = [] |
| 490 | try: | 535 | try: |
| 491 | with open(srcfile, 'r', encoding=encoding, newline='') as f: | 536 | with open(srcfile, 'r', encoding=encoding, newline='') as f: |
| 492 | for line in f: | 537 | for line in f: |
| 493 | if line.startswith(GitApplyTree.patch_line_prefix): | 538 | if line.startswith("%% " + GitApplyTree.original_patch): |
| 494 | outfile = line.split()[-1].strip() | ||
| 495 | continue | 539 | continue |
| 496 | if line.startswith(GitApplyTree.ignore_commit_prefix): | ||
| 497 | outfile = None | ||
| 498 | break | ||
| 499 | patchlines.append(line) | 540 | patchlines.append(line) |
| 500 | except UnicodeDecodeError: | 541 | except UnicodeDecodeError: |
| 501 | continue | 542 | continue |
| @@ -503,11 +544,16 @@ class GitApplyTree(PatchTree): | |||
| 503 | else: | 544 | else: |
| 504 | raise PatchError('Unable to find a character encoding to decode %s' % srcfile) | 545 | raise PatchError('Unable to find a character encoding to decode %s' % srcfile) |
| 505 | 546 | ||
| 506 | if outfile: | 547 | sha1 = patchlines[0].split()[1] |
| 507 | bb.utils.mkdirhier(os.path.join(outdir, name)) | 548 | notes = GitApplyTree.getNotes(os.path.join(tree, name), sha1) |
| 508 | with open(os.path.join(outdir, name, outfile), 'w') as of: | 549 | if GitApplyTree.ignore_commit in notes: |
| 509 | for line in patchlines: | 550 | continue |
| 510 | of.write(line) | 551 | outfile = notes.get(GitApplyTree.original_patch, os.path.basename(srcfile)) |
| 552 | |||
| 553 | bb.utils.mkdirhier(os.path.join(outdir, name)) | ||
| 554 | with open(os.path.join(outdir, name, outfile), 'w') as of: | ||
| 555 | for line in patchlines: | ||
| 556 | of.write(line) | ||
| 511 | finally: | 557 | finally: |
| 512 | shutil.rmtree(tempdir) | 558 | shutil.rmtree(tempdir) |
| 513 | 559 | ||
| @@ -555,28 +601,11 @@ class GitApplyTree(PatchTree): | |||
| 555 | 601 | ||
| 556 | return runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) | 602 | return runcmd(["sh", "-c", " ".join(shellcmd)], self.dir) |
| 557 | 603 | ||
| 558 | # Add hooks which add a pointer to the original patch file name in the commit message | ||
| 559 | reporoot = (runcmd("git rev-parse --show-toplevel".split(), self.dir) or '').strip() | 604 | reporoot = (runcmd("git rev-parse --show-toplevel".split(), self.dir) or '').strip() |
| 560 | if not reporoot: | 605 | if not reporoot: |
| 561 | raise Exception("Cannot get repository root for directory %s" % self.dir) | 606 | raise Exception("Cannot get repository root for directory %s" % self.dir) |
| 562 | gitdir = (runcmd("git rev-parse --absolute-git-dir".split(), self.dir) or '').strip() | 607 | |
| 563 | if not gitdir: | 608 | patch_applied = True |
| 564 | raise Exception("Cannot get gitdir for directory %s" % self.dir) | ||
| 565 | hooks_dir = os.path.join(gitdir, 'hooks') | ||
| 566 | hooks_dir_backup = hooks_dir + '.devtool-orig' | ||
| 567 | if os.path.lexists(hooks_dir_backup): | ||
| 568 | raise Exception("Git hooks backup directory already exists: %s" % hooks_dir_backup) | ||
| 569 | if os.path.lexists(hooks_dir): | ||
| 570 | shutil.move(hooks_dir, hooks_dir_backup) | ||
| 571 | os.mkdir(hooks_dir) | ||
| 572 | commithook = os.path.join(hooks_dir, 'commit-msg') | ||
| 573 | applyhook = os.path.join(hooks_dir, 'applypatch-msg') | ||
| 574 | with open(commithook, 'w') as f: | ||
| 575 | # NOTE: the formatting here is significant; if you change it you'll also need to | ||
| 576 | # change other places which read it back | ||
| 577 | f.write('echo "\n%s: $PATCHFILE" >> $1' % GitApplyTree.patch_line_prefix) | ||
| 578 | os.chmod(commithook, 0o755) | ||
| 579 | shutil.copy2(commithook, applyhook) | ||
| 580 | try: | 609 | try: |
| 581 | patchfilevar = 'PATCHFILE="%s"' % os.path.basename(patch['file']) | 610 | patchfilevar = 'PATCHFILE="%s"' % os.path.basename(patch['file']) |
| 582 | if self._need_dirty_check(): | 611 | if self._need_dirty_check(): |
| @@ -587,7 +616,7 @@ class GitApplyTree(PatchTree): | |||
| 587 | pass | 616 | pass |
| 588 | else: | 617 | else: |
| 589 | if output: | 618 | if output: |
| 590 | # The tree is dirty, not need to try to apply patches with git anymore | 619 | # The tree is dirty, no need to try to apply patches with git anymore |
| 591 | # since they fail, fallback directly to patch | 620 | # since they fail, fallback directly to patch |
| 592 | output = PatchTree._applypatch(self, patch, force, reverse, run) | 621 | output = PatchTree._applypatch(self, patch, force, reverse, run) |
| 593 | output += self._commitpatch(patch, patchfilevar) | 622 | output += self._commitpatch(patch, patchfilevar) |
| @@ -620,10 +649,12 @@ class GitApplyTree(PatchTree): | |||
| 620 | output = PatchTree._applypatch(self, patch, force, reverse, run) | 649 | output = PatchTree._applypatch(self, patch, force, reverse, run) |
| 621 | output += self._commitpatch(patch, patchfilevar) | 650 | output += self._commitpatch(patch, patchfilevar) |
| 622 | return output | 651 | return output |
| 652 | except: | ||
| 653 | patch_applied = False | ||
| 654 | raise | ||
| 623 | finally: | 655 | finally: |
| 624 | shutil.rmtree(hooks_dir) | 656 | if patch_applied: |
| 625 | if os.path.lexists(hooks_dir_backup): | 657 | GitApplyTree.addNote(self.dir, "HEAD", GitApplyTree.original_patch, os.path.basename(patch['file'])) |
| 626 | shutil.move(hooks_dir_backup, hooks_dir) | ||
| 627 | 658 | ||
| 628 | 659 | ||
| 629 | class QuiltTree(PatchSet): | 660 | class QuiltTree(PatchSet): |
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index c4dcdb4550..d37848bdef 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py | |||
| @@ -989,9 +989,10 @@ class DevtoolModifyTests(DevtoolBase): | |||
| 989 | self.assertIn(tempdir, result.output) | 989 | self.assertIn(tempdir, result.output) |
| 990 | # Check git repo | 990 | # Check git repo |
| 991 | self._check_src_repo(tempdir) | 991 | self._check_src_repo(tempdir) |
| 992 | # Check that the patch is correctly applied | 992 | # Check that the patch is correctly applied. |
| 993 | # last commit message in the tree must contain | 993 | # The last commit message in the tree must contain the following note: |
| 994 | # %% original patch: <patchname> | 994 | # Notes (devtool): |
| 995 | # original patch: <patchname> | ||
| 995 | # .. | 996 | # .. |
| 996 | patchname = None | 997 | patchname = None |
| 997 | for uri in src_uri: | 998 | for uri in src_uri: |
| @@ -999,7 +1000,7 @@ class DevtoolModifyTests(DevtoolBase): | |||
| 999 | patchname = uri.replace("file://", "").partition('.patch')[0] + '.patch' | 1000 | patchname = uri.replace("file://", "").partition('.patch')[0] + '.patch' |
| 1000 | self.assertIsNotNone(patchname) | 1001 | self.assertIsNotNone(patchname) |
| 1001 | result = runCmd('git -C %s log -1' % tempdir) | 1002 | result = runCmd('git -C %s log -1' % tempdir) |
| 1002 | self.assertIn("%%%% original patch: %s" % patchname, result.output) | 1003 | self.assertIn("Notes (devtool):\n original patch: %s" % patchname, result.output) |
| 1003 | 1004 | ||
| 1004 | # Configure the recipe to check that the git dependencies are correctly patched in cargo config | 1005 | # Configure the recipe to check that the git dependencies are correctly patched in cargo config |
| 1005 | bitbake('-c configure %s' % testrecipe) | 1006 | bitbake('-c configure %s' % testrecipe) |
diff --git a/scripts/lib/devtool/standard.py b/scripts/lib/devtool/standard.py index 6d7fd17fbd..7972b4f822 100644 --- a/scripts/lib/devtool/standard.py +++ b/scripts/lib/devtool/standard.py | |||
| @@ -937,14 +937,13 @@ def modify(args, config, basepath, workspace): | |||
| 937 | seen_patches = [] | 937 | seen_patches = [] |
| 938 | for branch in branches: | 938 | for branch in branches: |
| 939 | branch_patches[branch] = [] | 939 | branch_patches[branch] = [] |
| 940 | (stdout, _) = bb.process.run('git log devtool-base..%s' % branch, cwd=srctree) | 940 | (stdout, _) = bb.process.run('git rev-list devtool-base..%s' % branch, cwd=srctree) |
| 941 | for line in stdout.splitlines(): | 941 | for sha1 in stdout.splitlines(): |
| 942 | line = line.strip() | 942 | notes = oe.patch.GitApplyTree.getNotes(srctree, sha1.strip()) |
| 943 | if line.startswith(oe.patch.GitApplyTree.patch_line_prefix): | 943 | origpatch = notes.get(oe.patch.GitApplyTree.original_patch) |
| 944 | origpatch = line[len(oe.patch.GitApplyTree.patch_line_prefix):].split(':', 1)[-1].strip() | 944 | if origpatch and origpatch not in seen_patches: |
| 945 | if not origpatch in seen_patches: | 945 | seen_patches.append(origpatch) |
| 946 | seen_patches.append(origpatch) | 946 | branch_patches[branch].append(origpatch) |
| 947 | branch_patches[branch].append(origpatch) | ||
| 948 | 947 | ||
| 949 | # Need to grab this here in case the source is within a subdirectory | 948 | # Need to grab this here in case the source is within a subdirectory |
| 950 | srctreebase = srctree | 949 | srctreebase = srctree |
diff --git a/scripts/lib/devtool/upgrade.py b/scripts/lib/devtool/upgrade.py index ef58523dc8..fa5b8ef3c7 100644 --- a/scripts/lib/devtool/upgrade.py +++ b/scripts/lib/devtool/upgrade.py | |||
| @@ -261,7 +261,7 @@ def _extract_new_source(newpv, srctree, no_patch, srcrev, srcbranch, branch, kee | |||
| 261 | revs = {} | 261 | revs = {} |
| 262 | for path in paths: | 262 | for path in paths: |
| 263 | (stdout, _) = _run('git rev-parse HEAD', cwd=path) | 263 | (stdout, _) = _run('git rev-parse HEAD', cwd=path) |
| 264 | revs[os.path.relpath(path,srctree)] = stdout.rstrip() | 264 | revs[os.path.relpath(path, srctree)] = stdout.rstrip() |
| 265 | 265 | ||
| 266 | if no_patch: | 266 | if no_patch: |
| 267 | patches = oe.recipeutils.get_recipe_patches(crd) | 267 | patches = oe.recipeutils.get_recipe_patches(crd) |
| @@ -272,17 +272,35 @@ def _extract_new_source(newpv, srctree, no_patch, srcrev, srcbranch, branch, kee | |||
| 272 | _run('git checkout devtool-patched -b %s' % branch, cwd=path) | 272 | _run('git checkout devtool-patched -b %s' % branch, cwd=path) |
| 273 | (stdout, _) = _run('git branch --list devtool-override-*', cwd=path) | 273 | (stdout, _) = _run('git branch --list devtool-override-*', cwd=path) |
| 274 | branches_to_rebase = [branch] + stdout.split() | 274 | branches_to_rebase = [branch] + stdout.split() |
| 275 | target_branch = revs[os.path.relpath(path, srctree)] | ||
| 276 | |||
| 277 | # There is a bug (or feature?) in git rebase where if a commit with | ||
| 278 | # a note is fully rebased away by being part of an old commit, the | ||
| 279 | # note is still attached to the old commit. Avoid this by making | ||
| 280 | # sure all old devtool related commits have a note attached to them | ||
| 281 | # (this assumes git config notes.rewriteMode is set to ignore). | ||
| 282 | (stdout, _) = __run('git rev-list devtool-base..%s' % target_branch) | ||
| 283 | for rev in stdout.splitlines(): | ||
| 284 | if not oe.patch.GitApplyTree.getNotes(path, rev): | ||
| 285 | oe.patch.GitApplyTree.addNote(path, rev, "dummy") | ||
| 286 | |||
| 275 | for b in branches_to_rebase: | 287 | for b in branches_to_rebase: |
| 276 | logger.info("Rebasing {} onto {}".format(b, revs[os.path.relpath(path,srctree)])) | 288 | logger.info("Rebasing {} onto {}".format(b, target_branch)) |
| 277 | _run('git checkout %s' % b, cwd=path) | 289 | _run('git checkout %s' % b, cwd=path) |
| 278 | try: | 290 | try: |
| 279 | _run('git rebase %s' % revs[os.path.relpath(path, srctree)], cwd=path) | 291 | _run('git rebase %s' % target_branch, cwd=path) |
| 280 | except bb.process.ExecutionError as e: | 292 | except bb.process.ExecutionError as e: |
| 281 | if 'conflict' in e.stdout: | 293 | if 'conflict' in e.stdout: |
| 282 | logger.warning('Command \'%s\' failed:\n%s\n\nYou will need to resolve conflicts in order to complete the upgrade.' % (e.command, e.stdout.rstrip())) | 294 | logger.warning('Command \'%s\' failed:\n%s\n\nYou will need to resolve conflicts in order to complete the upgrade.' % (e.command, e.stdout.rstrip())) |
| 283 | _run('git rebase --abort', cwd=path) | 295 | _run('git rebase --abort', cwd=path) |
| 284 | else: | 296 | else: |
| 285 | logger.warning('Command \'%s\' failed:\n%s' % (e.command, e.stdout)) | 297 | logger.warning('Command \'%s\' failed:\n%s' % (e.command, e.stdout)) |
| 298 | |||
| 299 | # Remove any dummy notes added above. | ||
| 300 | (stdout, _) = __run('git rev-list devtool-base..%s' % target_branch) | ||
| 301 | for rev in stdout.splitlines(): | ||
| 302 | oe.patch.GitApplyTree.removeNote(path, rev, "dummy") | ||
| 303 | |||
| 286 | _run('git checkout %s' % branch, cwd=path) | 304 | _run('git checkout %s' % branch, cwd=path) |
| 287 | 305 | ||
| 288 | if tmpsrctree: | 306 | if tmpsrctree: |
