diff options
-rwxr-xr-x | git-deps | 111 |
1 files changed, 69 insertions, 42 deletions
@@ -27,6 +27,7 @@ import subprocess import types from textwrap import dedent, wrap + def abort(msg, exitcode=1): print(msg, file=sys.stderr) sys.exit(exitcode) @@ -39,7 +40,7 @@ except ImportError: import platform if platform.system() == 'Linux': distro, version, d_id = platform.linux_distribution() - distro = distro.strip() # why are there trailing spaces?? + distro = distro.strip() # why are there trailing spaces?? if distro == 'openSUSE': install_guide = \ "You should be able to install it with something like:\n\n" \ @@ -54,6 +55,7 @@ except ImportError: msg += "\n\n" + install_guide abort(msg) + class DependencyListener(object): """Class for listening to result events generated by DependencyDetector. Add an instance of this class to a @@ -78,6 +80,7 @@ class DependencyListener(object): def dependent_done(self, dependent, dependencies): pass + class CLIDependencyListener(DependencyListener): """Dependency listener for use when running in CLI mode. @@ -88,7 +91,7 @@ class CLIDependencyListener(DependencyListener): """ def new_dependency(self, dependent, dependency, path, line_num): - dependent_sha = dependent.hex + dependent_sha = dependent.hex dependency_sha = dependency.hex if self.options.recurse: @@ -101,14 +104,22 @@ class CLIDependencyListener(DependencyListener): print(dependency_sha) if self.options.log: - cmd = [ 'git', '--no-pager', '-c', 'color.ui=always', 'log', '-n1', dependency_sha ] + cmd = [ + 'git', + '--no-pager', + '-c', 'color.ui=always', + 'log', '-n1', + dependency_sha + ] print(subprocess.check_output(cmd)) # dependency = detector.get_commit(dependency_sha) # print(dependency.message + "\n") # for path in self.dependencies[dependency]: # print(" %s" % path) - # print(" %s" % ", ".join(sorted(self.dependencies[dependency][path].keys()))) + # keys = sorted(self.dependencies[dependency][path].keys() + # print(" %s" % ", ".join(keys))) + class DependencyDetector(object): """Class for automatically detecting dependencies between git commits. @@ -144,12 +155,12 @@ class DependencyDetector(object): # A TODO list (queue) and dict of dependencies which haven't # yet been recursively followed. Only useful when recursing. - self.todo = [] + self.todo = [] self.todo_d = {} # An ordered list and dict of commits whose dependencies we # have already detected. - self.done = [] + self.done = [] self.done_d = {} # A cache mapping SHAs to commit objects @@ -184,7 +195,7 @@ class DependencyDetector(object): formatter = logging.Formatter(fmt=log_format, datefmt=date_format) handler = logging.StreamHandler(stream=sys.stdout) handler.setFormatter(formatter) - #logger = logging.getLogger(__name__) + # logger = logging.getLogger(__name__) logger = logging.getLogger(self.__class__.__name__) logger.setLevel(logging.DEBUG) logger.addHandler(handler) @@ -213,17 +224,19 @@ class DependencyDetector(object): self.todo_d[dependent.hex] = True while self.todo: - self.logger.debug("TODO list: %s" % - " ".join([ commit.hex[:8] for commit in self.todo ])) + shas = [commit.hex[:8] for commit in self.todo] + self.logger.debug("TODO list: %s" % " ".join(shas)) dependent = self.todo.pop(0) del self.todo_d[dependent.hex] - self.logger.debug("Processing %s from TODO list" % dependent.hex[:8]) + self.logger.debug("Processing %s from TODO list" % + dependent.hex[:8]) for parent in dependent.parents: self.find_dependencies_with_parent(dependent, parent) self.done.append(dependent.hex) self.done_d[dependent.hex] = True - self.logger.debug("Found all dependencies for %s" % dependent.hex[:8]) + self.logger.debug("Found all dependencies for %s" % + dependent.hex[:8]) # A commit won't have any dependencies if it only added new files dependencies = self.dependencies.get(dependent.hex, {}) self.notify_listeners('dependent_done', dependent, dependencies) @@ -235,7 +248,8 @@ class DependencyDetector(object): """ self.logger.debug(" Finding dependencies of %s via parent %s" % (dependent.hex[:8], parent.hex[:8])) - diff = self.repo.diff(parent, dependent, context_lines=self.options.context_lines) + diff = self.repo.diff(parent, dependent, + context_lines=self.options.context_lines) for patch in diff: path = patch.old_file_path self.logger.debug(" Examining hunks in %s" % path) @@ -278,7 +292,7 @@ class DependencyDetector(object): line_to_culprit = {} for line in blame.split('\n'): - #self.logger.debug(' !' + line.rstrip()) + # self.logger.debug(' !' + line.rstrip()) m = re.match('^([0-9a-f]{40}) (\d+) (\d+)( \d+)?$', line) if not m: continue @@ -288,43 +302,51 @@ class DependencyDetector(object): line_to_culprit[line_num] = dependency.hex if self.is_excluded(dependency): - self.logger.debug(' Excluding dependency %s from line %s (%s)' % - (dependency_sha[:8], line_num, - self.oneline(dependency))) + self.logger.debug( + ' Excluding dependency %s from line %s (%s)' % + (dependency_sha[:8], line_num, + self.oneline(dependency))) continue if dependency_sha not in self.dependencies[dependent_sha]: if dependency_sha in self.todo_d: - self.logger.debug(' Dependency %s via line %s already in TODO' % - (dependency_sha[:8], line_num,)) + self.logger.debug( + ' Dependency %s via line %s already in TODO' % + (dependency_sha[:8], line_num,)) continue if dependency_sha in self.done_d: - self.logger.debug(' Dependency %s via line %s already done' % - (dependency_sha[:8], line_num,)) + self.logger.debug( + ' Dependency %s via line %s already done' % + (dependency_sha[:8], line_num,)) continue - self.logger.debug(' New dependency %s via line %s (%s)' % - (dependency_sha[:8], line_num, - self.oneline(dependency))) + self.logger.debug( + ' New dependency %s via line %s (%s)' % + (dependency_sha[:8], line_num, self.oneline(dependency))) self.dependencies[dependent_sha][dependency_sha] = {} - self.notify_listeners('new_dependency', dependent, dependency, path, line_num) + self.notify_listeners('new_dependency', + dependent, dependency, path, line_num) if dependency_sha not in self.dependencies: if self.options.recurse: self.todo.append(dependency) self.todo_d[dependency.hex] = True self.logger.debug(' added to TODO') - if path not in self.dependencies[dependent_sha][dependency_sha]: - self.dependencies[dependent_sha][dependency_sha][path] = {} - self.notify_listeners('new_path', dependent, dependency, path, line_num) + dep_sources = self.dependencies[dependent_sha][dependency_sha] + + if path not in dep_sources: + dep_sources[path] = {} + self.notify_listeners('new_path', + dependent, dependency, path, line_num) - if line_num in self.dependencies[dependent_sha][dependency_sha][path]: + if line_num in dep_sources[path]: abort("line %d already found when blaming %s:%s" % (line_num, parent.hex[:8], path)) - self.dependencies[dependent_sha][dependency_sha][path][line_num] = True - self.notify_listeners('new_line', dependent, dependency, path, line_num) + dep_sources[path][line_num] = True + 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) @@ -350,7 +372,8 @@ class DependencyDetector(object): return False def branch_contains(self, commit, branch): - self.logger.debug(" Does %s contain %s?" % (branch, commit.hex[:8])) + self.logger.debug(" Does %s contain %s?" % + (branch, commit.hex[:8])) branch_commit = self.get_commit(branch) if commit.hex not in self.branch_contains_cache: @@ -360,10 +383,10 @@ class DependencyDetector(object): self.logger.debug(" %s (memoized)" % memoized) return memoized - cmd = [ 'git', 'merge-base', commit.hex, branch_commit.hex ] - #self.logger.debug(" ".join(cmd)) + cmd = ['git', 'merge-base', commit.hex, branch_commit.hex] + # self.logger.debug(" ".join(cmd)) out = subprocess.check_output(cmd).strip() - #self.logger.debug(out) + # self.logger.debug(out) result = out == commit.hex self.logger.debug(" %s" % result) self.branch_contains_cache[commit.hex][branch_commit.hex] = result @@ -383,7 +406,7 @@ class DependencyDetector(object): if isinstance(tree_or_blob, pygit2.Tree): if dirent in tree_or_blob: tree_or_blob = self.repo[tree_or_blob[dirent].oid] - #self.logger.debug('%s in %s' % (dirent, path)) + # self.logger.debug('%s in %s' % (dirent, path)) if path: path += '/' path += dirent @@ -401,10 +424,12 @@ class DependencyDetector(object): def edges(self): return [ - [ (dependent, dependency) for dependency in self.dependencies[dependent] ] + [(dependent, dependency) + for dependency in self.dependencies[dependent]] for dependent in self.dependencies.keys() ] + def parse_args(): parser = argparse.ArgumentParser( description='Auto-detects commits which the given commit(s) depend on.', @@ -416,11 +441,12 @@ def parse_args(): help='Follow dependencies recursively') parser.add_argument('-e', '--exclude-commits', dest='exclude_commits', action='append', metavar='COMMITISH', - help='Exclude commits which are ancestors of the given COMMITISH' - ' (can be repeated)') - parser.add_argument('-c', '--context-lines', dest='context_lines', type=int, - metavar='NUM', default=1, - help='Number of lines of diff context to use [%(default)s]') + help='Exclude commits which are ancestors of the ' + 'given COMMITISH (can be repeated)') + parser.add_argument('-c', '--context-lines', dest='context_lines', + type=int, metavar='NUM', default=1, + help='Number of lines of diff context to use ' + '[%(default)s]') parser.add_argument('-d', '--debug', dest='debug', action='store_true', help='Show debugging') @@ -432,9 +458,10 @@ def parse_args(): return options, args + def main(): options, args = parse_args() - #rev_list = sys.stdin.readlines() + # rev_list = sys.stdin.readlines() listener = CLIDependencyListener(options) detector = DependencyDetector(options, listener=listener) |