Rendi l'attuale commit l'unico (iniziale) commit in un repository Git?


666

Al momento ho un repository Git locale, che invio a un repository Github.

Il repository locale ha ~ 10 commit e il repository Github ne è un duplicato sincronizzato.

Quello che vorrei fare è rimuovere TUTTA la cronologia delle versioni dal repository Git locale, quindi il contenuto corrente del repository appare come unico commit (e quindi le versioni precedenti dei file all'interno del repository non vengono archiviate).

Vorrei quindi inviare queste modifiche a Github.

Ho studiato Git rebase, ma questo sembra essere più adatto alla rimozione di versioni specifiche. Un'altra potenziale soluzione è quella di eliminare il repository locale e crearne uno nuovo, sebbene ciò creerebbe probabilmente molto lavoro!

ETA: ci sono directory / file specifici che non sono tracciati - se possibile, vorrei mantenere la non traccia di questi file.


6
Vedi anche stackoverflow.com/questions/435646/… ("Come posso unire i primi due commit di un repository Git?")
Anonymoose


Risposte:


983

Ecco l'approccio della forza bruta. Rimuove anche la configurazione del repository.

Nota : questo NON funziona se il repository ha sottomoduli! Se si utilizzano sottomoduli, è necessario utilizzare ad esempio rebase interattivo

Passaggio 1: rimuovi tutta la cronologia ( assicurati di disporre del backup, questo non può essere ripristinato )

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

Passaggio 2: ricostruire il repository Git con solo il contenuto corrente

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

Passaggio 3: premere su GitHub.

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

3
Grazie Larsmans - Ho scelto di usare questo come soluzione. Sebbene l'inizializzazione del repository Git perda il record di file non tracciati nel vecchio repository, questa è probabilmente una soluzione più semplice per il mio problema.
Kaese,

5
@kaese: Penso che .gitignoredovresti gestirli, giusto?
Fred Foo,

48
Salva prima il tuo .git / config e ripristinalo dopo.
lalebarde,

@lalebarde Se ripristini .git / config dopo, git commit -m "Initial commit"probabilmente puoi saltare la git remote add ...parte, supponendo che fosse già nella tua configurazione, e passare direttamente alla spinta. Ha funzionato per me.
Buttle Butkus,

24
Fai attenzione se stai cercando di rimuovere i dati sensibili: la presenza di un singolo commit nel ramo master appena spinto è fuorviante - la storia esisterà ancora , ma non sarà accessibile da quel ramo. Se si hanno tag, ad esempio, che indicano commit precedenti, questi commit saranno accessibili. In effetti, per chiunque abbia un po 'di git foo, sono sicuro che dopo questo git push, saranno ancora in grado di recuperare tutta la cronologia dal repository GitHub - e se hai altri rami o tag, allora non lo fanno anche bisogno di molto git foo.
Robert Muil,

621

L'unica soluzione che funziona per me (e fa funzionare i sottomoduli) è

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

L'eliminazione .git/provoca sempre enormi problemi quando ho i sottomoduli. L'uso in git rebase --rootqualche modo causerebbe conflitti per me (e richiederebbe molto tempo da quando avevo molta storia).


55
questa dovrebbe essere la risposta corretta! basta aggiungere a git push -f origin mastercome ultima operazione e il sole splenderà di nuovo sul tuo nuovo repository! :)
gru

2
Questo non mantiene i vecchi impegni in giro?
Brad

4
@JonePolvora git fetch; git reset --hard origin / master stackoverflow.com/questions/4785107/…
echo

5
dopo aver fatto questo, il repository libererà spazio?
Inuart,

8
Credo che dovresti aggiungere il suggerimento di @JasonGoemaat come ultima riga alla tua risposta. Senza git gc --aggressive --prune alltutto il punto di perdere la storia ci mancherebbe.
Tuncay Göncüoğlu,

93

Questo è il mio approccio preferito:

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

Questo creerà un nuovo ramo con un commit che aggiunge tutto in HEAD. Non altera nient'altro, quindi è completamente sicuro.


3
Il miglior approccio! Cancella e fai il lavoro. Inoltre, ho rinominato il ramo con molte modifiche da "master" a "local-work" e "new_branch_name" a "master". Nel master, procedi come segue: git -m local-changes git branch -m local-changes git checkout new_branch_name git branch -m master <
Valtoni Boaventura

Questo sembra davvero corto ed elegante, l'unica cosa che non capisco o non ho ancora visto è HEAD ^ {tree}, qualcuno potrebbe spiegare? A parte questo, leggo questo come "creare un nuovo ramo da un determinato commit, creato creando un nuovo oggetto commit con un dato messaggio commit da ___"
TomKeegasi,

3
Il posto definitivo in cui cercare risposte alle domande sulla sintassi di riferimento git è nei git-rev-parsedocumenti. Ciò che sta accadendo qui git-commit-treerichiede un riferimento a un albero (un'istantanea del repository), ma HEADè una revisione. Per trovare l'albero associato a un commit usiamo il <rev>^{<type>}modulo.
dan_waterworth,

Bella risposta. Funziona bene. Infine, dicogit push --force <remote> new_branch_name:<remote-branch>
Felipe Alvarez l'

31

L'altra opzione, che potrebbe rivelarsi molto impegnativa se hai molti commit, è un rebase interattivo (supponendo che la tua versione di git sia> = 1.7.12):git rebase --root -i

Quando viene presentato un elenco di commit nel tuo editor:

  • Cambia "pick" in "reword" per il primo commit
  • Cambia "pick" in "fixup" ogni altro commit

Salva e chiudi. Git inizierà a rinnovare.

Alla fine avresti un nuovo commit root che è una combinazione di tutti quelli che lo hanno seguito.

Il vantaggio è che non devi cancellare il tuo repository e se hai ripensamenti hai sempre un fallback.

Se vuoi davvero cancellare la tua cronologia, reimposta master su questo commit ed elimina tutti gli altri rami.


Dopo il rebase completato, non posso spingere:error: failed to push some refs to
Begueradj

@Begueradj se hai già spinto il ramo che hai rigenerato, dovrai forzare la spinta git push --force-with-lease. force-with-lease è usato perché è meno distruttivo di --force.
Carl

19

Variante del metodo proposto da Larsmans :

Salva il tuo elenco di file non tracciati:

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

Salva la tua configurazione git:

mv .git/config /tmp/

Quindi esegui i primi passi di Larsmans:

rm -rf .git
git init
git add .

Ripristina la tua configurazione:

mv /tmp/config .git/

Non tracciare i file non tracciati:

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

Quindi eseguire il commit:

git commit -m "Initial commit"

E infine spingi nel tuo repository:

git push -u --force origin master

6

Di seguito è riportato uno script adattato dalla risposta di @Zeelot. Dovrebbe rimuovere la cronologia da tutti i rami, non solo dal ramo principale:

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

Ha funzionato per i miei scopi (non sto usando i sottomoduli).


4
Penso che ti sia dimenticato di forzare il push master per completare la procedura.
not2qubit

2
Ho dovuto apportare una leggera modifica. git branchincluderà un asterisco accanto al ramo estratto, che verrà quindi globbed, causando la risoluzione in tutti i file o cartelle come se anche quelli fossero nomi di ramo. Invece, ho usato quello git branch --format="%(refname:lstrip=2)"che mi ha dato solo i nomi dei rami.
Ben Richards,

@ not2qubit: grazie per questo. Quale sarebbe il comando esatto? git push --force origin mastero git push --force-with-lease? A quanto pare il secondo è più sicuro (vedi stackoverflow.com/questions/5509543/... )
Shafique Jamal

@BenRichards. Interessante. Proverò di nuovo ad un certo punto con una cartella che corrisponde al nome di un ramo per testarlo, quindi aggiorno la risposta. Grazie.
Shafique Jamal,


4

git filter-branch è lo strumento di chirurgia maggiore.

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

--parent-filtermette i genitori su stdin e dovrebbe stampare i genitori riscritti su stdout; unix trueesce correttamente e non stampa nulla, quindi: niente genitori. @^!è l' abbreviazione di Git per "il capo commette ma non uno dei suoi genitori". Quindi eliminare tutti gli altri riferimenti e premere a piacere.


3

Basta eliminare il repository Github e crearne uno nuovo. Di gran lunga l'approccio più veloce, più semplice e più sicuro. Dopotutto, cosa devi guadagnare eseguendo tutti quei comandi nella soluzione accettata quando tutto ciò che vuoi è il ramo master con un unico commit?


1
Uno dei punti principali è poter vedere da dove è stato biforcato.
not2qubit

Ho appena fatto questo ed è tutto a posto
grazie

2

Il metodo seguente è esattamente riproducibile, quindi non è necessario eseguire nuovamente il clone se entrambe le parti erano coerenti, basta eseguire lo script anche dall'altra parte.

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

Se vuoi ripulirlo, prova questo script:

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

Ho scritto una sceneggiatura che "uccide la storia" per ogni ramo del repository:

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

vedi anche: http://sam.nipl.net/b/confirm


1
Grazie per questo. Cordiali saluti: la tua sceneggiatura per uccidere la cronologia di ogni ramo potrebbe usare qualche aggiornamento - dà i seguenti errori: git-hash: not foundeSupport for <GIT_DIR>/info/grafts is deprecated
Shafique Jamal

1
@ShafiqueJamal, grazie, la piccola sceneggiatura "git-hash" è git log HEAD~${1:-0} -n1 --format=%H, qui, sam.aiki.info/b/git-hash Sarebbe meglio mettere tutto in una sceneggiatura per il consumo pubblico. Se lo dovessi riutilizzare, potrei capire come farlo con la nuova funzionalità che sostituisce "innesti".
Sam Watkins,

2

Quello che vorrei fare è rimuovere TUTTA la cronologia delle versioni dal repository Git locale, quindi il contenuto corrente del repository appare come unico commit (e quindi le versioni precedenti dei file all'interno del repository non vengono archiviate).

Una risposta più concettuale:

git garbage raccoglie automaticamente i vecchi commit se nessun tag / branch / ref punta a loro. Quindi devi semplicemente rimuovere tutti i tag / rami e creare un nuovo commit orfano, associato a qualsiasi ramo - per convenzione lasceresti che il ramo masterpunti a quel commit.

I vecchi commit non raggiungibili non saranno mai più visti da nessuno a meno che non vadano a scavare con comandi git di basso livello. Se questo è abbastanza per te, mi fermerei lì e lascerei che il GC automatico facesse il suo lavoro ogni volta che lo desiderasse. Se vuoi sbarazzartene subito, puoi usarlo git gc(possibilmente con --aggressive --prune=all). Per il repository git remoto, tuttavia, non è possibile forzarlo, a meno che non si disponga dell'accesso shell al loro file system.


Bella aggiunta, se vista nel contesto della risposta di @Zeelot.
Mogens TrasherDK,

Sì, Zeelot ha i comandi che sostanzialmente fanno questo (solo in modo diverso, ricominciando completamente da capo, il che potrebbe andare bene per OP). @MogensTrasherDK
AnoE

0

Ecco qui:

#!/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"
}

Ospitato anche qui: https://gist.github.com/Zibri/76614988478a076bbe105545a16ee743


Gah! Non farmi fornire la mia password nascosta e non protetta dalla riga di comando! Inoltre, l'output di git branch è in genere scarsamente adatto per gli script. Potresti voler guardare gli strumenti idraulici.
D. Ben Knoble,

-1

Ho risolto un problema simile semplicemente cancellando la .gitcartella dal mio progetto e reintegrandomi con il controllo della versione tramite IntelliJ. Nota: la .gitcartella è nascosta. È possibile visualizzarlo nel terminale con ls -a, quindi rimuoverlo utilizzando rm -rf .git.


questo è quello che sta facendo nel passaggio 1: rm -rf .git?
notti

-1

Per questo usa il comando Shallow Clone git clone --depth 1 URL - Clona solo l'attuale HEAD del repository


-2

Per rimuovere l'ultimo commit da git, puoi semplicemente eseguirlo

git reset --hard HEAD^ 

Se si rimuovono più commit dall'alto, è possibile eseguire

git reset --hard HEAD~2 

per rimuovere gli ultimi due commit. Puoi aumentare il numero per rimuovere ancora più commit.

Maggiori informazioni qui.

Git tutoturial qui fornisce assistenza su come eliminare il repository:

si desidera rimuovere il file dalla cronologia e aggiungerlo a .gitignore per assicurarsi che non venga ripristinato accidentalmente. Per i nostri esempi, rimuoveremo Rakefile dal repository gem di 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

Ora che abbiamo cancellato il file dalla cronologia, assicuriamoci di non commetterlo di nuovo accidentalmente.

echo "Rakefile" >> .gitignore

git add .gitignore

git commit -m "Add Rakefile to .gitignore"

Se si è soddisfatti dello stato del repository, è necessario forzare le modifiche per sovrascrivere il repository remoto.

git push origin master --force

6
Rimuovere file o commit dal repository non ha assolutamente alcuna relazione con la domanda (che richiede di rimuovere la cronologia, una cosa completamente diversa). L'OP vuole una cronologia pulita ma vuole preservare lo stato attuale del repository.
Victor Schröder,

questo non produce il risultato richiesto nella domanda. stai eliminando tutte le modifiche dopo il commit che mantieni per ultimo e perdendo tutte le modifiche da allora, ma la domanda chiede di mantenere i file correnti e eliminare la cronologia.
Tuncay Göncüoğlu,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.