Domanda

Prima ho chiesto come annullare i primi due commit in un repository git.

Mentre le soluzioni sono piuttosto interessanti e non sono così strabilianti come alcune altre cose in git, sono ancora un po 'la proverbiale borsa del male se devi ripetere la procedura molte volte lungo lo sviluppo del tuo progetto .

Quindi, preferirei provare dolore solo una volta, e quindi essere in grado di usare per sempre il rebase interattivo standard.

Quello che voglio fare, quindi, è avere un commit iniziale vuoto che esiste esclusivamente allo scopo di essere il primo. Nessun codice, niente di niente. Basta occupare spazio in modo che possa essere la base per rebase.

La mia domanda è quindi, avendo un repository esistente, come posso fare per inserire un nuovo commit vuoto prima del primo e spostare tutti gli altri in avanti?

È stato utile?

Soluzione

Risposta di metà 2017

La creazione di un nuovo commit completamente vuoto senza effetti collaterali è probabilmente meglio se si utilizza direttamente l'idraulica di Git. Farlo in questo modo evita gli effetti collaterali: non toccare la copia di lavoro o l'indice, nessun ramo temporaneo da ripulire, ecc. Quindi:

  1. Per creare un commit, abbiamo bisogno di un albero di directory per esso, quindi ne creiamo prima uno vuoto:

    tree=`git hash-object -wt tree --stdin < /dev/null`
    
  2. Ora possiamo avvolgere un commit attorno ad esso:

    commit=`git commit-tree -m 'root commit' $tree`
    
  3. E ora possiamo tornare su quello:

    git rebase --onto $commit --root master
    

E questo è tutto. Puoi riorganizzare l'intera cosa in una riga, se conosci abbastanza bene la tua shell.

(N.B .: in pratica I & # 8217; d ora uso filtro-ramo . Lo modificherò in seguito.)


Risposta storica (a cui fanno riferimento altre risposte)

Ecco un'implementazione più pulita della stessa soluzione, in quanto funziona senza la necessità di creare un repository aggiuntivo, utilizzare i telecomandi e correggere una testa staccata:

# first you need a new empty branch; let's call it `newroot`
git checkout --orphan newroot
git rm -rf .

# then you apply the same steps
git commit --allow-empty -m 'root commit'
git rebase --onto newroot --root master
git branch -d newroot

Voila, sei finito su master con la sua cronologia riscritta per includere un commit root vuoto.


NB .: sulle vecchie versioni di Git che mancano di --orphan su checkout , è necessario l'impianto idraulico per creare un ramo vuoto:

git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d

Altri suggerimenti

Unisci le risposte di Aristotele Pagaltzis e Uwe Kleine-König e il commento di Richard Bronosky.

git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d
# touch .gitignore && git add .gitignore # if necessary
git commit --allow-empty -m 'initial'
git rebase --onto newroot --root master
git branch -d newroot

(solo per mettere tutto in un unico posto)

Mi piace la risposta di Aristotele. Ma ho scoperto che per un repository di grandi dimensioni (> 5000 commit) il filtro-ramo funziona meglio del rebase per diversi motivi 1) è più veloce 2) non richiede l'intervento umano in caso di conflitto di unione. 3) può riscrivere i tag conservandoli. Nota che il filtro-ramo funziona perché non ci sono dubbi sul contenuto di ciascun commit - è esattamente lo stesso di prima di questo 'rebase'.

I miei passi sono:

# first you need a new empty branch; let's call it `newroot`
git symbolic-ref HEAD refs/heads/newroot
git rm --cached -r .
git clean -f -d

# then you apply the same steps
git commit --allow-empty -m 'root commit'

# then use filter-branch to rebase everything on newroot
git filter-branch --parent-filter 'sed "s/^\$/-p <sha of newroot>/"' --tag-name-filter cat master

Nota che le opzioni '--tag-name-filter cat' significano che i tag verranno riscritti per puntare ai commit appena creati.

Ho usato con successo pezzi della risposta di Aristotele e Kent:

# first you need a new empty branch; let's call it `newroot`
git checkout --orphan newroot
git rm -rf .
git commit --allow-empty -m 'root commit'
git filter-branch --parent-filter \
'sed "s/^\$/-p <sha of newroot>/"' --tag-name-filter cat -- --all
# clean up
git checkout master
git branch -D newroot
# make sure your branches are OK first before this...
git for-each-ref --format="%(refname)" refs/original/ | \
xargs -n 1 git update-ref -d

Questo riscriverà anche tutti i rami (non solo master ) oltre ai tag.

git rebase --root --onto $ emptyrootcommit

dovrebbe fare facilmente il trucco

Mi sono emozionato e ho scritto una versione "idempotente" di questo simpatico script ... inserirà sempre lo stesso commit vuoto e, se lo esegui due volte, non cambierà gli hash di commit ogni volta. Quindi, ecco la mia opinione su git-insert-empty-root :

#!/bin/sh -ev
# idempotence achieved!
tmp_branch=__tmp_empty_root
git symbolic-ref HEAD refs/heads/$tmp_branch
git rm --cached -r . || true
git clean -f -d
touch -d '1970-01-01 UTC' .
GIT_COMMITTER_DATE='1970-01-01T00:00:00 +0000' git commit \
  --date='1970-01-01T00:00:00 +0000' --allow-empty -m 'initial'
git rebase --committer-date-is-author-date --onto $tmp_branch --root master
git branch -d $tmp_branch

Vale la complessità extra? forse no, ma userò questo.

Questo DOVREBBE anche consentire di eseguire questa operazione su diverse copie clonate del repository e finire con gli stessi risultati, quindi sono ancora compatibili ... test ... sì, funziona, ma è necessario anche eliminare e aggiungi nuovamente i telecomandi, ad esempio:

git remote rm origin
git remote add --track master user@host:path/to/repo

Bene, ecco cosa mi è venuto in mente:

# Just setting variables on top for clarity.
# Set this to the path to your original repository.
ORIGINAL_REPO=/path/to/original/repository

# Create a new repository…
mkdir fun
cd fun
git init
# …and add an initial empty commit to it
git commit --allow-empty -m "The first evil."

# Add the original repository as a remote
git remote add previous $ORIGINAL_REPO
git fetch previous

# Get the hash for the first commit in the original repository
FIRST=`git log previous/master --pretty=format:%H  --reverse | head -1`
# Cherry-pick it
git cherry-pick $FIRST
# Then rebase the remainder of the original branch on top of the newly 
# cherry-picked, previously first commit, which is happily the second 
# on this branch, right after the empty one.
git rebase --onto master master previous/master

# rebase --onto leaves your head detached, I don't really know why)
# So now you overwrite your master branch with the newly rebased tree.
# You're now kinda done.
git branch -f master
git checkout master
# But do clean up: remove the remote, you don't need it anymore
git remote rm previous

Penso che usare git repl e git filter-branch sia una soluzione migliore rispetto all'utilizzo di git rebase :

  • migliore preformance
  • più facile e meno rischioso (potresti verificare il tuo risultato ad ogni passaggio e annullare ciò che hai fatto ...)
  • funziona bene con più filiali con risultati garantiti

L'idea alla base è di:

  • Crea un nuovo commit vuoto lontano nel passato
  • Sostituisci il vecchio commit root con un commit esattamente simile tranne per il fatto che il nuovo commit root viene aggiunto come genitore
  • Verifica che tutto sia come previsto ed esegui git filter-branch
  • Ancora una volta, verifica che tutto sia OK e pulisci i file git non più necessari

Ecco uno script per i 2 primi passi:

#!/bin/bash
root_commit_sha=$(git rev-list --max-parents=0 HEAD)
git checkout --force --orphan new-root
find . -path ./.git -prune -o -exec rm -rf {} \; 2> /dev/null
git add -A
GIT_COMMITTER_DATE="2000-01-01T12:00:00" git commit --date==2000-01-01T12:00:00 --allow-empty -m "empty root commit"
new_root_commit_sha=$(git rev-parse HEAD)

echo "The commit '$new_root_commit_sha' will be added before existing root commit '$root_commit_sha'..."

parent="parent $new_root_commit_sha"
replacement_commit=$(
 git cat-file commit $root_commit_sha | sed "s/author/$parent\nauthor/" |
 git hash-object -t commit -w --stdin
) || return 3
git replace "$root_commit_sha" "$replacement_commit"

Potresti eseguire questo script senza rischi (anche se fare un backup prima di fare azioni che non hai mai fatto prima è una buona idea;)), e se il risultato non è quello previsto, cancella semplicemente i file creati nella cartella < codice> .git / refs / sostituisci e riprova;)

Dopo aver verificato che lo stato del repository è quello previsto, eseguire il comando seguente per aggiornare la cronologia di tutti i rami :

git filter-branch -- --all

Ora, devi vedere 2 storie, quella vecchia e quella nuova (vedi aiuto su filtro-ramo per maggiori informazioni). È possibile confrontare i 2 e controllare di nuovo se tutto è a posto. Se sei soddisfatto, elimina i file non più necessari:

rm -rf ./.git/refs/original
rm -rf ./.git/refs/replace

Potresti tornare al tuo ramo master ed eliminare il ramo temporaneo:

git checkout master
git branch -D new-root

Ora, tutto dovrebbe essere fatto;)

Ecco un semplice one-liner che può essere utilizzato per aggiungere un commit vuoto all'inizio di un repository, se hai dimenticato di creare un commit vuoto immediatamente dopo " git init " ;:

git rebase --root --onto $(git commit-tree -m 'Initial commit (empty)' 4b825dc642cb6eb9a060e54bf8d69288fbee4904)

Ecco il mio script bash basato sulla risposta di Kent con miglioramenti:

  • controlla il ramo originale, non solo master , quando fatto;
  • Ho cercato di evitare il ramo temporaneo, ma git checkout --orphan funziona solo con un ramo, non con uno stato a testa staccata, quindi è stato estratto abbastanza a lungo per rendere il nuovo root commesso e poi soppressa;
  • utilizza l'hash del nuovo commit root durante il filtro-ramo (Kent ha lasciato un segnaposto per la sostituzione manuale);
  • l'operazione filtro-ramo riscrive solo le filiali locali, non anche i telecomandi
  • i metadati dell'autore e del committer sono standardizzati in modo che il commit della radice sia identico tra i repository.

#!/bin/bash

# Save the current branch so we can check it out again later
INITIAL_BRANCH=`git symbolic-ref --short HEAD`
TEMP_BRANCH='newroot'

# Create a new temporary branch at a new root, and remove everything from the tree
git checkout --orphan "$TEMP_BRANCH"
git rm -rf .

# Commit this empty state with generic metadata that will not change - this should result in the same commit hash every time
export GIT_AUTHOR_NAME='nobody'
export GIT_AUTHOR_EMAIL='nobody@example.org'
export GIT_AUTHOR_DATE='2000-01-01T00:00:00+0000'
export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"
export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
git commit --allow-empty -m 'empty root'
NEWROOT=`git rev-parse HEAD`

# Check out the commit we just made and delete the temporary branch
git checkout --detach "$NEWROOT"
git branch -D "$TEMP_BRANCH"

# Rewrite all the local branches to insert the new root commit, delete the 
# original/* branches left behind, and check out the rewritten initial branch
git filter-branch --parent-filter "sed \"s/^\$/-p $NEWROOT/\"" --tag-name-filter cat -- --branches
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
git checkout "$INITIAL_BRANCH"

Per cambiare il commit della radice:

Innanzitutto, crea il commit desiderato come primo.

In secondo luogo, cambia l'ordine dei commit usando:

git rebase -i --root

Apparirà un editor con i commit fino al commit di root, come:

seleziona 1234 vecchio messaggio radice

scegli 0294 Un commit nel mezzo

scegli 5678 commit che vuoi mettere alla radice

È quindi possibile inserire prima il commit desiderato, posizionandolo nella prima riga. Nell'esempio:

scegli 5678 commit che vuoi mettere alla radice

seleziona 1234 vecchio messaggio radice

scegli 0294 Un commit nel mezzo

Esci dall'editor l'ordine di commit sarà cambiato.

PS: per modificare l'editor utilizzato da git, esegui:

git config --global core.editor name_of_the_editor_program_you_want_to_use

Seguendo la risposta Aristotele Pagaltzis e altri ma usando comandi più semplici

zsh% git checkout --orphan empty     
Switched to a new branch 'empty'
zsh% git rm --cached -r .
zsh% git clean -fdx
zsh% git commit --allow-empty -m 'initial empty commit'
[empty (root-commit) 64ea894] initial empty commit
zsh% git checkout master
Switched to branch 'master'
zsh% git rebase empty
First, rewinding head to replay your work on top of it...
zsh% git branch -d empty 
Deleted branch empty (was 64ea894).

Nota che il tuo repository non deve contenere modifiche locali in attesa di essere commesso.
Nota git checkout --orphan funzionerà con le nuove versioni di git, immagino.
Nota la maggior parte delle volte git status fornisce suggerimenti utili.

Avvia un nuovo repository.

Riporta la data alla data di inizio desiderata.

Fai tutto come desideri che avessi fatto, regolando l'ora del sistema in modo da riflettere quando avresti voluto farlo in quel modo. Estrai i file dal repository esistente secondo necessità per evitare un sacco di digitazioni inutili.

Quando arrivi a oggi, scambia i repository e il gioco è fatto.

Se sei solo pazzo (affermato) ma abbastanza intelligente (probabilmente, perché devi avere una certa quantità di intelligenza per pensare a idee folli come questa) dovrai scrivere il processo.

Ciò renderà anche più piacevole la tua decisione se desideri che il passato sia accaduto in un altro modo tra una settimana.

So che questo post è vecchio ma questa pagina è la prima in cui Google inserisce "commit git".

Perché complicare le cose semplici?

Hai A-B-C e vuoi A-B-Z-C.

  1. git rebase -i trunk (o qualsiasi altra cosa prima di B)
  2. cambia scegli per modificare sulla linea B
  3. apporta le tue modifiche: git add ..
  4. git commit ( git commit --amend che modificherà B e non creerà Z)

[Puoi fare tutti i git commit che vuoi qui per inserire più commit. Certo, potresti avere problemi con il passaggio 5, ma risolvere i conflitti di fusione con git è un'abilità che dovresti avere. In caso contrario, esercitati!]

  1. git rebase --continuue

Semplice, no?

Se capisci git rebase , aggiungere un commit 'root' non dovrebbe essere un problema.

Divertiti con git!

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top