|  | @@ -24,10 +24,8 @@ import subprocess
 | 
	
		
			
				|  |  |  # Find the root of the git tree
 | 
	
		
			
				|  |  |  #
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -git_root = (subprocess
 | 
	
		
			
				|  |  | -            .check_output(['git', 'rev-parse', '--show-toplevel'])
 | 
	
		
			
				|  |  | -            .decode('utf-8')
 | 
	
		
			
				|  |  | -            .strip())
 | 
	
		
			
				|  |  | +git_root = (subprocess.check_output(['git', 'rev-parse', '--show-toplevel'])
 | 
	
		
			
				|  |  | +            .decode('utf-8').strip())
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #
 | 
	
		
			
				|  |  |  # Parse command line arguments
 | 
	
	
		
			
				|  | @@ -36,19 +34,22 @@ git_root = (subprocess
 | 
	
		
			
				|  |  |  default_out = os.path.join(git_root, '.github', 'CODEOWNERS')
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  argp = argparse.ArgumentParser('Generate .github/CODEOWNERS file')
 | 
	
		
			
				|  |  | -argp.add_argument('--out', '-o',
 | 
	
		
			
				|  |  | -                  type=str,
 | 
	
		
			
				|  |  | -                  default=default_out,
 | 
	
		
			
				|  |  | -                  help='Output file (default %s)' % default_out)
 | 
	
		
			
				|  |  | +argp.add_argument(
 | 
	
		
			
				|  |  | +    '--out',
 | 
	
		
			
				|  |  | +    '-o',
 | 
	
		
			
				|  |  | +    type=str,
 | 
	
		
			
				|  |  | +    default=default_out,
 | 
	
		
			
				|  |  | +    help='Output file (default %s)' % default_out)
 | 
	
		
			
				|  |  |  args = argp.parse_args()
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #
 | 
	
		
			
				|  |  |  # Walk git tree to locate all OWNERS files
 | 
	
		
			
				|  |  |  #
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -owners_files = [os.path.join(root, 'OWNERS')
 | 
	
		
			
				|  |  | -                for root, dirs, files in os.walk(git_root)
 | 
	
		
			
				|  |  | -                if 'OWNERS' in files]
 | 
	
		
			
				|  |  | +owners_files = [
 | 
	
		
			
				|  |  | +    os.path.join(root, 'OWNERS') for root, dirs, files in os.walk(git_root)
 | 
	
		
			
				|  |  | +    if 'OWNERS' in files
 | 
	
		
			
				|  |  | +]
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #
 | 
	
		
			
				|  |  |  # Parse owners files
 | 
	
	
		
			
				|  | @@ -57,39 +58,40 @@ owners_files = [os.path.join(root, 'OWNERS')
 | 
	
		
			
				|  |  |  Owners = collections.namedtuple('Owners', 'parent directives dir')
 | 
	
		
			
				|  |  |  Directive = collections.namedtuple('Directive', 'who globs')
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  def parse_owners(filename):
 | 
	
		
			
				|  |  | -  with open(filename) as f:
 | 
	
		
			
				|  |  | -    src = f.read().splitlines()
 | 
	
		
			
				|  |  | -  parent = True
 | 
	
		
			
				|  |  | -  directives = []
 | 
	
		
			
				|  |  | -  for line in src:
 | 
	
		
			
				|  |  | -    line = line.strip()
 | 
	
		
			
				|  |  | -    # line := directive | comment
 | 
	
		
			
				|  |  | -    if not line: continue
 | 
	
		
			
				|  |  | -    if line[0] == '#': continue
 | 
	
		
			
				|  |  | -    # it's a directive
 | 
	
		
			
				|  |  | -    directive = None
 | 
	
		
			
				|  |  | -    if line == 'set noparent':
 | 
	
		
			
				|  |  | -      parent = False
 | 
	
		
			
				|  |  | -    elif line == '*':
 | 
	
		
			
				|  |  | -      directive = Directive(who='*', globs=[])
 | 
	
		
			
				|  |  | -    elif ' ' in line:
 | 
	
		
			
				|  |  | -      (who, globs) = line.split(' ', 1)
 | 
	
		
			
				|  |  | -      globs_list = [glob
 | 
	
		
			
				|  |  | -                    for glob in globs.split(' ')
 | 
	
		
			
				|  |  | -                    if glob]
 | 
	
		
			
				|  |  | -      directive = Directive(who=who, globs=globs_list)
 | 
	
		
			
				|  |  | -    else:
 | 
	
		
			
				|  |  | -      directive = Directive(who=line, globs=[])
 | 
	
		
			
				|  |  | -    if directive:
 | 
	
		
			
				|  |  | -      directives.append(directive)
 | 
	
		
			
				|  |  | -  return Owners(parent=parent,
 | 
	
		
			
				|  |  | -                directives=directives,
 | 
	
		
			
				|  |  | -                dir=os.path.relpath(os.path.dirname(filename), git_root))
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -owners_data = sorted([parse_owners(filename)
 | 
	
		
			
				|  |  | -                      for filename in owners_files],
 | 
	
		
			
				|  |  | -                     key=operator.attrgetter('dir'))
 | 
	
		
			
				|  |  | +    with open(filename) as f:
 | 
	
		
			
				|  |  | +        src = f.read().splitlines()
 | 
	
		
			
				|  |  | +    parent = True
 | 
	
		
			
				|  |  | +    directives = []
 | 
	
		
			
				|  |  | +    for line in src:
 | 
	
		
			
				|  |  | +        line = line.strip()
 | 
	
		
			
				|  |  | +        # line := directive | comment
 | 
	
		
			
				|  |  | +        if not line: continue
 | 
	
		
			
				|  |  | +        if line[0] == '#': continue
 | 
	
		
			
				|  |  | +        # it's a directive
 | 
	
		
			
				|  |  | +        directive = None
 | 
	
		
			
				|  |  | +        if line == 'set noparent':
 | 
	
		
			
				|  |  | +            parent = False
 | 
	
		
			
				|  |  | +        elif line == '*':
 | 
	
		
			
				|  |  | +            directive = Directive(who='*', globs=[])
 | 
	
		
			
				|  |  | +        elif ' ' in line:
 | 
	
		
			
				|  |  | +            (who, globs) = line.split(' ', 1)
 | 
	
		
			
				|  |  | +            globs_list = [glob for glob in globs.split(' ') if glob]
 | 
	
		
			
				|  |  | +            directive = Directive(who=who, globs=globs_list)
 | 
	
		
			
				|  |  | +        else:
 | 
	
		
			
				|  |  | +            directive = Directive(who=line, globs=[])
 | 
	
		
			
				|  |  | +        if directive:
 | 
	
		
			
				|  |  | +            directives.append(directive)
 | 
	
		
			
				|  |  | +    return Owners(
 | 
	
		
			
				|  |  | +        parent=parent,
 | 
	
		
			
				|  |  | +        directives=directives,
 | 
	
		
			
				|  |  | +        dir=os.path.relpath(os.path.dirname(filename), git_root))
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +owners_data = sorted(
 | 
	
		
			
				|  |  | +    [parse_owners(filename) for filename in owners_files],
 | 
	
		
			
				|  |  | +    key=operator.attrgetter('dir'))
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #
 | 
	
		
			
				|  |  |  # Modify owners so that parented OWNERS files point to the actual
 | 
	
	
		
			
				|  | @@ -98,24 +100,24 @@ owners_data = sorted([parse_owners(filename)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  new_owners_data = []
 | 
	
		
			
				|  |  |  for owners in owners_data:
 | 
	
		
			
				|  |  | -  if owners.parent == True:
 | 
	
		
			
				|  |  | -    best_parent = None
 | 
	
		
			
				|  |  | -    best_parent_score = None
 | 
	
		
			
				|  |  | -    for possible_parent in owners_data:
 | 
	
		
			
				|  |  | -      if possible_parent is owners: continue
 | 
	
		
			
				|  |  | -      rel = os.path.relpath(owners.dir, possible_parent.dir)
 | 
	
		
			
				|  |  | -      # '..' ==> we had to walk up from possible_parent to get to owners
 | 
	
		
			
				|  |  | -      #      ==> not a parent
 | 
	
		
			
				|  |  | -      if '..' in rel: continue
 | 
	
		
			
				|  |  | -      depth = len(rel.split(os.sep))
 | 
	
		
			
				|  |  | -      if not best_parent or depth < best_parent_score:
 | 
	
		
			
				|  |  | -        best_parent = possible_parent
 | 
	
		
			
				|  |  | -        best_parent_score = depth
 | 
	
		
			
				|  |  | -    if best_parent:
 | 
	
		
			
				|  |  | -      owners = owners._replace(parent = best_parent.dir)
 | 
	
		
			
				|  |  | -    else:
 | 
	
		
			
				|  |  | -      owners = owners._replace(parent = None)
 | 
	
		
			
				|  |  | -  new_owners_data.append(owners)
 | 
	
		
			
				|  |  | +    if owners.parent == True:
 | 
	
		
			
				|  |  | +        best_parent = None
 | 
	
		
			
				|  |  | +        best_parent_score = None
 | 
	
		
			
				|  |  | +        for possible_parent in owners_data:
 | 
	
		
			
				|  |  | +            if possible_parent is owners: continue
 | 
	
		
			
				|  |  | +            rel = os.path.relpath(owners.dir, possible_parent.dir)
 | 
	
		
			
				|  |  | +            # '..' ==> we had to walk up from possible_parent to get to owners
 | 
	
		
			
				|  |  | +            #      ==> not a parent
 | 
	
		
			
				|  |  | +            if '..' in rel: continue
 | 
	
		
			
				|  |  | +            depth = len(rel.split(os.sep))
 | 
	
		
			
				|  |  | +            if not best_parent or depth < best_parent_score:
 | 
	
		
			
				|  |  | +                best_parent = possible_parent
 | 
	
		
			
				|  |  | +                best_parent_score = depth
 | 
	
		
			
				|  |  | +        if best_parent:
 | 
	
		
			
				|  |  | +            owners = owners._replace(parent=best_parent.dir)
 | 
	
		
			
				|  |  | +        else:
 | 
	
		
			
				|  |  | +            owners = owners._replace(parent=None)
 | 
	
		
			
				|  |  | +    new_owners_data.append(owners)
 | 
	
		
			
				|  |  |  owners_data = new_owners_data
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #
 | 
	
	
		
			
				|  | @@ -123,106 +125,114 @@ owners_data = new_owners_data
 | 
	
		
			
				|  |  |  # a CODEOWNERS file for GitHub
 | 
	
		
			
				|  |  |  #
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  def full_dir(rules_dir, sub_path):
 | 
	
		
			
				|  |  | -  return os.path.join(rules_dir, sub_path) if rules_dir != '.' else sub_path
 | 
	
		
			
				|  |  | +    return os.path.join(rules_dir, sub_path) if rules_dir != '.' else sub_path
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  # glob using git
 | 
	
		
			
				|  |  |  gg_cache = {}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  def git_glob(glob):
 | 
	
		
			
				|  |  | -  global gg_cache
 | 
	
		
			
				|  |  | -  if glob in gg_cache: return gg_cache[glob]
 | 
	
		
			
				|  |  | -  r = set(subprocess
 | 
	
		
			
				|  |  | -      .check_output(['git', 'ls-files', os.path.join(git_root, glob)])
 | 
	
		
			
				|  |  | -      .decode('utf-8')
 | 
	
		
			
				|  |  | -      .strip()
 | 
	
		
			
				|  |  | -      .splitlines())
 | 
	
		
			
				|  |  | -  gg_cache[glob] = r
 | 
	
		
			
				|  |  | -  return r
 | 
	
		
			
				|  |  | +    global gg_cache
 | 
	
		
			
				|  |  | +    if glob in gg_cache: return gg_cache[glob]
 | 
	
		
			
				|  |  | +    r = set(
 | 
	
		
			
				|  |  | +        subprocess.check_output(
 | 
	
		
			
				|  |  | +            ['git', 'ls-files', os.path.join(git_root, glob)]).decode('utf-8')
 | 
	
		
			
				|  |  | +        .strip().splitlines())
 | 
	
		
			
				|  |  | +    gg_cache[glob] = r
 | 
	
		
			
				|  |  | +    return r
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def expand_directives(root, directives):
 | 
	
		
			
				|  |  | -  globs = collections.OrderedDict()
 | 
	
		
			
				|  |  | -  # build a table of glob --> owners
 | 
	
		
			
				|  |  | -  for directive in directives:
 | 
	
		
			
				|  |  | -    for glob in directive.globs or ['**']:
 | 
	
		
			
				|  |  | -      if glob not in globs:
 | 
	
		
			
				|  |  | -        globs[glob] = []
 | 
	
		
			
				|  |  | -      if directive.who not in globs[glob]:
 | 
	
		
			
				|  |  | -        globs[glob].append(directive.who)
 | 
	
		
			
				|  |  | -  # expand owners for intersecting globs
 | 
	
		
			
				|  |  | -  sorted_globs = sorted(globs.keys(),
 | 
	
		
			
				|  |  | -                        key=lambda g: len(git_glob(full_dir(root, g))),
 | 
	
		
			
				|  |  | -                        reverse=True)
 | 
	
		
			
				|  |  | -  out_globs = collections.OrderedDict()
 | 
	
		
			
				|  |  | -  for glob_add in sorted_globs:
 | 
	
		
			
				|  |  | -    who_add = globs[glob_add]
 | 
	
		
			
				|  |  | -    pre_items = [i for i in out_globs.items()]
 | 
	
		
			
				|  |  | -    out_globs[glob_add] = who_add.copy()
 | 
	
		
			
				|  |  | -    for glob_have, who_have in pre_items:
 | 
	
		
			
				|  |  | -      files_add = git_glob(full_dir(root, glob_add))
 | 
	
		
			
				|  |  | -      files_have = git_glob(full_dir(root, glob_have))
 | 
	
		
			
				|  |  | -      intersect = files_have.intersection(files_add)
 | 
	
		
			
				|  |  | -      if intersect:
 | 
	
		
			
				|  |  | -        for f in sorted(files_add): # sorted to ensure merge stability
 | 
	
		
			
				|  |  | -          if f not in intersect:
 | 
	
		
			
				|  |  | -            out_globs[os.path.relpath(f, start=root)] = who_add
 | 
	
		
			
				|  |  | -        for who in who_have:
 | 
	
		
			
				|  |  | -          if who not in out_globs[glob_add]:
 | 
	
		
			
				|  |  | -            out_globs[glob_add].append(who)
 | 
	
		
			
				|  |  | -  return out_globs
 | 
	
		
			
				|  |  | +    globs = collections.OrderedDict()
 | 
	
		
			
				|  |  | +    # build a table of glob --> owners
 | 
	
		
			
				|  |  | +    for directive in directives:
 | 
	
		
			
				|  |  | +        for glob in directive.globs or ['**']:
 | 
	
		
			
				|  |  | +            if glob not in globs:
 | 
	
		
			
				|  |  | +                globs[glob] = []
 | 
	
		
			
				|  |  | +            if directive.who not in globs[glob]:
 | 
	
		
			
				|  |  | +                globs[glob].append(directive.who)
 | 
	
		
			
				|  |  | +    # expand owners for intersecting globs
 | 
	
		
			
				|  |  | +    sorted_globs = sorted(
 | 
	
		
			
				|  |  | +        globs.keys(),
 | 
	
		
			
				|  |  | +        key=lambda g: len(git_glob(full_dir(root, g))),
 | 
	
		
			
				|  |  | +        reverse=True)
 | 
	
		
			
				|  |  | +    out_globs = collections.OrderedDict()
 | 
	
		
			
				|  |  | +    for glob_add in sorted_globs:
 | 
	
		
			
				|  |  | +        who_add = globs[glob_add]
 | 
	
		
			
				|  |  | +        pre_items = [i for i in out_globs.items()]
 | 
	
		
			
				|  |  | +        out_globs[glob_add] = who_add.copy()
 | 
	
		
			
				|  |  | +        for glob_have, who_have in pre_items:
 | 
	
		
			
				|  |  | +            files_add = git_glob(full_dir(root, glob_add))
 | 
	
		
			
				|  |  | +            files_have = git_glob(full_dir(root, glob_have))
 | 
	
		
			
				|  |  | +            intersect = files_have.intersection(files_add)
 | 
	
		
			
				|  |  | +            if intersect:
 | 
	
		
			
				|  |  | +                for f in sorted(files_add):  # sorted to ensure merge stability
 | 
	
		
			
				|  |  | +                    if f not in intersect:
 | 
	
		
			
				|  |  | +                        out_globs[os.path.relpath(f, start=root)] = who_add
 | 
	
		
			
				|  |  | +                for who in who_have:
 | 
	
		
			
				|  |  | +                    if who not in out_globs[glob_add]:
 | 
	
		
			
				|  |  | +                        out_globs[glob_add].append(who)
 | 
	
		
			
				|  |  | +    return out_globs
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def add_parent_to_globs(parent, globs, globs_dir):
 | 
	
		
			
				|  |  | -  if not parent: return
 | 
	
		
			
				|  |  | -  for owners in owners_data:
 | 
	
		
			
				|  |  | -    if owners.dir == parent:
 | 
	
		
			
				|  |  | -      owners_globs = expand_directives(owners.dir, owners.directives)
 | 
	
		
			
				|  |  | -      for oglob, oglob_who in owners_globs.items():
 | 
	
		
			
				|  |  | -        for gglob, gglob_who in globs.items():
 | 
	
		
			
				|  |  | -          files_parent = git_glob(full_dir(owners.dir, oglob))
 | 
	
		
			
				|  |  | -          files_child = git_glob(full_dir(globs_dir, gglob))
 | 
	
		
			
				|  |  | -          intersect = files_parent.intersection(files_child)
 | 
	
		
			
				|  |  | -          gglob_who_orig = gglob_who.copy()
 | 
	
		
			
				|  |  | -          if intersect:
 | 
	
		
			
				|  |  | -            for f in sorted(files_child): # sorted to ensure merge stability
 | 
	
		
			
				|  |  | -              if f not in intersect:
 | 
	
		
			
				|  |  | -                who = gglob_who_orig.copy()
 | 
	
		
			
				|  |  | -                globs[os.path.relpath(f, start=globs_dir)] = who
 | 
	
		
			
				|  |  | -            for who in oglob_who:
 | 
	
		
			
				|  |  | -              if who not in gglob_who:
 | 
	
		
			
				|  |  | -                gglob_who.append(who)
 | 
	
		
			
				|  |  | -      add_parent_to_globs(owners.parent, globs, globs_dir)
 | 
	
		
			
				|  |  | -      return
 | 
	
		
			
				|  |  | -  assert(False)
 | 
	
		
			
				|  |  | +    if not parent: return
 | 
	
		
			
				|  |  | +    for owners in owners_data:
 | 
	
		
			
				|  |  | +        if owners.dir == parent:
 | 
	
		
			
				|  |  | +            owners_globs = expand_directives(owners.dir, owners.directives)
 | 
	
		
			
				|  |  | +            for oglob, oglob_who in owners_globs.items():
 | 
	
		
			
				|  |  | +                for gglob, gglob_who in globs.items():
 | 
	
		
			
				|  |  | +                    files_parent = git_glob(full_dir(owners.dir, oglob))
 | 
	
		
			
				|  |  | +                    files_child = git_glob(full_dir(globs_dir, gglob))
 | 
	
		
			
				|  |  | +                    intersect = files_parent.intersection(files_child)
 | 
	
		
			
				|  |  | +                    gglob_who_orig = gglob_who.copy()
 | 
	
		
			
				|  |  | +                    if intersect:
 | 
	
		
			
				|  |  | +                        for f in sorted(files_child
 | 
	
		
			
				|  |  | +                                       ):  # sorted to ensure merge stability
 | 
	
		
			
				|  |  | +                            if f not in intersect:
 | 
	
		
			
				|  |  | +                                who = gglob_who_orig.copy()
 | 
	
		
			
				|  |  | +                                globs[os.path.relpath(f, start=globs_dir)] = who
 | 
	
		
			
				|  |  | +                        for who in oglob_who:
 | 
	
		
			
				|  |  | +                            if who not in gglob_who:
 | 
	
		
			
				|  |  | +                                gglob_who.append(who)
 | 
	
		
			
				|  |  | +            add_parent_to_globs(owners.parent, globs, globs_dir)
 | 
	
		
			
				|  |  | +            return
 | 
	
		
			
				|  |  | +    assert (False)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  todo = owners_data.copy()
 | 
	
		
			
				|  |  |  done = set()
 | 
	
		
			
				|  |  |  with open(args.out, 'w') as out:
 | 
	
		
			
				|  |  | -  out.write('# Auto-generated by the tools/mkowners/mkowners.py tool\n')
 | 
	
		
			
				|  |  | -  out.write('# Uses OWNERS files in different modules throughout the\n')
 | 
	
		
			
				|  |  | -  out.write('# repository as the source of truth for module ownership.\n')
 | 
	
		
			
				|  |  | -  written_globs = []
 | 
	
		
			
				|  |  | -  while todo:
 | 
	
		
			
				|  |  | -    head, *todo = todo
 | 
	
		
			
				|  |  | -    if head.parent and not head.parent in done:
 | 
	
		
			
				|  |  | -      todo.append(head)
 | 
	
		
			
				|  |  | -      continue
 | 
	
		
			
				|  |  | -    globs = expand_directives(head.dir, head.directives)
 | 
	
		
			
				|  |  | -    add_parent_to_globs(head.parent, globs, head.dir)
 | 
	
		
			
				|  |  | -    for glob, owners in globs.items():
 | 
	
		
			
				|  |  | -      skip = False
 | 
	
		
			
				|  |  | -      for glob1, owners1, dir1 in reversed(written_globs):
 | 
	
		
			
				|  |  | -        files = git_glob(full_dir(head.dir, glob))
 | 
	
		
			
				|  |  | -        files1 = git_glob(full_dir(dir1, glob1))
 | 
	
		
			
				|  |  | -        intersect = files.intersection(files1)
 | 
	
		
			
				|  |  | -        if files == intersect:
 | 
	
		
			
				|  |  | -          if sorted(owners) == sorted(owners1):
 | 
	
		
			
				|  |  | -            skip = True # nothing new in this rule
 | 
	
		
			
				|  |  | -            break
 | 
	
		
			
				|  |  | -        elif intersect:
 | 
	
		
			
				|  |  | -          # continuing would cause a semantic change since some files are
 | 
	
		
			
				|  |  | -          # affected differently by this rule and CODEOWNERS is order dependent
 | 
	
		
			
				|  |  | -          break
 | 
	
		
			
				|  |  | -      if not skip:
 | 
	
		
			
				|  |  | -        out.write('/%s %s\n' % (
 | 
	
		
			
				|  |  | -            full_dir(head.dir, glob), ' '.join(owners)))
 | 
	
		
			
				|  |  | -        written_globs.append((glob, owners, head.dir))
 | 
	
		
			
				|  |  | -    done.add(head.dir)
 | 
	
		
			
				|  |  | +    out.write('# Auto-generated by the tools/mkowners/mkowners.py tool\n')
 | 
	
		
			
				|  |  | +    out.write('# Uses OWNERS files in different modules throughout the\n')
 | 
	
		
			
				|  |  | +    out.write('# repository as the source of truth for module ownership.\n')
 | 
	
		
			
				|  |  | +    written_globs = []
 | 
	
		
			
				|  |  | +    while todo:
 | 
	
		
			
				|  |  | +        head, *todo = todo
 | 
	
		
			
				|  |  | +        if head.parent and not head.parent in done:
 | 
	
		
			
				|  |  | +            todo.append(head)
 | 
	
		
			
				|  |  | +            continue
 | 
	
		
			
				|  |  | +        globs = expand_directives(head.dir, head.directives)
 | 
	
		
			
				|  |  | +        add_parent_to_globs(head.parent, globs, head.dir)
 | 
	
		
			
				|  |  | +        for glob, owners in globs.items():
 | 
	
		
			
				|  |  | +            skip = False
 | 
	
		
			
				|  |  | +            for glob1, owners1, dir1 in reversed(written_globs):
 | 
	
		
			
				|  |  | +                files = git_glob(full_dir(head.dir, glob))
 | 
	
		
			
				|  |  | +                files1 = git_glob(full_dir(dir1, glob1))
 | 
	
		
			
				|  |  | +                intersect = files.intersection(files1)
 | 
	
		
			
				|  |  | +                if files == intersect:
 | 
	
		
			
				|  |  | +                    if sorted(owners) == sorted(owners1):
 | 
	
		
			
				|  |  | +                        skip = True  # nothing new in this rule
 | 
	
		
			
				|  |  | +                        break
 | 
	
		
			
				|  |  | +                elif intersect:
 | 
	
		
			
				|  |  | +                    # continuing would cause a semantic change since some files are
 | 
	
		
			
				|  |  | +                    # affected differently by this rule and CODEOWNERS is order dependent
 | 
	
		
			
				|  |  | +                    break
 | 
	
		
			
				|  |  | +            if not skip:
 | 
	
		
			
				|  |  | +                out.write('/%s %s\n' % (full_dir(head.dir, glob),
 | 
	
		
			
				|  |  | +                                        ' '.join(owners)))
 | 
	
		
			
				|  |  | +                written_globs.append((glob, owners, head.dir))
 | 
	
		
			
				|  |  | +        done.add(head.dir)
 |