Exemple #1
0
    def explode(self, commits, deps_from, deps_on):
        """
        Walk the dependency tree breadth-first starting with the
        leaves at the bottom.

        For each commit, figure out whether it should be exploded

        :param commits: dict mapping SHA1 hashes to pygit2.Commit objects
        :param deps_from: dict mapping dependents to dependencies
        :param deps_on: dict mapping in opposite direction
        """
        todo = self.get_leaves(commits, deps_from)

        # Each time we explode a commit, we'll remove it from any
        # dict which is a value of this dict.
        unexploded_deps_from = copy.deepcopy(deps_from)

        self.logger.debug("Initial queue of leaves:")
        for commit in todo:
            self.logger.debug('  ' + GitUtils.commit_summary(commit))

        self.current_branch = None

        while todo:
            commit = todo.pop(0)
            sha = commit.hex
            self.logger.debug("Exploding %s" % GitUtils.commit_summary(commit))
            if unexploded_deps_from[sha]:
                abort("BUG: unexploded deps from %s" %
                      GitUtils.commit_summary(commit))

            deps = deps_from[sha]
            self.prepare_cherrypick_base(sha, deps, commits)
            self.cherry_pick(sha)

            self.queue_new_leaves(todo, commit, commits, deps_on,
                                  unexploded_deps_from)
Exemple #2
0
    def __init__(self, repo, base, head, debug, context_lines):
        self.logger = standard_logger('git-explode', debug)

        self.debug = debug
        self.repo = repo
        self.base = base
        self.base_commit = GitUtils.ref_commit(repo, base)
        self.logger.debug("base commit %s is %s" %
                          (base, GitUtils.commit_summary(self.base_commit)))
        self.head = head
        self.context_lines = context_lines
        self.topic_mgr = TopicManager('topic%d', self.logger)

        # Map commits to their exploded version
        self.exploded = {}
Exemple #3
0
    def queue_new_leaves(self, todo, exploded_commit, commits, deps_on,
                         unexploded_deps_from):
        """When a commit is exploded, there may be other commits in the
        dependency tree which only had a single dependency on this
        commit.  In that case they have effectively become leaves on
        the dependency tree of unexploded commits, so they should be
        added to the explode queue.

        """
        sha1 = exploded_commit.hex
        for dependent in deps_on[sha1]:
            del unexploded_deps_from[dependent][sha1]
            if not unexploded_deps_from[dependent]:
                new = commits[dependent]
                self.logger.debug("+ pushed to queue: %s" %
                                  GitUtils.commit_summary(new))
                todo.insert(0, new)
Exemple #4
0
 def register_new_dependent(self, dependent, dependent_sha1):
     if dependent_sha1 not in self.dependencies:
         self.logger.info("          New dependent: %s" %
                           GitUtils.commit_summary(dependent))
         self.dependencies[dependent_sha1] = {}
         self.notify_listeners("new_dependent", dependent)
Exemple #5
0
    def blame_hunk(self, dependent, parent, path, hunk):
        """Run git blame on the parts of the hunk which exist in the older
        commit in the diff.  The commits generated by git blame are
        the commits which the newer commit in the diff depends on,
        because without the lines from those commits, the hunk would
        not apply correctly.
        """
        line_range_before = "-%d,%d" % (hunk.old_start, hunk.old_lines)
        line_range_after = "+%d,%d" % (hunk.new_start, hunk.new_lines)
        self.logger.debug("        Blaming hunk %s @ %s" %
                          (line_range_before, parent.hex[:8]))

        if not self.tree_lookup(path, parent):
            # This is probably because dependent added a new directory
            # which was not previously in the parent.
            return

        cmd = [
            'git', 'blame', '--porcelain', '-L',
            "%d,+%d" % (hunk.old_start, hunk.old_lines), parent.hex, '--', path
        ]
        blame = subprocess.check_output(cmd, universal_newlines=True)

        dependent_sha1 = dependent.hex
        if dependent_sha1 not in self.dependencies:
            self.logger.debug("          New dependent: %s" %
                              GitUtils.commit_summary(dependent))
            self.dependencies[dependent_sha1] = {}
            self.notify_listeners("new_dependent", dependent)

        line_to_culprit = {}

        for line in blame.split('\n'):
            self.logger.debug("        !" + line.rstrip())
            m = re.match('^([0-9a-f]{40}) (\d+) (\d+)( \d+)?$', line)
            if not m:
                continue
            dependency_sha1, orig_line_num, line_num = m.group(1, 2, 3)
            line_num = int(line_num)
            dependency = self.get_commit(dependency_sha1)
            line_to_culprit[line_num] = dependency.hex

            if self.is_excluded(dependency):
                self.logger.debug(
                    "        Excluding dependency %s from line %s (%s)" %
                    (dependency_sha1[:8], line_num,
                     GitUtils.oneline(dependency)))
                continue

            if dependency_sha1 not in self.dependencies[dependent_sha1]:
                if not self.seen_commit(dependency):
                    self.notify_listeners("new_commit", dependency)
                    self.dependencies[dependent_sha1][dependency_sha1] = {}

                self.notify_listeners("new_dependency", dependent, dependency,
                                      path, line_num)

                self.logger.debug(
                    "          New dependency %s -> %s via line %s (%s)" %
                    (dependent_sha1[:8], dependency_sha1[:8], line_num,
                     GitUtils.oneline(dependency)))

                if dependency_sha1 in self.todo_d:
                    self.logger.debug(
                        "        Dependency on %s via line %s already in TODO"
                        % (
                            dependency_sha1[:8],
                            line_num,
                        ))
                    continue

                if dependency_sha1 in self.done_d:
                    self.logger.debug(
                        "        Dependency on %s via line %s already done" % (
                            dependency_sha1[:8],
                            line_num,
                        ))
                    continue

                if dependency_sha1 not in self.dependencies:
                    if self.options.recurse:
                        self.todo.append(dependency)
                        self.todo_d[dependency.hex] = True
                        self.logger.debug("  + Added %s to TODO" %
                                          dependency.hex[:8])

            dep_sources = self.dependencies[dependent_sha1][dependency_sha1]

            if path not in dep_sources:
                dep_sources[path] = {}
                self.notify_listeners('new_path', dependent, dependency, path,
                                      line_num)

            if line_num in dep_sources[path]:
                abort("line %d already found when blaming %s:%s\n"
                      "old:\n  %s\n"
                      "new:\n  %s" % (line_num, parent.hex[:8], path,
                                      dep_sources[path][line_num], line))

            dep_sources[path][line_num] = line
            self.logger.debug("          New line for %s -> %s: %s" %
                              (dependent_sha1[:8], dependency_sha1[:8], line))
            self.notify_listeners('new_line', dependent, dependency, path,
                                  line_num)

        diff_format = '      |%8.8s %5s %s%s'
        hunk_header = '@@ %s %s @@' % (line_range_before, line_range_after)
        self.logger.debug(diff_format % ('--------', '-----', '', hunk_header))
        line_num = hunk.old_start
        for line in hunk.lines:
            if "\n\\ No newline at end of file" == line.content.rstrip():
                break
            if line.origin == '+':
                rev = ln = ''
            else:
                rev = line_to_culprit[line_num]
                ln = line_num
                line_num += 1
            self.logger.debug(diff_format %
                              (rev, ln, line.origin, line.content.rstrip()))