Git-svn dcommit dopo la fusione in git è pericoloso?


133

La mia motivazione per provare git-svn è la fusione e ramificazione senza sforzo. Poi ho notato che man git-svn (1) dice:

L'esecuzione di git-merge o git-pull NON è consigliata su un ramo da cui si prevede di non comunicare. Subversion non rappresenta fusioni in alcun modo ragionevole o utile; quindi gli utenti che usano Subversion non possono vedere alcuna fusione che hai fatto. Inoltre, se si unisce o si estrae da un ramo git che è un mirror di un ramo SVN, dcommit potrebbe impegnarsi nel ramo sbagliato.

Questo significa che non posso creare un ramo locale da svn / trunk (o un ramo), tagliare via, unire nuovamente in svn / trunk, quindi dcommit? Capisco che gli utenti svn vedranno sempre lo stesso pasticcio che si unisce in svn pre 1.5.x, ma ci sono altri svantaggi? Anche quest'ultima frase mi preoccupa. Le persone fanno abitualmente questo genere di cose?


14
Questo è semplicemente sbagliato: Subversion non rappresenta le fusioni in modo ragionevole o utile; quindi gli utenti che usano Subversion non possono vedere alcuna fusione che hai fatto. Inoltre, se si unisce o si estrae da un ramo git che è un mirror di un ramo SVN, dcommit potrebbe impegnarsi nel ramo sbagliato. Subversion potrebbe rappresentare non solo informazioni di tracciamento di git merge, ma anche informazioni molto più dettagliate. È git-svn che non riesce a comporre il corretto svn: mergeinfo o a fare il dcommit a un ramo appropriato.
Alexander Kitaev,

2
@AlexanderKitaev: i documenti git-svn sono cambiati da allora e c'è anche --mergeinfo=<mergeinfo>un'opzione che potrebbe essere in grado di passare le informazioni di unione all'SVN. Non sono sicuro di come dovrebbe essere usato però.
Mr_and_Mrs_D,

Risposte:


174

In realtà, ho trovato un modo ancora migliore con l' --no-ffopzione su git merge. Tutta questa tecnica di squash che ho usato prima non è più necessaria.

Il mio nuovo flusso di lavoro ora è il seguente:

  • Ho un ramo "master" che è l'unico ramo che ho dcommit da e che clonare il repository SVN ( -ssuppone che si abbia un layout standard di SVN nel repository trunk/, branches/e tags/):

    git svn clone [-s] <svn-url>
    
  • Lavoro su un ramo locale "lavoro" ( -bcrea il ramo "lavoro")

    git checkout -b work
    
  • impegnarsi localmente nel ramo "lavoro" ( -sper firmare il messaggio di commit). Nel sequel, suppongo che tu abbia fatto 3 commit locali

    ...
    (work)$> git commit -s -m "msg 1"
    ...
    (work)$> git commit -s -m "msg 2"
    ...
    (work)$> git commit -s -m "msg 3"
    

Ora vuoi impegnarti sul server SVN

  • [Alla fine] nascondi le modifiche che non vuoi vedere impegnate sul server SVN (spesso hai commentato del codice nel file principale solo perché vuoi accelerare la compilazione e concentrarti su una determinata funzionalità)

    (work)$> git stash
    
  • rebase il ramo master con il repository SVN (per aggiornare dal server SVN)

    (work)$> git checkout master
    (master)$> git svn rebase
    
  • tornare al ramo di lavoro e rebase con il maestro

    (master)$> git checkout work
    (work)$> git rebase master
    
  • Assicurati che tutto vada bene usando, ad esempio:

    (work)$> git log --graph --oneline --decorate
    
  • Ora è il momento di unire tutti e tre i commit dal ramo "lavoro" in "master" usando questa meravigliosa --no-ffopzione

    (work)$> git checkout master
    (master)$> git merge --no-ff work
    
  • È possibile notare lo stato dei registri:

    (master)$> git log --graph --oneline --decorate
    * 56a779b (work, master) Merge branch 'work'
    |\  
    | * af6f7ae msg 3
    | * 8750643 msg 2
    | * 08464ae msg 1
    |/  
    * 21e20fa (git-svn) last svn commit
    
  • Ora probabilmente vuoi modificare ( amend) l'ultimo commit per i tuoi tizi SVN (altrimenti vedranno solo un singolo commit con il messaggio "Unisci ramo 'lavoro'"

    (master)$> git commit --amend
    
  • Finalmente commit sul server SVN

    (master)$> git svn dcommit
    
  • Torna al lavoro e infine ripristina i file memorizzati:

    (master)$> git checkout work
    (work)$> git stash pop
    

4
Questo è ESATTAMENTE il flusso di lavoro che questo git n00b stava cercando. Crea un ramo locale per correggere un bug, fai qualunque cosa, quindi invialo a svn con un SINGOLO commit. Usando la risposta più votata qui ho incasinato quello che stavo facendo - non potevo git svn rebase senza errori.
João Bragança,

19
Non è esattamente ciò a cui i documenti git-svn citati nell'op mettono in guardia? Eseguendo l'unione con l' --no-ffopzione, si sta creando esplicitamente un commit unione (un commit con due genitori) anziché un avanzamento rapido. Per garantire che tutti i commit nel ramo svn-tracking siano commit monoparentali, tutte le fusioni devono essere anticipate ( --ff-onlypuò aiutare con questo) o, se il tronco è cambiato dietro la schiena, a --squash, giusto?
jemmons,

4
Funziona per un ramo una tantum che elimini immediatamente, ma git svn dcommitriscrive il commit git che gli dai. Ciò significa che perdi l'altro genitore e ora il tuo repository git non ha record in cui hai mai unito quel ramo master. Questo può anche lasciare la directory di lavoro in uno stato incoerente.
rescdsk,

9
usa git merge --no-ff work -m "commit message"invece di fare un git commit --amendpasso in più
tekumara

3
per me preferirei usare "git merge --ff-only work" poiché voglio preservare tutti i miei commit e non solo l'ultimo
Moataz Elmasry,

49

La creazione di filiali locali è sicuramente possibile con git-svn. Fintanto che stai usando solo i rami locali per te stesso e non stai cercando di usare git per unire i rami svn a monte, dovresti andare bene.

Ho un ramo "master" che uso per tracciare il server svn. Questa è l'unica branca da cui do il mio parere. Se sto facendo un po 'di lavoro, creo un ramo tematico e ci lavoro. Quando voglio impegnarlo, faccio quanto segue:

  1. Impegna tutto nel ramo dell'argomento
  2. git svn rebase (risolvi eventuali conflitti tra il tuo lavoro e svn)
  3. git checkout master
  4. git svn rebase (questo rende il passaggio successivo una fusione rapida, vedi i commenti di Aaron di seguito)
  5. git merge topic_branch
  6. risolvere eventuali conflitti di unione (non dovrebbe essercene nessuno a questo punto)
  7. git svn dcommit

Ho anche un'altra situazione in cui ho bisogno di mantenere alcune modifiche locali (per il debug) che non dovrebbero mai essere inviate a svn. Per questo, ho il ramo master sopra ma anche un ramo chiamato "lavoro" dove normalmente lavoro. I rami degli argomenti sono ramificati dal lavoro. Quando voglio eseguire il commit del lavoro lì, controllo master e utilizzo cherry-pick per selezionare i commit dal ramo di lavoro che voglio impegnare in svn. Questo perché voglio evitare di impegnare i tre commit di modifica locali. Quindi, dico dal ramo principale e ribasso tutto.

Vale la pena correre git svn dcommit -nprima per assicurarsi che stai per impegnarti esattamente quello che intendi impegnare. A differenza di Git, riscrivere la storia in svn è difficile!

Ritengo che ci debba essere un modo migliore per unire la modifica in una branca dell'argomento mentre si saltano gli impegni di modifica locali rispetto all'uso di cherry-pick, quindi se qualcuno ha qualche idea sarebbe il benvenuto.


Se lo capisco correttamente, in git, fondere git con svn è OK, ma non svn con svn? Quindi non riesco a unire il mio svn / branch con svn / trunk in git?
Knut Eldhuset,

1
Riscrivere la storia in SVN è difficile se vuoi fare qualcosa di banale. In casi non banali è praticamente impossibile.
Aristotele Pagaltzis,

19
La risposta di Greg Hewgill ha molti voti, ma credo che sia sbagliata. La fusione da topic_branch a master è sicura solo se si tratta solo di una fusione in avanti veloce. Se si tratta di un'unione reale che richiede un commit di unione, il commit di unione andrà perso quando si esegue il dcommit. La prossima volta che provi a fondere topic_branch in master, git pensa che la prima unione non sia mai avvenuta e si scatena l'inferno. Visualizza la domanda Perché git svn dcommit perde la cronologia degli commit di fusione per le filiali locali?
Aaron,

1
@Greg Hewgill, la modifica non è chiara. Hai scritto che il rebase "rende il prossimo commit una fusione in avanti veloce". Non so cosa significhi, perché un'unione di avanzamento rapido non comporta un commit, ma forse intendevi "rendere l'unione successiva un'unione di avanzamento rapido". Ma se questo è ciò che intendevi, non è corretto. Se un'unione da topic_branch a master è un'unione di avanzamento rapido o meno dipende dal fatto che siano stati effettuati commit su master dal punto di diramazione. Quando si ribatte il master, ciò crea nuovi commit sul master, quindi un'unione successiva non è necessariamente un'unione con avanzamento rapido.
Aaron,

1
@Aaron: Sì, non so cosa stavo pensando lì. L'ho corretto di nuovo, si spera che abbia un senso. Uno dei problemi è che ci sono così tanti modi di fare le cose in Git, che ci vuole una certa spiegazione per descrivere una specifica sequenza di azioni.
Greg Hewgill,

33

Soluzione semplice: rimuovere il ramo "lavoro" dopo l'unione

Risposta breve: puoi usare git come preferisci (vedi sotto per un semplice flusso di lavoro), inclusa l'unione. Assicurati solo di seguire ogni ' git merge work ' con ' git branch -d work ' per eliminare il ramo di lavoro temporaneo.

Spiegazione di fondo: il problema di unione / dcommit è che ogni volta che 'git svn dcommit' un ramo, la cronologia di unione di quel ramo è 'appiattita': git dimentica tutte le operazioni di unione che sono entrate in questo ramo: viene conservato solo il contenuto del file, ma il fatto che questo contenuto (parzialmente) provenga da un altro ramo specifico è perso. Vedi: Perché git svn dcommit perde la cronologia degli commit di fusione per le filiali locali?

(Nota: non c'è molto che git-svn possa fare al riguardo: svn semplicemente non capisce le fusioni git molto più potenti. Quindi, all'interno del repository svn queste informazioni di unione non possono essere rappresentate in alcun modo.)

Ma questo è l' intero problema. Se elimini il ramo 'lavoro' dopo che è stato unito al 'ramo principale', il tuo repository git è pulito al 100% e assomiglia esattamente al tuo repository svn.

Il mio flusso di lavoro: ovviamente, per prima cosa ho clonato il repository svn remoto in un repository git locale (questo potrebbe richiedere del tempo):

$> git svn clone <svn-repository-url> <local-directory>

Quindi tutto il lavoro avviene all'interno della "directory locale". Ogni volta che devo ottenere aggiornamenti dal server (come 'svn update'), faccio:

$> git checkout master
$> git svn rebase

Faccio tutto il mio lavoro di sviluppo in un 'lavoro' di ramo separato creato in questo modo:

$> git checkout -b work

Naturalmente, puoi creare tutti i rami per il tuo lavoro che desideri e unirli e riformularli tra loro come preferisci (eliminali quando hai finito con loro --- come discusso di seguito). Nel mio lavoro normale, mi impegno molto frequentemente:

$> git commit -am '-- finished a little piece of work'

Il prossimo passo (git rebase -i) è facoltativo --- sta semplicemente ripulendo la storia prima di archiviarla su svn: Una volta raggiunta una pietra miliare stabile che voglio condividere con gli altri, riscrivo la storia di questo 'lavoro' diramare e ripulire i messaggi di commit (gli altri sviluppatori non hanno bisogno di vedere tutti i piccoli passi ed errori che ho fatto lungo la strada --- solo il risultato). Per questo, lo faccio

$> git log

e copia l'hash sha-1 dell'ultimo commit attivo nel repository svn (come indicato da un git-svn-id). Quindi chiamo

$> git rebase -i 74e4068360e34b2ccf0c5869703af458cde0cdcb

Basta incollare l'hash sha-1 del nostro ultimo svn commit anziché il mio. Potresti voler leggere la documentazione con 'git help rebase' per i dettagli. In breve: questo comando prima apre un editor che presenta i tuoi commit ---- cambia 'pick' in 'squash' per tutti quei commit che vuoi schiacciare con i commit precedenti. Naturalmente, la prima riga dovrebbe rimanere come un 'pick'. In questo modo, puoi condensare i tuoi molti piccoli impegni in una o più unità significative. Salva ed esci dall'editor. Riceverai un altro editor che ti chiederà di riscrivere i messaggi del registro di commit.

In breve: dopo aver finito 'l'hacking del codice', massaggio il mio ramo 'lavoro' fino a quando non appare come voglio presentarlo agli altri programmatori (o come voglio vedere il lavoro tra qualche settimana quando sfoglio la cronologia) .

Per inviare le modifiche al repository svn, faccio:

$> git checkout master
$> git svn rebase

Ora siamo di nuovo al vecchio ramo 'master' aggiornato con tutte le modifiche avvenute nel frattempo nel repository svn (le tue nuove modifiche sono nascoste nel ramo 'work').

Se ci sono cambiamenti che potrebbero scontrarsi con i tuoi nuovi cambiamenti di "lavoro", devi risolverli localmente prima di poter spingere il tuo nuovo lavoro (vedi i dettagli più avanti). Quindi, possiamo inviare le nostre modifiche a svn:

$> git checkout master
$> git merge work        # (1) merge your 'work' into 'master'
$> git branch -d work    # (2) remove the work branch immediately after merging
$> git svn dcommit       # (3) push your changes to the svn repository

Nota 1: Il comando 'git branch -d work' è abbastanza sicuro: ti permette solo di eliminare i rami che non ti servono più (perché sono già uniti nel tuo ramo attuale). Se si esegue questo comando per errore prima di unire il proprio lavoro al ramo "master", viene visualizzato un messaggio di errore.

Nota 2: assicurati di eliminare il tuo ramo con 'git branch -d work' tra fusione e dcommit: se provi a cancellare il ramo dopo dcommit, ricevi un messaggio di errore: Quando fai 'git svn dcommit', git dimentica che la tua filiale è stata unita a "master". Devi rimuoverlo con 'git branch -D work' che non fa il controllo di sicurezza.

Ora creo immediatamente un nuovo ramo "lavoro" per evitare l'hacking accidentale sul ramo "principale":

$> git checkout -b work
$> git branch            # show my branches:
  master
* work

Integrazione del tuo "lavoro" con le modifiche su svn: Ecco cosa faccio quando "git svn rebase" rivela che altri hanno cambiato il repository svn mentre stavo lavorando sul mio ramo "lavoro":

$> git checkout master
$> git svn rebase              # 'svn pull' changes
$> git checkout work           # go to my work
$> git checkout -b integration # make a copy of the branch
$> git merge master            # integrate my changes with theirs
$> ... check/fix/debug ...
$> ... rewrite history with rebase -i if needed

$> git checkout master         # try again to push my changes
$> git svn rebase              # hopefully no further changes to merge
$> git merge integration       # (1) merge your work with theirs
$> git branch -d work          # (2) remove branches that are merged
$> git branch -d integration   # (2) remove branches that are merged
$> git svn dcommit             # (3) push your changes to the svn repository

Esistono soluzioni più potenti: il flusso di lavoro presentato è semplicistico: usa i poteri di git solo all'interno di ogni round di 'update / hack / dcommit' --- ma lascia la storia del progetto a lungo termine altrettanto lineare come il repository svn. Questo va bene se vuoi solo iniziare a usare git merges in piccoli primi passi in un progetto svn legacy.

Quando acquisisci maggiore familiarità con l'unione di git, non esitare a esplorare altri flussi di lavoro: se sai cosa stai facendo, puoi mescolare le fusioni di git con le fusioni di svn ( Usando git-svn (o simili) solo per dare una mano con svn merge? )


Questo sembra inutilmente complesso. Ho sentito parlare di gente git merge --squash work, perché non farlo? Riesco a vedere che esegui commit di squash nel ramo prima di unirti se stai creando più di un 'pick' (supponiamo che tu abbia 8 commit e stai trasformando ciascuno di 4 commit in 1 e unendo 2 commit a master). Inoltre, quando aggiorno il mio ramo 'lavoro', lo faccio rebase, è più semplice che creare un altro ramo per il mio ramo e fare fusioni ...
void.pointer

8

Greg Hewgill la risposta in cima non è sicura! Se un nuovo commit apparisse sul trunk tra i due "git svn rebase", l'unione non avanzi rapidamente.

Può essere garantito usando il flag "--ff-only" per git-merge, ma di solito non eseguo "git svn rebase" nel ramo, solo "git rebase master" su di esso (supponendo che sia solo un locale ramo). Quindi, un "git merge thebranch" è garantito per avanzare rapidamente.


6

Un modo sicuro per unire i rami svn in git è usare git merge --squash. Ciò creerà un unico commit e si interromperà l'aggiunta di un messaggio.

Supponiamo che tu abbia un argomento svn branch, chiamato svn-branch.

git svn fetch
git checkout remotes/trunk -b big-merge
git merge --squash svn-branch

a questo punto hai tutte le modifiche dal ramo svn schiacciate in un commit in attesa nell'indice

git commit

Come altri hanno sottolineato, tuttavia, questo perde la granularità degli impegni.
Kzqai,

@Tchalvak: a volte vuoi proprio questo. Mi sono spesso impegnato in una sezione di funzioni "disordine risolto dal precedente commit" e questi vicoli ciechi che mi piace nascondere a causa di una buona reputazione :-)
schoetbi

1
Sì, anche se lo considero una camminata sul filo del rasoio, poiché ogni volta che schiacci, perdi anche la capacità di separare i singoli impegni in seguito. Solo una questione di diverse scelte di standard di impegno.
Kzqai,

1
@schoetbi sì, a volte è necessario ripulire la cronologia disordinata prima della pubblicazione. È qui che 'git rebase -i <trunk>' è di grande aiuto: puoi unirti / riordinare / rimuovere e persino dividere (!) Commit, aggiustando msg in modo selettivo.
inger il

5

Rebase il ramo git locale sul ramo master git quindi dcommit e in quel modo sembra che tu abbia fatto tutti quei commit in sequenza in modo che le persone svn possano vederlo linearmente come sono abituati. Quindi supponendo che tu abbia un ramo locale chiamato topic che potresti fare

git rebase master topic

che quindi eseguirà i tuoi commit sul ramo principale pronto per te per essere scomposto

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.