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)
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')
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())
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())
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)
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)
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])
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()))
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)
def usage(): abort("usage: git-handler URL")