Come unisco una sottodirectory in Git?


86

È possibile unire solo le modifiche per una sottodirectory da un ramo Git locale a un ramo Git remoto o è "tutto o niente"?

Ad esempio, ho:

branch-a
 - content-1
 - dir-1
   - content-2

e

branch-b
 - content-1
 - dir-1
   - `content-2

Voglio solo unire il contenuto di branch-a dir-1 con il contenuto di branch-b dir-1.


1
Penso che questo sia un duplicato di: stackoverflow.com/questions/449541/…
Karl Voigtland

Risposte:


81

Proprio come alternativa alla domanda SO " Come si uniscono file selettivi con git-merge? ", Ho appena trovato questo thread GitHub che potrebbe essere più adattato per unire un'intera sottodirectory, basato su git read-tree :

  • My repository => cookbooks
    My repository target directory =>cookbooks/cassandra
  • Repository remoto => infochimps
    Fonte del repository remoto in cui desidero unire cookbooks/cassandra=>infochimps/cookbooks/cassandra

Ecco i comandi che ho usato per unirli

  • Aggiungi il repository e recuperalo
git remote add -f infochimps git: //github.com/infochimps/cluster_chef.git
  • Esegui l'unione
git merge --allow-unrelated-histories -s ours --no-commit infochimps / master

(questo esegue un'unione utilizzando la strategia "ours" ( -s ours), che elimina le modifiche dal ramo di origine. Questo registra il fatto che infochimps/masterè stato unito, senza effettivamente modificare alcun file nel ramo di destinazione)

  • Unisci solo infochimps/cookbooks/cassandraincassandra
git read-tree --prefix = cassandra / -u infochimps / master: cookbooks / cassandra

Questo legge l'albero solo per la sottodirectory sorgente richiesta cookbooks/cassandra, cioè sul ramo a monte del repository sorgente .

Nota che anche il nome della sottodirectory di destinazione dovrebbe essere cookbooks/cassandra, altrimenti vedrai:

fatal: Not a valid object name
  • Commetti il ​​cambiamento
 git commit -m 'fusione in infochimps cassandra'

Addendum

È bizzarro, [modificami] , ma il read-treepassaggio potrebbe fallire in questo modo:

error: Entry 'infochimps/cookbooks/cassandra/README' overlaps with 'cookbooks/cassandra/README'. Cannot bind.

... anche quando entrambi i file sono identici . Questo potrebbe aiutare:

git rm -r cassandra
git read-tree --prefix=cassandra/ -u infochimps/master:cookbooks/cassandra

Ma ovviamente, verifica manualmente che questo faccia ciò che desideri.


3
@ Martin git-scm.com/docs/git-rev-parse#_specifying_revisions cercare <rev>:<path>, ad esempio HEAD:README, :README,master:./README
VonC

6
Il git read-treepassaggio fallisce per me:error: Entry 'foo/bar/baz.php' overlaps with 'bar/baz.php'. Cannot bind.
Weston Ruter

1
@ VonC ma no, ho bisogno della cronologia. Questa risposta non tiene conto del caso in cui sono presenti modifiche locali ai file all'interno dell'albero. Quindi deve fondersi.
Weston Ruter

Per me, l' overlaps witherrore di cui sopra si apre anche su file identici . Quanto è strano?
ulidtko

1
@ChrisHalcrow Per registrare il fatto che infochimps/masterè stato unito, ma senza modificare effettivamente alcun file nel ramo di destinazione. Perché il passaggio successivo git read-tree --prefix=cassandraeseguirà la modifica. Il commit finale registrerà il contenuto effettivo di "unione".
VonC

31

Per il mio esempio, supponi di avere un ramo "origine" e un ramo "destinazione" che riflettono entrambi le versioni a monte di se stessi (o meno, se solo locale) e vengono inseriti nel codice più recente. Diciamo che voglio la sottodirectory nel repository chiamata newFeature che esiste solo nel ramo "source".

git checkout destination
git checkout source newFeature/
git commit -am "Merged the new feature from source to destination branch."
git pull --rebase
git push

È significativamente meno contorto di tutto il resto che ho visto e questo ha funzionato perfettamente per me, trovato qui .

Nota che questa non è una "vera unione", quindi non avrai le informazioni di commit su newFeature nel ramo di destinazione, ma solo le modifiche ai file in quella sottodirectory. Ma dal momento che presumibilmente unirai di nuovo l'intero ramo in un secondo momento o lo scarterai, potrebbe non essere un problema.


2
Preserva la storia?
Bibrak

2
@Bibrak questo approccio NON preserva la storia. Almeno non con i comandi attuali.
Ravi Gidwani

6

L'ho preso da un thread del forum su Eclipse e ha funzionato a meraviglia:

git checkout source-branch
git checkout target-branch <directories-or-files-you-do-**NOT**-want> 
git commit
git checkout target-branch
git merge source-branch

1
Ho provato questo e sono finito con una directory dal ramo di origine nel ramo di destinazione che non volevo. Nonostante sia stato specificato con il set di directory, non volevo in quel 2 ° comando.
maratona

2
Questo non si unisce, sostituisce la cartella con quella del ramo di origine.
Gp2mv3

@ Gp2mv3 Penso che sia solido. checkoutrende le cartelle uguali => la differenza è solo un- le checkoutcartelle => mergedifferenza. È una strategia valida.
Caveman

6

Dato lo scenario dell'OP in cui hanno due rami, ma vogliono unire solo la cronologia di dir-1 dal ramo-a al ramo-b :

# Make sure you are in the branch with the changes you want
git checkout branch-a

# Split the desired folder into its own temporary branch
# This replays all commits, so it could take a while
git subtree split -P dir-1 -b temp-branch

# Enter the branch where you want to merge the desired changes into
git checkout branch-b

# Merge the changes from the temporary branch
git subtree merge -P dir-1 temp-branch

# Handle any conflicts
git mergetool

# Commit
git commit -am "Merged dir-1 changes from branch-a"

# Delete temp-branch
git branch -d temp-branch

1

Utilizzare git cherry-pickper selezionare i commit desiderati e unire solo questi commit. Il trucco chiave qui è ottenere questi commit in modo semplice (in modo da non doverli capire controllando manualmente il registro di Git e inserendoli a mano). Ecco come: utilizzare git logper stampare l' id SHA-1 del commit , in questo modo:

git log ^<commit-a> <commit-b> --pretty=format:"%h" --reverse -- <subdir>

'commit-a' è il commit immediatamente prima del punto di inizio del ramo da unire e 'commit-b' è l'ultimo commit sul ramo da unire. '--reverse' stampa questi commit in ordine inverso per la selezione successiva.

Quindi fallo come:

git cherry-pick $(git log ^<commit-a> <commit-b> --pretty=format:"%h" --reverse -- <subdir>)

Sono due passaggi, semplici e stabili!


ottimo e unico modo per unire una singola dir da un altro ramo e preservare la storia. ma ... ... se vuoi un solo commit, crea un ramo da tutti i commit dal primo comando, quindi unisci quel nuovo ramo in una fusione di squash ....
tom

1
Ciò presuppone che i commit trovati influenzino solo la directory in questione. Se un commit influisce sia sulla directory che vuoi unire che su quelle che non vuoi unire, questo selezionerà troppo.
Scott

Two steps, simple and stable!per niente semplice :)
Rafa

Allora in che modo pensi che sia semplice? @Rafa
Robert,

@Robert Non volevo implicare che la tua risposta non fosse semplice; la colpa è tutta su git, che ha un'interfaccia terribile e un terribile modello mentale pieno di misteri e nomi e significati casuali.
Rafa

0

Crea un repository Git per contenere sia branch-a che branch-b:

git checkout branch-a
git diff branch-b dir-1 > a.diff
patch -R -p1 < a.diff

14
Questa risposta richiede più informazioni. Cosa di questo è il codice reale contro i commenti?
qodeninja

2
Il richiedente vuole unire. L'utilizzo di una patch per trasferire le modifiche cancella automaticamente tutti i commit in una patch e la cronologia viene persa.
Eric
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.