Domanda

Sto usando git su un nuovo progetto che ha due rami di sviluppo paralleli, ma attualmente sperimentali:

  • master: importazione di codebase esistenti più alcune mod di cui sono generalmente sicuro
  • exp1: ramo sperimentale n. 1
  • exp2: ramo sperimentale n. 2

git merge --no-commit e git checkout rappresentano due approcci architetturali molto diversi. Fino a quando non andrò oltre non ho modo di sapere quale (se uno dei due) funzionerà. Mentre faccio progressi in un ramo a volte ho delle modifiche che potrebbero essere utili nell'altro ramo e vorrei unire solo quelle.

Qual è il modo migliore per unire i cambiamenti selettivi da un ramo di sviluppo a un altro lasciando indietro tutto il resto?

Approcci che ho considerato:

  1. exp seguito da unstaging manuale di un gran numero di modifiche che non voglio rendere comuni tra i rami.

  2. Copia manuale dei file comuni in una directory temporanea seguita da git-merge per spostarsi sull'altro ramo e quindi copia manuale più estesa dalla directory temporanea nell'albero di lavoro.

  3. Una variazione di quanto sopra. Abbandonare i rami <=> per ora e utilizzare due repository locali aggiuntivi per la sperimentazione. Ciò rende la copia manuale dei file molto più semplice.

Tutti e tre questi approcci sembrano noiosi e soggetti a errori. Spero che ci sia un approccio migliore; qualcosa di simile a un parametro del percorso del filtro che renderebbe <=> più selettivo.

È stato utile?

Soluzione

Si utilizza il comando cherry-pick per ottenere singoli commit da un ramo.

Se le modifiche desiderate non rientrano nei singoli commit, utilizzare il metodo mostrato qui per dividere il impegnarsi in impegni individuali . In parole povere, si utilizza git rebase -i per ottenere il commit originale da modificare, quindi git reset HEAD^ per ripristinare selettivamente le modifiche, quindi git commit per eseguire il commit di quel bit come nuovo commit nella cronologia.

C'è un altro bel metodo qui in Red Hat Magazine, dove usano git add --patch o possibilmente git add --interactive che ti permette di aggiungere solo parti di un pezzo, se vuoi dividere in modo diverso cambia in un singolo file (cerca in quella pagina " split ").

Dopo aver diviso le modifiche, ora puoi scegliere solo quelle che desideri.

Altri suggerimenti

Ho avuto lo stesso identico problema menzionato in precedenza. Ma ho trovato questo più chiaro nello spiegare la risposta.

Sommario:

  • Verifica i percorsi dal ramo che desideri unire,

    $ git checkout source_branch -- <paths>...
    

    Suggerimento: funziona anche senza -- come visto nel post collegato.

  • o per unire selettivamente gli hunk

    $ git checkout -p source_branch -- <paths>...
    

    In alternativa, utilizzare reset e quindi aggiungere con l'opzione -p,

    $ git reset <paths>...
    $ git add -p <paths>...
    
  • Finalmente commit

    $ git commit -m "'Merge' these changes"
    

Per unire selettivamente i file da un ramo a un altro ramo, eseguire

git merge --no-ff --no-commit branchX

dove branchX è il ramo da cui si desidera unire il ramo corrente.

L'opzione --no-commit metterà in scena i file che sono stati uniti da Git senza effettivamente eseguirne il commit. Questo ti darà l'opportunità di modificare i file uniti come desideri e poi di impegnarli da soli.

A seconda di come si desidera unire i file, ci sono quattro casi:

1) Vuoi una vera unione.

In questo caso, accetti i file uniti nel modo in cui Git li ha uniti automaticamente e poi li commetti.

2) Ci sono alcuni file che non vuoi unire.

Ad esempio, si desidera conservare la versione nel ramo corrente e ignorare la versione nel ramo da cui si sta unendo.

Per selezionare la versione nel ramo corrente, eseguire:

git checkout HEAD file1

Questo recupererà la versione di file1 nel ramo corrente e sovrascriverà il git status generato automaticamente da Git.

3) Se vuoi la versione in branchX (e non una vera unione).

Esegui:

git checkout branchX file1

Questo recupererà la versione di file2 in file3 e sovrascriverà master unito automaticamente da Git.

4) L'ultimo caso è se si desidera selezionare solo fusioni specifiche in git diff --cached.

In questo caso, puoi modificare direttamente il git commit modificato, aggiornarlo a quello che vorresti che diventasse la versione di file4 e quindi eseguire il commit.

Se Git non riesce a unire automaticamente un file, lo riporterà come " non unito " e produrre una copia in cui sarà necessario risolvere manualmente i conflitti.


Per spiegare ulteriormente con un esempio, supponiamo che tu voglia unire <=> nel ramo corrente:

git status

# On branch master
# Changes to be committed:
#
#       modified:   file1
#       modified:   file2
#       modified:   file3
# Unmerged paths:
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
#       both modified:      file4
#

Esegui quindi il comando <=> per visualizzare lo stato dei file modificati.

Ad esempio:

git diff --cached file1
git diff --cached file2
git diff --cached file3

Dove <=>, <=> e <=> sono i file git che si sono uniti correttamente.

Ciò significa che le modifiche in <=> e <=> per tutti e tre questi file sono state combinate insieme senza conflitti.

Puoi controllare come è stata eseguita l'unione eseguendo <=>;

git checkout branchX file2

Se ritieni che alcune fusioni siano indesiderabili, puoi

  1. modifica direttamente il file
  2. risparmiare
  3. <=>

Se non si desidera unire <=> e si desidera conservare la versione nel ramo corrente

Esegui

<*>

Se non si desidera unire <=> e si desidera solo la versione in <=>

Esegui

<*>

Se vuoi che <=> venga unito automaticamente, non fare nulla.

Git lo ha già unito a questo punto.

<=> sopra è un'unione fallita di Git. Ciò significa che ci sono cambiamenti in entrambi i rami che si verificano sulla stessa linea. Qui è dove dovrai risolvere i conflitti manualmente. Puoi scartare l'unione fatta modificando direttamente il file o eseguendo il comando di checkout per la versione nel ramo che vuoi <=> diventare.

Infine, non dimenticare di <=>.

Non mi piacciono gli approcci di cui sopra. L'uso di cherry-pick è ottimo per scegliere una singola modifica, ma è una seccatura se si desidera apportare tutte le modifiche ad eccezione di alcune non valide. Ecco il mio approccio.

Non ci sono --interactive argomenti che puoi passare a git merge.

Ecco l'alternativa:

Hai alcune modifiche nel ramo 'feature' e vuoi portarne alcune ma non tutte su 'master' in un modo non sciatto (cioè non vuoi scegliere e impegnare ognuna di esse)

git checkout feature
git checkout -b temp
git rebase -i master

# Above will drop you in an editor and pick the changes you want ala:
pick 7266df7 First change
pick 1b3f7df Another change
pick 5bbf56f Last change

# Rebase b44c147..5bbf56f onto b44c147
#
# Commands:
# pick = use commit
# edit = use commit, but stop for amending
# squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

git checkout master
git pull . temp
git branch -d temp

Quindi basta avvolgerlo in uno script di shell, cambiare master in $ a e cambiare funzionalità in $ da e sei a posto:

#!/bin/bash
# git-interactive-merge
from=$1
to=$2
git checkout $from
git checkout -b ${from}_tmp
git rebase -i $to
# Above will drop you in an editor and pick the changes you want
git checkout $to
git pull . ${from}_tmp
git branch -d ${from}_tmp

Esiste un altro modo di procedere:

git checkout -p

È un mix tra git checkout e git add -p e potrebbe essere esattamente quello che stai cercando:

   -p, --patch
       Interactively select hunks in the difference between the <tree-ish>
       (or the index, if unspecified) and the working tree. The chosen
       hunks are then applied in reverse to the working tree (and if a
       <tree-ish> was specified, the index).

       This means that you can use git checkout -p to selectively discard
       edits from your current working tree. See the “Interactive Mode”
       section of git-add(1) to learn how to operate the --patch mode.

Mentre alcune di queste risposte sono piuttosto buone, mi sembra che nessuna di esse abbia effettivamente risposto al vincolo originale di OP: selezionare determinati file da determinati rami. Questa soluzione lo fa, ma può essere noiosa se ci sono molti file.

Supponiamo che tu abbia i rami master, exp1 e exp2. Si desidera unire un file da ciascuno dei rami sperimentali in master. Vorrei fare qualcosa del genere:

git checkout master
git checkout exp1 path/to/file_a
git checkout exp2 path/to/file_b

# save these files as a stash
git stash
# merge stash with master
git merge stash

Questo ti darà differenze nel file per ciascuno dei file che desideri. Niente di più. Nientemeno. È utile avere cambiamenti di file radicalmente diversi tra le versioni: nel mio caso, cambiare un'app da Rails 2 a Rails 3.

MODIFICA : questo unirà i file, ma farà una fusione intelligente. Non sono riuscito a capire come utilizzare questo metodo per ottenere informazioni diff nel file (forse lo faranno ancora per differenze estreme. Piccole cose fastidiose come gli spazi bianchi vengono ricongiunte a meno che tu non usi l'opzione -s recursive -X ignore-all-space)

La risposta di 1800 INFORMATION è completamente corretta. Come git noob, tuttavia, & Quot; usa git cherry-pick & Quot; non era abbastanza per farmi capire senza scavare un po 'di più su Internet, quindi ho pensato di pubblicare una guida più dettagliata nel caso in cui qualcuno fosse su una barca simile.

Il mio caso d'uso era voler selezionare selettivamente le modifiche dal ramo github di qualcun altro nel mio. Se disponi già di una filiale locale con le modifiche, devi solo eseguire i passaggi 2 e 5-7.

  1. Crea (se non creato) una filiale locale con le modifiche che desideri apportare.

    $ git branch mybranch <base branch>

  2. Passaci.

    $ git checkout mybranch

  3. Rimuovi le modifiche desiderate dall'account dell'altra persona. Se non l'hai già fatto, ti consigliamo di aggiungerli come telecomando.

    $ git remote add repos-w-changes <git url>

  4. Rimuovi tutto dal loro ramo.

    $ git pull repos-w-changes branch-i-want

  5. Visualizza i log di commit per vedere quali modifiche desideri:

    $ git log

  6. Torna al ramo in cui desideri inserire le modifiche.

    $ git checkout originalbranch

  7. Cherry scegli i tuoi commit, uno per uno, con gli hash.

    $ git cherry-pick -x hash-of-commit

Suggerimento per il cappello: http://www.sourcemage.org/Git_Guide

Ecco come è possibile sostituire il file Myclass.java nel ramo master con feature1 nel ramo <=>. Funzionerà anche se <=> non esiste su <=>.

git checkout master
git checkout feature1 Myclass.java

Nota che questo sovrascriverà - non unirà - e ignorerà piuttosto le modifiche locali nel ramo principale.

Il modo semplice, per unire file specifici da due rami, non semplicemente sostituire i file specifici con quelli di un altro ramo.

Passaggio uno: diff. i rami

git diff branch_b > my_patch_file.patch

Crea un file patch della differenza tra il ramo corrente e branch_b

Passaggio due: applica la patch ai file corrispondenti a un modello

git apply -p1 --include=pattern/matching/the/path/to/file/or/folder my_patch_file.patch

note utili sulle opzioni

È possibile utilizzare * come carattere jolly nel modello di inclusione.

Le barre non devono essere sfuggite.

Inoltre, puoi usare --exclude invece e applicarlo a tutto tranne i file che corrispondono allo schema, o invertire la patch con -R

L'opzione -p1 è un blocco dal comando * unix patch e il fatto che il contenuto del file patch anteponga ogni nome di file con a/ o b/ (o più a seconda di come è stato generato il file patch) è necessario eseguire lo striping in modo che possa capire il file reale nel percorso del file a cui deve essere applicata la patch.

Dai un'occhiata alla pagina man per git-apply per ulteriori opzioni.

Passaggio tre: non esiste un passaggio tre

Ovviamente vorresti impegnare le tue modifiche, ma chi può dire che non hai altre modifiche correlate da fare prima di effettuare il commit.

Ecco come puoi fare in modo che la cronologia segua solo un paio di file da un altro ramo con un minimo sforzo, anche se un più " semplice " l'unione avrebbe comportato molte più modifiche indesiderate.

Per prima cosa, farai il passo insolito di dichiarare in anticipo che ciò che stai per commettere è un'unione, senza che git faccia nulla ai file nella tua directory di lavoro:

git merge --no-ff --no-commit -s ours branchname1

. . . dove " branchname " è qualunque cosa tu pretenda di fondere. Se dovessi impegnarti subito, non farebbe alcun cambiamento ma mostrerebbe comunque origini da parte dell'altro ramo. Puoi aggiungere più rami / tag / ecc. alla riga di comando, se necessario, anche. A questo punto, tuttavia, non ci sono modifiche da eseguire, quindi procurati i file dalle altre revisioni, in seguito.

git checkout branchname1 -- file1 file2 etc

Se ti stai unendo da più di un altro ramo, ripeti se necessario.

git checkout branchname2 -- file3 file4 etc

Ora i file dell'altro ramo sono nell'indice, pronti per il commit, con cronologia.

git commit

e avrai molte spiegazioni da fare in quel messaggio di commit.

Si prega di notare, tuttavia, nel caso in cui non fosse chiaro, che questa è una cosa da fare. Non è nello spirito di ciò che un & Quot; ramo & Quot; è, e cherry-pick è un modo più onesto di fare quello che faresti, qui. Se vuoi fare un altro & Quot; unisci & Quot; per altri file sullo stesso ramo che non hai portato l'ultima volta, ti fermerà con un " già aggiornato " Messaggio. È un sintomo di non ramificarsi quando dovremmo avere, tra & Quot; da & Quot; il ramo dovrebbe essere più di un ramo diverso.

So di essere un po 'in ritardo, ma questo è il mio flusso di lavoro per unire file selettivi.

#make a new branch ( this will be temporary)
git checkout -b newbranch
# grab the changes 
git merge --no-commit  featurebranch
# unstage those changes
git reset HEAD
(you can now see the files from the merge are unstaged)
# now you can chose which files are to be merged.
git add -p
# remember to "git add" any new files you wish to keep
git commit

Ho trovato questo post per contenere la risposta più semplice. Fai semplicemente:

$ #git checkout <branch from which you want files> <file paths>

Esempio:

$ #pulling .gitignore file from branchB into current branch
$ git checkout branchB .gitignore

Vedi il post per maggiori informazioni.

Il modo più semplice è impostare il repository sul ramo con cui si desidera unire, quindi eseguire,

git checkout [branch with file] [path to file you would like to merge]

Se corri

git status

vedrai il file già in scena ...

Quindi esegui

git commit -m "Merge changes on '[branch]' to [file]"

Semplice.

È strano che git non abbia ancora uno strumento così conveniente " out of the box " ;. Lo uso pesantemente quando aggiorno un ramo della versione precedente (che ha ancora molti utenti software) con solo alcuni correzioni di bug dal ramo della versione corrente. In questo caso è spesso necessario ottenere rapidamente solo alcune righe di codice dal file nel trunk, ignorando molte altre modifiche (che non dovrebbero andare nella vecchia versione) ... E ovviamente in questo caso è necessaria l'unione interattiva a tre vie , git checkout --patch <branch> <file path> non è utilizzabile per questo scopo di unione selettiva.

Puoi farlo facilmente:

Aggiungi questa riga alla sezione [alias] nel tuo .gitconfig file globale .git/config locale:

[alias]
    mergetool-file = "!sh -c 'git show $1:$2 > $2.theirs; git show $(git merge-base $1 $(git rev-parse HEAD)):$2 > $2.base; /C/BCompare3/BCompare.exe $2.theirs $2 $2.base $2; rm -f $2.theirs; rm -f $2.base;' -"

Implica che usi Beyond Compare. Passa al software che preferisci, se necessario. Oppure puoi cambiarlo in unione automatica a tre vie se non hai bisogno della fusione selettiva interattiva:

[alias]
    mergetool-file = "!sh -c 'git show $1:$2 > $2.theirs; git show $(git merge-base $1 $(git rev-parse HEAD)):$2 > $2.base; git merge-file $2 $2.base $2.theirs; rm -f $2.theirs; rm -f $2.base;' -"

Quindi usa in questo modo:

git mergetool-file <source branch> <file path>

Questo ti darà la vera opportunità selettiva alberata di unire qualsiasi file in un altro ramo.

Non è esattamente quello che stavi cercando, ma mi è stato utile:

git checkout -p <branch> -- <paths> ...

È un mix di alcune risposte.

Ho avuto lo stesso identico problema menzionato in precedenza. Ma ho trovato questo blog git più chiaro nello spiegare la risposta.

Comando dal link sopra:

#You are in the branch you want to merge to
git checkout <branch_you_want_to_merge_from> <file_paths...>

Vorrei fare un

  

git diff commit1..commit2 filepattern | git-apply --index & amp; & amp; git commit

In questo modo è possibile limitare l'intervallo di commit per un filepattern da un ramo.

Rubato da: http: //www.gelato.unsw .edu.au / archives / git / 0701 / 37964.html

Mi piace la risposta 'git-interactive-merge', sopra, ma ce n'è una più semplice. Lascia che git faccia questo per te usando una combinazione rebase di interattivo e su:

      A---C1---o---C2---o---o feature
     /
----o---o---o---o master

Quindi, nel caso tu voglia C1 e C2 dal ramo 'feature' (punto del ramo 'A'), ma per ora niente del resto.

# git branch temp feature
# git checkout master
# git rebase -i --onto HEAD A temp

Che, come sopra, ti porta nell'editor interattivo in cui selezioni le linee 'pick' per C1 e C2 (come sopra). Salva ed esci, e poi procederà con il rebase e ti darà il ramo 'temp' e anche HEAD al master + C1 + C2:

      A---C1---o---C2---o---o feature
     /
----o---o---o---o-master--C1---C2 [HEAD, temp]

Quindi puoi semplicemente aggiornare master su HEAD ed eliminare il ramo temporaneo e sei a posto:

# git branch -f master HEAD
# git branch -d temp

So che questa domanda è vecchia e ci sono molte altre risposte, ma ho scritto il mio script chiamato 'pmerge' per unire parzialmente le directory. È un work in progress e sto ancora imparando sia script git che bash.

Questo comando utilizza git merge --no-commit e quindi non applica le modifiche che non corrispondono al percorso fornito.

Utilizzo: git pmerge branch path
Esempio: git merge develop src/

Non l'ho testato ampiamente. La directory di lavoro dovrebbe essere libera da qualsiasi modifica senza commit e file non tracciati.

#!/bin/bash

E_BADARGS=65

if [ $# -ne 2 ]
then
    echo "Usage: `basename $0` branch path"
    exit $E_BADARGS
fi

git merge $1 --no-commit
IFS=$'\n'
# list of changes due to merge | replace nulls w newlines | strip lines to just filenames | ensure lines are unique
for f in $(git status --porcelain -z -uno | tr '\000' '\n' | sed -e 's/^[[:graph:]][[:space:]]\{1,\}//' | uniq); do
    [[ $f == $2* ]] && continue
    if git reset $f >/dev/null 2>&1; then
        # reset failed... file was previously unversioned
        echo Deleting $f
        rm $f
    else
        echo Reverting $f
        git checkout -- $f >/dev/null 2>&1
    fi
done
unset IFS

Che dire di git reset --soft branch? Sono sorpreso che nessuno lo abbia ancora menzionato.

Per me, è il modo più semplice per selezionare selettivamente le modifiche da un altro ramo, poiché questo comando inserisce nel mio albero di lavoro tutte le modifiche alle differenze e posso facilmente selezionare o ripristinare quella di cui ho bisogno. In questo modo, ho il pieno controllo dei file impegnati.

È possibile utilizzare read-tree per leggere o unire l'albero remoto specificato nell'indice corrente, ad esempio:

git remote add foo git@example.com/foo.git
git fetch foo
git read-tree --prefix=my-folder/ -u foo/master:trunk/their-folder

Per eseguire l'unione, utilizzare invece -m.

Vedi anche: Come unire una sottodirectory in git?

Un approccio semplice per la fusione / il commit selettivi per file:

git checkout dstBranch git merge srcBranch // make changes, including resolving conflicts to single files git add singleFile1 singleFile2 git commit -m "message specific to a few files" git reset --hard # blow away uncommitted changes

Se non hai troppi file che sono stati modificati, questo ti lascerà senza ulteriori commit.

1. Ramo duplicato temporaneamente
$ git checkout -b temp_branch

2. Ripristina l'ultimo commit desiderato
$ git reset --hard HEAD~n, dove n è il numero di commit necessari per tornare indietro

3. Verifica ogni file dalla filiale originale
$ git checkout origin/original_branch filename.ext

Ora puoi eseguire il commit e forzare il push (per sovrascrivere il telecomando), se necessario.

Quando sono cambiati solo pochi file tra i commit correnti dei due rami, unisco manualmente le modifiche passando attraverso i diversi file.

git difftoll <branch-1>..<branch-2>

Se hai solo bisogno di unire una particolare directory e lasciare tutto il resto intatto e tuttavia preservare la cronologia, potresti provare questo ... crea un nuovo target-branch fuori dal master prima di sperimentare.

I passaggi seguenti presuppongono che tu abbia due rami source-branch e dir-to-merge e la directory dir-to-retain che desideri unire si trova in <=>. Supponi anche di avere altre directory come <=> nella destinazione che non desideri modificare e conservare la cronologia. Inoltre, presuppone che ci siano conflitti di unione in <=>.

git checkout target-branch
git merge --no-ff --no-commit -X theirs source-branch
# the option "-X theirs", will pick theirs when there is a conflict. 
# the options "--no--ff --no-commit" prevent a commit after a merge, and give you an opportunity to fix other directories you want to retain, before you commit this merge.

# the above, would have messed up the other directories that you want to retain.
# so you need to reset them for every directory that you want to retain.
git reset HEAD dir-to-retain
# verify everything and commit.
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top