Exemplo n.º 1
0
    def get_repo(cls, path='.'):
        try:
            repo_path = pygit2.discover_repository(path)
        except KeyError:
            abort("Couldn't find a repository in the current directory.")

        return pygit2.Repository(repo_path)
Exemplo n.º 2
0
    def find_dependencies(self, dependent_rev, recurse=None):
        """Find all dependencies of the given revision, recursively traversing
        the dependency tree if requested.
        """
        if recurse is None:
            recurse = self.options.recurse

        try:
            dependent = self.get_commit(dependent_rev)
        except InvalidCommitish as e:
            abort(e.message())

        self.todo.append(dependent)
        self.todo_d[dependent.hex] = True

        first_time = True

        while self.todo:
            sha1s = [commit.hex[:8] for commit in self.todo]
            if first_time:
                self.logger.info("Initial TODO list: %s" % " ".join(sha1s))
                first_time = False
            else:
                self.logger.info("  TODO list now: %s" % " ".join(sha1s))
            dependent = self.todo.pop(0)
            dependent_sha1 = dependent.hex
            del self.todo_d[dependent_sha1]
            self.logger.info("  Processing %s from TODO list" %
                             dependent_sha1[:8])

            if dependent_sha1 in self.done_d:
                self.logger.info("  %s already done previously" %
                                 dependent_sha1)
                continue

            self.notify_listeners('new_commit', dependent)
            if self.is_cherry_picked(dependent):
                self.logger.debug("%s is already cherry picked",
                                  dependent.hex[:8])
                continue
            else:
                self.logger.debug("%s is not cherry picked", dependent.hex[:8])

            parent = dependent.parents[0]
            self.find_dependencies_with_parent(dependent, parent)
            self.done.append(dependent_sha1)
            self.done_d[dependent_sha1] = True
            self.logger.info("  Found all dependencies for %s" %
                             dependent_sha1[:8])
            # A commit won't have any dependencies if it only added new files
            dependencies = self.dependencies.get(dependent_sha1, {})
            self.notify_listeners('dependent_done', dependent, dependencies)

        self.logger.info("Finished processing TODO list")
        self.notify_listeners('all_done')
Exemplo n.º 3
0
def main(args):
    options, args = parse_args()
    # rev_list = sys.stdin.readlines()

    if options.serve:
        serve(options)
    else:
        try:
            cli(options, args)
        except InvalidCommitish as e:
            abort(e.message())
Exemplo n.º 4
0
def main(args):
    options, args = parse_args()
    # rev_list = sys.stdin.readlines()

    if options.serve:
        serve(options)
    else:
        sys.stdout = os.fdopen(sys.stdout.fileno(), 'w')
        try:
            cli(options, args)
        except InvalidCommitish as e:
            abort(e.message())
Exemplo n.º 5
0
    def record_dependency_source(self, parent, dependent, dependent_sha1,
                                 dependency, dependency_sha1, path, line_num,
                                 line):
        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)
Exemplo n.º 6
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)
Exemplo n.º 7
0
def main(args):
    if len(args) != 1:
        usage()

    logger = get_logger()

    url = args[0]
    logger.debug("received URL: %s" % url)
    if re.search(r'%23', url):
        # Uh-oh, double-encoded URIs!  Some versions of Chrome
        # encode the value you set location.href too.
        url = urllib.unquote(url)
        logger.debug("unquoted: %s" % url)
    url = urlparse(url)
    logger.debug("parsed: %r" % repr(url))

    if url.scheme != 'gitfile':
        abort("URL must use gitfile:// scheme")

    repo = os.path.join(url.netloc, url.path)
    rev = url.fragment
    os.chdir(repo)

    subprocess.Popen(['gitk', '--all', '--select-commit=%s' % rev])
Exemplo n.º 8
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()))
Exemplo n.º 9
0
def serve(options):
    try:
        import flask
        from flask import Flask, send_file, safe_join
        from flask.json import jsonify
    except ImportError:
        abort("Cannot find flask module which is required for webserver mode.")

    logger = standard_logger(__name__, options.debug)

    webserver = Flask('git-deps')
    here = os.path.dirname(os.path.realpath(__file__))
    root = os.path.join(here, 'html')
    webserver.root_path = root
    logger.debug("Webserver root is %s" % root)

    ##########################################################
    # Static content

    @webserver.route('/')
    def main_page():
        return send_file('git-deps.html')

    @webserver.route('/tip-template.html')
    def tip_template():
        return send_file('tip-template.html')

    @webserver.route('/test.json')
    def data():
        return send_file('test.json')

    def make_subdir_handler(subdir):
        def subdir_handler(filename):
            path = safe_join(root, subdir)
            path = safe_join(path, filename)
            if os.path.exists(path):
                return send_file(path)
            else:
                flask.abort(404)
        return subdir_handler

    for subdir in ('node_modules', 'css', 'js'):
        fn = make_subdir_handler(subdir)
        route = '/%s/<path:filename>' % subdir
        webserver.add_url_rule(route, subdir + '_handler', fn)

    ##########################################################
    # Dynamic content

    def json_error(status_code, error_class, message, **extra):
        json = {
            'status': status_code,
            'error_class': error_class,
            'message': message,
        }
        json.update(extra)
        response = jsonify(json)
        response.status_code = status_code
        return response

    @webserver.route('/options')
    def send_options():
        client_options = options.__dict__
        client_options['repo_path'] = os.getcwd()
        return jsonify(client_options)

    @webserver.route('/deps.json/<revspec>')
    def deps(revspec):
        detector = DependencyDetector(options)
        listener = JSONDependencyListener(options)
        detector.add_listener(listener)

        if '..' in revspec:
            try:
                revisions = GitUtils.rev_list(revspec)
            except subprocess.CalledProcessError:
                return json_error(
                    422, 'Invalid revision range',
                    "Could not resolve revision range '%s'" % revspec,
                    revspec=revspec)
        else:
            revisions = [revspec]

        for rev in revisions:
            try:
                detector.get_commit(rev)
            except InvalidCommitish:
                return json_error(
                    422, 'Invalid revision',
                    "Could not resolve revision '%s'" % rev,
                    rev=rev)

            detector.find_dependencies(rev)

        tip_commit = detector.get_commit(revisions[0])
        tip_sha1 = tip_commit.hex

        json = listener.json()
        json['query'] = {
            'revspec': revspec,
            'revisions': revisions,
            'tip_sha1': tip_sha1,
            'tip_abbrev': GitUtils.abbreviate_sha1(tip_sha1),
        }
        return jsonify(json)

    # We don't want to see double-decker warnings, so check
    # WERKZEUG_RUN_MAIN which is only set for the first startup, not
    # on app reloads.
    if options.debug and not os.getenv('WERKZEUG_RUN_MAIN'):
        print("!! WARNING!  Debug mode enabled, so webserver is completely "
              "insecure!")
        print("!! Arbitrary code can be executed from browser!")
        print()
    webserver.run(port=options.port, debug=options.debug,
                  host=options.bindaddr)
Exemplo n.º 10
0
def usage():
    abort("usage: git-handler URL")