En git, ¿hay una manera simple de introducir una rama no relacionada en un repositorio?

329

Mientras ayudaba a un amigo con un problema de git hoy, tuve que introducir una rama que necesitaba estar totalmente separada de la masterrama. El contenido de esta rama realmente tenía un origen diferente del que se había desarrollado en la masterrama, pero iban a fusionarse en la masterrama más adelante.

Recordé al leer Git de John Wiegley de abajo hacia arriba cómo las ramas son esencialmente una etiqueta para una confirmación que sigue una determinada convención y cómo una confirmación está vinculada a un árbol de archivos y, opcionalmente, a las confirmaciones principales. Fuimos a crear una confirmación sin padres para el repositorio existente usando la fontanería de git:

Así que nos deshicimos de todos los archivos en el índice ...

$ git rm -rf .

... extrajo directorios y archivos de un tarball, los agregó al índice ...

$ git add .

... y creó un objeto de árbol ...

$ git write-tree

( git-write-treenos contó el sha1sum del objeto de árbol creado).

Luego, confirmamos el árbol, sin especificar las confirmaciones principales ...

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

( git-commit-treenos dijo el sha1sum del objeto commit creado).

... y creó una nueva rama que apunta a nuestro commit recién creado.

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

Finalmente, volvimos a la mastersucursal para continuar trabajando allí.

$ git checkout -f master

Esto parece haber funcionado según lo planeado. Pero claramente este no es el tipo de procedimiento que recomendaría a alguien que recién comienza a usar git, por decirlo suavemente. ¿Existe una manera más fácil de crear una nueva rama que no esté relacionada con todo lo que ha sucedido en el repositorio hasta ahora?

hillu
fuente

Respuestas:

510

Hay una nueva característica (desde V1.7.2) que hace que esta tarea sea un poco más de alto nivel que lo que está en cualquiera de las otras respuestas.

git checkoutahora es compatible con la --orphanopción. Desde la página del manual :

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

Cree una nueva rama huérfana , llamada <new_branch>, iniciada desde <start_point> y cámbiela. El primer compromiso realizado en esta nueva rama no tendrá padres y será la raíz de una nueva historia totalmente desconectada de todas las demás ramas y compromisos.

Esto no hace exactamente lo que el autor de la pregunta quería, porque llena el índice y el árbol de trabajo <start_point>(ya que, después de todo, este es un comando de pago). La única otra acción necesaria es eliminar cualquier elemento no deseado del árbol de trabajo e índice. Desafortunadamente, git reset --hardno funciona, pero git rm -rf .se puede usar en su lugar (creo que esto es equivalente a lo que se rm .git/index; git clean -fdxda en otras respuestas).


En resumen:

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

Me fui sin <start_point>especificar porque el valor predeterminado es HEAD, y de todos modos no nos importa. Esta secuencia hace esencialmente lo mismo que la secuencia de comandos en la respuesta de Artem , solo que sin recurrir a comandos de plomería aterradores.

tcovo
fuente
¿Sabes si es posible crear una rama huérfana que permanezca visible cuando desproteges cualquier rama del otro "árbol"?
JJD
1
@JJD: Creo que lo que quieres es git merge. ( git checkout master && git merge --no-commit "orphan-branch" ) Algunos trucos similares funcionarán usando git-reset o jugando con el índice. Pero depende de su flujo de trabajo deseado.
Phord
@Matthew Aquí está el registro de cambios para git 1.7.2 .
JJD
@phord Más o menos pensé en el huérfano para contener cosas como pruebas unitarias o documentación separada del código fuente del proyecto.
JJD
2
@JJD: El guión que di debería darte lo que quieres, a menos que te haya entendido mal. Cuando dijo "permanece visible", ¿quiere decir que los archivos permanecerán en el directorio de trabajo aunque haya extraído una rama diferente? Usar --no-commiton git mergelogrará esto. Es posible que deba realizar un seguimiento git reset origin/masterpara que su próxima confirmación vaya donde lo desee, pero luego los archivos de su rama huérfana se mostrarán como "archivos no rastreados" a menos que también los incluya en su archivo .gitignore.
Phord
32

Del libro de la comunidad de 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
fuente
1
La siguiente respuesta es mejor para las versiones modernas de git.
kikito
14
@kikito: Re: "La siguiente respuesta es mejor" ... El pedido aquí en SO no es estable. ¿Podría agregar un enlace que apunte a lo que cree que es una mejor respuesta?
David J.
2
Me refería a stackoverflow.com/a/4288660/312586 . Tienes razón, no debería haber dicho "siguiente". Tenía menos puntaje que esta respuesta cuando comenté.
kikito
1
rm .git/indexes feo :-)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Este funciona mejor para el caso "Quiero comprometerme con la rama, creándola si no existía antes"
max630
22

Aunque la solución con git symbolic-refy eliminando el índice funciona, podría ser conceptualmente más limpio crear un nuevo repositorio

$ 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

luego ir a buscarlo

$ 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

Ahora puede eliminar / ruta / a / no relacionado

Jakub Narębski
fuente
En mi opinión, un concepto limpio implicaría una opción para git brancho git checkout. Me alegra que git haga posible este tipo de cosas, pero ¿por qué no debería ser más fácil?
hillu
3
Porque lo es, y debería ser algo raro. Si tiene una rama de cosas no relacionadas, generalmente pertenece a un repositorio no relacionado, en lugar de rellenarlo en uno existente (aunque hay excepciones).
Jakub Narębski
1
+1. Para otros novatos git, aparentemente puede enumerar las ramas a través git branchy cambiar entre ellas a través de git checkout BRANCH_NAME.
akavel
Gracias por esto, no lo habría pensado por mi cuenta. Esto es muy útil porque mantiene el historial (si hay alguno) para el otro repositorio.
BM5k
13

Github tiene una característica llamada Páginas del proyecto donde puede crear una rama con nombre en particular en su proyecto para proporcionar archivos que serán atendidos por Github. Sus instrucciones son las siguientes:

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

Desde allí, tiene un repositorio vacío al que puede agregar su nuevo contenido.

Greg Hewgill
fuente
10

La respuesta seleccionada actualmente es correcta, solo agregaría eso por coincidencia ...

Así es exactamente cómo github.com permite a los usuarios crear páginas de Github para sus repositorios, a través de una rama huérfana llamada gh-pages. Los bonitos pasos se dan y explican aquí:

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

Básicamente, los comandos de git para configurar esto son los siguientes:

  1. git checkout --orphan gh-pages (cree una rama sin padres llamada gh-pages en su repositorio)
  2. git rm -rf . (elimina cualquier archivo del árbol de trabajo de la rama)
  3. rm '.gitignore' (incluso el gitignore)
  4. Ahora agregue contenido del sitio web (agregue index.html, etc.) y confirme y presione.
  5. Lucro.

Tenga en cuenta que también puede designar una carpeta / docs en su repositorio para que sea la fuente del "Sitio del proyecto" que Github usa para construir el sitio web.

¡Espero que esto ayude!

protocolo desconocido
fuente
3

A veces solo quiero crear una rama vacía en el proyecto al instante y luego comenzar a trabajar, simplemente ejecutaré el siguiente 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"
Canta
fuente
1

Si su contenido existente ya estaba comprometido, ahora (Git 2.18 Q2 2018) puede extraerlo en su propia nueva rama huérfana, ya que la implementación de " git rebase -i --root" se ha actualizado para usar más la maquinaria del secuenciador.

Ese secuenciador es el que ahora permite trasplantar la topología completa del gráfico de confirmación en otro lugar .

Ver commit 8fa6eea , commit 9c85a1c , commit ebddf39 , commit 21d0764 , commit d87d48b , commit ba97aea (03 de mayo de 2018) por Johannes Schindelin ( dscho) .
(Fusionada por Junio ​​C Hamano - gitster- en commit c5aa4bc , 30 de mayo de 2018)

secuenciador: permitir la introducción de nuevas confirmaciones de raíz

En el contexto del nuevo --rebase-mergesmodo, que fue diseñado específicamente para permitir cambiar liberalmente la topología de rama existente, un usuario puede querer extraer confirmaciones en una rama completamente nueva que comience con una confirmación de raíz recién creada .

Esto ahora es posible insertando el comando reset [new root]antes de pickingresar el commit que quiere convertirse en root commit. Ejemplo:

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

Esto no entra en conflicto con otros usos del resetcomando porque [new root]no es (parte de) un nombre de referencia válido: tanto el corchete de apertura como el espacio son ilegales en los nombres de referencia.

VonC
fuente
0

¡Encontré este script en http://wingolog.org/archives/2008/10/14/merging-in-unrelated-git-branches y funciona muy bien!

#!/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
fuente
1
Creo que se puede lograr el mismo efecto usando git fetch $REMOTE $REMOTE_BRANCH:$LOCAL_BRANCH. ¿Me estoy perdiendo de algo?
hillu