Fusión no trivial de repositorios con git-filter-repo

Esta es la segunda parte de la historia sobre la fusión de repositorios . Brevemente, la esencia del problema es la siguiente: debe fusionar el repositorio con el subrepositorio conservando el historial. La solución de gitpython funcionó en 6 horas y dio un resultado satisfactorio. Pero una sobreabundancia de tiempo libre y un clavo en el trasero de la curiosidad innata me llevaron a familiarizarme con el mundo mágico de git-filter-repo .





¿Qué es git-filter-repo?

Este es un programa maravilloso que llama git fast-export



por un lado, git fast-import



por otro lado, y en el medio, te permite hacer todo tipo de filtrado. git fast-export



, a su vez, simplemente toma todos los objetos del repositorio de git (y el repositorio de git no es más que una base de datos de objetos con objetos de un tipo fijo) y los imprime en texto a la salida estándar, de modo que para cualquier objeto X, todos los objetos en la que se refiere se muestran antes que el objeto X en sí. git fast-import



, como no es difícil de adivinar, hace la operación contraria: toma esta hoja y la esparce entre los archivos de la carpeta .git.





Consecuencias de escribir artículos

Habiendo escrito el último artículo, me di cuenta de que toda esta construcción de un subgrafo es una operación innecesaria. Después de todo, el lugar donde se insertan las confirmaciones del repositorio secundario es muy fácil de encontrar: estas son las confirmaciones donde cambia el archivo .version secundaria. Además, el rango de confirmaciones secundarias se considera como una lista (versión secundaria antes del cambio) .. (versión secundaria después del cambio). Y, en consecuencia, puede ejecutar el gráfico de confirmaciones e inmediatamente realizar las operaciones necesarias. Al reescribir el guión teniendo en cuenta estos conocimientos, logramos reducir el tiempo de trabajo a unas 3 horas. Pero este no es el límite. Después de todo, ahora las confirmaciones del repositorio original se procesan todas en una fila de forma lineal, es decir, exactamente como se muestran git fast-export



. Esto significa que puede usar git-filter-repo.





No solo una aplicación

, git filter-repo



, . , . - . , . , git filter-repo



. FastExportParser, git fast-export



(Blob, Commit ..), dump() git fast-import



.





RepoFilter , (git fast-export



) (git fast-import



) FastExportParser'.





fep_cmd = ['git', '-C', args.source, 'fast-export', '--show-original-ids', '--progress=128'
           , '--signed-tags=strip', '--tag-of-filtered-object=rewrite', '--mark-tags'
           , '--fake-missing-tagger', '--reference-excluded-parents', '--all']
fep = subproc.Popen(fep_cmd, bufsize=-1, stdout=subproc.PIPE)
inpt = fep.stdout

fip_cmd = ['git', '-C', args.target, '-c', 'core.ignorecase=false'
           , 'fast-import', '--force']
fip = subproc.Popen(fip_cmd, bufsize=-1, stdin=subproc.PIPE, stdout=subproc.PIPE)
otpt = fip.stdin

processor = Processor(args.source, args.secondary, otpt, fip.stdout)
parser = gfr.FastExportParser(blob_callback = processor.blob_callback
                              , commit_callback = processor.commit_callback
                              , progress_callback = processor.progress_cb)


parser.run(inpt, otpt)
otpt.close()
inpt.close()

      
      



Processor



c . — . secondary.version. , , , git fast-export



. , , git fast-export



FastExportParser', secondary , git fast-import



. , git fast-import , , git , , , ( ). Commit .





  class Commit(_GitElementWithId):
  """
  This class defines our representation of commit elements. Commit elements
  contain all the information associated with a commit.
  """

  def __init__(self, branch,
               author_name,    author_email,    author_date,
               committer_name, committer_email, committer_date,
               message,
               file_changes,
               parents,
               original_id = None,
               encoding = None, # encoding for message; None implies UTF-8
               **kwargs):
        pass
    
class Processor : 
    def process_commit(self, commit) :
        idx = len(commit.file_changes)
        commit.message = self.message_reformat(commit.message)
        while idx > 0 :
            idx -= 1
            fc = commit.file_changes[idx]
            if fc.type == b'M' :
                process_meth = self.MAP.get(fc.filename)
                if process_meth is not None:
                    commit.file_changes.pop(idx)
                    process_meth(commit, fc)

      
      



Como puede ver, process_commit llama a métodos basados ​​en el nombre del archivo. Método para la versión secundaria:





    def process_secondary_version(self, commit) :
        parent = commit.original_id.decode('ascii')
        res = subproc.run(['git', '-C', self.prepo, 'cat-file'
                           , 'blob', f'{parent}^:secondary.version']
                          , capture_output = True)
        hsh_from = res.stdout[:40].decode('ascii')
        if len(hsh_from) < 40 :
            sr_range = f'{hsh}^!'
        else :
            sr_range = f'{hsh_from}..{hsh}'
        self.sub_parsed = False
        self.super_commit = commit
        self.sub_top_hsh = hsh.encode('ascii')

        sub_fep_cmd = ['git', '-C', self.srepo, 'fast-export', '--show-original-ids'
                       , '--signed-tags=strip', '--tag-of-filtered-object=drop'
                       , '--import-marks=secondary.marks'
                       , sr_range]
        sub_fep = subproc.Popen(sub_fep_cmd, bufsize=-1, stdout=subproc.PIPE)
        self.sub_parser.run(sub_fep.stdout, self.output)
        sub_fep.stdout.close()

      
      



Epílogo

La nueva implementación funciona en 10 minutos en lugar de 6 horas. Moraleja: los algoritmos correctos son buenos, pero las mejores herramientas dan los mejores resultados.








All Articles