Зробити поточну фіксацію єдиною (початковою) комісією у сховищі Git?


664

Наразі у мене є локальне сховище Git, яке я пересилаю до сховища Github.

Локальне сховище має ~ 10 комітів, а сховище Github - це синхронізований дублікат цього.

Що я хотів би зробити, це видалити ВСЮ історію версій з локального сховища Git, тому поточний вміст сховища видається єдиним комітом (і тому старіші версії файлів у сховищі не зберігаються).

Тоді я хотів би перенести ці зміни до Github.

Я досліджував Git rebase, але, здається, це більше підходить для видалення конкретних версій. Ще одне потенційне рішення - видалити локальне репо і створити нове - хоча це, мабуть, створить багато роботи!

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


6
Дивіться також stackoverflow.com/questions/435646/… ("Як з’єднати перші два
файли


Відповіді:


981

Ось підхід грубої сили. Він також видаляє конфігурацію сховища.

Примітка . Це НЕ працює, якщо у сховищі є підмодулі! Якщо ви використовуєте підмодулі, вам слід використовувати, наприклад, інтерактивну базу даних

Крок 1: видаліть усю історію ( переконайтеся, що у вас є резервна копія, її неможливо повернути )

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

Крок 2: реконструюйте Git repo лише з поточним вмістом

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

Крок 3: натисніть на GitHub.

git remote add origin <github-uri>
git push -u --force origin master

3
Спасибі larsmans - я вирішив використовувати це як своє рішення. Хоча ініціалізація репортажу Git втрачає запис нерозроблених файлів у старому репо, це, мабуть, більш просте рішення для моєї проблеми.
kaese

5
@kaese: Я думаю, що ти .gitignoreповинен впоратися з цим, правда?
Фред Фоо

48
Збережіть .git / config раніше та відновіть його після.
lalebarde

@lalebarde Якщо ви відновите .git / config після git commit -m "Initial commit"цього, ви, ймовірно, можете пропустити git remote add ...частину, припускаючи, що вона вже була у вашому конфігурації, і переходити прямо до натискання. Це працювало для мене.
Buttle Butkus

24
Будьте обережні з цим, якщо ви намагаєтесь видалити конфіденційні дані: наявність лише однієї комісії в щойно висунутій головній гілці вводить в оману - історія все ще буде існувати, вона просто не буде доступна з цієї гілки. Якщо у вас є, наприклад, теги, які вказують на більш старі коміти, ці комісії будуть доступними. Насправді, для всіх, хто має трохи git foo, я впевнений, що після цього git push вони все одно зможуть відновити всю історію з сховища GitHub - і якщо у вас є інші гілки або теги, вони не мають навіть потрібно багато git foo.
Роберт Мюїл

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якось спричинило б конфлікти для мене (і це займе давно, оскільки у мене було багато історії).


55
це має бути правильна відповідь! просто додайте, git push -f origin masterяк останній оп, і сонце знову засяє вашим свіжим репо! :)
гру

2
Це не підтримує старих зобов’язань?
Бред

4
@JonePolvora git fetch; мерзотник скидання --hard походження / майстер stackoverflow.com/questions/4785107 / ...
відлуння -

5
після цього, чи буде вільний простір у репо?
Inuart

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. Це не змінює нічого іншого, тому є абсолютно безпечним.


3
Найкращий підхід! Очистіть і зробіть роботу. Крім того, я перейменую гілку з великою кількістю змін від "master" до "local-work" та "new_branch_name" в "master". У майстрі зробіть наступне: git -m local-changes git branch -m local-changes git checkout new_branch_name git branch -m master <
Valtoni Boaventura

Це виглядає насправді коротко і гладко, єдине, чого я ще не розумію або ще не бачив - це ГОЛА ^ {дерево}, хтось може пояснити? Крім того, я читав це як "створити нову гілку з даного комітету, створений створенням нового об'єкта" фіксація "із заданим повідомленням на
комісію

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. сила-з орендою використовується тому, що вона менш руйнівна, ніж - сила.
Карл

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

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
Я думаю, ви забули змусити майстра натиснення завершити процедуру.
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
Такого клону не можна перенести в нове сховище.
Seweryin Niemiec

1
Було б корисно знати, як обійти це обмеження. Хтось може пояснити, чому це не можна натиснути силою?
not2qubit

Відповідь на ваше запитання: stackoverflow.com/questions/6900103/…
Маттіас М

4

git filter-branch є основним інструментом хірургічного втручання.

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

--parent-filterотримує батьків на stdin і повинен друкувати переписані батьки на stdout; Unix trueуспішно виходить і нічого не друкує, отже: немає батьків. @^!є скороченням Гіта для "керівника, але не будь-якого з батьків". Потім видаліть усі інші відгуки та натисніть на дозвілля.


3

Просто видаліть репортаж Github і створіть нове. На сьогоднішній день найшвидший, найпростіший і безпечний підхід. Зрештою, що вам доводиться отримувати, виконуючи всі ці команди у прийнятому рішенні, коли все, що вам потрібно, - це головна гілка з одним комітом?


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
Дякую за це Просто FYI: ваш сценарій для вбивства історії для кожної гілки може використовувати деяке оновлення - воно дає такі помилки: 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 автоматично сміття збирає старі комісії, якщо жодні теги / гілки / refs не вказують на них. Таким чином, вам просто потрібно видалити всі теги / гілки та створити нове завдання-сиріт, пов’язане з будь-якою гілкою - за умовою ви дозволите цю гілкуmaster вказувати на цю комісію.

Старі, недосяжні завдання ніколи більше ніколи не побачать, якщо вони не перекопаються за допомогою команд git низького рівня. Якщо цього вам достатньо, я б просто зупинився на цьому і дозволив автоматичному GC робити свою роботу коли завгодно. Якщо ви хочете їх позбутися відразу, можете скористатися git gc(можливо, разом з --aggressive --prune=all). Для віддаленого сховища git ви не можете це змусити, якщо у вас немає доступу до оболонки до їх файлової системи.


Приємне доповнення, коли його бачимо в контексті відповіді @Zeelot.
Mogens TrasherDK

Так, Zeelot має команди, які в основному роблять це (зовсім по-іншому, починаючи повністю, що може бути добре для 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


Гах! Не змушуйте мене в командному рядку надати неприхований, незахищений пароль! Крім того, вихід гіткової гілки зазвичай погано підходить для сценаріїв. Можливо, ви захочете подивитися на сантехнічні інструменти.
D. Ben Knoble

-1

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


ось що він робить на кроці 1: rm -rf .git?
ночі

-1

Для цього використовуйте команду Shallow Clone git clone --depth 1 URL - Він буде клонувати лише поточну HEAD сховища


-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

6
Видалення файлів або комітетів із сховища абсолютно не пов'язане з питанням (яке вимагає видалити історію, зовсім інша річ). ОП хоче чистої історії, але хоче зберегти поточний стан сховища.
Віктор Шредер

це не дає результату, заданого у запитанні. ви відміняєте всі зміни після того, як ви продовжуєте виконувати цю програму, і втрачаєте всі зміни з тих пір, але питання вимагає зберегти поточні файли та видалити історію.
Tuncay Göncüoğlu
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.