Git es un sistema de control de versiones.
[sudo] apt-get install git
[sudo] yum install git
Con homebrew
brew install git
http://git-scm.com/download/mac
Si aún no has podido instalar, ve a:
$ mkdir project && cd project
$ git init
Initialized empty Git repository in .../project/.git
$ git status
On branch master
Initial commit
nothing to commit (create/copy files and use "git add" to track)
Para que git se haga cargo de tus archivos tienes que añadirlos explícitamente
(git add
).
Para que se guarden los cambios realizados sobre un archivo hay que
confirmar dichos cambios (git commit
).
Git guarda los commits en un grafo acíclico unidireccional (vamos, que cada
commit tiene un padre). Eso permite que varios commits tengan un mismo padre,
es decir, que se diverja. Cada divergencia de la rama por defecto (master
) es
un branch (más o menos, detached head, etc...), y el último commit realizado a
un branch se llama HEAD
(salvo cuando te encuentras en detached head).
Los branchs tienen nombre, y se crean con git branch <nombre>
.
Para cambiar de un branch a otro se usa git checkout <nombre>
.
El comando git checkout -b <nombre>
es un alias para git branch <nombre> && git checkout <nombre>
Si creamos un archivo, git se da cuenta de que todavía no tiene control sobre
ese archivo, y nos lo muestra al usar git status
.
$ echo "Hola git" > hola.txt
$ git status
On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
hola.txt
Cuando usamos git add <nombre-del-archivo>
, git guarda los cambios en lo que
llama staging area. Todavía los cambios no están confirmados.
$ git add hello.txt
$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: hello.txt
Al usar git commit
, confirmamos los cambios añadidos. Cada commit va
acompañado de un mensaje, que se puede especificar con el flag -m
, o escribir
en el editor de nuestra preferencia.
$ git commit -m "Add hello.txt"
[master (root-commit) deb85ac] Add hello.txt
1 file changed, 1 insertion(+)
create mode 100644 hello.txt
¡Genial! Ya hemos creado nuestro primer commit.
Para ver la historia de nuestro repositorio podemos usar git log
. Ahora sólo
tenemos un commit así que va a ser una historia un poco cutre:
commit deb85ac1a081a5ccacfa9a813f497d61577522e1
Author: Emilio Cobos Álvarez <[email protected]>
Date: Sat Oct 24 17:01:14 2015 +0200
Add hello.txt
Nuestro fichero hello.txt
es un poco cutre, vamos a cambiarlo.
Además, queremos generar a partir de él un pdf, así que a ver cómo podemos hacerlo:
$ echo "Git rules" >> hello.txt
$ cat > Makefile
hello.pdf: hello.txt
wkhtmltopdf $< $@
$ make
wkhtmltopdf hello.txt hello.pdf
Loading pages (1/6)
Counting pages (2/6)
Resolving links (4/6)
Loading headers and footers (5/6)
Printing pages (6/6)
Done
Hey, parece que ha funcionado, vamos a añadir los cambios!
$ git add --all
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: Makefile
new file: hello.pdf
modified: hello.txt
Jum... Hemos estado a punto de hacer commit de nuestro archivo pdf, que no queremos que esté en nuestro repositorio.
Para eliminarlo de la staging area podemos usar:
$ git reset HEAD hello.pdf
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: Makefile
modified: hello.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
hello.pdf
Eso está mejor, confirmemos los cambios:
$ git commit -m "Modify hello.txt and allow converting to pdf"
[master 2a1c2dd] Modify hello.txt and allow converting to pdf
2 files changed, 3 insertions(+)
create mode 100644 Makefile
Git antes de añadir cambios lee un archivo llamado .gitignore
. Ignora todos
los archivos o directorios que se especifiquen en él. Es perfecto para
archivos compilados o autogenerados, logs, credenciales...
Vamos a usarlo para evitar que nuestro archivo hello.pdf
sea añadido al
repositorio:
$ git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
hello.pdf
nothing added to commit but untracked files present (use "git add" to track)
$ cat > .gitignore
hello.pdf
$ git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
nothing added to commit but untracked files present (use "git add" to track)
Genial! Parece que git ya ignora nuestro archivo hello.pdf
. Vamos a guardar
los cambios!
$ git add --all # Ahora podemos usar --all estando seguros de que hello.pdf no será añadido
$ git commit -m "Add .gitignore"
[master e32ad43] Add .gitignore
1 file changed, 1 insertion(+)
create mode 100644 .gitignore
Parece que nuestro gran proyecto (/joke) ha alcanzado un estado en el que merece la pena compartir los cambios, o simplemente guardarlos en un sitio remoto para no perderlo.
Vamos a usar github como ejemplo, aunque se podrían usar cualquier otra plataforma (gitlab, bitbucket...) o incluso un servidor propio.
Una vez creemos nuestro repositorio, podemos hacer:
$ git remote add origin [email protected]:user/repo.git
$ git push origin master
El primer comando añade un remote a nuestro repositorio, llamado origin
, y
con la url [email protected]:user/repo.git.
El segundo hace push
(empuja los cambios) del branch actual al branch
master
del repositorio remoto llamado origin
.
GitHub ofrece una interfaz muy amigable para ver los cambios entre commits... Pero no es nada que no podamos hacer desde la shell!
$ git diff # Muestra los cambios hechos sobre la staging area
$ git diff HEAD # Muestra los cambios sin confirmar
$ git diff HEAD^ HEAD # Muestra los cambios del último commit
$ git diff <hash-1> <hash-2> # Muestra los cambios entre el commit <hash-1> y el commit <hash-2>
$ git diff branch1..branch2 # Muestra las diferencias entre ambos branchs
Antes habíamos definido HEAD
, como (casi siempre) el último commint hecho al
branch actual. Esto es casi siempre cierto, pero para dar una definición más
exacta, podríamos decir que:
HEAD es un alias, un puntero a un commit. Ese commit es el commit sobre el que estás trabajando en un determinado momento, y por lo tanto será el padre de tu siguiente commit.
Hay que diferenciar entre HEAD
y los heads de un repositorio. Los heads
de un repositorio son los commits que tienen un nombre particular dentro del
repositorio, por lo general los branches. El commit HEAD
, apunta al head
actual, que puede tener nombre (master
, por ejemplo), o puede ser un commit
cualquiera.
Por ejemplo, si hacemos:
$ cat .git/HEAD
ref: refs/heads/master
Veremos que actualmente está apuntando a refs/heads/master
, que en este caso
es:
cat .git/refs/heads/master
e32ad43bbd01287fcb208fac5d1425f9412ba237
Si hacemos checkout
del commit anterior a nuestro HEAD
, pasaremos a lo que
se llama detached head, un estado en el que nuestro HEAD
no apunta a ningun
commit con nombre, y que por lo tanto, salvo que hagamos un branch nuevo
apuntando a ese commit (y por lo tanto un head con nombre), nuestros commits
quedarán perdidos y sólo podrán ser accesibles via hash.
$ git checkout HEAD^
Note: checking out 'HEAD^'.
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 new_branch_name
HEAD is now at 2a1c2dd... Modify hello.txt and allow converting to pdf
$ cat .git/HEAD
2a1c2dd7f0febaa0029c5fbb356b87a6526268e1
- El símbolo
~
se puede usar para especificar commits anteriores en la historia. AsíHEAD~2
será el antepenúltimo commit,HEAD~3
el anterior a ese, y así sucesivamente. - El símbolo
^
, representa el commit anterior, por lo tantoHEAD^
es sólo un alias paraHEAD~1
, que será el penúltimo commit.