No git, existe uma maneira simples de introduzir uma ramificação não relacionada a um repositório?

328

Enquanto ajudava um amigo com um problema de git hoje, tive que introduzir um ramo que precisava ser totalmente separado do masterramo. O conteúdo desse ramo realmente tinha uma origem diferente do que havia sido desenvolvido no masterramo, mas eles seriam mesclados nomaster ramo posteriormente.

Lembrei-me de ler o Git de John Wiegley de baixo para cima como os ramos são essencialmente um rótulo para um commit que segue uma certa convenção e como um commit está vinculado a uma árvore de arquivos e, opcionalmente, aos commit dos pais. Criamos um commit sem pai no repositório existente usando o encanamento do git:

Então nos livramos de todos os arquivos no índice ...

$ git rm -rf .

... extraiu diretórios e arquivos de um tarball, adicionou-os ao índice ...

$ git add .

... e criou um objeto em árvore ...

$ git write-tree

(git-write-tree nos disse o sha1sum do objeto de árvore criado.)

Em seguida, confirmamos a árvore, sem especificar os pais confirmados ...

$ echo "Imported project foo" | git commit-tree $TREE

( git-commit-treenos disse o sha1sum do objeto de confirmação criado.)

... e criou uma nova ramificação que aponta para nosso recém-criado commit.

$ git update-ref refs/heads/other-branch $COMMIT

Finalmente, voltamos à masterfilial para continuar trabalhando lá.

$ git checkout -f master

Isso parece ter funcionado como planejado. Mas esse claramente não é o tipo de procedimento que eu recomendaria para alguém que está começando a usar o git, para dizer o mínimo. Existe uma maneira mais fácil de criar uma nova ramificação que não tenha relação com tudo o que aconteceu no repositório até agora?

hillu
fonte

Respostas:

510

Há um novo recurso (desde a V1.7.2) que torna essa tarefa um pouco mais de alto nível do que as outras respostas.

git checkoutagora suporta a --orphanopção Na página do manual :

git checkout [-q] [-f] [-m] --orphan <new_branch> [<start_point>]

Crie um novo ramo órfão , chamado <new_branch>, iniciado a partir de <start_point> e mude para ele. O primeiro commit feito neste novo branch não terá pais e será a raiz de uma nova história totalmente desconectada de todos os outros branches e commit.

Isso não faz exatamente o que o solicitante queria, porque preenche o índice e a árvore de trabalho de <start_point>(já que este é, afinal, um comando de checkout). A única outra ação necessária é remover todos os itens indesejados da árvore e do índice de trabalho. Infelizmente, git reset --hardnão funciona, mas git rm -rf .pode ser usado (acredito que isso é equivalente ao rm .git/index; git clean -fdxdado em outras respostas).


Em suma:

git checkout --orphan newbranch
git rm -rf .
<do work>
git add your files
git commit -m 'Initial commit'

Deixei <start_point>não especificado porque o padrão é HEAD, e realmente não nos importamos. Essa sequência faz essencialmente a mesma coisa que a sequência de comandos na resposta de Artem , apenas sem recorrer a comandos de encanamento assustadores.

tcovo
fonte
Você sabe se é possível criar um ramo órfão que fique visível quando você faz o checkout de qualquer ramo da outra "árvore"?
JJD 07/07
1
@JJD: Eu acho que o que você quer é git merge. ( git checkout master && git merge --no-commit "orphan-branch" ) Alguns truques semelhantes funcionarão usando git-reset ou jogando com o índice. Mas isso depende do seu fluxo de trabalho desejado.
phord
@ Matthew Aqui está o changelog para o git 1.7.2 .
JJD 30/09
@phord Pensei mais ou menos no órfão que contém itens como testes de unidade ou documentação separados do código-fonte do projeto.
JJD
2
@JJD: O roteiro que eu dei deve dar o que você quer, a menos que eu não o entenda. Quando você disse "permanece visível", você quer dizer que os arquivos permanecerão no diretório ativo, mesmo que você tenha feito check-out de uma ramificação diferente? Usando --no-commiton git mergeconseguirá isso. Pode ser necessário fazer um acompanhamento git reset origin/masterpara que seu próximo commit vá para onde você quiser, mas os arquivos do seu ramo órfão aparecerão como "arquivos não rastreados", a menos que você também os inclua no arquivo .gitignore.
phord
32

Do livro da comunidade Git :

git symbolic-ref HEAD refs/heads/newbranch 
rm .git/index 
git clean -fdx 
<do work> 
git add your files 
git commit -m 'Initial commit'
Artem Tikhomirov
fonte
1
A próxima resposta é melhor para versões modernas do git.
Kikito
14
@kikito: Re: "A próxima resposta é melhor" ... A encomenda aqui no SO não é estável. Você poderia adicionar um link apontando para o que você acha que é uma resposta melhor.
David J.
2
Eu estava me referindo a stackoverflow.com/a/4288660/312586 . Você está certo, eu não deveria ter dito "próximo". Tinha menos pontuação que essa resposta quando comentei.
Kikito
1
rm .git/indexé feio :-)
Ciro Santilli escreveu
Este funciona melhor para o caso "I querem se comprometer com o ramo, criando-lo se ele não existia antes"
max630
22

Embora a solução com git symbolic-refe removendo o índice funcione, pode ser conceitualmente mais limpo criar um novo repositório

$ cd /path/to/unrelated
$ git init
[edit and add files]
$ git add .
$ git commit -m "Initial commit of unrelated"
[master (root-commit) 2a665f6] Initial commit of unrelated
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 foo

então busque nele

$ cd /path/to/repo
$ git fetch /path/to/unrelated master:unrelated-branch
warning: no common commits
remote: Counting objects: 3, done.
Unpacking objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
From /path/to/unrelated
 * [new branch]      master     -> unrelated-branch

Agora você pode excluir / caminho / para / não relacionado

Jakub Narębski
fonte
Na minha opinião, um conceito limpo envolveria uma opção para git branchou git checkout. Fico feliz que o git torne esse tipo de coisa possível, mas por que não deveria ser mais fácil?
hillu 6/09/09
3
Porque é e deve ser uma coisa rara. Se você possui uma ramificação não relacionada, ela geralmente pertence ao repositório não relacionado, em vez de preenchê-lo em um existente (embora haja exceções).
Jakub Narębski 6/09/09
1
+1. Para colegas novatos no git, aparentemente você pode listar os ramos via git branche alternar entre eles via git checkout BRANCH_NAME.
akavel
Obrigado por isso, eu não teria pensado sozinho. Isso é extremamente útil, pois mantém o histórico (se houver) do outro repositório.
BM5k
13

O Github possui um recurso chamado Project Pages, no qual você pode criar uma ramificação nomeada específica no seu projeto para fornecer arquivos que serão atendidos pelo Github. Suas instruções são as seguintes:

$ cd /path/to/fancypants
$ git symbolic-ref HEAD refs/heads/gh-pages
$ rm .git/index
$ git clean -fdx

A partir daí, você tem um repositório vazio ao qual pode adicionar seu novo conteúdo.

Greg Hewgill
fonte
10

A resposta atualmente selecionada está correta, gostaria de acrescentar que por coincidência ...

Na verdade, é exatamente assim que o github.com permite que os usuários criem páginas do Github para seus repositórios, por meio de um ramo órfão chamado gh-pages. Os passos bonitos são dados e explicados aqui:

https://help.github.com/articles/creating-project-pages-manually

Basicamente, os comandos git para configurar isso são os seguintes:

  1. git checkout --orphan gh-pages (crie um ramo sem pai chamado gh-pages no seu repositório)
  2. git rm -rf . (remove todos os arquivos da árvore de trabalho da ramificação)
  3. rm '.gitignore' (até o gitignore)
  4. Agora adicione conteúdo do site (adicione index.html, etc) e confirme e pressione.
  5. Lucro.

Observe que você também pode designar uma pasta / docs no repositório para ser a fonte do "Site do Projeto" que o Github usa para criar o site.

Espero que isto ajude!

protocolo desconhecido
fonte
3

Às vezes, eu só quero criar uma ramificação vazia no projeto instantaneamente e começar a trabalhar, apenas executarei o seguinte comando:

git checkout --orphan unrelated.branch.name
git rm --cached -r .
echo "init unrelated branch" > README.md
git add README.md
git commit -m "init unrelated branch"
Cantar
fonte
1

Se seu conteúdo existente já foi confirmado, você agora (Git 2.18 Q2 2018) pode extraí-lo em seu novo ramo órfão, desde a implementação de "git rebase -i --root " foi atualizada para usar mais o mecanismo do seqüenciador.

Esse sequenciador é o que agora permite transplantar toda a topologia do gráfico de confirmação em outro lugar .

Consulte commit 8fa6eea , commit 9c85a1c , commit ebddf39 , commit 21d0764 , commit d87d48b , commit ba97aea (03 de maio de 2018) por Johannes Schindelin ( dscho) .
(Mesclado por Junio ​​C Hamano - gitster- no commit c5aa4bc , 30 de maio de 2018)

seqüenciador: permite a introdução de novos commit root

No contexto do novo --rebase-mergesmodo, que foi projetado especificamente para permitir a liberalização da topologia de ramificação existente, um usuário pode extrair confirmações em uma ramificação completamente nova que começa com uma confirmação raiz recém-criada .

Agora isso é possível inserindo o comando reset [new root]antes pickda confirmação que deseja se tornar uma confirmação raiz. Exemplo:

reset [new root]
pick 012345 a commit that is about to become a root commit
pick 234567 this commit will have the previous one as parent

Isso não entra em conflito com outros usos do resetcomando, porque [new root]não é (parte de) um nome de referência válido: tanto o colchete de abertura quanto o espaço são ilegais nos nomes de referência.

VonC
fonte
0

Encontrei esse script em http://wingolog.org/archives/2008/10/14/merging-in-unrelated-git-branches e funciona muito bem!

#!/bin/bash

set -e

if test -z "$2" -o -n "$3"; then
    echo "usage: $0 REPO BRANCHNAME" >&2
    exit 1
fi

repo=$1
branch=$2

git fetch "$repo" "$branch"

head=$(git rev-parse HEAD)
fetched=$(git rev-parse FETCH_HEAD)
headref=$(git rev-parse --symbolic-full-name HEAD)

git checkout $fetched .

tree=$(git write-tree)

newhead=$(echo "merged in branch '$branch' from $repo" | git commit-tree $tree -p $head -p $fetched)
git update-ref $headref $newhead $head
git reset --hard $headref
Benoît
fonte
1
Eu acredito que o mesmo efeito pode ser alcançado usando git fetch $REMOTE $REMOTE_BRANCH:$LOCAL_BRANCH. Estou esquecendo de algo?
hillu 25/08/10