Created at:
Modified at:
Notas sobre o git
Primeiros passos com o Git
Comecei com sistemas de controle de revisão já há algum tempo, com CVS. Naquela época, já existiam softwares de controle de revisão mais modernos mas simplesmente amávamos a simplicidade do CVS. Algum tempo depois a empresa que eu trabalhava mudou o sistema, mas que ainda era da abordagem cliente-servidor (centralizado): começamos a utilizar o Subversion, que ainda acho o melhor sistema para um ambiente centralizado, como em algumas empresas, mas essa é uma outra história.
Quando comecei a desenvolver software livre, fui apresentado aos sistemas de versionamentos modernos e decentralizados. Depois de experimentar o Mercurial, Bazaar (usado pela Canonical para no projeto Ubuntu Linux), Monotone (sistema muito interessante que foi uma das principais inspirações para Linus Torvalds construir o git) e Fossil (outro sistema muito interessante que tem um sistema de rastreamento de bugs -- bug tracking -- junto com o sistema de versionamento de código). Também experimentei o git, que tem a enorme vantagem de ter sido adotado por uma comunidade gigantesca de desenvolvedores, dando origem a vários serviços que proveem hospedagem com o git, como o GitHub e outros.
Esse guia não irá te dizer a diferença entre os sistemas mencionados (vale a pena dar uma olhada em cada um deles) mas irá te ajudar com o básico do git. Antes de começar, verifique se você tem o git instalado em sua máquina. Iremos utilizar a interface de linha de comando. Existem interfaces gráficas para o git, tanto quanto plugins para sua IDE favorita, mas vamos nos ater à simples, e ainda poderosa, interface de linha de comando.
Iniciando com um serviço de hospedagem
Se você não está utilizando o GitHub, pode ir direto para a seção "Modo standalone".
Primeiro: git é um sistema de controle de versão *decentralizado*, no qual
você não precisa de um servidor. Você pode sincronizar repositórios de
diferentes máquinas, como seu desktop
, seu laptop
e computadores de seus
amigos. Mas ele permite que você tenha um *repositório de referência* a
partir do qual todos podem adquirir o código-fonte. Você pode começar usando
um dos vários serviços de hospedagem git. Vamos começar com o mais comum:
GitHub. O GitHub pede que você registre uma chave SSH para que você possa
colocar código no seu repositório remoto. Neste artigo, não vamos entrar em
detalhes em como fazer isso. Você pode ver o link abaixo para maiores informações.
Crie uma conta no GitHub, configure suas chaves SSH e crie um repositório de
teste. GitHub irá te fornecer um link para que você faça o *clone* de seu
repositório. Então, simplesmente o clone com git clone
!
$ git clone git@github.com:username/project.git
Estou supondo que o nome de usuário é username
e o nome do repositório que
você escolheu é
project
.
O comando acima irá criar um diretório chamado project
na sua máquina. É
um diretório vazio, porque seu repositório no GitHub também está vazio por
enquanto. Dentro dele, existe um diretório solitário com o nome .git
que
tem metadados de seu repositório e no qual você não deve fazer alterações (a
menos que você saiba o que está fazendo).
Modo standalone
Se você não quer utilizar o GitHub, pode configurar seu próprio repositório.
Primeiro, crie o diretório de seu projeto, então utilize o git init
:
$ mkdir project
$ cd project
$ git init
É importante notar que, diferentemente do CVS or do Subversion, o repositório
não é armazenado em outro lugar, mas dentro do diretório da cópia de trabalho.
Depois que você modificar seus arquivos no diretório project
, faça um
commit
(veja "Brincando com o git") e as mudanças serão armazenadas no
diretório .git
.
Brincando com o git
De acordo com o gittutorial, depois de instalar o git, é uma ideia dizer quem
você é, com git config
:
$ git config --global user.name "Your Name Comes Here"
$ git config --global user.email your@email.comes.here.com
Será criado um arquivo no seu home
chamado .gitconfig
com esses e outras
configurações que você mudou com
git config
. É importante que você se
identifique, como acima, assim os
commits
serão atribuídos corretamente aos
desenvolvedores.
Agora, dentro do diretório project
, crie dois arquivos, fileA.txt
e
fileB.txt
. Neles, coloque qualquer conteúdo, pois isso não importa por
enquanto. A estrutura será como essa:
* project/
* fileA.txt
* fileB.txt
Agora, adicione arquivos ao seu repositório com git add
:
$ git add *
Eles estão *marcados para adição*, mas não foram salvas no repositório ainda.
Na verdade, git add
adiciona arquivo à *stage area* ou *index*. Essa
é uma funcionalidade muito poderosa do git, mas está além do escopo desse
texto. Para saber mais, leia sobre a "stage area":
Em português, não há uma palavra adequada para traduzir commit
. Neste
tutorial vamos utilizar o verbo *salvar* quando nos referirmos ao verbo
to commit
em inglês, ou ainda vamos dizer a expressão *fazer commit*.
Para salvar os arquivos no repositório, use git commit
:
$ git commit
Seu editor de texto favorito surgirá, no qual é uma boa ideia escrever uma
mensagem, explicando brevemente o que você mudou neste commit
. Quando se
trata do primeiro commit
de um repositório, costumo colocar a mensagem
Initial commit
.
Agora, faça uma mudança nos seus arquivos e use git diff
_ para ver as
diferenças::
$ git diff
Voilà! Esse comando mostra as diferenças entre a última versão salva e os arquivos na cópia de trabalho. Para salvar as mudanças, execute::
$ git commit -a
Não esqueça da mensagem de commit
com um resumo das mudanças que você fez.
A flag -a
é necessária para não ter que digitar
git add
para adicionar
as mudanças. Git não salva as mudanças que você fez nos arquivos com
git
commit
antes de marcá-las para adição (mesmo que as mudanças tenham sido
feitas nos arquivos já adicionados ao repositório anteriormente).
Você pode passar a mensagem de commit
para o comando git commit
diretamente, sem abrir o seu editor. Utilize a flag
-m
::
$ git commit -a -m 'Commit message comes here'
Para novos arquivos git commit -a
não vai funcionar. Você terá que
adicioná-los manualmente com
git add
.
Finalmente, o comando git log
irá mostrar todo o histórico de mudanças. Se
você não estiver convencido da importância de preencher uma mensagem de commit,
você deve se convencer agora. O comando git log
mostra as alterações feitas,
junto com o nome do autor, data, etc.
Vejam como fica a saída dos comandos executados acima, em um repositório de teste que fiz:
$ cd /tmp/
$ mkdir project
$ cd project/
$ git init
Initialized empty Git repository in /tmp/project/.git/
$ touch fileA.txt fileB.txt
$ git add *
$ git commit -a -m 'Initial commit'
[master (root-commit) 112f5c1] Initial commit
2 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 fileA.txt
create mode 100644 fileB.txt
$ echo 'A test line' >> fileA.txt
$ git diff
diff --git a/fileA.txt b/fileA.txt
index e69de29..9efab9b 100644
--- a/fileA.txt
+++ b/fileA.txt
@@ -0,0 +1 @@
+A test line
$ git commit -a -m 'Adding a test line'
[master cc28b9f] Adding a test line
1 file changed, 1 insertion(+)
$ git log
commit cc28b9f73a5d487c9d3166b0a3585926ab7d8961
Author: My Name <my@email.com>
Date: Thu Jan 5 15:39:43 2017 -0200
Adding a test line
commit 112f5c1ec517bb25d1a054c063652c796d918a77
Author: My Name <my@email.com>
Date: Thu Jan 5 15:39:24 2017 -0200
Initial commit
Repositório e cópia de trabalho
Em um sistema de controle de revisão tradicional e centralizado como o CVS e
o Subversion, o repositório é sempre separado da cópia de trabalho local, onde
desenvolvedores trabalham. O repositório é normalmente localizado em uma
máquina remota e o usuário faz checkout
do repositório para a cópia de
trabalho e faz commit
da cópia de trabalho ao repositório.
No git, o repositório fica junto da cópia de trabalho. Veja todos os arquivos de seu project:
$ ls -A1
.git
fileA.txt
fileB.txt
Juntamente com fileA.txt
e fileB.txt
existe o diretório .git
.
Esse é seu repositório! Todos os logs, commits, endereços de repositórios
remotos, etc., está dentro desse diretório e se você excluí-lo irá perder
todos os dados.
O repositório localizado no GitHub é um tipo especial de repositório chamado *repositório bare*.
Repositórios bare
Repositórios bare
são como repositórios locais, sem a cópia de trabalho.
Eles são concebidos para armazenar apenas a informação do repositório, não
havendo a necessidade da cópia de trabalho. Supõe-se que os desenvolvedores
não tenham acesso direto a um repositório bare
e devem acessá-lo
remotamente, utilizando os comandos do git.
Para configurar um repositório bare
, passe a opção --bare
ao comando
git init
. Vamos fazer isso com um outro exemplo::
$ cd /tmp/
$ mkdir project2.git
$ cd project2.git
$ git init --bare
Initialized empty Git repository in /tmp/project2.git/
Você pode agora cloná-lo, como faria com qualquer outro repositório::
$ cd /tmp
$ git clone /tmp/project2.git /tmp/project2-working-copy
Cloning into '/tmp/project2-working-copy'...
warning: You appear to have cloned an empty repository.
done.
Git nos avisa que clonamos um repositório vazio. Mas isso não importa.
Podemos agora adicionar arquivos, fazer commit
e por aí em diante. Mas os
arquivos ficarão em nosso clone (/tmp/project2-working-copy
). Para enviar
as mudanças de volta ao repositório que nós acabamos acabamos de clonar,
utilizamos o comando
git push
_::
$ git push origin master
origin
é o repositório que nós clonamos (esse nome pode ser mudado com
git remote rename
). master
é o nome da branch que estamos
sincronizando com os outros repositórios. Veremos sobre branches na seção
"Branch, checkout e merge".
Na discussão de e-mails do git existe uma discussão interessante sobre repositórios bare que talvez você queira dar uma olhada:
Discussion: how to use two bare repositories?
Remote, push, fetch e pull
Agora que você sabe o que repositórios são (e repositórios bare), vamos
descobrir como sincronizá-los. Voltei ao diretório project
(que você fez
clone do GitHub) ou
project2
(o que você clonou de seu repositório
bare). Utilize o comando git remote
:
$ git remote -v
origin git@github.com:username/project.git (fetch)
origin git@github.com:username/project.git (push)
Quando clonamos um repositório, o nome do repositório que clonamos é, por
padrão, origin
(você pode mudá-lo com git remote mv
). Quando fazemos
commit ao repositório, mudanças **não** são sincronizadas de volta ao
repositório origin
por padrão. Para enviar as mudanças, precisamos
executar o seguinte comando::
$ git push origin master
origin
é o repositório ao qual desejamos enviar as mudanças e master
é
o nome do branch que queremos sincronizar. Verifique a seção ;Branch,
checkout e merge" para mais informações sobre branches.
Você pode adicionar diferentes remotes
. Use o comando git remote add
::
$ git remote add test mygithost:/foo/bar
$ git remote -v
origin git@github.com:username/project.git (fetch)
origin git@github.com:username/project.git (push)
test mygithost:/foo/bar (fetch)
test mygithost:/foo/bar (push)
Agora pode enviar também as mudanças ao repositório remoto test
. Sem
dúvidas, o ambiente do git precisa também estar instalado e funcionando na
máquina remota.
Agora, e se outro desenvolvedor também enviou suas mudanças para o repositório
remoto? Como as obtemos? Usando o comando git pull
_::
$ git pull origin master
É o oposto de git push
.
Existe também o comando git fetch
_::
$ git fetch origin
A diferença entre git fetch
e git pull
é que o fetch obtém as
diferenças do repositório remoto mas não as consolida na sua cópia de trabalho
local, enquanto git pull
já faz isso para você. Na prática, git pull
é o mesmo que git fetch
+ git merge
. Para mais informações sobre
merge, veja a seção "Branch, checkout e merge".
Branch, checkout e merge
Uma tradução adequada para branch
seria *galho*. Entretanto, o termo é
tão comumente utilizado no original em inglês, que vamos utilizar a
própria palavra branch
no decorrer do texto.
Branching é normalmente uma funcionalidade avançada dos sistemas de revisão
de controle mas no git é apresentado desde o início ao desenvolvedor, pois
você trabalha com branches
todo tempo, mesmo que não perceba ou que não use o
comando git branch
.
Todo repositório tem pelo menos um branch, normalmente chamado master
(você pode mudar seu nome com git branch -m
). Vamos experimentar em nosso
diretório project
:
$ git branch
* master
O asterisco no começo da linha mostra o branch atual. Para criar um novo
branch, apenas passe um parâmetro para git branch
_::
$ git branch mybranch
E mostre os branches novamente:
$ git branch
* master
mybranch
mybranch
já está criado (é uma cópia exata de master
) but não está
ainda obtido na sua cópia de trabalho. Para mudar para esse branch, use
git checkout
::
$ git checkout mybranch
Switched to branch 'mybranch'
E mostre os branches novamente:
$ git branch
master
* mybranch
Agora, toda mudança salva com git commit
irá no branch mybranch
, não
master
.
Nota: O comando git checkout
tem muitos usos diferentes, não apenas obter um
branch
diferente. Ele pode ser usado, por exemplo, para obter uma
revisão antiga de um determinado arquivo.
Finalmente, e se você quiser trazer as mudanças para o branch master
?
Primeiro, precisamos mudar (checkout
) para o branch que irá receber as
modificações:
$ git checkout master
Switched to branch 'master'
E executamos o comando git merge
:
$ git merge mybranch
Updating cc28b9f..af0381b
Fast-forward
fileA.txt | 1 +
1 file changed, 1 insertion(+)
Nota: Você percebeu a linha Fast-forward
? É o algoritmo utilizado por
git
merge
. Git usa diferentes algoritmos de
merge
, dependendo do caso.
git merge
é um comando muito poderoso com muitas opções. Vale a pena
ler seu manual__.
Outras páginas
Esse tutorial é apenas um ponto de partida para uma ferramenta tão poderosa. É altamente recomendável que você leia a documentação oficial do Git que tem muitas informações e também os manuais dos comandos que você está utilizando.
Além disso, existe um livro chamado Pro Git que percorre desde aspectos básicos até os mais avançados. É altamente recomendado!
Finalmente, quando tiver uma dúvida, procure pelas soluções na web. Muitas pessoas usam git e é provável que alguém já tenha postado algo em um fórum, fazendo as mesmas perguntas que você.
Boa sorte!
Dicas aleatórias
Fazendo mirror de repositórios bare
Como o git é um sistema decentralizado, você pode clonar repositórios bare e
rastreá-los (tracking). É útil, digamos, se você tiver um repositório bare em
algum lugar (exemplo: no GitHub), mas, por algum motivo, você ainda precisar de
um repositório bare localmente. Suponha que a.git
é o seu repositório primário
e b.git
é o repositório que você quer "copiar", e ambos estão localizados em
/tmp
(esse caminho pode ser remoto também), siga esses passos:
$ cd /tmp/
$ git clone --bare /tmp/a.git b.git
$ cd b.git
$ git remote add a.git /tmp/a.git # --mirror flag can appear here
Então, sempre que você quiser sincronizar b.git
, obtendo as modificações feitas em a.git
, faça:
$ cd /tmp/b.git
$ git fetch a.git
Discussion: how to use two bare repositories?
Branches e update de refs em um repositório bare
(2012-10-18)
Frequentemente utilizo repositórios bare não como "reposotório fim", mas como "repositório meio". Por exemplo, no meu servidor tenho alguns repositórios bare de projetos de software livre. Por que manter um repositório bare que rastreia outro repositório? Eles são organizados da seguinte forma:
Então, nesse diagrama, "upstream" é um repositório hospedado em provedores como o SourceForge ou o GitHub. "origin" é um repositório bare no meu servidor pessoal que clona o "upstream" e obtém novas mudanças a partir dele. É onde eu faço "push" das minhas branches privadas. "work" é a minha cópia local de trabalho.
O problema aparece quando obtenho mudanças do "upstream", usando git fetch
:
$ git fetch upstream
Trará "objetos", mas não "refs". Refs (dentro do diretório refs
no diretório de controle do git) são apenas arquivos texto apontando para commits na árvore. Por exemplo, se você tem uma tag chamada "bla", haverá uma ref em refs/tags/bla
e, se você ver seu conteúdo com cat
, verá uma hash representando um commit. Um exemplo de minha ref refs/heads/master
atual:
$ cat refs/heads/master
a82cd88b10906fae8d5a0f10020eb6d7fb9274a7
Então, de volta para o nosso exemplo, em um repositório bare, não posso fazer "rebase" ou mesclar ("merge") branches, como faria com um repositório com cópia de trabalho. E quando uso o git-fetch
seme specificar outros argumentos, ele não atualiza as refs. Então, tenho que usar:
$ git fetch upstream master:master
Então o arquivo refs/heads/master
será atualizado para o ponto em que o "head"
de "upstream" aponta. Às vezes pode não ser fácil saber o nome da branch que
você está obtendo as modificações (em algumas situações como um shell script,
por exemplo), então pode ser uma boa ideia usar o git-update-ref
...
$ git update-ref refs/heads/master refs/remotes/upstream/master
... irá atualizar refs/heads/master
, para que aponte para o mesmo objeto que refs/remotes/upstream/master
aponta.
Trabalhando com bundles
Às vezes você não tem uma conexão de internet para fazer push e pull nos
repositórios. Mover seus dados para outro tipo de mídia (email, um HD, etc.), use git bundle
. Ele cria um "bundle" (um arquivo) com o conteúdo de um repositório. Esse arquivo pode ser transferido de forma segura.
Primeiro, entre no diretório de seu repositório
$ cd myrepo
Então, cria o bundle com todas as branches, tags, (todas as refs), etc.:
$ git bundle create myrepo.bundle --all
Irá criar um bundle com todas as branches. Depois, para clonar para um repositório com uma cópia de trabalho, faça:
$ git clone myrepo.bundle -b master myrepo
Isso irá criar o diretório myrepo
com a branch master. Você pode então obter
as outras branches:
git fetch ../myrepo.bundle mybranch:mybranch
Excluindo uma branch remota
(2014-05-30)
Se sua branch remota é chamada "foo", faça:
$ git push origin :foo
Veja a seção "remote: *** Deleting a branch is not allowed in this repository" se você ver um erro como esse.
Resolução de problemas
Essa seção tenta explicar algumas dificuldades que tive tentando configurar o Git, e as soluções que encontrei.
Erro "*** Project description file hasn't been set" ao usar git pull origin
Quando tentei enviar modificações (com git push
) para um novo repositório:
obtive a seguinte mensagem de erro:
*** Project description file hasn't been set
error: hooks/update exited with error code 1
error: hook declined to update refs/heads/master
Tive que mudar o arquivo description
do repositório remoto, o que significa
que tive que prover uma pequena descrição para ele. Lembre-se: é o arquivo description
do repositório remoto (não local).
"Project description file" error in git?
Push do Git dando erro de Project description
"fatal: git upload-pack: aborting due to possible repository corruption on the"
Initialized empty Git repository in /tmp/repo/.git/
error: git upload-pack: git-pack-objects died with error.
remote: aborting due to possible repository corruption on the remote side.
fatal: early EOF
fatal: git upload-pack: aborting due to possible repository corruption on the remote side.
fatal: index-pack failed
Antes de chamar git fsck
desesperadamente, tenha certeza de que o shell do usuário remoto pode encontrar o git no $PATH
. Meu problema é que eu tinha instalado o git pelo pkgsrc, que por padrão instala tudo em /usr/pkg
.
Isso foi facilmente resolvido fazendo:
# cd /usr/local/bin
# ln -s /usr/pkg/bin/git
# ln -s /usr/pkg/bin/git-receive-pack
# ln -s /usr/pkg/bin/git-upload-archive
# ln -s /usr/pkg/bin/git-upload-pack
git-clone: path or ssh problem with git-upload-pack in 1.6.0?
erro: "patch failed .. Applying: .. trailing whitespace when git svn rebase"
Talvez você obtenha esse erro:
Applying: <Commit messasge>
.../path-to-repository/.git/rebase-apply/patch:21: trailing whitespace.
Quando git svn rebase
retorna esse erro, significa que alguém fez commite no
repositório subversion que "quebra" as restrições impostas pelo git sobre estilo
de código. Para permitir que esse código seja "commitado" no seu repositório
local, execute esse comando:
git config core.whitespace -trailing-space,-space-before-tab
erro: "patch failed .. patch does not apply .. Patch already applied"
Depois de um git svn rebase
, se você ver um erro como:
Applying: <Commit message>
error: patch failed: <file:line>
error: <file>: patch does not apply
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
No changes -- Patch already applied.
Por alguma razão seu repositório local e seu repositório subversion não estão sincronizados e git tenta obter as mudanças do SVN que ja estão sincronizadas. Tente:
git rebase --skip
Pode ser uma boa ideia executar git diff
para verificar se o merge funcionou.
"warning: remote HEAD refers to nonexistent ref, unable to checkout."
Esse erro é causado quando feito clone de um bundle (verifique git-bundle
para detalhes). O clone foi feito, mas git não obteve uma cópia de trabalho.
Um git checkout
com os parâmetros corretos é suficiente. Então:
$ git checkout -b master origin/master
remote: *** Deleting a branch is not allowed in this repository
Se você receber um erro como esse quando tentando excluir uma branch remota,
possivelmente você tem um "hook" habilitado no repositório remoto que te impede
de excluir a branch. Possivelmente está em seurepositorio.git/hooks/update
.
Faça login na máquina remota e desabilite o "hook":
$ git --git-dir /path/to/yourrepo.git config --bool hooks.allowdeletebranch true
Cannot delete remote branch in git
fatal: unable to access '...': SSL certificate
Isso normalmente acontece para certificados auto-assinados ao tentar fazer clone de um repositório. Para fazer o git ignorar a verificação de certificados, configure a variável GIT_SSL_NO_VERIFY
:
$ GIT_SSL_NO_VERIFY=true git ...