Utilisations pratiques de git reset --soft?

136

Je travaille avec git depuis un peu plus d'un mois. En effet, j'ai utilisé la réinitialisation pour la première fois hier seulement, mais la réinitialisation logicielle n'a toujours pas beaucoup de sens pour moi.

Je comprends que je peux utiliser la réinitialisation logicielle pour modifier un commit sans modifier l'index ou le répertoire de travail, comme je le ferais avec git commit --amend.

Ces deux commandes sont-elles vraiment les mêmes ( reset --softvs commit --amend)? Une raison d'utiliser l'un ou l'autre en termes pratiques? Et plus important encore, y a-t-il d'autres utilisations en reset --softdehors de la modification d'un commit?

AJJ
la source

Réponses:

110

git resetest une question de déménagement HEAD, et généralement la branche ref .
Question: qu'en est-il de l'arbre de travail et de l'index?
Lorsqu'il est employé avec --soft, se déplace HEAD, le plus souvent en mettant à jour la référence de la branche, et seulement leHEAD .
Cela diffère de commit --amend:

  • cela ne crée pas de nouveau commit.
  • il peut en fait déplacer HEAD vers n'importe quel commit (comme il commit --amendne s'agit que de ne pas déplacer HEAD, tout en permettant de refaire le commit actuel)

Je viens de trouver cet exemple de combinaison:

  • une fusion classique
  • une fusion de sous-arbres

tout en un (octopus, car il y a plus de deux branches fusionnées) commit merge.

Tomas "wereHamster" explique Carnecky dans son article "Subtree Octopus merge" :

  • La stratégie de fusion de sous-arborescence peut être utilisée si vous souhaitez fusionner un projet dans un sous-répertoire d'un autre projet, puis maintenir le sous-projet à jour. C'est une alternative aux sous-modules git.
  • La stratégie de fusion octopus peut être utilisée pour fusionner trois branches ou plus. La stratégie normale ne peut fusionner que deux branches et si vous essayez de fusionner plus que cela, git revient automatiquement à la stratégie octopus.

Le problème est que vous ne pouvez choisir qu'une seule stratégie. Mais je voulais combiner les deux afin d'obtenir un historique propre dans lequel tout le référentiel est mis à jour de manière atomique vers une nouvelle version.

J'ai un superprojet, appelons-le projectA, et un sous-projet projectB, que j'ai fusionné dans un sous-répertoire de projectA.

(c'est la partie de fusion de sous-arborescence)

Je maintiens également quelques commits locaux.
ProjectAest régulièrement mis à jour, projectBa une nouvelle version tous les deux jours ou semaines et dépend généralement d'une version particulière de projectA.

Lorsque je décide de mettre à jour les deux projets, je ne me contente pas de tirer sur projectAet projectB car cela créerait deux commits pour ce qui devrait être une mise à jour atomique de l'ensemble du projet .
Au lieu de cela, je crée un seul commit de fusion qui combine projectA, projectBet mes commits locaux .
La partie délicate ici est qu'il s'agit d'une fusion de poulpe (trois têtes), mais projectBdoit être fusionnée avec la stratégie de sous-arbre . Alors voici ce que je fais:

# Merge projectA with the default strategy:
git merge projectA/master

# Merge projectB with the subtree strategy:
git merge -s subtree projectB/master

Ici, l'auteur a utilisé a reset --hard, puis read-treepour restaurer ce que les deux premières fusions avaient fait à l'arbre de travail et à l'index, mais c'est là que cela reset --softpeut aider:
Comment refaire ces deux fusions , qui ont fonctionné, c'est-à-dire que mon arbre de travail et mon index sont bien, mais sans avoir à enregistrer ces deux commits?

# Move the HEAD, and just the HEAD, two commits back!
git reset --soft HEAD@{2}

Maintenant, nous pouvons reprendre la solution de Tomas:

# Pretend that we just did an octopus merge with three heads:
echo $(git rev-parse projectA/master) > .git/MERGE_HEAD
echo $(git rev-parse projectB/master) >> .git/MERGE_HEAD

# And finally do the commit:
git commit

Donc, à chaque fois:

  • vous êtes satisfait de ce que vous obtenez (en terme d'arborescence de travail et d'index)
  • vous n'êtes pas satisfait de tous les commits qui vous ont amenés à y arriver:

git reset --soft Est la réponse.

VonC
la source
8
Note à moi-même: exemple simple d'écrasement avec git reset --soft: stackoverflow.com/questions/6869705
...
3
c'est également utile si vous vous êtes engagé dans la mauvaise branche. tous les changements retournent à la zone de préparation et se déplacent avec vous pendant que vous vérifiez la bonne branche.
smoebody
44

Cas d'utilisation - Combinez une série de commits locaux

"Oups. Ces trois commits pourraient être juste un."

Donc, annulez les 3 derniers commits (ou autre) (sans affecter l'index ni le répertoire de travail). Puis validez tous les changements en un seul.

Par exemple

> git add -A; git commit -m "Start here."
> git add -A; git commit -m "One"
> git add -A; git commit -m "Two"
> git add -A' git commit -m "Three"
> git log --oneline --graph -4 --decorate

> * da883dc (HEAD, master) Three
> * 92d3eb7 Two
> * c6e82d3 One
> * e1e8042 Start here.

> git reset --soft HEAD~3
> git log --oneline --graph -1 --decorate

> * e1e8042 Start here.

Désormais, toutes vos modifications sont conservées et prêtes à être validées en une seule pièce.

Réponses courtes à vos questions

Ces deux commandes sont-elles vraiment les mêmes ( reset --softvs commit --amend)?

  • Non.

Une raison d'utiliser l'un ou l'autre en termes pratiques?

  • commit --amend pour ajouter des fichiers / rm à partir du tout dernier commit ou pour changer son message.
  • reset --soft <commit> pour combiner plusieurs commits séquentiels en un nouveau.

Et plus important encore, y a-t-il d'autres utilisations en reset --softdehors de la modification d'un commit?

  • Voir d'autres réponses :)
Shaun Luttin
la source
2
Voir d'autres réponses pour "y a-t-il d'autres utilisations pour en reset --softdehors de la modification d'un commit - Non"
Antony Hatchkins
En réponse au dernier commentaire - accepté en partie, mais je dirais que modifier un seul commit est trop spécifique. Par exemple, vous voudrez peut-être, une fois qu'une fonctionnalité est écrite, tout écraser et créer de nouveaux commits plus utiles pour les réviseurs. Je dirais que les réinitialisations logicielles (y compris simples git reset) sont bonnes lorsque vous (a) voulez réécrire l'historique, (b) ne vous souciez pas des anciens commits (vous pouvez donc éviter l'agitation du rebase interactif), et (c) avoir plus d'un commit à changer (sinon, commit --amendc'est plus simple).
johncip
18

Je l'utilise pour modifier plus que le dernier commit.

Disons que j'ai fait une erreur dans le commit A, puis j'ai fait le commit B.Maintenant, je ne peux que modifier B. Donc je le fais git reset --soft HEAD^^, je corrige et réengage A, puis réengage B.

Bien sûr, ce n'est pas très pratique pour les gros commits ... mais vous ne devriez pas faire de gros commits de toute façon ;-)

Simon
la source
3
git commit --fixup HEAD^^ git rebase --autosquash HEAD~Xfonctionne bien aussi.
Niklas
3
git rebase --interactive HEAD^^où vous choisissez de modifier à la fois les validations A et B. De cette façon, les messages de validation de A et B sont conservés, si nécessaire, vous pouvez toujours les modifier.
Paul Pladijs
2
Comment réengager B après être réinitialisé à A?
northben
1
Une autre façon qui est sans doute moins de travail: vérifier votre journal git, obtenir le hachage commettras de B, puis git reset A, faire et ajouter des changements, git commit --amend, git cherry-pick <B-commit-hash>.
naught101
15

Une autre utilisation potentielle est une alternative au stashing (que certaines personnes n'aiment pas, voir par exemple https://codingkilledthecat.wordpress.com/2012/04/27/git-stash-pop-consemed-harmful/ ).

Par exemple, si je travaille sur une branche et que je dois réparer quelque chose de toute urgence sur master, je peux simplement faire:

git commit -am "In progress."

puis contrôlez le maître et faites le correctif. Quand j'ai fini, je retourne dans ma succursale et fais

git reset --soft HEAD~1

pour continuer à travailler là où j'ai laissé.

deltacrux
la source
3
Mise à jour (maintenant que je comprends mieux git): --softest en fait inutile ici, à moins que vous ne vous souciez vraiment que les modifications soient immédiatement mises en scène. J'utilise maintenant juste git reset HEAD~pour faire cela. Si j'ai quelques changements mis en scène quand je dois changer de branches, et que vous voulez le garder de cette façon, alors je le fais git commit -m "staged changes", puis git commit -am "unstaged changes", puis plus tard git reset HEAD~suivie git reset --soft HEAD~pour rétablir complètement l'état de travail. Bien que, pour être honnête, je fasse beaucoup moins ces deux choses maintenant que je sais git-worktree:)
deltacrux
7

Vous pouvez utiliser git reset --softpour changer la version que vous souhaitez avoir comme parent pour les modifications que vous avez dans votre index et votre arborescence de travail. Les cas où cela est utile sont rares. Parfois, vous pouvez décider que les modifications apportées à votre arbre de travail doivent appartenir à une autre branche. Ou vous pouvez l'utiliser comme un moyen simple de réduire plusieurs commits en un seul (similaire à squash / fold).

Voir cette réponse de VonC pour un exemple pratique: écraser les deux premiers commits dans Git?

Johannes Rudolph
la source
C'est une bonne question que je n'avais pas trouvée auparavant. Mais je ne suis pas sûr de pouvoir voir comment mettre des modifications sur une autre branche en utilisant reset soft. Donc, j'ai checkout Branch, puis j'utilise git reset --soft anotherBranchet puis je m'engage là-bas? Mais vous ne changez pas vraiment la branche ckeckout, alors allez-vous vous engager dans Branch ou dans anotherBranch ?
AJJ
Lorsque vous faites cela, il est important de ne pas utiliser git checkout car cela modifiera votre arbre. Pensez à git reset --soft comme un moyen de manipuler simplement la révision HEAD vers laquelle pointe.
Johannes Rudolph
7

Une utilisation possible serait lorsque vous souhaitez continuer votre travail sur une machine différente. Cela fonctionnerait comme ceci:

  1. Extraire une nouvelle branche avec un nom semblable à une cachette,

    git checkout -b <branchname>_stash
    
  2. Poussez votre branche cachée vers le haut,

    git push -u origin <branchname>_stash
    
  3. Passez à votre autre machine.

  4. Tirez à la fois votre réserve et les branches existantes,

    git checkout <branchname>_stash; git checkout <branchname>
    
  5. Vous devriez être sur votre branche existante maintenant. Fusionner les modifications de la branche stash,

    git merge <branchname>_stash
    
  6. Réinitialisez votre branche existante à 1 avant votre fusion,

    git reset --soft HEAD^
    
  7. Supprimez votre branche de cache,

    git branch -d <branchname>_stash
    
  8. Supprimez également votre branche de réserve de l'origine,

    git push origin :<branchname>_stash
    
  9. Continuez à travailler avec vos modifications comme si vous les aviez stockées normalement.

Je pense qu'à l'avenir, GitHub et co. devrait offrir cette fonctionnalité de «stockage à distance» en moins d'étapes.

Llaughlin
la source
2
Je tiens à souligner que le premier stash et pop sur votre première machine sont complètement inutiles, vous pouvez simplement créer une nouvelle branche directement à partir d'une copie de travail sale, valider, puis transmettre les modifications à la télécommande.
7

Une utilisation pratique est que si vous vous êtes déjà engagé dans votre dépôt local (c'est-à-dire. Git commit -m), vous pouvez annuler ce dernier commit en faisant git reset --soft HEAD ~ 1

Aussi pour votre connaissance, si vous avez déjà mis en scène vos modifications (c'est-à-dire avec git add.), Vous pouvez inverser la mise en scène en faisant git reset --mixed HEAD ou j'ai couramment utiliségit reset

enfin, git reset --hard efface tout, y compris vos modifications locales. La tête ~ après vous indique le nombre de commits à effectuer depuis le haut.

j2emanue
la source
1
git reset --soft HEAD ~1me donne fatal: Cannot do soft reset with paths.je pense que nous devons supprimer l'espace après HEAD donc ce seraitgit reset --soft HEAD~1
Andrew Lohr
6

Une bonne raison d'utiliser ' git reset --soft <sha1>' est de se déplacer HEADdans un repo nu.

Si vous essayez d'utiliser l' option --mixedou --hard, vous obtiendrez une erreur car vous essayez de modifier et de travailler une arborescence et / ou un index qui n'existe pas.

Remarque: Vous devrez le faire directement à partir du dépôt nu.

Remarque à nouveau: vous devrez vous assurer que la branche que vous souhaitez réinitialiser dans le dépôt nu est la branche active. Sinon, suivez la réponse de VonC sur la façon de mettre à jour la branche active dans un dépôt nu lorsque vous avez un accès direct au dépôt.

Hazok
la source
1
Comme mentionné dans votre réponse précédente ( stackoverflow.com/questions/4624881/… ), cela est vrai lorsque vous avez un accès direct au dépôt nu (que vous mentionnez ici). +1 cependant.
VonC
@VonC Oui, tout à fait correct et merci d'avoir ajouté la note! J'oublie toujours d'ajouter cela puisque je suppose que les réinitialisations sont effectuées directement à partir du dépôt. De plus, je suppose que la branche que la personne souhaite réinitialiser dans le repo nu est sa branche active. Si la branche n'est pas sa branche active, alors la nécessité de mettre à jour selon votre réponse ( stackoverflow.com/questions/3301956/… ) sur la façon de mettre à jour la branche active pour les dépôts nus. Je mettrai également à jour la réponse avec les informations de la branche active. Merci encore!!!
Hazok
2

SourceTree est une interface graphique git qui a une interface assez pratique pour mettre en scène uniquement les bits que vous voulez. Il n'a rien de similaire à distance pour modifier une révision appropriée.

C'est donc git reset --soft HEAD~1beaucoup plus utile que commit --amenddans ce scénario. Je peux annuler le commit, récupérer toutes les modifications dans la zone de préparation et reprendre le peaufinage des bits mis en scène à l'aide de SourceTree.

Vraiment, il me semble que commit --amendc'est la commande la plus redondante des deux, mais git est git et ne craint pas les commandes similaires qui font des choses légèrement différentes.

Roman Starkov
la source
1

Bien que j'aime vraiment les réponses dans ce fil, j'utilise git reset --softpour un scénario légèrement différent, mais néanmoins très pratique.

J'utilise un IDE pour le développement qui a un bon outil de comparaison pour afficher les changements (par étapes et non) après mon dernier commit. Maintenant, la plupart de mes tâches impliquent plusieurs commits. Par exemple, disons que je fais 5 commits pour terminer une tâche particulière. J'utilise l'outil diff dans l'EDI lors de chaque commit incrémentiel de 1 à 5 pour regarder mes modifications depuis le dernier commit. Je trouve que c'est un moyen très utile de revoir mes modifications avant de valider.

Mais à la fin de ma tâche, quand je veux voir toutes mes modifications ensemble (avant le 1er commit), pour faire une auto-révision du code avant de faire une pull request, je ne verrais que les changements de mon commit précédent (après commit 4) et ne change pas de tous les commits de ma tâche actuelle.

J'utilise donc git reset --soft HEAD~4pour revenir 4 commits. Cela me permet de voir tous les changements ensemble. Lorsque je suis sûr de mes changements, je peux alors le faire git reset HEAD@{1}et le pousser à distance en toute confiance.

Codeur chic
la source
1
... puis faites git add --patchet à git commitplusieurs reprises pour construire la série de commit que vous auriez construite si vous saviez ce que vous faisiez depuis le début. Vos premiers commits sont comme les notes sur votre bureau ou le premier brouillon d'un mémo, ils sont là pour organiser votre réflexion, pas pour la publication.
jthill
Hé, je n'ai pas tout à fait compris ce que vous suggérez.
Swanky Coder
1
Au lieu de git reset @{1}restaurer votre première série de brouillons, vous pouvez créer une série pour publication à partir de git add -pet git commit.
jthill
Oui correct. Une autre façon est (celle que je suis habituellement) de rebaser sur amont / maître et d'écraser les commits en un seul.
Swanky Coder
1

Un autre cas d'utilisation est lorsque vous souhaitez remplacer l'autre branche par la vôtre dans une pull request, par exemple, disons que vous avez un logiciel avec les fonctionnalités A, B, C en développement.

Vous développez avec la prochaine version et vous:

  • Fonctionnalité B supprimée

  • Ajout de la fonctionnalité D

Dans le processus, développez les correctifs ajoutés pour la fonctionnalité B.

Vous pouvez fusionner le développement avec le suivant, mais cela peut parfois être compliqué, mais vous pouvez également utiliser git reset --soft origin/develop et créer un commit avec vos modifications et la branche est fusionnable sans conflits et conserver vos modifications.

Il s'avère que git reset --softc'est une commande pratique. Personnellement, je l'utilise beaucoup pour écraser les commits qui n'ont pas de "travail terminé" comme "WIP" donc quand j'ouvre la pull request, tous mes commits sont compréhensibles.

cnexans
la source