martes, 26 de abril de 2016

AngularJS. Registrando el controlador hijo con el padre para poder hacer llamadas síncronas

Como estàn?

Había estado buscando formas de enviar mensajes desde un controlador padre hacia el hijo en AngularJS. Hay formas de hacerlo con $watchers o servicios comunes. Voy a describir una forma que estoy utilizando:

Registrar el controlador hijo con el controlador padre

Supongamos que tenemos un controlador como este:

bla.controller('ControladorHijo', function($scope) {

    $scope.metodoQueHayQueLlamar = function(unaCadena) {
        console.log(unaCadena);
    }
});

Un controlador hijo puede llamar a los métodos del controlador padre pero viceversa no funciona así que esto no va a funcionar:

bla.controller('ControladorPadre', function($scope) {

    $scope.unClick = function() {
        $scope.metodoQueHayQueLlamar('Hola!'); // buena suerte!!!
    }

});

Eso no va a funcionar, cierto?

Pero si registramos el hijo con el padre, entonces el padre tendría la instancia del $scope del hijo y así podría utilizarlo de forma síncrona. Algo como esto:

bla.controller('ControladorHijo', function($scope) {

    $scope.registrarHijo($scope);

    $scope.unClick = function() {
        $scope.metodoQueHayQueLlamar('Hola!');
    }

});

Y ahora en el controlador padre:

bla.controller('ControladorPadre', function($scope) {

    $scope.scopeHijo = null;
    $scope.registrarHijo = function(scopeHijo) {
        $scope.scopeHijo = scopeHijo;
    }

    $scope.unClick = function() {
        if ($scope.scopeHijo == null) {
            return;
        }

        $scope.scopeHijo.metodoQueHayQueLlamar('Hola!');
    }

});


Eso debería funcionar. Espero que les sirva.

domingo, 28 de febrero de 2016

git es como svn (I)


Bugatti Veyron 16.4 – Frontansicht (1), 5. April 2012, Düsseldorf.jpg
Fuente de la Imagen: Wikipedia

Como están?

Desde hace unos 8 años he estado usando controladores de versiones distribuidos. En un principio utilicé bzr y hace unos 3 años probé git y no hubo forma de volver la vista atrás.

Llevo casi 2 años trabajando en un proyecto relativamente grande donde se usa svn para controlar las versiones. Desde hace unos 8 meses he estado usando git-svn para poder utilizar git por mi cuenta e integrar los cambios en svn (esto luego de poner "bajo control" la configuración de git para que no jodiera los saltos de línea de los archivos)

De vez en cuando surge el punto conversando con cualquier desarrollador acerca de los controladores de versiones (incluso desarrolladores veteranos de la 3a guerra mundial) y sigue sorprendiéndome la frase:

- "pero git y svn hacen lo mismo".

Normalmente cuando la escucho y pongo mi cara de "no puedo creer esta mierda viniendo de X" (X siendo el desarrollador de marras) entonces la frase se convierte luego de unos segundos de silencio en:

- "pero git y svn hacen lo mismo..... no?"

Pues sí..... hacen lo mismo. En más o menos el mismo sentido en el que un Volkswagen Escarabajo hace lo mismo que un Bugatti Veyron Super Sport. Ambos son carros no? Si, son carros... Ambos te pueden llevar de un lado al otro, pero es obvio que uno es más carro que otro... o no? (dejando aparte el hecho de definir cual considerarían ustedes más carro que el otro, queda claro que son carros MUY diferentes).

Las ventajas son difíciles de ver "en el aire" mientras estamos conversando así que normalmente hacer que las personas capten las diferencia requiere que las siente en una silla (normalmente en contra de su voluntad, como en La Naranja Mecánica) y vean con sus propios ojos las cosas que puede hacer git que svn solo podría hacer en sueños lúcidos.

The Garcia Effect Nitpicks A Clockwork Orange So You Don't Have To
Image source: Gizmodo (espero no estarme metiendo en problemas por usar esta imagen :-))

Voy a intentar resumir algunos de los problemas que yo puedo afrontar utilizando git que mis compañeros de trabajo no pueden por utilizar svn (o sí pueden, pero la técnica para hacerlo es engorrosa) mientras trabajamos en nuestro proyecto usando svn así que el enfoque sigue siendo para colocar los cambios en un servidor centralizado y no para explorar todas las posibilidades que nos brinda git.

Exposición de interés: recientemente me convertí en un (muy muy) pequeño contribuidor de git con unos parches para generar salida de progreso (como la que van a ver un poco más abajo cuando hago git blame). No tengo afilicación con el proyecto svn o con algún otro proyecto apache (hasta lo mejor de mi conocimiento).

1 Trabajar sobre varias ramas desde una sola copia de trabajo

Quién ha tenido que trabajar sobre varias ramas de un proyecto al mismo tiempo? Y cuantos de ustedes mantienen una copia de trabajo por cada una de esas ramas? En este caso voy a elucubrar diciendo que la mayoría. Saben que existe la operación "svn switch" que les permitiría saltar de una rama a la otra sin necesidad de tener que crear una nueva copia de trabajo en su equipo cierto? Sí, es probable que la mayoría de los desarrolladores que utilizan svn lo sepan. Y entonces... por qué no la usan? Ah, porque saltar de una rama a la otra tarda mucho tiempo, cierto? Exacto. Pero considerarían utilizar svn switch si ese salto solo tardara algunos segundos sin importar qué tan "lejos" vayan a saltar? Excelente. Les presento "git checkout" que hace exactamente eso.

Hagamos una comparación:

~/proyectos/svn/svn  
15:20 $ svn info
Path: .
Working Copy Root Path: /home/antoranz/proyectos/svn/svn
URL: https://svn.apache.org/repos/asf/subversion/trunk
Relative URL: ^/subversion/trunk
Repository Root: https://svn.apache.org/repos/asf
Repository UUID: 13f79535-47bb-0310-9956-ffa450edef68
Revision: 1732667
Node Kind: directory
Schedule: normal
Last Changed Author: stsp
Last Changed Rev: 1732400
Last Changed Date: 2016-02-25 17:41:02 -0600 (Thu, 25 Feb 2016)

15:21 $ git show --summary
commit 95e34ade485c774b580ef29b0560b12750f1eb5b (HEAD -> trunk, origin/trunk, origin/HEAD)
Author: Stefan Sperling
Date:   Thu Feb 25 23:41:02 2016 +0000

   * subversion/libsvn_client/conflicts.c
     (conflict_type_specific_setup,
      conflict_tree_get_description_incoming_delete): Remove unused variables.
  
   Found by: philip
  
  
   git-svn-id: https://svn.apache.org/repos/asf/subversion/trunk@1732400 13f79535-47bb-0310-9956-ffa450edef68


Sí, apache mantiene un espejo del repositorio de subversion sobre un repositorio en github. No se molesten en buscar un espejo del repositorio de git montado sobre svn porque no lo van a encontrar.

Cuanto tiempo me llevaría moverme a una rama/revisión relativamente distante?

~/proyectos/svn/svn  
15:30 $ time svn switch --quiet https://svn.apache.org/repos/asf/subversion/tags/1.0.0

real    0m5.020s

user    0m1.012s
sys     0m0.640s


Nada mal.... 5 segundos para saltar a la etiqueta 1.0.0. Había hecho una prueba antes y me llevó 13 segundos... pero no importa. Veamos cuanto le lleva a git hacer el mismo salto:

~/proyectos/svn/git [trunk|]  
15:28 $ time git checkout 1.0.0
Checking out files: 100% (3012/3012), done.
Note: checking out '1.0.0'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

 git checkout -b

HEAD is now at 0844db9... Add tag 1.0.0

real    0m0.345s

user    0m0.232s
sys     0m0.096s


5 segundos vs 345 milisegundos. Ahora piensen en los (probablemente) minutos que lleva saltar de una rama a la otra en su proyecto y piensen en cómo podría mejorar su propio desempeño en el trabajo si los saltos se pudieran hacer en solo segundos o fracciones de segundo en vez de minutos. Probablemente dejarían de utilizar una copia de trabajo por rama y trabajarían en un sólo árbol de trabajo (el término en la jerga de git para referse a la copia de trabajo en svn).

2 No se necesita una conexión al servidor para poder trabajar

En este punto les presento otra de las ventajas de usar un controlador distribuído. Voy a desconectar mi equipo de internet por unos segundos para la próxima demostración. Regresemos al trunk:

~/proyectos/svn/svn  
15:31 $ time svn switch --quiet https://svn.apache.org/repos/asf/subversion/trunk
svn: E170013: Unable to connect to a repository at URL 'https://svn.apache.org/repos/asf/subversion/trunk'
svn: E670003: Temporary failure in name resolution

real    0m0.727s
user    0m0.060s
sys     0m0.012s


Ok, problemas de conexión al servidor svn. Y en git?

~/proyectos/svn/git [1.0.0|]  
15:29 $ time git checkout trunk
Checking out files: 100% (3012/3012), done.
Previous HEAD position was 0844db9... Add tag 1.0.0
Switched to branch 'trunk'
Your branch is up-to-date with 'origin/trunk'.

real    0m0.881s
user    0m0.524s
sys     0m0.132s


Git mantiene una copia completa del repositorio de forma local. Y no empiecen a hablar paja del espacio que esto implica. El repositorio del clon de subversion pesa unos 220 MBs para las 76000+ revisiones lo que implica un promedio de unos 3k por revisión y con los GBs y TBs de los que se habla en estos días yo estaría dispuesto a hacer el sacrificio de espacio (si es tanto espacio como esto) para tener una copia completa del repositorio conmigo a donde vaya (lo cual implica un full backup, por cierto).

Pero esta ventaja aplica no solamente para un switch/checkout. Básicamente todas las operaciones que se hacen sobre una copia de trabajo requieren de una conexión al servidor en svn (excepto un revert, quizás?).

~/proyectos/svn/svn  
15:44 $ svn annotate dist.sh
svn: E170013: Unable to connect to a repository at URL 'https://svn.apache.org/repos/asf/subversion/tags/1.0.0/dist.sh'
svn: E670003: Temporary failure in name resolution


~/proyectos/svn/git [1.0.0|]  
15:45 $ time git blame dist.sh > /dev/null

real    0m0.199s
user    0m0.200s
sys     0m0.000s


~/proyectos/svn/git [1.0.0|]  
15:46 $ time git log > /dev/null

real    0m0.254s
user    0m0.248s
sys     0m0.004s


~/proyectos/svn/svn  
15:47 $ svn log
svn: E170013: Unable to connect to a repository at URL 'https://svn.apache.org/repos/asf/subversion/tags/1.0.0'
svn: E670003: Temporary failure in name resolution


Sin conexión al servidor su copia de trabajo vuelve temporalmente a la época de las cavernas. En el caso de que tengamos conexión de red, la ventaja de trabajar local se vuelve a ver reflejada en la velocidad a la cual se pueden hacer las operaciones. Al igual que el checkout de git fue mas de un órden de mágnitud más rápido que el svn switch, muchas otras operaciones se verán afectadas por el tema de la latencia de la conexión al servidor. Me vuelvo a conectar y voy a pedir un log: (que ya vimos que le tomó 254 milisegundos generarlo a git sobre la etiqueta 1.0.0 de subversion):

~/proyectos/svn/svn  
15:52 $ time svn log > /dev/null

real    0m12.150s
user    0m0.512s
sys     0m0.024s


(Esta fue la mejor de 3 pruebas. La que más se tardó llegó a 30+ segundos). Si lo hacemos desde trunk (que tiene una historia mucho más larga):

~/proyectos/svn/svn  
15:54 $ time svn log > /dev/null

real    2m42.288s
user    0m2.036s
sys     0m0.076s

~/proyectos/svn/git [trunk|]  
15:59 $ time git log > /dev/null

real    0m1.777s
user    0m1.292s
sys     0m0.040s


2+ minutos vs 1.777 segundos. En este caso fue una diferencia de casi 3 órdenes de magnitud.

~/proyectos/svn/svn  
15:56 $ time svn annotate subversion/svn/svn.c > /dev/null

real    0m6.247s
user    0m0.688s
sys     0m0.196s


~/proyectos/svn/git [trunk ↓·1|]  
16:01 $ time git blame subversion/svn/svn.c > /dev/null
Blaming lines: 100% (3118/3118), done.

real    0m5.626s
user    0m2.272s
sys     0m0.100s


Ok... este se acercó. 6.247 vs 5.626 (aunque igual svn fue más de 10% más lento que git).

Un diff entre trunk y 1.0.0?

~/proyectos/svn/git [trunk ↓·3|]  
21:04 $ time git diff 1.0.0 > /dev/null

real    0m1.157s
user    0m1.076s
sys     0m0.084s


~/proyectos/svn/svn  
20:57 $ time svn diff --old ^/subversion/tags/1.0.0 --new ^/subversion/trunk > /dev/null
^Csvn: E200015: Caught signal
svn: E200042: Additional errors:
svn: E200015: Caught signal

real    5m18.315s
user    0m3.020s
sys     0m0.720s

(luego de 5 minutos me cansé de esperar por el resultado... y mejor no me pongan a hablar acerca de la sintaxis en la que hay que explicarle a svn cuales son las ramas que quiero comparar porque empiezo a botar espuma por la boca).

3 Puedo crear las ramas que me venga en gana

Sí, como lo escuchan (leen?). Como yo soy el dueño y señor de mi repositorio local, eso quiere decir que yo puedo crear todas las ramas que me de la gana crear sin necesidad de esperar a que se creen en el repositorio central. Y entonces normalmente se escucha la pregunta (y normalmente con el tonito más fastidioso que haya conocido ser humano alguna vez): "Pero pa'quéeeeeee????!!!!".

Cuantos de ustedes habrán tenido que trabajar en varias soluciones (pensemos en tickets, quizás) al mismo tiempo sobre el proyecto? En los repositorios donde usan subversion normalmente no se tomarían la molestia de crear una rama por ticket (aunque es técnicamente posible, que quede claro). Por qué no? Supongo que es porque es demasiado trabajo para los administradores (aunque podría haber otras razones mas técnicas que escapan a mi entendimiento/conocimiento/comprensión de cómo trabaja svn). Si este es el caso, entonces se utiliza una sola rama de desarrollo donde todos los desarrolladores van apilando sus cambios. Pero si yo estoy desarrollando varios cambios al mismo tiempo eso quiere decir que los voy a tener todos "regados" sobre mi copia de trabajo mientras no los acometa (y no me van a engañar haciéndome creer que tienen una copia de trabajo por cada ticket, verdad que no?). Eso significa que tengo que ser bastante cuidadoso para no acometer cambios que pertenecen a un ticket Z cuando quiero acometer los cambios de un ticket X. Eso entonces significa que tengo que ser bastante cuidadoso (más de lo normal, quiero decir) a la hora de acometer los cambios. El caso en el que se tiene cambios flotando de varios tickets sobre un solo archivo lo voy a nombrar para ilustrar un problema adicional (voy a acometer los cambios del ticket X? Tengo que comentar o sacar por unos segundos los del ticket Z. Luego de acometer los del ticket X puedo poner el archivo como estaba).

Hay una forma al alcance de los desarrolladores que usan subversion para poder "manejar" esa situación: Utilizar parches para poder limpiar la copia de trabajo cuando se desea deja de trabajar en un ticket X y comenzar a trabajar en un ticket Y.

Digamos que estoy trabajando en el ticket X y no está listo aún para acometerse y me solicitan trabajar inmediatamente sobre el ticket Y.
1 creo un parche para el trabajo que tengo actualmente para el ticket X. 2 Revert 3: comienzo a trabajar en el ticket Y.

Si deseo regresar a trabajar al ticket X (sin haber acometido los cambios del ticket Y):
1 creo un parche para el trabajo que tengo actualmente para el ticket Y. 2 Revert 3: aplico el parche del ticket X. 4 Sigo trabajando en el ticket X.

Este manejo de los parches "offline" (quiero decir, fuera de svn) se puede volver bastante engorroso (se puede volver? ES!). Incluso creo que hay IDEs que le dan soporte al manejo de los parches para facilitarle la vida a los desarrolladores. De cualquier forma, el manejo de estos parches no es algo integral al core de svn.

Ahora supongamos que nuestro administrador del repositorio svn es suficientemente benévolo como para crear una rama para cada ticket. En este momento la vida se vuelve mucho más bella: Podemos hacer svn switch entre las ramas para poder trabajar sobre cada uno de los tickets de forma independiente y así podemos manejar los cambios para cada ticket de forma separada. Cuando un ticket está listo (luego de probablemente varias acometidas, dependiendo de la dificultad de desarrollarlo/resolverlo) se podría mezclar dicho ticket a la rama principal de desarrollo (o llevar los cambios con un svn merge -c, mejor conocido en los bajos fondos como cherry-pick). Si pueden visualizar eso entonces imaginen la situación donde ustedes sean los administradores del repositorio svn y no le tengan que pedirle permiso a nadie para crear las ramas. La vida no podría ser más bella, cierto? Bueno, eso es lo que sucede cuando trabajan con git sobre su repositorio local. Ustedes pueden crear las ramas que necesiten (una por ticket, por ejemplo) para desarrollar cada ticket de forma independiente sin necesidad de esperar que nadie haga ese trabajo por ustedes.

Por ejemplo, en el proyecto donde trabajo todavía no hemos entrado a la fase de desarrollo (comienza dentro de unas 2 semanas) y no tenemos ni si quiera una rama de desarrollo donde ir colocando los cambios, ni aunque los tuviéramos listos (y en mi proyecto no van a crear una rama por ticket, lo tengo muy claro). Yo en este momento estoy desarrollando (adelantándome a los acontecimientos) 4 tickets de variada dificultad sobre la rama de pruebas del release anterior al que voy a estar trabajando utilizando una rama separada por cada ticket. Cuando me inspiro para trabajar en un ticket hago un git checkout a esa rama, hago las acometidas que requiera y luego cuando quiero trabajar en otro ticket simplemente hago un git checkout de dicha rama (que como ya vimos es una operación bastante rápida) y continúo trabajando en dicho ticket. La vida es bella! O dicho en términos de Costa Rica, que es donde estoy viviendo hace casi dos años: PURA VIDA!!!!

Esto también aplica para poder desarrollar varias ideas de un solo ticket. Digamos que tienen 2 ideas diferentes para hacer el mismo trabajo (el mismo ticket). Si trabajamos con svn podríamos, igual que había dicho antes, implementar una idea, crear un parche, revertir, implementar la otra idea y crear un parche para la misma. Cuando quiero probar una idea, aplico el parche de dicha idea, pruebo, limpio. Cuando quiero probar la otra idea aplico el parche, pruebo, limpio. Pero no sería mucho más sencillo crear 2 ramas (con todas las ventajas desde el punto de vista de control de versiones que conlleva usar ramas separadas) y saltar entre una rama y la otra?

Pero esto no solo aplica para Tickets. Imagínen que tienen en su cabeza un cambio arquitectónico de la aplicación (o un módulo de la misma) y que crear una prueba de concepto para ello requeriría de varios días o semanas. Para una prueba de concepto va a ser difícil que consigan una rama en el repositorio (especialmente si es una prueba de concepto, digamos, extraoficial). Pero varios días de trabajo implicarían un cambio probablemente monumental que a la hora de revisarlo sería difícil para cualquier persona abordarlo de un solo golpe en un parche. Si pudieran tener una rama para poder ir haciendo los cambios de forma paulatina acometiendo cambios más pequeños sin duda sería más fácil de asimilarla por las personas que tuvieran que revisarla/analizarla al ver el trabajo que requirió cada paso.

Resumen: Luego de utilizar ramas locales no van a querer sentarse a trabajar sin ellas en su vida.

Conclusión
Quien dijo que no podríamos recorrer Nürburgring en un Escarabajo? Nadie! Pero cuantos preferirían hacerlo en el Bugatti?

Hasta luego!


PS: Hay otras muchas (muchísimas ventajas) de utilizar controladores distribuidos vs centralizados pero en este artículo me enfoqué en las cosas que yo puedo hacer con git mientras trabajamos en un proyecto que está usando svn para controlar versiones así que no iba a ahondar en temas como push vs pull, los flujos de trabajo con gatekeeper etc etc. Para las ventajas/diferencias entre los dos modelos (centralizado vs distribuido) escribiré otro artículo (quien sabe cuando... no se queden aguantando la respiración esperando por él).

lunes, 7 de abril de 2014

Curso de git - Parte 1

Como están?

Llevo un año utilizando git de forma "seria" y de vez en cuando me sorprendo teniendo que explicarle a personas qué es o cuales son las diferencias que hay entre git y subversion (o algún otro controlador de versiones). Para tratar de dejar una guía sencilla para los interesados, he decidido crear una secuencia de capítulos en mi blog donde voy a explicar lo que es git y como utilizarlo.


Sin más que agregar, aquí arranca mi "curso".

Qué es git?

Git es, entre otras cosas y en pocas palabras, un controlador de versiones distribuido.

Qué es un controlador de versiones distribuido?

Esta tenía que ser la siguiente pregunta, cierto?

Un controlador de versiones distribuido es aquel en donde no existe un solo repositorio centralizado (desde el punto de vista de diseño de la aplicación, quiero decir) sino que se pueden tener tantos repositorios como se considere necesarios (en equipos locales o remotos).

Para qué se quiere tener más de un repositorio?

Esto puede parecer trivial pero en realidad es una de las grandes ventajas del uso de DVCS (Distributed Version Control Systems). Imaginemos las siguientes situaciones:

- Queremos trabajar en un "feature" totalmente experimental y queremos tener control de versiones.... pero no queremos hacerlo público.... o por lo menos, no hacerlo público hasta que sea estable o por lo menos presentable.

- No tenemos acceso al servidor centralizado (se cayó la red, estoy en mitad de un vuelo al pacífico).

- Queremos movernos hacia otra revisión del proyecto (esto puede tomar algo de tiempo utilizando un CVCS).... en realidad este punto no solo tiene que ver con movernos sino con el hecho de hacer casi cualquier operación que requiera control de versiones como comparar revisiones, o ver anotaciones de algún archivo, todas ellas van a requerir acceso (on-line) al servidor y todas ellas entonces van a depender entonces de la latencia de los recursos involucrados (velocidad de la red, velocidad del servidor por discos/cpu/carga, etc).

- Lo casi peor que puede pasar: Se jodió el servidor.

- Lo peor que puede pasar: Lo anterior pero agréguenle que no hay backups.

Junten todas esas razones y me parecen más que suficientes para querer manejar versiones de forma distribuida. Continuemos.

Qué hay en un repositorio git?

En un repositorio git hay básicamente una cosa:
- Commits (acometidas, changesets, revisiones, como lo quieran llamar) que indican el estado de los directorios/archivos en el mismo. Son "revisiones" de un proyecto en un momento dado y que se apuntan unos a otros (los hijos apuntan a sus padres, básicamente). Eso es _casi_ todo lo que hay.

En serio......Pero EN SERIO? Y las ramas? Aquí es donde la versatilidad de git se comienza a poner de manifiesto. Las ramas también están.... pero, y esta es una de las características más poderosas de git, no son más que "apuntadores" a revisiones. Apuntadores que pueden ser creados, movidos y eliminados (y recreados si fuera necesario) a discreción del dueño de un repositorio.

Y las etiquetas? Son exactamente igual que las ramas, apuntadores a revisiones que pueden ser creados, movidos, eliminados (y también recreados si fuera necesario) a discreción del dueño de un repositorio.

Entonces cual es la diferencia entre rama y etiqueta? Que al hacer una nueva revisión sobre una rama, el apuntador se mueve a la nueva revisión, pero al crear una nueva revisión sobre una etiqueta, la etiqueta no se mueve a la nueva revisión.



Pongamos en práctica estos pocos conocimientos que tenemos hasta el momento.

Primero que todo, debemos indicarle a git quienes somos. Esto se hace con dos sencillas instrucciones (ajústenlas a sus propias identidades):

$ git config --global user.name "Edmundo Carmona"
$ git config --global user.email eantoranz@gmail.com

Lo interesante de usar --global en este caso es para que esto quede configurado  para la "globalidad" de git _pero_ si al trabajar en un proyecto en el futuro necesitan utilizar una identidad particular, pueden indicar en el repositorio de dicho proyecto una identidad diferente ejecutando estos mismos comandos sin el --global para que dicha identidad se mantenga solo en dicho repositirio manteniendo la identidad global en el resto de los repositorios.

Hecho esto, inicialicemos un "repositorio". Las instrucciones que voy a dictar en este punto son dadas sobre un equipo con gnu/linux sobre un terminal pero se pueden llevar a otros ambientes (por ejemplo gráficos o con un IDE) y SOs.


Digamos que creo un directorio para tener el proyecto curso-git (que se llama de la misma forma). Nada más sencillo:

$ git init curso-gitInitialized empty Git repository in /home/antoranz/curso-git/.git/


Habiendo hecho eso, git creó el directorio "vacío" en mi equipo que se llama curso-git (sobre el home que es donde estaba parado al momento de correr git init).

Qué hay dentro de este repositorio? No mucho, la verdad:

$ cd curso-git/
$ ls -l
total 0
$ git status# On branch master
#
# Initial commit
#
nothing to commit (create/copy files and use "git add" to track)


En el mensaje de creación del repositorio (si se fijaron bien) git creó un directorio "escondido" que es donde el guarda toda su magia:

$ ls -latotal 12
drwxr-xr-x  3 antoranz antoranz 4096 Apr  7 20:15 .
drwxr-xr-x 62 antoranz antoranz 4096 Apr  7 20:15 ..
drwxr-xr-x  7 antoranz antoranz 4096 Apr  7 20:16 .git


En el directorio .git es donde git guarda toda la información del repositorio y es el único directorio que "embasurará" el proyecto (hay otros detalles como el archivo .gitignore que trataremos en su momento).

Ahora procedamos a crear algunas revisiones de este proyecto. Creen un par de archivos de texto dentro del protecto o traiganlos de alguna otra parte.

$ ls -ltotal 8
-rw-r--r-- 1 antoranz antoranz 47 Apr  7 20:20 archivo1.txt
-rw-r--r-- 1 antoranz antoranz 48 Apr  7 20:20 archivo2.txt



Veamos qué nos dice git acerca de estos dos archivos:

$ git status# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add ..." to include in what will be committed)
#
#       archivo1.txt
#       archivo2.txt
nothing added to commit but untracked files present (use "git add" to track)



Lo que git nos está diciendo es que de esos archivos git no tiene _ni idea_ de donde salieron y que hasta el momento no le hemos indicado a git qué hacer con ellos.

Procedamos a "agregarlos" y los acometemos tal cual están en este momento.

$ git add archivo*.txt
$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached ..." to unstage)
#
#       new file:   archivo1.txt
#       new file:   archivo2.txt
#
$ git commit -m "Revision inicial del proyecto del curso de git"[master (root-commit) a8d7d41] Revision inicial del proyecto del curso de git
 2 files changed, 3 insertions(+)
 create mode 100644 archivo1.txt
 create mode 100644 archivo2.txt


En este punto hay varias cosas muy interesantes que no son tan obvias a primera vista.

Primero: Se crea una "revisión" del proyecto (esta sí es obvia, no?). Las revisiones en git no son incrementales (esta no era obvia, ven?), las revisiones tienen un id sha-1 que las identifica de forma unívoca. No hay dos revisiones que tengan el mismo identificador sha-1. Cual es el identificador sha-1 de esta revisión? Veamos:

$ git show --summary HEADcommit a8d7d412b058916ed75a12fa88285739b655b275
Author: Edmundo Carmona Antoranz
Date:   Mon Apr 7 20:27:39 2014 -0500

    Revision inicial del proyecto del curso de git

 create mode 100644 archivo1.txt
 create mode 100644 archivo2.txt


El identificador está en la primera línea de salida del comando (a8d7d412b058916ed75a12fa88285739b655b275).

Segundo: La rama "por defecto" en la que se trabaja en un repositorio con git se llama "master" y acaba de ser "creada" y está apuntando a la revisión a8d7d412b058916ed75a12fa88285739b655b275 (que es la primera revisión de nuestro proyecto).


Ahora borremos un archivo, modifiquemos el otro archivo y creemos otro archivo.

$ git status# On branch master
# Changes not staged for commit:
#   (use "git add/rm ..." to update what will be committed)
#   (use "git checkout -- ..." to discard changes in working directory)
#
#       deleted:    archivo1.txt
#       modified:   archivo2.txt
#
# Untracked files:
#   (use "git add ..." to include in what will be committed)
#
#       archivo3.txt
no changes added to commit (use "git add" and/or "git commit -a")



Muy interesante. Git se da cuenta del archivo que modificamos, del que borramos y del nuevo archivo del que git no tiene ni idea de donde salió.

Ahora procedamos a incluir _todos_ los cambios en la siguiente acometida del proyecto. Para ello debemos "agregar" el archivo nuevo y el modificado e indicarle a git que el otro archivo fue borrado (si no le indicamos a git que guarde todos los cambios, el procedería a acometer solo los cambios que le indiquemos expresamente y dejaría otros cambios "flotando" en el proyecto para acometidas posteriores).

$ git add archivo2.txt archivo3.txt
$ git rm archivo1.txt
rm 'archivo1.txt'
$ git status# On branch master
# Changes to be committed:
#   (use "git reset HEAD ..." to unstage)
#
#       deleted:    archivo1.txt
#       modified:   archivo2.txt
#       new file:   archivo3.txt
#
$ git commit -m "Segunda revision del proyecto del curso"[master 37662b9] Segunda revision del proyecto del curso
 3 files changed, 3 insertions(+), 2 deletions(-)
 delete mode 100644 archivo1.txt
 create mode 100644 archivo3.txt


Ahora se creó la segunda revisión del proyecto. Veamos la información del sumario de la revisión:

$ git show --summary HEADcommit 37662b930de0742c38a2cd63db4368ed80e2c1a9
Author: Edmundo Carmona Antoranz
Date:   Mon Apr 7 20:37:50 2014 -0500

    Segunda revision del proyecto del curso

 delete mode 100644 archivo1.txt
 create mode 100644 archivo3.txt



De nuevo podemos ver el sha-1 de la revisión (como les dije, no son revisiones incrementales) y vemos los archivos nuevos/borrados. Los archivos modificados no salen en el sumario aunque efectivamente forman parte del cambio:

$ git show --stat   HEADcommit 37662b930de0742c38a2cd63db4368ed80e2c1a9
Author: Edmundo Carmona Antoranz
Date:   Mon Apr 7 20:37:50 2014 -0500

    Segunda revision del proyecto del curso

 archivo1.txt | 2 --
 archivo2.txt | 2 ++
 archivo3.txt | 1 +
 3 files changed, 3 insertions(+), 2 deletions(-)



La rama en este punto se ve asi:



Tenemos la rama master que tiene dos revisiones lineales en su historia. Ahora juguemos un poco con el concepto de los apuntadores de los que hablaba anteriormente.

Digamos que vamos a crear una nueva rama a partir de la primera revisión del proyecto. Como lo podemos hacer? Muy sencillo: Le indicamos a git que coloque una nueva rama sobre la primera revisión del proyecto y eso es todo. Recordemos que para git las ramas son solo apuntadores así que para él se trata solo de crear un nuevo apuntador a la revisión deseada. Digamos que la rama se va a llamar "rama1" y para indicarle la revisión pueden indicar el sha-1 de dicha revisión (en realidad se puede indicar menos que el sha-1 completo pero eso lo dejaré para otra ocasión):

$ git branch rama1 a8d7d412b058916ed75a12fa88285739b655b275

Podemos ver que ahora tenemos una nueva rama que se llama "rama1" y que está justo donde lo esperábamos:



Ahora saquemos esta rama del repositorio a nuestro árbol de trabajo y veamos qué sucede con la historia de nuestra rama:


$ git checkout rama1Switched to branch 'rama1'


Como podemos ver, es como si la segunda revisión que hicimos hubiera desaparecido.... efectivamente desapareció, pero solo de nuestro radar. La rama "master" sigue existiendo y sigue apuntando a la misma revisión donde la dejaron en caso de que la necesiten.


Veamos el contenido del proyecto:

$ ls -ltotal 8
-rw-r--r-- 1 antoranz antoranz 47 Apr  7 20:51 archivo1.txt
-rw-r--r-- 1 antoranz antoranz 48 Apr  7 20:51 archivo2.txt
$ git status# On branch rama1
nothing to commit, working directory clean




Justo lo que esperábamos y miren cual es la rama que git dice que estamos trabajando.

Ahora modifiquemos alguno de los archivos y acometamos:

$ git status# On branch rama1
# Changes not staged for commit:
#   (use "git add ..." to update what will be committed)
#   (use "git checkout -- ..." to discard changes in working directory)
#
#       modified:   archivo2.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
$ git add archivo2.txt
$ git commit -m "Una modificacion que va a quedar en la rama 1"
[rama1 d1580e8] Una modificacion que va a quedar en la rama 1
 1 file changed, 2 insertions(+)


Ahora veamos la historia de ambas ramas juntas:



Podemos ver ambas ramas y como se bifurcaron desde la primera revisión del proyecto:

Ahora regresemos a la rama "master" y, para terminar de demostrar el concepto de las ramas como apuntadores, juguemos a "borrar" la rama rama1:

$ git checkout masterSwitched to branch 'master'
$ git branch -D rama1Deleted branch rama1 (was d1580e8).

$ git checkout rama1error: pathspec 'rama1' did not match any file(s) known to git.

Oops! Borré la rama!!!! No tengan miedo, como había explicado antes, las ramas no son más que apuntadores y mientras git tenga conocimiento de la revisión dentro del repositorio (que es al fin y al cabo casi que lo único que hay en un repositirio git), una rama siempre es recuperable siempre y cuando se tenga el ID de la revisión en cuestión (en este caso, git cuando borró la rama fue tan amable de indicarme la parte "significativa" del identificador sha-1 necesaria para indicar la revisión a la que estaba apuntando la rama previamente, lo ven?):

$ git branch rama1 d1580e8
$ git checkout rama1
Switched to branch 'rama1'
$ git show --summary HEADcommit d1580e8b7f28ff9a33e6e3569b33e67a9b56415f
Author: Edmundo Carmona Antoranz
Date:   Mon Apr 7 20:55:33 2014 -0500

    Una modificacion que va a quedar en la rama 1




Y con esto doy por terminada la primera lección esperando que haya sido suficientemente clara.

Hasta la próxima.