| Login | Tour | Inicio | Chat | Descargas | Wallpapers | Páginas recientes | FAQ | | ||
Custom Search
![]() CRIPTOGRAFÍA EN LA PRESIDENCIA DE PORFIRIO DÍAZ tonathiu “Yo no tengo que explicarle por qué no: usted debe explicarme por qué sí.” tonathiu Authors@Google: Noam Chomsky aarkerio PDF Helper aarkerio Emacs como IDE para CakePHP aarkerio La Negación del Viaje Lunar tonathiu Porque los mononeurones si tenemos madre! blacksoul BrunoFerías thot The Art vendaval Aclimatación extraterrestre ¿para qué? ahuramazdah Pidiendo OpenSolaris 2008.5 vendaval Sospechosismo aarkerio Sistema Infalible ordbal Slackware 12.1 Final vendaval Calderón puede ser sujeto a juicio político, sostiene Carrancá tonathiu La desnutrición en México aarkerio Jaime Maussan da por auténtico video trucado del chupacabras hecho en Blender 3D asarch Histórico aarkerio Indonesia: Nueva muerte por gripe aviar saidjose Amarok 2 vendaval ![]() GNU/Linux ![]() Hacktivismo ![]() Debian ![]() NetBSD ![]() WWW ![]() Guia Linux ![]() Server Side ![]() Ofimatica ![]() Despabilando... ![]() Mundo Maya ![]() Literatura ![]() Agora ![]() Psicologia ![]() Economia ![]() Ambientalismo ![]() Desarrollo Biologia ![]() |
Desarrollo \ Introducción a Subversion Este artículo ha sido consultado en 666 ocasiones. Autor de este documento: Juan Alvarez Gestión de proyectos con SubversionIntroducciónSubversion es un sistema de control de versiones, es decir, sirve para tener un control sobre nuestro proyecto y permitirnos realizar cambios a los distintos componentes del mismo manteniendo un histórico de estos cambios y permitiéndonos en cualquier momento deshacer los cambios hechos en un momento dado, actualizar el proyecto a la última versión de los programadores, ver históricos de cambios y comentarios, y unir nuestros cambios con los que otros hayan podido hacer sobre los mismos ficheros. De hecho uno de los motivos por los que estos sistemas se utilicen tanto en el mundo del software libre se debe a que permite, de forma cómoda, coordinar el trabajo de varios profesionales a través de Internet cómodamente. Sin duda el sistema de control de versiones (SCV de aquí en adelante) más utilizado actualmente por los programadores de software libre es el viejo y probado CVS. Este sistema tiene ya bastantes años detrás y aunque sirve bastante bien para sus propósitos tiene una serie de limitaciones que han hecho que poco a poco otros SCV están ocupando su (todavía predominante) cuota de mercado. Entre estos nuevos sistemas podemos destacar tres: Arch, BitKeeper y Subversion. De los tres, en mi opinión, uno de los más interesantes para los que conozcan y hayan utilizado CVS con anterioridad es Subversion porque es el que más se le parece dado que sus programadores lo que han intentado es hacer una evolución natural de CVS superando sus limitaciones y mejorándolo allá donde estaba claro que se podía mejorar. Arch sigue una filosofía bastante distinta de CVS y BitKeeper, aún siendo probablemente el más avanzado de los tres, no es libre (sí de uso gratuito si se va a utilizar en proyectos libres). Así que en este artículo vamos a explicar como trabajar con Subversion, aunque dada la similitud con CVS lo aprendido aquí es fácilmente aplicable a ese otro SCV. Ciclo de uso de SubversionLa mayoría de los programadores utilizan CVS y Subversion de una forma más bien cíclica. En este apartado vamos a ver en que consiste ese ciclo y que comandos vamos a utilizar para cada paso. 1. Obteniendo una copia de trabajoAntes de empezar a explicar comandos y operaciones conviene entender un poco como se va a trabajar normalmente con un SCV como Subversion. Supongamos que decidimos colaborar a un proyecto de software libre que utiliza Subversion como SCV y dispone de un repositorio publico del que podemos bajarnos las fuentes listas para trabajar, y que disponemos de permiso de escritura para ese repositorio. Si tenemos ya instalado Subversion, el comando habitual para descargar esas fuentes sería algo parecido a esto (supongamos que el proyecto se llama SuperMail):
Esto nos copiaráa desde la red a un directorio 'supermail' en nuestro sistema de archivos todo el código fuente del proyecto hospedado en el repositorio Subversion del proyecto. 'svn' es el comando que vamos a utilizar casi siempre mientras trabajamos con Subversion, indicándolo subcomandos que especifican la operación a realizar; 'co' es la abreviatura de 'checkout' que es la operación que creará una copia del repositorio en nuestro sistema de archivos local sobre la que podamos trabajar y a continuación vemos una URL que indica la dirección de donde obtener las fuentes (Podemos observar que la es una dirección HTTP: Subversion utiliza Apache+WebDav para los repositorios de acceso por Internet; si nuestro repositorio va a ser privado no necesitaremos Apache). Por último, la palabra 'supermail' indica el nombre del directorio local donde se copiará el contenido del anterior repositorio (de no existir se crearía) y es necesario porque en este caso (como sucede con bastantes repositorios de Subversion) el repositorio del proyecto no se ha denominado 'supermail' sino 'tronco' (o más habitualmente su equivalente en inglés 'trunk'). Esto suele hacerse así porque en ocasiones puede convenir que en un mismo repositorio coexista más de un proyecto. Una vez hemos ejecutado el comando vemos que empieza a aparecer una salida en la que se va enumerando cada fichero y directorio contenido en el proyecto con una letra 'A' antepuesta. Esta letra indica que el fichero se ha añdido nuevo, más adelante veremos que existen otras letras que indican otras operaciones sobre nuestra copia del repositorio. 2. TrabajandoCuando los ficheros se han terminado de descargar ya podemos meternos en el directorio supermail/ y empezar a hacer los cambios que queramos (programación, documentación, etc), teniendo solamente en cuenta que cuando creemos un fichero nuevo tenemos que utilizar el comando 'svn add fichero' (si el fichero no existe en el directorio especificado lo creará vacío) para informar a Subversion de los ficheros nuevos que añdamos, 'svn rm fichero' para informarle de los ficheros que eliminemos, y 'svn mv fichero' y 'svn cp fichero' para mover y copiar, respectivamente, los ficheros. Es decir, si dividiéramos los tipos de cambios en 'cambios a ficheros' y 'cambios al árbol de directorios' los primeros no necesitarían de ningún comando especial (tan sólo necesitamos hacer los cambios como los haríamos si no estuviera Subversion, es decir, con nuestro editor favorito en el caso de ficheros de texto) mientras que los cambios al árbol de directorios del proyecto si necesitan utilizar los comandos que hemos visto para indicarle a Subversion estos cambios (svn [add|rm|cp|mv], normalmente). 3. Comprobando los cambiosUna vez hemos quedado satisfechos con los cambios realizados, es hora de comprobar el conjunto de cambios que hemos realizado sobre la copia original que descargamos al principio. Esto se realiza mediante el comando 'svn status' que producirá una salida con un lista de los ficheros que han cambiado con respecto al original y una letra indicando el tipo de cambio. el significado para algunas de estas letras es:
Como podemos ver por el significado de cada carácter, este comando es muy útil para asegurarnos de que no nos ha olvidado añdir o eliminar ficheros o de que no hemos realizado modificaciones inesperadas a ficheros que no pretendíamos modificar (en este caso podríamos deshacer las modificaciones inesperadas haciendo 'svn revert FICHERO'). Por defecto svn status ignora los ficheros que concuerdan terminan con .o, .lo, .la, y algunos más, pues casi siempre estos ficheros son ficheros temporales creados por la compilación de archivos. La expresión regular que contiene los ficheros a ignorar es la propiedad svn:ignore del directorio padre (veremos más sobre propiedades un poco más adelante). También, como con casi todos los comandos de Subversion, podemos obtener información de estado sobre un sólo fichero o directorio indicando su ruta:
Además, si ejecutamos svn status con el parámetro -v aparecerán dos nuevas columnas indicando cual fue la última revisión en la que se cambió el fichero y quien lo hizo. 4. Actualizando la copia de trabajoEl último paso antes de hacer efectivos nuestros cambios con el repositorio global es actualizar nuestra copia local para que se reflejen esos cambios, y poder resolver los conflictos que hayan podido surgir. Para ello utilizamos 'svn up' (de update) que nos irá mostrando una lista de los ficheros que han cambiado con, de nuevo, una letra indicando el tipo de cambio. El significado de esta letra es el mismo que hemos explicado con 'svn status' pero además pueden darse:
Si se produce un conflicto en algún fichero debemos resolverlo manualmente (Subversion aún no sabe programar) para lo cual abrimos el fichero, observamos las partes que Subversion nos ha marcado indicando los cambios locales y los del repositorio global y resolvemos el conflicto. Una vez lo hemos hecho ejecutamos tenemos que ejecutar 'svn resolve FICHERO' para indicar a Subversion que el conflicto está resuelto. 5. Envíando nuestros cambiosUna vez hemos comprobado (mediante svn status y svn update) que nuestros cambios son correctos y que no son conflictivos con lo que actualmente existe en el repositorio global (y si no lo fueran, lo resolvemos y ejecutamos de nuevo svn status y svn update para asegurarnos) llega el momento de enviar nuestros cambios a dicho repositorio. Para ello se utiliza el commando 'svn commit' al que normalmente le vamos a pasar el parámetro -m con un mensaje de texto indicando los cambios que hemos realizado (luego la gente podrá ver ese mensaje cuando consulte los históricos). Si no le pasamos el parámetro -m utilizará la variable de entorno $EDITOR para abrir un editor de texto en el que introduzcamos un mensaje. Por ejemplo, el comando podría quedar de la siguiente forma:
svn commit fallará si se han realizado nuevos cambios en el repositorio global; en este caso tendremos que actualizar (svn update) y volver al punto 4. En el caso de que no tuviéramos permiso de escritura, Subversion nos permite crear un parche, que podamos enviar por email a los autores, de forma muy sencilla; Sólo tenemos que ejecutar desde el directorio superior del proyecto:
Y ya tendríamos nuestro parche en el archivo parche.diff, que podríamos mandar al autor o autores para que lo añdieran utilizando el comando patch < parche.diff desde el mismo directorio. Y ya está, en realidad, salvo casos muy concretos, no vamos a salirnos casi nunca de los comandos especificados en este apartado. Una vez hemos terminado de trabajar, y nos hemos asegurado de que nuestros cambios se han enviado correctamente al repositorio global podemos o bien borrar nuestro directorio de trabajo y volver a hacer el checkout la próxima vez que queramos trabajar en el, o no borrarlo y hacer el update en su lugar. Otras operaciones con SubversionUna vez que hemos visto el manejo fundamental de Subversion, vamos a recorrer otras operaciones habituales que podemos realizar con el mismo, y algunas 'bases teóricas' fundamentales. El sistema de versionesEl sistema de versiones de Subversion es distinto al de CVS. En el segundo, cada fichero tiene su propia versión, y los directorios no tienen versiones pues CVS los considera simplemente como 'contenedores de ficheros', lo que causa muchos dolores de cabeza a sus usuarios a la hora de reorganizar el árbol de un proyecto. En Subversion las versiones se aplican a todo el árbol de nuestro proyecto (incrementándose en uno en cada commit), y por lo tanto todos los ficheros tendrán la misma versión en una misma 'captura' del árbol, incluyendo los directorios. Por ejemplo, si tuviéramos un sencillo proyecto que contuviera los siguientes ficheros: hola/README hola/INSTALL hola/holamundo.c En la primera versión todos los ficheros tendrían la versión '1'. Si después hiciéramos un cambio al fichero INSTALL y ejecutáramos el commit, las versión del árbol, y por lo tanto la de todos sus archivos, cambiaría a '2'. Creando nuestro repositorioCrear un repositorio es tan sencillo como utilizar los comando:
Con esto se nos crearía un repositorio básico (vacío) en el cual podríamos empezar a crear ficheros y directorios. Pero normalmente nos interesará importar algún arbol de código existente, para lo cual tendríamos que hacer a continuación:
Analicemos. El comando para importar un código existente es 'svn import'. A continuación le hemos pasado la URL a nuestro repositorio (que como resulta que está en nuestro sistema de archivos tiene la forma file://[ruta_completa], hay que recordar que svn siempre trabaja con URLs cuando se refiere a repositorios globales, no así svnadmin), después le indicamos donde está el árbol existente que queremos importar (en este caso /home/usuario/HolaMundo) y por último el subdirectorio dentro del repositorio en el que queremos que se importe, de modo que si quisiéramos hacer un checkout para crear una copia local y comprobar que se ha importado correctamente tendríamos que hacer:
Lo que nos crearía en el directorio actual un subdirectorio 'copia_local' conteniendo el árbol (Subversionizado) de nuestro proyecto 'HolaMundo'. Sin embargo normalmente nos va a interesar crear más de un subdirectorio dentro de nuestro repositorio de 'HolaMundo' de forma que en uno de ellos contengamos el árbol de la versión en desarrollo o inestable, en otro las distintas ramas (veremos algo sobre ramas más adelante) y en otro las distintas versiones estables. Por ello nos conviene utilizar el subcomando mkdir para crear los distintos subdirectorios dentro del repositorio de nuestro proyecto 'holamundo':
mkdir /home/usuario/svn Tras esto nos quedaría un repositorio conteniendo a su vez tres directorios (que podríamos considerar 'subrepositorios') que serían 'head', con código ya añdido, y que contendrá las versiones en continuo desarrollo, "ramas" que podría contener en el futuro distintas ramas del desarrollo y "finales" que contendría imágenes de las versiones finales estables de nuestro programa. Deshaciendo un cambioExisten dos formas de deshacer un cambio, dependiendo de en que fase del desarrollo lo hayamos hecho. Si el cambio se ha producido sólo localmente, y queremos volver en determinado archivo a la versión del repositorio global, tan sólo tenemos que hacer El segundo caso es que queramos deshacer uno o más cambios ya enviados al repositorio global en un commit anterior. En este caso tendremos que utilizar el subcomando 'merge' indicando en primer lugar la revisión desde la que queremos volver atrás y en segundo lugar la revisión a la que queremos volver. Por ejemplo, si en nuestro proyecto (revisión 5) un día que estábamos borrachos hicimos dos horrendos commits (revisión 6 y revisión 7) y al día siguiente comprobáramos con resacoso horror el estropicio realizado, podríamos volver tranquilamente a nuestra revisión pre-pedal con:
Aquí podemos ver que hemos utilizado el subcomando merge con un parámetro '-r' que nos permite indicar dos revisiones sobre las que efectuar el cambio, y hemos indicado en primer lugar la última revisión que hicimos tolingas seguido de dos puntos y la última revisión que hicimos sobrios. Como con casi todos los comandos, además, podemos especificar como parámetro uno o varios archivos o directorios para que los cambios se realicen sólamente sobre ellos. 'merge' tiene otros usos que veremos más adelante. Históricos y registrosEn muchas ocasiones nos resultará tremendamente útil comprobar que mensajes se adjuntaron a cada revisión, los cambios producidos en ellas, los cambios entre dos revisiones especificadas, etc. Todas estas operaciones se realizan mediante los comandos 'svn log' y 'svn diff'. 'svn log' permite obtener los mensajes que el autor de cada revisión escribió. Si lo ejecutamos en el directorio raíz y sin parámetros nos mostrará todos los mensajes de todas las revisiones, pero también podemos ejecutarlo sobre un archivo o directorio en cuyo caso sólo nos mostrará los mensajes de las revisiones que modificaran ese archivo. También puede especificarse un conjunto de revisiones con el parámetro -r (como vimos anteriormente con svn merge) si sólo queremos ver las revisiones comprendidas en ese rango.
'svn diff' ejecutado sin parámetros nos permite, como vimos anteriormente, ver las diferencias
entre nuestra copia de trabajo y el fichero original en el repositorio global, pero, como era de esperar,
también admite el parámetro -r para especificar un rango de revisiones entre las que queremos que se nos
muestren los cambios a los ficheros, por ejemplo RamasEl concepto de rama es uno de los más útiles a la hora de trabajar con sistemas de control de versiones. Se explica mejor con un ejemplo; imaginamos que nuestro afamado programa 'HolaMundo' ha llegado a un punto en el que tiene la estabilidad y características necesarias para poder considerarlo una versión 'Beta', pero tenemos otras características en mente que quisiéramos añdir (como colorear el HolaMundo, efectos de fuegos artificiales que forman las palabras, etc) pero a una versión posterior a esta beta. Una solución sería hacer un paquete con el código fuente de la versión actual, publicarlo como 'HolaMundo 1.0-Beta' y seguir trabajando en las nuevas características en nuestro repositorio Subversion. El problema obvio es que si, como es natural, a nuestra versión beta le salen fallos, ya no podemos incorporarlos al repositorio sin incorporar al mismo tiempo a esa versión las nuevas (e inestables) características que hemos desarrollado, con lo cual podríamos introducir nuevos fallos. Tendríamos que, o bien seguir desarrollando el código de esa beta por separado sin ningún repositorio, o bien crear un nuevo repositorio sólo para ella hasta que se convierta en 'versión estable'. En cualquier caso si arregláramos un fallo en la versión beta, y quisiéramos incorporar ese arreglo también a la versión en desarrollo, tendríamos que hacerlo 'a mano'. Las ramas nos proporcionan una solución elegante a todo este embrollo. Las ramas ('branches') nos permiten seguir desarrollando una versión paralela del proyecto, surgida a partir de una determinada última revisión común, y nos permitirán seguir intercambiando cambios entre ambas cuando queramos. Para crear una rama a partir de una revisión existente se utiliza el subcomando 'cp'. En este caso vamos a suponer que la revisión 173 de nuestro proyecto 'HolaMundo' es lo bastante estable como para poder considerarla una beta, por lo que decidimos crear una rama del proyecto llamada 'holamundo-beta'. Suponiendo que hubiéramos utilizado la estructura explicada en el apartado de creación de un repositorio haríamos:
Ya tenemos nuestra rama lista para ser modificada independientemente de la principal. Es importante destacar que la nueva rama no ocupará nada de espacio en disco mientras no modifiquemos nada, pues sus ficheros en realidad serán punteros a los mismos ficheros de la revisión en la que salga en la rama inestable pero según vayamos modificando ficheros se irán creando copias con las modificaciones en esta rama. Como vemos, ya podemos arreglar fallos tranquilamente en la rama holamundo-beta mientras seguimos añdiendo felizmente características inútiles y llenas de bugs a la rama head. Aún nos queda el segundo problema que teníamos ¿cómo hacemos que las soluciones a los bugs de la versión beta se apliquen también a la versión inestable? Para ello se utiliza el comando svn merge, que ya conocíamos anteriormente aplicado a deshacer una revisión o parte de ella. En este caso, suponiendo que la solución al fallo se haya hecho en la revisión 177 de la rama holamundo-beta, el comando a utilizar sería (suponiendo que estuviéramos en el directorio raíz de la versión inestable):
Con esto (si no hay conflictos) ya deberíamos tener también la solución al fallo incorporada en la versión inestable. Por supuesto para que este comando sólo incorpore la solución, esta debe de haber el único cambio incorporado entre la revisión 176 y la 177, es decir, no se deben de haber realizado otros cambios en ese mismo commit pues sino esos otros cambios también se aplicarán en nuestra versión inestable (salvo que especifiquemos ficheros pero, de nuevo, sólo suponiendo que la solución al fallo sea la única fuente de modificación de esos ficheros). Por ello (entre otros muchos motivos como por ejemplo la conveniencia de poder deshacer un cambio)es más que conveniente intentar que los commits sean pequeños y que nunca incorporen cambios que no estén relacionados. ¿Y qué son los tags? Los tags en terminología CVS no son más que etiquetas que le dan un nombre a una determinada revisión del árbol, por ejemplo llamar '1.0Final' a la revisión 345. Subversion no tiene tags pero es fácil conseguir el mismo resultado simplemente copiando la revisión que interese con 'svn cp' al directorio 'finales' con el nombre de la versión real (que podríamos haber llamado igualmente 'tags') y ajustando la propiedad del directorio a sólo-lectura (veremos más adelante como ajustar las propiedades de un fichero o directorio):
Una vez que hemos terminado de trabajar con una rama, y creado una nueva versión (como hemos visto antes, con svn cp al directorio de tags) o incorporado sus cambios experimentales en nuestra rama principal, podemos eliminarla con 'svm rm'. PropiedadesLas propiedades son valores textuales que podemos asociar con cada fichero o directorio de un repositorio. Por ejemplo en el apartado anterior hemos puesto la propiedad 'svn:read-only' de un directorio del repositorio a uno. svn:read-only en realidad es una propiedad especial con un significado concreto, pero nosotros podemos poner propiedades con los nombre y los valores que queramos simplemente teniendo cuidado de que sus nombres no empiecen por 'svn:' pues este prefijo se ha reservado para las propiedades con un significado especial para Subversion. Entre estas 'propiedades especiales', destacaremos:
Pero ¿cómo cambiar y obtener las propiedades de un fichero o directorio? Muy sencillo, simplemente utilizamos 'svn propget propiedad fichero' para obtener las propiedades y 'svn propset propiedad valor fichero' para ponerlas. En el caso de que queramos editar una propiedad con nuestro editor de texto (algo interesante para propiedades que puedan tener valores de varias líneas como svn:ignore) utilizaremos 'svn propedit propiedad fichero'. Administración general de un repositoriosvnlookEsta utilidad sirve para analizar los cambios que se han ido
produciendo a un repositorio a lo largo del tiempo, sin cambiarlo.
La forma más obvia de utilizarlo es con
svnadminYa hemos visto anteriormente que podemos utilizar svnadmin para crear nuevo repositorios mediante su subcomando 'create'. Pero svnadmin tiene otros subcomandos bastante interesantes, entre los que están:
Haciendo que un repositorio sea accesible a través de InternetEn algunos ejemplos anteriores hemos visto que cuando accedemos a un repositorio en Internet con Subversion especificamos una URL de protocolo HTTP (http://www.midominio,org/mirepositorio, por ejemplo). Esto es así porque para publicar repositorios por Internet Subversion utiliza Apache con el módulo WebDav, que es un protocolo que permite acceder y modificar ficheros de el servidor Apache. No nos vamos a extender en como instalar Apache (por cierto, necesita la versión 2.0) y el módulo WebDav, pero indicaré los pasos a seguir:
Los pasos uno a tres suelen poderse conseguir fácilmente si utilizamos una distribución moderna de Linux, pues casi todas incluyen ya entre sus paquetes Apache 2.0, el módulo WebDav para el mismo, y en algunas ocasiones hasta el plugin para el módulo webDav. El último paso (configurar el fichero http.conf) consiste en añdir al final del fichero http.conf lo siguiente: <Location /repos/mirepo> DAV svn SVNPath /home/usuario/svn </Location> Cambiando '/home/usuario/svn' por la ruta real de nuestro repositorio y 'repos/mirepo' por la parte final de la URL que queramos que represente nuestro repositorio a través de Internet, por ejemplo, si nuestro dominio es http://www.midominio.com y queremos que nuestro repositorio, situado en /home/usuario/repositorio se vea a través de internet como http://www.midominio.com/repositorio/HolaMundo tendríamos que añdir al http.conf: <Location /repositorio/HolaMundo> DAV svn SVNPath /home/usuario/repositorio </Location> Ya sólo nos queda restringir los permisos de acceso para que sólo puedan leer y hacer commits a nuestro repositorio las personas que nosotros consideremos oportuno. Para el caso más habitual de un acceso de lectura público y un acceso de escritura por usuario creamos un fichero que contenga nombres de usuarios y claves cifradas con el comando crypt con el siguiente formato: usuario1:clave_cifrada1 usuario2:clave_cifrada2 ... Después dentro del grupo 'Location' que acabamos de definir en el archivo http.conf añdimos las siguientes opciones: AuthType Basic AuthName "Repositorio Subversion" AuthUserFile /ruta/al/fichero/de/usuarios Sustituyendo "Repositorio Subversion" por el texto que queramos que aparezca en la cadena de autenticación y "/ruta/al/fichero/de/usuarios" por la ruta real del fichero que hemos creado anteriormente con el listado de usuarios. Última actualización: 2007-04-29 10:56:58-05 on 23/2/08 Guillermo wrote: Muchas gracias, de los 3 ó 4 tutoriales que he leído he encontrado buscando por "introducción a Subversión" en Google, este es sin duda el más claro y conciso de todos. |
![]() comiendo pastura 9 hours, 11 minutes ago Faltan 10 minutos para cerrar el chagarro, eh irme a comer 12 hours, 5 minutes ago Trabajando, yo si trabajo ;-) 14 hours, 35 minutes ago Que felicidad! :'-D 15 hours, 8 minutes ago Solucionado problema del viernes en una WorkStation xw6000 :-D gracias al chntpw! 15 hours, 9 minutes ago Escuchando a Raven Crown/Winter wind 16 hours, 11 minutes ago Que estuvimos haciendo >> 10485 lecturas Sexualidad infantil y juvenil 9217 lecturas Anticoncepción de Emergencia 7933 lecturas Rompiendo cualquier clave WEP en unos pocos minutos 6963 lecturas Sinapsis y exocitosis 6303 lecturas Mi primer CakePHP, mmmmm cakeee 5279 lecturas Evolución filética en las hepáticas 4727 lecturas BASH y Primeros Comandos 4031 lecturas CakePHP II Active Record 3759 lecturas Cómo convertirse en hacker 3632 lecturas
|
| ||
| Como ser más eficiente con Windows XP (MANUAL DE DESINSTALACION) | ||
| Este trabajo está licenciado bajo la MonoNeurona Commons License. 2002-2008 © :: Colectivo MonoNeurona.org :: | ||