J'utilise git sur un nouveau projet comportant deux branches de développement parallèles - mais actuellement expérimentales -:
master
: importation de la base de code existante plus quelques mods dont je suis généralement sûr exp1
: branche expérimentale n ° 1exp2
: branche expérimentale n ° 2exp1
et exp2
représentent deux approches architecturales très différentes. Jusqu'à ce que je continue, je n'ai aucun moyen de savoir lequel (si l'un ou l'autre) fonctionnera. Au fur et à mesure que je progresse dans une branche, j'ai parfois des modifications qui pourraient être utiles dans l'autre branche et j'aimerais fusionner celles-ci.
Quel est le meilleur moyen de fusionner des modifications sélectives d'une branche de développement en une autre tout en laissant derrière tout le reste?
Approches que j'ai envisagées:
git merge --no-commit
suivi de la décomposition manuelle d’un grand nombre de modifications que je ne souhaite pas mettre en commun entre les branches.
Copie manuelle des fichiers communs dans un répertoire temporaire suivie de git checkout
pour passer à l’autre branche, puis copie plus manuelle du répertoire temporaire dans l’arborescence de travail.
Une variation sur le dessus. Abandonnez les branches exp
pour le moment et utilisez deux référentiels locaux supplémentaires à des fins d’expérimentation. Cela rend la copie manuelle de fichiers beaucoup plus simple.
Ces trois approches semblent fastidieuses et sujettes aux erreurs. J'espère qu'il y a une meilleure approche; quelque chose qui ressemble à un paramètre de chemin de filtre qui rendrait git-merge
plus sélectif.
Vous utilisez la commande cherry-pick pour obtenir des commits individuels d’une branche.
Si les modifications souhaitées ne sont pas dans des commits individuels, utilisez la méthode présentée ici pour diviser le commit en commits individuels . En gros, vous utilisez git rebase -i
pour obtenir le commit d'origine à éditer, puis git reset HEAD^
pour annuler sélectivement les modifications, puis git commit
pour valider ce bit en tant que nouveau commit dans l'historique.
Il existe une autre méthode Nice ici dans Red Hat Magazine, dans laquelle ils utilisent git add --patch
ou éventuellement git add --interactive
, ce qui vous permet d’ajouter des parties d’un morceau si vous souhaitez fractionner différentes modifications dans un fichier individuel (recherchez dans cette page "Divisé").
Après avoir scindé les modifications, vous pouvez désormais sélectionner uniquement celles que vous souhaitez.
J'ai eu exactement le même problème que mentionné par vous ci-dessus. Mais j’ai trouvé ceci plus clair dans l’explication de la réponse.
Résumé:
Vérifiez le ou les chemins de la branche que vous souhaitez fusionner,
$ git checkout source_branch -- <paths>...
Astuce: Cela fonctionne aussi sans --
comme dans la publication liée.
ou pour fusionner sélectivement des mecs
$ git checkout -p source_branch -- <paths>...
Sinon, utilisez reset puis ajoutez avec l'option -p
,
$ git reset <paths>...
$ git add -p <paths>...
Enfin commettre
$ git commit -m "'Merge' these changes"
Pour fusionner de manière sélective des fichiers d’une branche à une autre, exécutez
git merge --no-ff --no-commit branchX
où branchX
est la branche à partir de laquelle vous souhaitez fusionner dans la branche actuelle.
L'option --no-commit
organisera les fichiers qui ont été fusionnés par Git sans les valider réellement. Cela vous donnera la possibilité de modifier les fichiers fusionnés comme vous le souhaitez, puis de les valider vous-même.
Selon la manière dont vous souhaitez fusionner les fichiers, il existe quatre cas:
Dans ce cas, vous acceptez les fichiers fusionnés de la manière dont Git les a automatiquement fusionnés, puis vous les validez.
Par exemple, vous souhaitez conserver la version de la branche actuelle et ignorer la version de la branche à partir de laquelle vous effectuez la fusion.
Pour sélectionner la version de la branche actuelle, exécutez:
git checkout HEAD file1
Cela récupérera la version de file1
dans la branche actuelle et écrasera le file1
généré automatiquement par Git.
Courir:
git checkout branchX file1
Cela va récupérer la version de file1
dans branchX
et écraser file1
automatiquement fusionné par Git.
file1
.Dans ce cas, vous pouvez éditer directement le file1
modifié, le mettre à jour avec ce que vous souhaitez que la version de file1
devienne, puis valider.
Si Git ne peut pas fusionner un fichier automatiquement, il le signalera comme " unmerged " et produira une copie où vous devrez résoudre les conflits manuellement.
Pour expliquer davantage avec un exemple, supposons que vous vouliez fusionner branchX
dans la branche actuelle:
git merge --no-ff --no-commit branchX
Vous exécutez ensuite la commande git status
pour afficher l'état des fichiers modifiés.
Par exemple:
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
#
Où file1
, file2
et file3
sont les fichiers git ont été fusionnés avec succès.
Cela signifie que les modifications apportées aux master
et branchX
de ces trois fichiers ont été combinées sans aucun conflit.
Vous pouvez vérifier comment la fusion a été effectuée en exécutant le git diff --cached
;
git diff --cached file1
git diff --cached file2
git diff --cached file3
git commit
file1
et que vous voulez conserver la version dans la branche actuelleCourir
git checkout HEAD file1
file2
et ne voulez que la version dans branchX
Courir
git checkout branchX file2
file3
soit fusionné automatiquement, ne faites rien.Git l'a déjà fusionné à ce stade.
file4
ci-dessus est une fusion échouée par Git. Cela signifie que des changements dans les deux branches se produisent sur la même ligne. C'est ici que vous devrez résoudre les conflits manuellement. Vous pouvez ignorer la fusion effectuée en modifiant directement le fichier ou en exécutant la commande d'extraction pour la version de la branche que vous souhaitez que file4
devienne.
Enfin, n'oubliez pas de git commit
.
Je n'aime pas les approches ci-dessus. Utiliser cherry-pick est idéal pour sélectionner un seul changement, mais il est difficile d’apporter tous les changements, à l’exception de certains mauvais. Voici mon approche.
Il n'y a pas d'argument --interactive
que vous pouvez passer à git merge.
Voici l'alternative:
Vous avez quelques changements dans la 'fonctionnalité' de la branche et vous voulez en apporter certains mais pas tous à 'maîtriser' d'une manière non négligeable (c'est-à-dire que vous ne voulez pas sélectionner et valider chacun d'eux)
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
En résumé, dans un script Shell, modifiez master en $ to et changez la fonctionnalité en $ from et vous êtes prêt à partir:
#!/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
Il y a une autre façon d'y aller:
git checkout -p
C’est un mélange de git checkout
et de git add -p
et pourrait correspondre exactement à ce que vous recherchez:
-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.
Bien que certaines de ces réponses soient plutôt bonnes, j’ai le sentiment qu’aucune d’entre elles n’a répondu à la contrainte initiale de OP: sélectionner des fichiers particuliers dans des branches particulières. Cette solution fait cela, mais peut être fastidieuse s'il y a beaucoup de fichiers.
Disons que vous avez les branches master
, exp1
et exp2
. Vous souhaitez fusionner un fichier de chacune des branches expérimentales dans le fichier maître. Je ferais quelque chose comme ça:
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
Cela vous donnera des différences dans le fichier pour chacun des fichiers que vous voulez. Rien de plus. Rien de moins. Il est utile de modifier radicalement les fichiers d’une version à l’autre - dans mon cas, la modification d’une application de Rails 2 à Rails 3.
EDIT: ceci fusionnera des fichiers, mais fera une fusion intelligente. Je ne pouvais pas comprendre comment utiliser cette méthode pour obtenir des informations de diff dans le fichier (peut-être que ce sera toujours le cas pour les différences extrêmes. Des petites choses gênantes telles que les espaces sont fusionnées à moins d'utiliser l'option -s recursive -X ignore-all-space
)
La réponse de 1800 INFORMATION est tout à fait correcte. En tant que git noob, "utiliser git cherry-pick" ne m'a pas suffi pour comprendre cela sans un peu plus de fouilles sur Internet, j'ai donc pensé poster un guide plus détaillé au cas où quelqu'un bateau similaire.
Mon cas d'utilisation voulait attirer de manière sélective les modifications de la branche github de quelqu'un d'autre vers la mienne. Si vous avez déjà une branche locale avec les modifications, il vous suffit de suivre les étapes 2 et 5-7.
Créez (si non créé) une branche locale avec les modifications que vous souhaitez apporter.
$ git branch mybranch <base branch>
Basculez dedans.
$ git checkout mybranch
Déroulez les modifications que vous souhaitez du compte de l'autre personne. Si vous ne l'avez pas déjà fait, vous voudrez les ajouter en tant que télécommande.
$ git remote add repos-w-changes <git url>
Déroulez tout de leur branche.
$ git pull repos-w-changes branch-i-want
Affichez les journaux de validation pour connaître les modifications souhaitées:
$ git log
Revenez à la branche dans laquelle vous souhaitez extraire les modifications.
$ git checkout originalbranch
Choisissez vos commits, un par un, avec les hachages.
$ git cherry-pick -x hash-of-commit
Astuce de chapeau: http://www.sourcemage.org/Git_Guide
Voici comment vous pouvez remplacer le fichier Myclass.Java
dans la branche master
par Myclass.Java
dans la branche feature1
. Cela fonctionnera même si Myclass.Java
n'existe pas sur master
.
git checkout master
git checkout feature1 Myclass.Java
Notez que cela écrasera - pas fusionnera - et ignorera plutôt les modifications locales dans la branche principale.
Le moyen le plus simple de fusionner des fichiers spécifiques de deux branches, ne consiste pas simplement à remplacer des fichiers spécifiques par des fichiers d'une autre branche.
git diff branch_b > my_patch_file.patch
Crée un fichier de correctif de la différence entre la branche actuelle et branch_b
git apply -p1 --include=pattern/matching/the/path/to/file/or/folder my_patch_file.patch
Vous pouvez utiliser *
comme caractère générique dans le modèle d'inclusion.
Les barres obliques n'ont pas besoin d'être échappées.
Vous pouvez aussi utiliser --exclude et l’appliquer à tout sauf aux fichiers correspondant au motif, ou inverser le patch avec -R.
L'option -p1 est une retenue de la commande * unix patch et le fait que le contenu du fichier de correctif ajoute chaque nom de fichier avec a/
ou b/
(ou plus, selon la manière dont le fichier de correctif a été généré), que vous devez supprimer Déterminez le vrai fichier dans le chemin du fichier auquel le correctif doit être appliqué.
Consultez la page de manuel de git-apply pour plus d'options.
De toute évidence, vous voudriez valider vos modifications, mais qui peut dire que vous ne souhaitez pas effectuer d’autres modifications connexes avant de vous engager;
Voici comment vous pouvez faire en sorte que l'historique suive quelques fichiers d'une autre branche avec un minimum de complications, même si une fusion plus "simple" aurait entraîné beaucoup plus de changements que vous ne souhaitiez pas.
Tout d’abord, vous allez prendre l’étape inhabituelle de déclarer à l’avance que ce que vous êtes sur le point de commettre est une fusion, sans rien faire des fichiers dans votre répertoire de travail:
git merge --no-ff --no-commit -s ours branchname1
. . . où "nom de branche" est ce que vous prétendez fusionner. Si vous deviez vous engager immédiatement, cela ne ferait aucun changement, mais cela montrerait tout de même une ascendance de l'autre branche. Vous pouvez ajouter plus de branches/tags/etc. à la ligne de commande si vous avez besoin, aussi bien. À ce stade cependant, il n'y a aucune modification à valider, aussi récupérez les fichiers des autres révisions, ensuite.
git checkout branchname1 -- file1 file2 etc
Si vous effectuez une fusion à partir de plusieurs autres branches, recommencez si nécessaire.
git checkout branchname2 -- file3 file4 etc
Maintenant, les fichiers de l'autre branche sont dans l'index, prêts à être validés, avec un historique.
git commit
et vous aurez beaucoup d'explications à faire dans ce message de validation.
S'il vous plaît noter cependant, au cas où ce n'était pas clair, que c'est une chose foirée à faire. Ce n’est pas dans l’esprit de ce qu’est une "branche", et choisir à la perfection est une façon plus honnête de faire ce que vous feriez, ici. Si vous souhaitez effectuer une autre "fusion" pour d'autres fichiers de la même branche que vous n'avez pas apportés la dernière fois, le message "déjà à jour" vous arrêtera. C'est un symptôme de ne pas créer de branche quand nous aurions dû, dans la branche "de" devrait être plus d'une branche différente.
J'ai trouvé ce post contenir la réponse la plus simple. Faire simplement:
$ #git checkout <branch from which you want files> <file paths>
Exemple:
$ #pulling .gitignore file from branchB into current branch
$ git checkout branchB .gitignore
Voir le post pour plus d'informations.
Je sais que je suis un peu en retard, mais ceci est mon flux de travail pour la fusion de fichiers sélectifs.
#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
Le moyen le plus simple est de définir votre référentiel sur la branche avec laquelle vous souhaitez fusionner
git checkout [branch with file] [path to file you would like to merge]
Si vous courez
git status
vous verrez le fichier déjà mis en scène ...
Puis courir
git commit -m "Merge changes on '[branch]' to [file]"
Simple.
J'ai eu exactement le même problème que mentionné par vous ci-dessus. Mais j'ai trouvé ce blog git plus clair dans l'explication de la réponse.
Commande depuis le lien ci-dessus:
#You are in the branch you want to merge to
git checkout <branch_you_want_to_merge_from> <file_paths...>
Ce n'est pas exactement ce que vous cherchiez, mais cela m'a été utile:
git checkout -p <branch> -- <paths> ...
C'est un mélange de quelques réponses.
J'aime la réponse 'git-interactive-merge' ci-dessus, mais il y en a une plus facile. Laissez git le faire pour vous en utilisant une combinaison de bases interactive et sur:
A---C1---o---C2---o---o feature
/
----o---o---o---o master
Le cas est donc que vous souhaitiez C1 et C2 à partir de la branche 'fonctionnalité' (point de branche 'A'), mais pas du reste pour l'instant.
# git branch temp feature
# git checkout master
# git rebase -i --onto HEAD A temp
Comme ci-dessus, vous êtes redirigé vers l'éditeur interactif dans lequel vous sélectionnez les lignes de sélection pour C1 et C2 (comme ci-dessus). Enregistrez et quittez, puis la rebase continuera et vous donnera la branche 'temp' ainsi que HEAD sur le maître + C1 + C2:
A---C1---o---C2---o---o feature
/
----o---o---o---o-master--C1---C2 [HEAD, temp]
Ensuite, vous pouvez simplement mettre à jour master vers HEAD et supprimer la branche temporaire. Vous pouvez continuer:
# git branch -f master HEAD
# git branch -d temp
Je ferais un
git diff commit1..commit2 filepattern | git-apply --index && git commit
De cette façon, vous pouvez limiter le nombre de commits d'un fichier à partir d'une branche.
Volé à: http://www.gelato.unsw.edu.au/archives/git/0701/37964.html
Je sais que cette question est ancienne et qu'il existe de nombreuses autres réponses, mais j'ai écrit mon propre script appelé «pmerge» pour fusionner partiellement des répertoires. C'est un travail en cours et j'apprends toujours les scripts git et bash.
Cette commande utilise git merge --no-commit
et applique ensuite les modifications qui ne correspondent pas au chemin fourni.
Utilisation: git pmerge branch path
Exemple: git merge develop src/
Je ne l'ai pas testé de manière approfondie. Le répertoire de travail ne doit contenir aucune modification non validée ni aucun fichier non suivi.
#!/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
Qu'en est-il de git reset --soft branch
? Je suis surpris que personne n'en ait encore parlé.
Pour moi, c’est le moyen le plus simple de sélectionner sélectivement les modifications d’une autre branche, car cette commande place dans mon arbre de travail toutes les modifications et que je peux facilement choisir ou annuler celle dont j’ai besoin. De cette façon, j'ai un contrôle total sur les fichiers validés.
Vous pouvez utiliser read-tree
pour lire ou fusionner une arborescence distante donnée dans l'index actuel, par exemple:
git remote add foo [email protected]/foo.git
git fetch foo
git read-tree --prefix=my-folder/ -u foo/master:trunk/their-folder
Pour effectuer la fusion, utilisez plutôt -m
.
Voir aussi: Comment fusionner un sous-répertoire dans git?
Une approche simple pour la fusion/validation sélective par fichier:
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
Lorsque seuls quelques fichiers ont changé entre les validations actuelles des deux branches, je fusionne manuellement les modifications en parcourant les différents fichiers.
git difftoll <branch-1>..<branch-2>
Si vous avez seulement besoin de fusionner un répertoire particulier et de laisser tout le reste intact tout en préservant l'historique, vous pouvez éventuellement essayer ceci ... créez un nouveau target-branch
à partir de la master
avant de faire des expériences.
Les étapes ci-dessous supposent que vous avez deux branches target-branch
et source-branch
et que le répertoire dir-to-merge
que vous souhaitez fusionner se trouve dans le source-branch
. Supposez également que vous avez d'autres répertoires tels que dir-to-retain
dans la cible que vous ne souhaitez pas modifier et conserver l'historique. En outre, supposons qu'il existe des conflits de fusion dans le dir-to-merge
.
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.
Si vous n'avez pas trop de fichiers qui ont changé, cela ne vous laissera pas de commits supplémentaires.
1. Double branche temporairement $ git checkout -b temp_branch
2. Réinitialiser au dernier commit voulu $ git reset --hard HEAD~n
, où n
est le nombre de commits dont vous avez besoin pour revenir en arrière
3. Extraire chaque fichier de la branche d'origine $ git checkout Origin/original_branch filename.ext
Maintenant, vous pouvez valider et forcer Push (pour écraser la télécommande), si nécessaire.