2012-04-17 12 views
24

Tenemos un repositorio svn con muchos directorios y archivos y nuestro sistema de compilación necesita poder encontrar todas las propiedades svn: externals, recursivamente para una rama en el repositorio, antes de echarle un vistazo. Actualmente utilizamos:Obtenga rápidamente una lista de todos los svn: externos para un repositorio de svn remoto

svn propget svn:externals -R http://url.of.repo/Branch 

Esto ha demostrado ser extremadamente lento y es un verdadero problema de ancho de banda. Parece que el cliente está recibiendo todos los accesorios para todo en el repositorio y filtrando a nivel local (aunque no he confirmado esto con wireshark). ¿Hay una manera más rápida de hacer esto? Preferiblemente, una forma de hacer que el servidor solo devuelva los datos deseados.

Respuesta

0

Es lento debido al interruptor -R; todos los directorios dentro de su ruta de repositorio se buscan la propiedad recursivamente, lo cual es mucho trabajo.

+0

Sí, descubrí por qué lo hace, pero me preguntaba si alguien tenía una manera más rápida. Sin embargo, no parece prometedor. – NeoSkye

+0

Creo que es así de simple: si necesita hacerlo recursivamente, solo será lento. – Argeman

2

Finalmente encontré una solución. Decidí dividir la solicitud en múltiples pequeñas solicitudes de svn y luego hacer que cada una de ellas sea ejecutada por un grupo de subprocesos. Este tipo de golpea el servidor svn, pero en nuestro caso el servidor svn está en la LAN y esta consulta solo se realiza durante compilaciones completas, por lo que no parece ser un problema.

import os 
import sys 
import threading 
import ThreadPool 

thread_pool = ThreadPool.ThreadPool(8) 
externs_dict = {} 
externs_lock = threading.Lock() 

def getExternRev(path, url): 
    cmd = 'svn info "%s"' % url 
    pipe = os.popen(cmd, 'r') 
    data = pipe.read().splitlines() 

    #Harvest last changed rev 
    for line in data: 
     if "Last Changed Rev" in line: 
      revision = line.split(":")[1].strip() 
      externs_lock.acquire() 
      externs_dict[path] = (url, revision) 
      externs_lock.release() 

def getExterns(url, base_dir): 
    cmd = 'svn propget svn:externals "%s"' % url 
    pipe = os.popen(cmd, 'r') 
    data = pipe.read().splitlines() 
    pipe.close() 

    for line in data: 
     if line: 
      line = line.split() 
      path = base_dir + line[0] 
      url = line[1] 
      thread_pool.add_task(getExternRev, path, url) 

def processDir(url, base_dir): 
    thread_pool.add_task(getExterns, url, base_dir) 

    cmd = 'svn list "%s"' % url 
    pipe = os.popen(cmd, 'r') 
    listing = pipe.read().splitlines() 
    pipe.close() 

    dir_list = [] 
    for node in listing: 
     if node.endswith('/'): 
      dir_list.append(node) 

    for node in dir_list: 
     #externs_data.extend(analyzePath(url + node, base_dir + node)) 
     thread_pool.add_task(processDir, url+node, base_dir+node) 

def analyzePath(url, base_dir = ''): 
    thread_pool.add_task(processDir, url, base_dir) 
    thread_pool.wait_completion() 


analyzePath("http://url/to/repository") 
print externs_dict 
+0

La dependencia de ThreadPool se puede encontrar aquí https://gist.github.com/metal3d/5075460 – ceilfors

+0

Tengo un problema con 'os.popen()' cuando se ejecuta en un hilo. Simplemente muere en silencio. Dejé de ejecutarlo en un hilo y eliminé toda la parte de enhebrar de este script. Aunque estoy sacrificando la velocidad aquí, este script es más confiable en comparación con 'propget -R' que simplemente muere en silencio cuando un repositorio es demasiado grande. – ceilfors

+1

La versión que no usa ThreadPool https: // gist.github.com/ceilfors/741d8152106a310dd454 – ceilfors

25

Como mencionaste, consume ancho de banda de red. Sin embargo, si tiene acceso al servidor donde están alojados esos repositorios, puede ejecutarlo a través del protocolo file://. Está demostrado que es más rápido y no consume la red.

svn propget svn:externals -R file:///path/to/repo/Branch

Además, si usted tenía toda la copia de trabajo en su lugar, puede también funcionar dentro de su copia de trabajo.

svn propget svn:externals -R /path/to/WC 

Espero que te ayude a lograr los resultados más rápido !.

+1

¡Gran solución! Ahorró MUCHO tiempo! – donV

+1

Desafortunadamente, nuestro sistema de compilación no tiene acceso directo a los servidores SVN, por lo que esto no nos permite superar el problema, pero estoy votando tu respuesta porque realmente es una buena solución si tienes ese tipo de acceso. – NeoSkye

+0

Estoy usando SVN Server 1.4.6 y el uso del 'file: //' me dará * Aborted * después de unos segundos. Similar a http://svn.haxx.se/users/archive-2007-04/0500.shtml – ceilfors

0

No solución ideal (pueden tener efectos secundarios) y no responder a su problema, pero

Puede volver a escribir todas las definiciones externas y añadir (reescrito) en un lugar común, conocido - de esta manera se eliminará recursividad en pg después del cambio

0

Si no les importa usar Python y la biblioteca pysvn, aquí es un completo programa de línea de comandos que estoy usando para lo externo SVN:

""" 
@file 
@brief SVN externals utilities. 
@author Lukasz Matecki 
""" 
import sys 
import os 
import pysvn 
import argparse 

class External(object): 

    def __init__(self, parent, remote_loc, local_loc, revision): 
     self.parent = parent 
     self.remote_loc = remote_loc 
     self.local_loc = local_loc 
     self.revision = revision 

    def __str__(self): 
     if self.revision.kind == pysvn.opt_revision_kind.number: 
      return """\ 
Parent:  {0} 
Source:  {1}@{2} 
Local name: {3}""".format(self.parent, self.remote_loc, self.revision.number, self.local_loc) 
     else: 
      return """\ 
Parent:  {0} 
Source:  {1} 
Local name: {2}""".format(self.parent, self.remote_loc, self.local_loc) 


def find_externals(client, repo_path, external_path=None): 
    """ 
    @brief Find SVN externals. 
    @param client (pysvn.Client) The client to use. 
    @param repo_path (str) The repository path to analyze. 
    @param external_path (str) The URL of the external to find; if omitted, all externals will be searched. 
    @returns [External] The list of externals descriptors or empty list if none found. 
    """ 
    repo_root = client.root_url_from_path(repo_path) 

    def parse(ext_prop): 
     for parent in ext_prop: 
      external = ext_prop[parent] 
      for line in external.splitlines(): 
       path, name = line.split() 
       path = path.replace("^", repo_root) 
       parts = path.split("@") 
       if len(parts) > 1: 
        url = parts[0] 
        rev = pysvn.Revision(pysvn.opt_revision_kind.number, int(parts[1])) 
       else: 
        url = parts[0] 
        rev = pysvn.Revision(pysvn.opt_revision_kind.head) 
       retval = External(parent, url, name, rev) 
       if external_path and not external_path == url: 
        continue 
       else: 
        yield retval 

    for entry in client.ls(repo_path, recurse=True): 
     if entry["kind"] == pysvn.node_kind.dir and entry["has_props"] == True: 
      externals = client.propget("svn:externals", entry["name"]) 
      if externals: 
       for e in parse(externals): 
        yield e 


def check_externals(client, externals_list): 
    for i, e in enumerate(externals_list): 
     url = e.remote_loc 
     rev = e.revision 
     try: 
      info = client.info2(url, revision=rev, recurse=False) 
      props = info[0][1] 
      url = props.URL 
      print("[{0}] Existing:\n{1}".format(i + 1, "\n".join([" {0}".format(line) for line in str(e).splitlines()]))) 
     except: 
      print("[{0}] Not found:\n{1}".format(i + 1, "\n".join([" {0}".format(line) for line in str(e).splitlines()]))) 

def main(cmdargs): 
    parser = argparse.ArgumentParser(description="SVN externals processing.", 
            formatter_class=argparse.RawDescriptionHelpFormatter, 
            prefix_chars='-+') 

    SUPPORTED_COMMANDS = ("check", "references") 

    parser.add_argument(
     "action", 
     type=str, 
     default="check", 
     choices=SUPPORTED_COMMANDS, 
     help="""\ 
the operation to execute: 
    'check' to validate all externals in a given location; 
    'references' to print all references to a given location""") 

    parser.add_argument(
     "url", 
     type=str, 
     help="the URL to operate on") 

    parser.add_argument(
     "--repo", "-r", 
     dest="repo", 
     type=str, 
     default=None, 
     help="the repository (or path within) to perform the operation on, if omitted is inferred from url parameter") 

    args = parser.parse_args() 

    client = pysvn.Client() 

    if args.action == "check": 
     externals = find_externals(client, args.url) 
     check_externals(client, externals) 
    elif args.action == "references": 
     if args.repo: 
      repo_root = args.repo 
     else: 
      repo_root = client.root_url_from_path(args.url) 
     for i, e in enumerate(find_externals(client, repo_root, args.url)): 
      print("[{0}] Reference:\n{1}".format(i + 1, "\n".join([" {0}".format(line) for line in str(e).splitlines()]))) 

if __name__ == "__main__": 
    sys.exit(main(sys.argv)) 

Esto debería funcionar tanto en Python 2 y Python 3. Y ou puede utilizar de esta manera (direcciones reales eliminados):

python svn_externals.py references https://~~~~~~~~~~~~~~/cmd_utils.py 
[1] Reference: 
    Parent:  https://~~~~~~~~~~~~~~/BEFORE_MK2/scripts/utils 
    Source:  https://~~~~~~~~~~~~~~/tools/python/cmd_utils.py 
    Local name: cmd_utils.py 
[2] Reference: 
    Parent:  https://~~~~~~~~~~~~~~/VTB-1425_PCU/scripts/utils 
    Source:  https://~~~~~~~~~~~~~~/tools/python/cmd_utils.py 
    Local name: cmd_utils.py 
[3] Reference: 
    Parent:  https://~~~~~~~~~~~~~~/scripts/utils 
    Source:  https://~~~~~~~~~~~~~~/tools/python/cmd_utils.py 
    Local name: cmd_utils.py 

En cuanto al rendimiento, esto funciona bastante rápido (aunque mi repositorio es bastante pequeña). Tienes que verificarlo por ti mismo.

Cuestiones relacionadas