Czy w git istnieje prosty sposób na wprowadzenie niezwiązanej gałęzi do repozytorium?

328

Pomagając dziś znajomemu z problemem git, musiałem wprowadzić gałąź, która musiała być całkowicie oddzielona od mastergałęzi. Zawartość tej gałęzi naprawdę miała inne pochodzenie niż to, co zostało opracowane na mastergałęzi, ale później zostaną połączone z mastergałęzią.

Przypomniałem sobie, jak czytałem Git Johna Wiegleya od podstaw, w jaki sposób gałęzie są w zasadzie etykietą zatwierdzenia zgodnego z pewną konwencją oraz tego, jak zatwierdzenie jest powiązane z drzewem plików i, opcjonalnie, zatwierdzeniem rodzica. Poszliśmy stworzyć bez-rodzicielskie zatwierdzenie do istniejącego repozytorium przy użyciu instalacji hydraulicznej git:

Więc pozbyliśmy się wszystkich plików w indeksie ...

$ git rm -rf .

... wyodrębniłem katalogi i pliki z tarballa, dodałem je do indeksu ...

$ git add .

... i utworzył obiekt drzewa ...

$ git write-tree

( git-write-treepowiedział nam sha1sum utworzonego obiektu drzewa).

Następnie popełniliśmy drzewo, nie określając zobowiązań rodziców ...

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

( git-commit-treepowiedział nam sha1sum utworzonego obiektu zatwierdzenia).

... i utworzył nowy oddział, który wskazuje na nasze nowo utworzone zatwierdzenie.

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

W końcu wróciliśmy do masteroddziału, aby kontynuować tam pracę.

$ git checkout -f master

Wydaje się, że zadziałało to zgodnie z planem. Ale najwyraźniej nie jest to procedura, którą poleciłbym komuś, kto dopiero zaczyna używać git, delikatnie mówiąc. Czy istnieje łatwiejszy sposób na utworzenie nowej gałęzi, która jest całkowicie niezwiązana ze wszystkim, co do tej pory wydarzyło się w repozytorium?

Hillu
źródło

Odpowiedzi:

510

Jest nowa funkcja (od wersji 1.7.2), która sprawia, że ​​to zadanie jest nieco bardziej na wysokim poziomie niż w przypadku innych odpowiedzi.

git checkoutteraz obsługuje tę --orphanopcję. Od strony man :

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

Utwórz nową gałąź osieroconą , o nazwie <nowa_granica>, uruchomioną z <start_point> i przejdź na nią. Pierwsze zatwierdzenie dokonane w tej nowej gałęzi nie będzie miało rodziców i będzie źródłem nowej historii całkowicie odłączonej od wszystkich innych gałęzi i zobowiązań.

Nie robi to dokładnie tego , czego chciał pytający, ponieważ zapełnia indeks i drzewo robocze <start_point>(ponieważ jest to przecież polecenie kasowania). Jedynym innym niezbędnym działaniem jest usunięcie niechcianych elementów z działającego drzewa i indeksu. Niestety git reset --hardnie działa, ale git rm -rf .można go zamiast tego użyć (uważam, że jest to równoważne z rm .git/index; git clean -fdxinnymi odpowiedziami).


W podsumowaniu:

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

Zostawiłem <start_point>nieokreślony, ponieważ domyślnie jest ustawiony na HEAD i tak naprawdę nas to nie obchodzi. Ta sekwencja robi w zasadzie to samo, co sekwencja poleceń w odpowiedzi Artema , bez uciekania się do przerażających poleceń hydraulicznych.

tcovo
źródło
Czy wiesz, czy możliwe jest utworzenie gałęzi osieroconej, która będzie widoczna podczas pobierania dowolnej gałęzi z drugiego „drzewa”?
JJD
1
@JJD: Myślę, że to, czego chcesz, to git merge. ( git checkout master && git merge --no-commit "orphan-branch" ) Niektóre podobne sztuczki będą działać przy użyciu git-reset lub zabawy z indeksem. Ale to zależy od pożądanego przepływu pracy.
phord
@Matthew Oto dziennik zmian dla git 1.7.2 .
JJD
@phord Mniej więcej myślałem, że sierota zawiera takie rzeczy, jak testy jednostkowe lub dokumentacja oddzielone od kodu źródłowego projektu.
JJD
2
@JJD: Skrypt, który podałem, powinien dać ci to, czego chcesz, chyba że cię źle zrozumiałem. Kiedy powiedziałeś „pozostaje widoczny”, czy masz na myśli, że pliki pozostaną w katalogu roboczym, mimo że sprawdziłeś inną gałąź? Korzystanie --no-commitz git mergespowoduje osiągnięcie tego. Być może konieczne będzie kontynuowanie git reset origin/masteroperacji, aby następne zatwierdzenie poszło tam, gdzie chcesz, ale wtedy pliki z Twojej gałęzi sierocej pojawią się jako „pliki bez śledzenia”, chyba że umieścisz je również w pliku .gitignore.
phord
32

Z Git Community Book :

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 Tichomirow
źródło
1
Następna odpowiedź jest lepsza dla nowoczesnych wersji git.
kikito,
14
@kikito: Re: „Następna odpowiedź jest lepsza” ... Kolejność tutaj na SO nie jest stabilna. Czy możesz dodać link wskazujący, co uważasz za lepszą odpowiedź.
David J.
2
Miałem na myśli stackoverflow.com/a/4288660/312586 . Masz rację, nie powinienem był mówić „dalej”. Kiedy skomentowałem, miał mniej punktów niż ta odpowiedź.
kikito
1
rm .git/indexjest brzydka :-)
Ciro Santilli 28 冠状 病 六四 事件 法轮功
Ten działa lepiej w przypadku „Chcę zatwierdzić oddział, tworząc go, jeśli wcześniej nie istniał”
max630,
22

Chociaż rozwiązanie z git symbolic-refindeksem i usuwaniem indeksu działa, może być koncepcyjnie czystsze tworzenie nowego repozytorium

$ 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

następnie pobierz z niego

$ 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

Teraz możesz usunąć / path / to / nonrelated

Jakub Narębski
źródło
Moim zdaniem czysta koncepcja wymagałaby opcji git branchlub git checkout. Cieszę się, że git umożliwia takie rzeczy, ale dlaczego nie powinno być łatwiejsze?
hillu
3
Ponieważ tak jest i powinno być rzadką rzeczą. Jeśli masz gałąź niepowiązanych rzeczy, zwykle należy ona do niepowiązanego repozytorium, zamiast umieszczać go w istniejącym (chociaż są wyjątki).
Jakub Narębski
1
+1. Dla innych początkujących gitów, możesz najwyraźniej następnie wymienić gałęzie poprzez git branchi przełączać się między nimi git checkout BRANCH_NAME.
akavel
Dzięki za to, nie pomyślałbym o tym sam. Jest to szczególnie pomocne, ponieważ zachowuje historię (jeśli istnieje) dla drugiego repozytorium.
BM5k
13

Github ma funkcję o nazwie Strony projektu, w której możesz utworzyć konkretną nazwaną gałąź w swoim projekcie, aby zapewnić pliki, które będą obsługiwane przez Github. Ich instrukcje są następujące:

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

Stamtąd masz puste repozytorium, do którego możesz następnie dodać nową zawartość.

Greg Hewgill
źródło
10

Aktualnie wybrana odpowiedź jest poprawna, dodam tylko, że przypadkowo ...

Dokładnie w ten sposób github.com pozwala użytkownikom tworzyć strony Github dla swoich repozytoriów przez osieroconą gałąź o nazwie gh-pages. Ładne kroki podano i wyjaśniono tutaj:

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

Zasadniczo polecenia git do skonfigurowania tego są następujące:

  1. git checkout --orphan gh-pages (utwórz gałąź repozytorium o nazwie gh-pages na swoim repozytorium)
  2. git rm -rf . (usuwa wszystkie pliki z drzewa roboczego gałęzi)
  3. rm '.gitignore' (nawet gitignore)
  4. Teraz dodaj treść strony (dodaj index.html itp.) Oraz zatwierdzaj i wypychaj.
  5. Zysk.

Uwaga: możesz także wyznaczyć folder / docs w repozytorium, który będzie źródłem „witryny projektu”, której Github używa do budowy witryny.

Mam nadzieję że to pomoże!

nieznany protokół
źródło
3

Czasami chcę po prostu natychmiast utworzyć pustą gałąź w projekcie, a następnie rozpocząć pracę, po prostu wykreślę następujące polecenie:

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"
Śpiewać
źródło
1

Jeśli twoja istniejąca treść została już zatwierdzona, możesz (Git 2.18 Q2 2018) wyodrębnić ją do własnej nowej gałęzi sierocych, ponieważ implementacja „git rebase -i --root ” została zaktualizowana w celu większego wykorzystania maszyn sekwencera.

Ten sekwencer umożliwia teraz przeszczepienie całej topologii grafu zatwierdzeń w innym miejscu .

Zobacz zatwierdzenie 8fa6eea , zatwierdzenie 9c85a1c , zatwierdzenie ebddf39 , zatwierdzenie 21d0764 , zatwierdzenie d87d48b , zatwierdzenie ba97aea (03 maja 2018 r.) Autor: Johannes Schindelin ( dscho) .
(Połączone przez Junio ​​C Hamano - gitster- w commit c5aa4bc , 30 maja 2018 r.)

sekwencer: pozwala na wprowadzenie nowych zatwierdzeń roota

W kontekście nowego --rebase-mergestrybu, który został zaprojektowany specjalnie, aby umożliwić swobodne zmienianie istniejącej topologii gałęzi, użytkownik może chcieć wyodrębnić zatwierdzenia do zupełnie nowej gałęzi, która zaczyna się od nowo utworzonego zatwierdzenia głównego .

Jest to teraz możliwe poprzez wstawienie polecenia reset [new root]przed pickzatwierdzeniem, które chce stać się głównym zatwierdzeniem. Przykład:

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

Nie koliduje to z innymi zastosowaniami resetpolecenia, ponieważ [new root]nie jest (częścią) poprawnej nazwy odwołania: zarówno nawias otwierający, jak i spacja są niedozwolone w nazwach odwołania.

VonC
źródło
0

Znalazłem ten skrypt na http://wingolog.org/archives/2008/10/14/merging-in-unrelated-git-branches i działa bardzo dobrze!

#!/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
Benoit
źródło
1
Uważam, że ten sam efekt można osiągnąć za pomocą git fetch $REMOTE $REMOTE_BRANCH:$LOCAL_BRANCH. Czy coś brakuje?
hillu