Сделать текущий коммит единственным (начальным) коммитом в Git-репозитории?

666

В настоящее время у меня есть локальный репозиторий Git, который я помещаю в репозиторий Github.

Локальный репозиторий имеет ~ 10 коммитов, а репозиторий Github является синхронизированным дубликатом этого.

Я хотел бы удалить ВСЕ историю версий из локального репозитория Git, чтобы текущее содержимое репозитория отображалось как единственная фиксация (и, следовательно, более старые версии файлов в репозитории не сохраняются).

Затем я хотел бы отправить эти изменения в Github.

Я исследовал Git ReBase, но это больше подходит для удаления определенных версий. Другим потенциальным решением является удаление локального репо и создание нового - хотя это, вероятно, создаст много работы!

ETA: Существуют определенные каталоги / файлы, которые не отслеживаются, - если возможно, я хотел бы сохранить отслеживание этих файлов.

kaese
источник
6
См. Также stackoverflow.com/questions/435646/… («Как мне объединить первые два коммита Git-репозитория?»)
Anonymoose

Ответы:

983

Вот подход грубой силы. Также удаляет конфигурацию хранилища.

Примечание : это НЕ работает, если в хранилище есть субмодули! Если вы используете субмодули, вы должны использовать, например, интерактивный ребаз

Шаг 1: удалить всю историю ( убедитесь, что у вас есть резервная копия, это не может быть восстановлено )

cat .git/config  # note <github-uri>
rm -rf .git

Шаг 2: реконструируйте репозиторий Git только с текущим контентом

git init
git add .
git commit -m "Initial commit"

Шаг 3: нажмите на GitHub.

git remote add origin <github-uri>
git push -u --force origin master
Fred Foo
источник
3
Спасибо larsmans - я решил использовать это как мое решение. Хотя инициализация репозитория Git теряет запись неотслеживаемых файлов в старом репо, это, вероятно, более простое решение моей проблемы.
kaese
5
@kaese: Я думаю, ты .gitignoreдолжен справиться с этим, верно?
Фред Фу
48
Сохраните ваш .git / config до и восстановите его после.
lalebarde
@lalebarde Если вы восстановите .git / config после git commit -m "Initial commit"этого, вы, вероятно, можете пропустить git remote add ...часть, предполагая, что она уже была в вашей конфигурации, и сразу перейти к нажатию. Это сработало для меня.
Баттл Буткус
24
Будьте осторожны с этим, если вы пытаетесь удалить конфиденциальные данные: присутствие только одного коммита в только что отправленной ветке master вводит в заблуждение - история все равно будет существовать, просто не будет доступна из этой ветки. Например, если у вас есть теги, которые указывают на более ранние коммиты, эти коммиты будут доступны. Фактически, для тех, у кого есть немного git foo, я уверен, что после этого git push они все равно смогут восстановить всю историю из репозитория GitHub - и если у вас есть другие ветки или теги, то они не даже нужно много мерзавца.
Роберт Мюил
621

Единственное решение, которое работает для меня (и поддерживает работу подмодулей)

git checkout --orphan newBranch
git add -A  # Add all files and commit them
git commit
git branch -D master  # Deletes the master branch
git branch -m master  # Rename the current branch to master
git push -f origin master  # Force push master branch to github
git gc --aggressive --prune=all     # remove the old files

Удаление .git/всегда вызывает огромные проблемы, когда у меня есть подмодули. Использование git rebase --rootможет как-то вызвать конфликт для меня (и займет много времени, так как у меня было много истории).

Zeelot
источник
55
это должен быть правильный ответ! просто добавьте git push -f origin masterпоследний оп, и солнце снова засияет на вашем новом репо! :)
гру
2
Разве это не удерживает старые коммиты?
Брэд
4
@JonePolvora git fetch; git reset --hard origin / master stackoverflow.com/questions/4785107/…
echo
5
После этого будет ли свободное место в репо?
Инуарт
8
Я считаю, что вы должны добавить предложение @JasonGoemaat в качестве последней строки к вашему ответу. Без git gc --aggressive --prune allцели потерять историю будет упущено.
Tuncay Göncüoğlu
93

Это мой любимый подход:

git branch new_branch_name $(echo "commit message" | git commit-tree HEAD^{tree})

Это создаст новую ветку с одним коммитом, который добавляет все в HEAD. Это ничего не меняет, так что это абсолютно безопасно.

dan_waterworth
источник
3
Лучший подход! Очисти и сделай работу. Кроме того, я переименую ветку с большим количеством изменений от "master" до "local-work" и от "new_branch_name" до "master". В master выполните следующие действия: git -m local-changes git branch -m local-changes git checkout new_branch_name имя-git branch -m master <
Валтони Боавентура
Это выглядит очень коротко и гладко, единственное, что я не понимаю или еще не видел, это ГОЛОВА ^ {дерево}, кто-нибудь может объяснить? Кроме того, я прочитал бы это как «создать новую ветку из данного коммита, созданного путем создания нового объекта коммита с данным сообщением коммита из ___»
TomKeegasi
3
Определенное место для поиска ответов на вопросы о синтаксисе git-ссылок - в git-rev-parseдокументации. То, что здесь происходит, git-commit-treeтребует ссылки на дерево (снимок репо), но HEADявляется ревизией. Чтобы найти дерево, связанное с коммитом, мы используем <rev>^{<type>}форму.
dan_waterworth
Хороший ответ. Работает хорошо. Напоследок скажуgit push --force <remote> new_branch_name:<remote-branch>
Фелипе Альварес
31

Другой вариант, который может оказаться трудоемким, если у вас много коммитов, это интерактивная перебазировка (при условии, что ваша версия git> = 1.7.12):git rebase --root -i

Когда в вашем редакторе представлен список коммитов:

  • Измените «pick» на «reword» для первого коммита
  • Измените «pick» на «fixup» при каждом другом коммите

Сохранить и закрыть. Git начнет перебазировать.

В конце у вас будет новый корневой коммит, представляющий собой комбинацию всех тех, что пришли после него.

Преимущество состоит в том, что вам не нужно удалять свой репозиторий, и если у вас есть мысли, у вас всегда есть запасной вариант.

Если вы действительно хотите уничтожить историю, сбросьте мастер до этого коммита и удалите все остальные ветви.

деревенщина
источник
После того, как ребаз завершен, я не могу нажать:error: failed to push some refs to
Begueradj
@Begueradj, если вы уже нажали ветку, которую перебазировали, то вам нужно будет принудительно нажать git push --force-with-lease. сила с арендой используется потому, что она менее разрушительна, чем --force.
Карл
19

Вариант предложенного метода Ларсмана :

Сохраните свой список неотслеживаемых файлов:

git ls-files --others --exclude-standard > /tmp/my_untracked_files

Сохраните вашу конфигурацию git:

mv .git/config /tmp/

Затем выполните первые шаги Ларсмана:

rm -rf .git
git init
git add .

Восстановите ваш конфиг:

mv /tmp/config .git/

Удалите файлы, которые не были отслежены:

cat /tmp/my_untracked_files | xargs -0 git rm --cached

Затем совершите:

git commit -m "Initial commit"

И, наконец, нажмите на ваш репозиторий:

git push -u --force origin master
lalebarde
источник
6

Ниже приведен скрипт, адаптированный из ответа @Zeelot. Следует удалить историю из всех веток, а не только из основной ветки:

for BR in $(git branch); do   
  git checkout $BR
  git checkout --orphan ${BR}_temp
  git commit -m "Initial commit"
  git branch -D $BR
  git branch -m $BR
done;
git gc --aggressive --prune=all

Это сработало для моих целей (я не использую подмодули).

Шафике Джамал
источник
4
Я думаю, что вы забыли заставить push master завершить процедуру.
not2qubit
2
Я должен был сделать небольшую модификацию. git branchрядом с вашей извлеченной веткой будет отмечен звездочкой, которая затем будет скомбинирована, что приведет к ее разрешению для всех файлов или папок, как если бы это были также имена ветвей. Вместо этого я использовал, git branch --format="%(refname:lstrip=2)"который дал мне только названия филиалов.
Бен Ричардс
@ not2qubit: Спасибо за это. Какой будет точная команда? git push --force origin masterили git push --force-with-lease? По-видимому, последний безопаснее (см. Stackoverflow.com/questions/5509543/… )
Шафик Джамал
@BenRichards. Интересно. Я попробую это снова в какой-то момент с папкой, которая соответствует имени ветви, чтобы проверить это, затем обновлю ответ. Спасибо.
Шафике Джамал
5

Вы можете использовать мелкие клоны (git> 1.9):

git clone --depth depth remote-url

Дополнительное чтение: http://blogs.atlassian.com/2014/05/handle-big-repositories-git/

Матиас М
источник
4
Такой клон не может быть перенесен в новый репозиторий.
Северин Нимец
1
Было бы полезно узнать, как обойти это ограничение. Может кто-нибудь объяснить, почему это не может быть принудительно вытеснено?
not2qubit
Ответ на ваш вопрос: stackoverflow.com/questions/6900103/…
Матиас М
4

git filter-branch является инструментом основной хирургии.

git filter-branch --parent-filter true -- @^!

--parent-filterполучает родителей на стандартный ввод и должен напечатать переписанные родители на стандартный вывод; unix trueуспешно выходит и ничего не печатает, а значит: нет родителей. @^!это Git сокращение от «глава коммит, но не любой из его родителей». Затем удалите все другие ссылки и нажмите на досуге.

jthill
источник
3

Просто удалите репозиторий Github и создайте новый. Безусловно, самый быстрый, простой и безопасный подход. В конце концов, что вы должны получить, выполняя все эти команды в принятом решении, когда все, что вам нужно, это основная ветка с одним коммитом?

AndroidDev
источник
1
Одним из главных моментов является возможность увидеть, откуда он был разветвлен.
not2qubit
Я только что сделал это, и это хорошо
thanos.a
2

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

git log -n1 --format=%H >.git/info/grafts
git filter-branch -f
rm .git/info/grafts

Если вы хотите очистить его, попробуйте этот скрипт:

http://sam.nipl.net/b/git-gc-all-ferocious

Я написал сценарий, который «убивает историю» для каждой ветви в хранилище:

http://sam.nipl.net/b/git-kill-history

см. также: http://sam.nipl.net/b/confirm

Сэм Уоткинс
источник
1
Спасибо за это. Только к вашему сведению: ваш скрипт для уничтожения истории для каждой ветви может использовать некоторые обновления - он дает следующие ошибки: git-hash: not foundиSupport for <GIT_DIR>/info/grafts is deprecated
Shafique Jamal
1
@ShafiqueJamal, спасибо, маленький скрипт "git-hash" git log HEAD~${1:-0} -n1 --format=%Hздесь sam.aiki.info/b/git-hash Было бы лучше поместить все это в один скрипт для общественного потребления. Если я когда-нибудь воспользуюсь им снова, я могу выяснить, как это сделать с помощью новой функции, которая заменяет «трансплантаты».
Сэм Уоткинс
2

Я хотел бы удалить ВСЕ историю версий из локального репозитория Git, чтобы текущее содержимое репозитория отображалось как единственная фиксация (и, следовательно, более старые версии файлов в репозитории не сохраняются).

Более концептуальный ответ:

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

Тогда старые, недоступные коммиты больше никогда не будут видны никому, если они не будут копаться с низкоуровневыми командами git. Если вам этого достаточно, я бы просто остановился на этом и позволил автоматическому GC выполнить свою работу, когда захочет. Если вы хотите избавиться от них сразу, вы можете использовать git gc(возможно, с --aggressive --prune=all). Для удаленного git-репозитория вы не можете заставить это сделать, если только у вас нет доступа оболочки к их файловой системе.

Anoe
источник
Хорошее дополнение, если смотреть в контексте ответа @Zeelot.
Могенс TrasherDK
Да, у Zeelot's есть команды, которые в основном делают это (просто по-другому, начиная полностью заново, что может быть хорошо для OP). @MogensTrasherDK
AnoE
0

Ну вот:

#!/bin/bash
#
# By Zibri (2019)
#
# Usage: gitclean username password giturl
#
gitclean () 
{ 
    odir=$PWD;
    if [ "$#" -ne 3 ]; then
        echo "Usage: gitclean username password giturl";
        return 1;
    fi;
    temp=$(mktemp -d 2>/dev/null /dev/shm/git.XXX || mktemp -d 2>/dev/null /tmp/git.XXX);
    cd "$temp";
    url=$(echo "$3" |sed -e "s/[^/]*\/\/\([^@]*@\)\?\.*/\1/");
    git clone "https://$1:$2@$url" && { 
        cd *;
        for BR in "$(git branch|tr " " "\n"|grep -v '*')";
        do
            echo working on branch $BR;
            git checkout $BR;
            git checkout --orphan $(basename "$temp"|tr -d .);
            git add -A;
            git commit -m "Initial Commit" && { 
                git branch -D $BR;
                git branch -m $BR;
                git push -f origin $BR;
                git gc --aggressive --prune=all
            };
        done
    };
    cd $odir;
    rm -rf "$temp"
}

Также размещено здесь: https://gist.github.com/Zibri/76614988478a076bbe105545a16ee743

Zibri
источник
Г! Не заставляйте меня указывать мой скрытый, незащищенный пароль в командной строке! Кроме того, вывод ветви git обычно плохо подходит для сценариев. Вы можете посмотреть на сантехнические инструменты.
Д. Бен Кнобл
-1

Я решил похожую проблему, просто удалив .gitпапку из моего проекта и реинтегрировав ее с контролем версий через IntelliJ. Примечание. .gitПапка скрыта. Вы можете просмотреть его в терминале с помощью ls -a, а затем удалить его с помощью rm -rf .git.

Дж. Б. Ловелл
источник
вот что он делает на шаге 1: rm -rf .git?
ночи с
-1

Для этого используйте команду Shallow Clone git clone --depth 1 URL - он будет клонировать только текущий заголовок хранилища.

kkarki
источник
-2

Чтобы удалить последний коммит из git, вы можете просто запустить

git reset --hard HEAD^ 

Если вы удаляете несколько коммитов сверху, вы можете запустить

git reset --hard HEAD~2 

удалить последние два коммита. Вы можете увеличить количество, чтобы удалить еще больше коммитов.

Больше информации здесь.

Git tutoturial предоставляет помощь по очистке репозитория:

Вы хотите удалить файл из истории и добавить его в .gitignore, чтобы он не был случайно повторно зафиксирован. Для наших примеров мы собираемся удалить Rakefile из репозитория GitHub.

git clone https://github.com/defunkt/github-gem.git

cd github-gem

git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch Rakefile' \
  --prune-empty --tag-name-filter cat -- --all

Теперь, когда мы стерли файл из истории, давайте позаботимся о том, чтобы мы случайно не зафиксировали его снова.

echo "Rakefile" >> .gitignore

git add .gitignore

git commit -m "Add Rakefile to .gitignore"

Если вы довольны состоянием хранилища, вам нужно принудительно нажать изменения, чтобы перезаписать удаленное хранилище.

git push origin master --force
kiriloff
источник
6
Удаление файлов или фиксаций из хранилища не имеет абсолютно никакого отношения к вопросу (который просит удалить историю, совершенно другая вещь). ОП хочет чистую историю, но хочет сохранить текущее состояние хранилища.
Виктор Шредер
это не дает результат, заданный в вопросе. вы отбрасываете все изменения после последнего коммита и теряете все изменения с тех пор, но вопрос требует сохранения текущих файлов и удаления истории.
Tuncay Göncüoğlu