Abbiamo un repository Git con oltre 400 commit, la prima dozzina di questi sono stati un sacco di tentativi ed errori. Vogliamo ripulire questi commit eliminandone molti in un unico commit. Naturalmente, git-rebase sembra la strada da percorrere. Il mio problema è che finisce con unire conflitti e questi conflitti non sono facili da risolvere. Non capisco perché ci dovrebbero essere dei conflitti, dato che sto solo schiacciando i commit (non eliminando o riorganizzando). Molto probabilmente, questo dimostra che non capisco completamente come git-rebase faccia le sue zucche.
Ecco una versione modificata degli script che sto usando:
repo_squash.sh (questo è lo script effettivamente eseguito):
rm -rf repo_squash
git clone repo repo_squash
cd repo_squash/
GIT_EDITOR=../repo_squash_helper.sh git rebase --strategy theirs -i bd6a09a484b8230d0810e6689cf08a24f26f287a
repo_squash_helper.sh (questo script è utilizzato solo da repo_squash.sh):
if grep -q "pick " $1
then
# cp $1 ../repo_squash_history.txt
# emacs -nw $1
sed -f ../repo_squash_list.txt < $1 > $1.tmp
mv $1.tmp $1
else
if grep -q "initial import" $1
then
cp ../repo_squash_new_message1.txt $1
elif grep -q "fixing bad import" $1
then
cp ../repo_squash_new_message2.txt $1
else
emacs -nw $1
fi
fi
repo_squash_list.txt: (questo file è utilizzato solo da repo_squash_helper.sh)
# Initial import
s/pick \(251a190\)/squash \1/g
# Leaving "Needed subdir" for now
# Fixing bad import
s/pick \(46c41d1\)/squash \1/g
s/pick \(5d7agf2\)/squash \1/g
s/pick \(3da63ed\)/squash \1/g
Lascerò i contenuti del "nuovo messaggio" alla tua immaginazione. Inizialmente, l'ho fatto senza l'opzione "--strategy theirs" (vale a dire, usando la strategia predefinita, che se capisco correttamente la documentazione è ricorsiva, ma non sono sicuro di quale strategia ricorsiva venga utilizzata), e inoltre non funziona. Inoltre, dovrei sottolineare che, usando il codice commentato in repo_squash_helper.sh, ho salvato il file originale su cui funziona lo script sed e ho eseguito lo script sed contro di esso per assicurarmi che stesse facendo quello che volevo ( era). Ancora una volta, non so nemmeno perché ci sarebbe un conflitto, quindi non sembra importare così tanto quale strategia viene utilizzata. Qualsiasi consiglio o intuizione sarebbe utile, ma soprattutto voglio solo far funzionare questo schiacciare.
Aggiornato con ulteriori informazioni dalla discussione con Jefromi:
Prima di lavorare sul nostro enorme repository "reale", ho usato script simili su un repository di test. Era un repository molto semplice e il test ha funzionato in modo pulito.
Il messaggio che ricevo quando fallisce è:
Finished one cherry-pick.
# Not currently on any branch.
nothing to commit (working directory clean)
Could not apply 66c45e2... Needed subdir
Questa è la prima scelta dopo il primo commit di squash. L'esecuzione git status
produce una directory di lavoro pulita. Se poi faccio un messaggio git rebase --continue
, ricevo un messaggio molto simile dopo qualche altro commit. Se lo faccio di nuovo, ricevo un altro messaggio molto simile dopo un paio di dozzine di commit. Se lo faccio ancora una volta, questa volta passa circa un centinaio di commit e genera questo messaggio:
Automatic cherry-pick failed. After resolving the conflicts,
mark the corrected paths with 'git add <paths>', and
run 'git rebase --continue'
Could not apply f1de3bc... Incremental
Se poi corro git status
, ottengo:
# Not currently on any branch.
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: repo/file_A.cpp
# modified: repo/file_B.cpp
#
# Unmerged paths:
# (use "git reset HEAD <file>..." to unstage)
# (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both modified: repo/file_X.cpp
#
# Changed but not updated:
# (use "git add/rm <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# deleted: repo/file_Z.imp
Il bit "entrambi modificato" suona strano per me, poiché questo era solo il risultato di una scelta. Vale anche la pena notare che se guardo al "conflitto", si riduce a una singola riga con una versione che inizia con un carattere [tab] e l'altra con quattro spazi. Sembrava che potesse essere un problema con il modo in cui ho impostato il mio file di configurazione, ma non contiene nulla del genere. (Ho notato che core.ignorecase è impostato su true, ma evidentemente git-clone lo ha fatto automaticamente. Non sono del tutto sorpreso dal fatto che l'origine originale fosse su un computer Windows.)
Se correggo manualmente file_X.cpp, in seguito fallisce poco dopo con un altro conflitto, questa volta tra un file (CMakeLists.txt) che una versione pensa dovrebbe esistere e una versione pensa che non dovrebbe. Se risolvo questo conflitto dicendo che voglio questo file (cosa che faccio), qualche commit dopo ottengo un altro conflitto (in questo stesso file) dove ora ci sono alcune modifiche piuttosto banali. È ancora solo il 25% circa del conflitto.
Vorrei anche sottolineare, poiché questo potrebbe essere molto importante, che questo progetto è iniziato in un repository svn. Quella storia iniziale molto probabilmente è stata importata da quel repository svn.
Aggiornamento n. 2:
Su un'allodola (influenzata dai commenti di Jefromi), ho deciso di fare il mio cambio repo_squash.sh per essere:
rm -rf repo_squash
git clone repo repo_squash
cd repo_squash/
git rebase --strategy theirs -i bd6a09a484b8230d0810e6689cf08a24f26f287a
E poi, ho appena accettato le voci originali, così come sono. Vale a dire, il "rebase" non avrebbe dovuto cambiare nulla. È finito con gli stessi risultati descritti in precedenza.
Aggiornamento n. 3:
In alternativa, se ometto la strategia e sostituisco l'ultimo comando con:
git rebase -i bd6a09a484b8230d0810e6689cf08a24f26f287a
Non ricevo più i problemi di rebase "niente da commettere", ma sono ancora rimasto con gli altri conflitti.
Aggiornamento con repository di giocattoli che ricrea il problema:
test_squash.sh (questo è il file che effettivamente esegui):
#========================================================
# Initialize directories
#========================================================
rm -rf test_squash/ test_squash_clone/
mkdir -p test_squash
mkdir -p test_squash_clone
#========================================================
#========================================================
# Create repository with history
#========================================================
cd test_squash/
git init
echo "README">README
git add README
git commit -m"Initial commit: can't easily access for rebasing"
echo "Line 1">test_file.txt
git add test_file.txt
git commit -m"Created single line file"
echo "Line 2">>test_file.txt
git add test_file.txt
git commit -m"Meant for it to be two lines"
git checkout -b dev
echo Meaningful code>new_file.txt
git add new_file.txt
git commit -m"Meaningful commit"
git checkout master
echo Conflicting meaningful code>new_file.txt
git add new_file.txt
git commit -m"Conflicting meaningful commit"
# This will conflict
git merge dev
# Fixes conflict
echo Merged meaningful code>new_file.txt
git add new_file.txt
git commit -m"Merged dev with master"
cd ..
#========================================================
# Save off a clone of the repository prior to squashing
#========================================================
git clone test_squash test_squash_clone
#========================================================
#========================================================
# Do the squash
#========================================================
cd test_squash
GIT_EDITOR=../test_squash_helper.sh git rebase -i HEAD@{7}
#========================================================
#========================================================
# Show the results
#========================================================
git log
git gc
git reflog
#========================================================
test_squash_helper.sh (utilizzato da test_sqash.sh):
# If the file has the phrase "pick " in it, assume it's the log file
if grep -q "pick " $1
then
sed -e "s/pick \(.*\) \(Meant for it to be two lines\)/squash \1 \2/g" < $1 > $1.tmp
mv $1.tmp $1
# Else, assume it's the commit message file
else
# Use our pre-canned message
echo "Created two line file" > $1
fi
PS: Sì, conosco alcuni di voi rabbrividire quando mi vedete usare emacs come editor fall-back.
PPS: Sappiamo che dovremo spazzare via tutti i nostri cloni del repository esistente dopo il rebase. (Sulla falsariga di "non rifare il repository dopo che è stato pubblicato".)
PPPS: Qualcuno può dirmi come aggiungere una taglia a questo? Non vedo l'opzione da nessuna parte in questa schermata, sia in modalità modifica che in modalità visualizzazione.
rebase --interactive
: quelli sono una specie di elenco di azioni che Git deve tentare. Speravo che potessi essere in grado di ridurlo a una singola compressione che causava conflitti ed evitare tutta la complessità aggiuntiva dei tuoi script di supporto. L'altra informazione mancante è quando si verificano i conflitti: quando git applica le patch per formare la zucca o quando tenta di passare oltre la zucca e applicare la patch successiva? (E sei sicuro che non accada nulla di brutto con il tuo kludge GIT_EDITOR? Un altro voto per un semplice test.)
rebase -p
comunque)