Создание репозитория GitHub только с подмножеством истории локального хранилища

Фон: я приближаюсь к открывать исходный код для исследования Я работаю уже более двух лет. Он начал свою жизнь как репозиторий SVN, но я переехал в Git около года назад, и я хотел бы поделиться кодом на GitHub. Однако на протяжении многих лет он накапливал много шума, и я бы предпочел, чтобы публичная версия начала свою жизнь в ее текущем состоянии. Тем не менее, я все равно хотел бы внести свой вклад в это и включить в него другие потенциальные вклады.

Вопрос: есть ли способ "разветкить" репозиторий Git, чтобы на fork (который живет на GitHub) не сохранилось никакой истории, но у моего локального репозитория все еще есть полная история, и я могу потянуть/нажать на GitHub?

У меня нет опыта в администрировании конца больших репозиториев, поэтому детали очень ценятся.

Ответ 1

Вы можете легко создать новую свежую историю в Git. Допустим, вы хотите, чтобы ваша ветка master была той, которую вы нажмете на GitHub, и ваша полная история будет сохранена в old-master. Вы можете просто переместить ветвь master на old-master, а затем начать новую новую ветку без истории, используя git checkout --orphan:

git branch -m master old-master
git checkout --orphan master
git commit -m "Import clean version of my code"

Теперь у вас есть новая ветвь master без истории, которую вы можете нажать на GitHub. Но, как вы говорите, вы хотели бы видеть всю старую историю в своем локальном репозитории; и, вероятно, ему хотелось бы, чтобы он не был отключен.

Вы можете сделать это, используя git replace. Замена ref - это способ указания альтернативного фиксации в любое время, когда Git смотрит на данный коммит. Таким образом, вы можете сказать Git, чтобы посмотреть на последнюю фиксацию своей старой ветки, а не на первую фиксацию вашей новой ветки, когда смотрите историю. Чтобы сделать это, вам нужно привести отключенную историю из старого репо.

git replace master old-master

Теперь у вас есть новая ветвь, в которой вы можете увидеть всю свою историю, но фактические объекты фиксации отключены от старой истории, и поэтому вы можете перетащить новые коммиты в GitHub без старых коммитов. Подключите ветку master к GitHub, и только новые коммиты перейдут в GitHub. Но посмотрите историю в gitk или git log, и вы увидите полную историю.

git push github master:master
gitk --all

Gotchas

Если вы когда-либо создавали новые ветки на старых коммитах, вам нужно быть осторожными, чтобы сохранить историю отдельной; в противном случае новые фиксации на этих ветвях будут действительно иметь старые фиксации в их истории, и поэтому вы будете тянуть всю историю, если вы подтолкните ее до GitHub. Если вы сохраните все свои новые коммиты на основе вашего нового master, все равно будет хорошо.

Если вы когда-нибудь запустили git push --tags github, это подтолкнет все ваши теги, в том числе старые, что приведет к тому, что все ваши старые истории будут вытащены вместе с ним. Вы можете справиться с этим, удалив все ваши старые теги (git tag -d $(git tag -l)) или никогда не используя git push --tags, но только когда-либо нажав теги вручную или используя два репозитория, как описано ниже.

Основная проблема, лежащая в основе обоих этих ошибок, заключается в том, что если вы когда-либо нажимаете какой-либо реф, который соединяется с какой-либо старой историей (кроме как с помощью замененных коммитов), вы будете подталкивать всю старую историю. Вероятно, лучшим способом избежать этого является использование двух репозиториев, в котором содержатся только новые коммиты, и один, который содержит как старую, так и новую историю, для проверки полной истории. Вы выполняете всю свою работу, свою фиксацию, выталкивание и вытаскивание из GitHub, в репо только с новыми коммитами; Таким образом, вы не можете случайно подтолкнуть свои старые обвинения. Затем вы переносите все свои новые коммиты в свое репо, которое имеет полную историю, когда вам нужно посмотреть на все. Вы можете либо вытащить из GitHub, либо другое местное репо, в зависимости от того, что удобнее. Это будет ваш архив, но чтобы избежать случайной публикации вашей старой истории, вы никогда не будете нажимать на GitHub. Здесь вы можете настроить его:

~$ mkdir newrepo
~$ cd newrepo
newrepo$ git init
newrepo$ git pull ~/oldrepo master
# now newrepo has just the new history; we can set up oldrepo to pull from it
newrepo$ cd ~/oldrepo
oldrepo$ git remote add newrepo ~/newrepo
oldrepo$ git remote update
oldrepo$ git branch --set-upstream master newrepo/master
# ... do work in newrepo, commit, push to GitHub, etc.
# Now if we want to look at the full history in oldrepo:
oldrepo$ git pull

Если вы находитесь на Git старше 1.7.2

У вас нет git checkout --orphan, поэтому вам придется делать это вручную, создав новый репозиторий из текущей версии существующего репозитория, а затем потянув свою старую отключенную историю. Вы можете сделать это, например:

oldrepo$ mkdir ~/newrepo
oldrepo$ cp $(git ls-files) ~/newrepo
oldrepo$ cd ~/newrepo
newrepo$ git init
newrepo$ git add .
newrepo$ git commit -m "Import clean version of my code"
newrepo$ git fetch ~/oldrepo master:old-master

Если вы находитесь на Git старше 1.6.5

git replace и заменить ref были добавлены в 1.6.5, поэтому вам придется использовать более старый, несколько менее гибкий механизм, известный как grafts, которые позволяют указывать альтернативных родителей для данного коммита. Вместо команды git replace запустите:

echo $(git rev-parse master) $(git rev-parse old-master) >> .git/info/grafts

Это заставит его выглядеть локально, как будто master commit имеет old-master commit в качестве своего родителя, поэтому вы увидите еще одну фиксацию, чем с git replace.

Ответ 2

Ответ Брайана выше кажется полным и знающим, но немного сложным.

Легким (ier) решением было бы сохранить два репозитория.

Частный репозиторий github, над которым вы работаете. Вы делаете все полные истории в этом репозитории.

Второй репозиторий - это публичный репозиторий github, который вы публикуете, только когда хотите опубликовать новую версию для публики. Вы публикуете его с помощью простого патча diff +, а затем commit + push.

Ответ 3

Очень простой и интересный способ сделать это, как показано ниже:

Предположим, что у вас в REPO-A фиксируется C1-C10, где C1 является начальным фиксатором, а C10 - последним HEAD. И вы хотите создать новый REPO-B таким образом, чтобы он фиксировал C4-C8 (подмножество).

ПРИМЕЧАНИЕ.. Используя этот метод, в этом случае будут изменены SHA (например, C4 'на C8), но изменения каждого фиксации фиксации останутся такими же, и ваш первый фиксат теперь начнется с все изменения ваших ранее совершаются до этого момента.

Что делать?


Рекурсивно скопировать все на вашем локальном компьютере

cp -R REPO-A REPO-B

Опционально удалите все пульты из REPO-B, так как, скорее всего, вы хотите использовать это как отдельный репозиторий.

cd REPO-B
git remote -v 
git remote remove REMOTE_NAME

Переместите указатель ветвления на более поздний конец вашего подмножества. Для субъекта C4-C8, который будет C8. Но, скорее всего, вам понадобятся подмножества до HEAD (например: форма C4-C10 или C6-C10), и в этом случае следующий шаг не требуется.

git checkout -b temp
git branch -f master C8
git checkout master
git branch -D temp

Введите SHC фиксации более раннего конца вашего подмножества в каталог .git/info/grafts. В этом случае это SHA commit C4.

git rev-parse --verify C4 >> .git/info/grafts

Проделайте фильтрацию ветвей git без каких-либо аргументов

git filter-branch

Или это не работает

git filter-branch --all

Теперь вы можете нажать это на отдельный/новый пульт, если вы хотите

git remote add origin NEWREMOTE
git push -u origin master

Как это работает?


Эта ссылка рассказывает вам, как она действительно работает - http://git.661346.n2.nabble.com/how-to-delete-the-entire-history-before-a-certain-commit-td5000540.html

Вы можете прочитать о трансплантатах в man-странице git -filter-branch (1), в gitrepository-layout (5) git описание макета репозитория и в gitglossary (7) a git глоссарий.

Короче говоря, каждая строка в .git/info/grafts состоит из sha1 id объекта, а затем список его эффективных (привитых) родителей. Таким образом, чтобы сократить историю, например. после совершения a3eb250f996bf5e, вам нужно поставить строка, содержащая только этот SHA-1 в файле .git/info/grafts, например:

$git rev-parse --verify a3eb250f996bf5e → .git/info/grafts