Ne cacher que les changements par étapes dans git - est-ce possible?

368

Existe-t-il un moyen de stocker uniquement mes modifications par étapes? Le scénario avec lequel je rencontre des problèmes est lorsque j'ai travaillé sur plusieurs bogues à un moment donné et que j'ai plusieurs modifications non mises en scène. J'aimerais pouvoir mettre en scène ces fichiers individuellement, créer mes fichiers .patch et les ranger jusqu'à ce que le code soit approuvé. De cette façon, quand il est approuvé, je peux ranger toute ma session (en cours), faire apparaître ce bogue et pousser le code.

Suis-je en train de mal faire ça? Suis-je en train de mal comprendre comment git peut fonctionner de différentes manières pour simplifier mon processus?

MrDuk
la source
Oui, vous faites probablement des erreurs pour vous retrouver dans cette situation. Encore une question utile. Vous devez vraiment ranger ou créer une branche avant de commencer le prochain correctif. La réponse tangentielle stackoverflow.com/a/50692885 est probablement une meilleure façon de gérer cela dans git. Jouer avec la cachette fait souvent des trucs bizarres dans ma zone de travail si j'ai tiré des commits en amont.
Samuel Åslund

Réponses:

472

Oui, c'est possible avec DOUBLE STASH

  1. Mettez en scène tous vos fichiers que vous devez ranger.
  2. Courez git stash --keep-index. Cette commande créera une cachette avec TOUTES vos modifications (par étapes et non par étapes ), mais laissera les modifications par étapes dans votre répertoire de travail (toujours à l'état par étapes).
  3. Courir git stash push -m "good stash"
  4. Maintenant, votre fichier"good stash" contient uniquement des fichiers intermédiaires .

Maintenant, si vous avez besoin de fichiers non organisés avant le stockage, appliquez simplement le premier stockage ( celui créé avec--keep-index ) et vous pouvez maintenant supprimer les fichiers que vous avez stockés "good stash".

Prendre plaisir

Bartłomiej Semańczyk
la source
Il stocke les modifications dans les sous-modules même si elles ne sont pas mises en scène. Y a-t-il un moyen de contourner ceci?
rluks
1
cela a en quelque sorte laissé tous les nouveaux fichiers (même mis en scène).
Aurimas
8
@Aurimas, pour cacher de nouveaux fichiers, vous devez utiliser le -ucommutateur.
Gyromite
2
lorsque vous réappliquez la première cachette et que tout est changé, vous ne serez peut-être intéressé que par votre git stash apply --indexoption d' utilisation des changements d'unstages . Cela va essayer de garder votre état non (par étapes). Il est maintenant plus facile de supprimer les modifications indésirables de l'arborescence de travail.
otomo
Même si je n'avais pas besoin de faire exactement ce que disait cette réponse, il était très utile de connaître l'indicateur --keep-index.
Aaron Krauss
129

Avec le dernier git, vous pouvez utiliser l' --patchoption

git stash push --patch  

git stash save --patch   # for older git versions

Et git vous demandera chaque modification de vos fichiers à ajouter ou non dans la cachette.
Vous venez de répondre youn


Alias UPD pour DOUBLE STASH :

git config --global alias.stash-staged '!bash -c "git stash --keep-index; git stash push -m "staged" --keep-index; git stash pop stash@{1}"'

Vous pouvez maintenant mettre en scène vos fichiers, puis exécuter git stash-staged.
En conséquence, vos fichiers intermédiaires seront enregistrés dans la cachette .

Si vous ne souhaitez pas conserver les fichiers intermédiaires et que vous souhaitez les placer dans la réserve. Ensuite, vous pouvez ajouter un autre alias et exécuter git move-staged:

git config --global alias.move-staged '!bash -c "git stash-staged;git commit -m "temp"; git stash; git reset --hard HEAD^; git stash pop"'
Eugen Konkov
la source
17
Techniquement, cela ne répond pas à la question - mais une très bonne technique qui permet un stockage sélectif.
alexreardon
6
D'accord, ça va, mais l'idée ici avec la question est que j'ai DÉJÀ fait tout ce travail de mise en scène des changements avec lesquels je veux faire quelque chose (ostensiblement à l'origine pour commettre, mais maintenant je veux cacher), ne pas regarder juste refais le encore.
Steven Lu
4
ne fonctionne pas pour les fichiers nouvellement créés (fonctionne uniquement sur les fichiers modifiés)
Derek Liang
@DerekLiang: Les fichiers nouvellement créés ne sont pas du tout suivis. Vous devriez probablement vérifier l' -u|--include-untrackedoption degit-stash
Eugen Konkov
2
Extrait de la documentation : " save : cette option est déconseillée au profit de git stash push . Elle diffère de" stash push "en ce qu'elle ne peut pas prendre pathspecs, et aucun argument non-option ne forme le message."
Borjovsky
53

J'ai fait un script qui ne cache que ce qui est actuellement mis en scène et laisse tout le reste. C'est génial quand je commence à faire trop de changements sans rapport. Il vous suffit de mettre en scène ce qui n'est pas lié au commit souhaité et de le cacher.

(Merci à Bartłomiej pour le point de départ)

#!/bin/bash

#Stash everything temporarily.  Keep staged files, discard everything else after stashing.
git stash --keep-index

#Stash everything that remains (only the staged files should remain)  This is the stash we want to keep, so give it a name.
git stash save "$1"

#Apply the original stash to get us back to where we started.
git stash apply stash@{1}

#Create a temporary patch to reverse the originally staged changes and apply it
git stash show -p | git apply -R

#Delete the temporary stash
git stash drop stash@{1}
Joe
la source
7
J'ajouterais que vous pouvez transformer le script en une commande git en suivant thediscoblog.com/blog/2014/03/29/custom-git-commands-in-3-steps
Petr Bela
3
C'est bien! Je l'ai modifié pour inviter l'utilisateur à fournir une description de la dissimulation s'il n'en saisit
brookinc
1
Cela ne fonctionne pas du tout avec git 2.23.0.
thnee
Merci, j'ai voté positivement et je l'ai transformé en alias ici: stackoverflow.com/a/60875067/430128 .
Raman
36

TL; DR Il suffit d'ajouter -- $(git diff --staged --name-only)pour votre <pathspec>paramètre git

Voici un simple doublure:

git stash -- $(git diff --staged --name-only)

Et pour ajouter un message simplement:

git stash push -m "My work in progress" -- $(git diff --staged --name-only)

Testé sur v2.17.1 et v2.21.0.windows.1

Limites:

  • Veuillez noter que cela stockera tout, si aucun fichier n'est organisé.
  • De plus, si vous avez un fichier qui n'est que partiellement mis en scène (c.-à-d. Seulement certaines lignes modifiées, sont mises en scène alors que d'autres lignes modifiées ne le sont pas), alors le fichier entier sera mis en cache (y compris les lignes non mises en scène).
Somo S.
la source
6
Je pense que c'est la meilleure option dans la situation décrite: facile à comprendre et sans magie noire!
Luis
1
C'est assez soigné. J'ai fini par en créer un alias!
Kalpesh Panchal
garder les votes comin 😉
Somo S.
@KalpeshPanchal pouvez-vous partager votre alias? Je ne sais pas comment y échapper, donc ce n'est pas l'interpréter correctement.
Igor Nadj
2
@IgorNadj Bien sûr! Le voici: github.com/panchalkalpesh/git-aliases/commit/…
Kalpesh Panchal
15

Pour accomplir la même chose ...

  1. Mettez en scène uniquement les fichiers sur lesquels vous souhaitez travailler.
  2. git commit -m 'temp'
  3. git add .
  4. git stash
  5. git reset HEAD~1

Boom. Les fichiers dont vous ne voulez pas sont cachés. Les fichiers que vous souhaitez sont tous prêts pour vous.

Michael
la source
3
C'est facilement la meilleure réponse et la plus facile à retenir
Kevin
9

Dans ce scénario, je préfère créer de nouvelles branches pour chaque problème. J'utilise un préfixe temp / donc je sais que je peux supprimer ces branches plus tard.

git checkout -b temp/bug1

Mettez en scène les fichiers qui corrigent bug1 et validez-les.

git checkout -b temp/bug2

Vous pouvez ensuite sélectionner les commits dans les succursales respectives et soumettre une demande de tirage.

Shamps
la source
2
Bien qu'il soit agréable de connaître les sons fantastiques, en pratique, cela semble être une approche que je suis moins susceptible de bork.
ryanjdillon
1
Utilisez "git cherry-pick tmpCommit" pour récupérer la validation temporaire sans fusion-validation ou "git merge tmpCommit" + "git reset HEAD ^" pour obtenir les modifications sans la validation.
Samuel Åslund
1
Comme cette réponse le montre parfois, il vaut mieux demander directement ce que vous voulez réaliser plutôt que comment le réaliser avec une technique donnée. Les branches temporaires et la cueillette sont utiles dans les situations compliquées.
Guney Ozsan
si vous avez partiellement organisé un fichier, vous devrez ranger vos modifications avant de revenir à la branche d'origine et de les réapparaître
jan-glx
6

Pourquoi ne commettez-vous pas la modification pour un certain bogue et ne créez-vous pas de correctif à partir de cette validation et de son prédécesseur?

# hackhackhack, fix two unrelated bugs
git add -p                   # add hunks of first bug
git commit -m 'fix bug #123' # create commit #1
git add -p                   # add hunks of second bug
git commit -m 'fix bug #321' # create commit #2

Ensuite, pour créer les correctifs appropriés, utilisez git format-patch:

git format-patch HEAD^^

Cela va créer deux fichiers: 0001-fix-bug-123.patchet0002-fix-bug-321.patch

Ou vous pouvez créer des branches distinctes pour chaque bogue, de sorte que vous pouvez fusionner ou rebaser les correctifs de bogue individuellement, ou même les supprimer, s'ils ne fonctionnent pas.

knittl
la source
2

git stash --keep-index est une bonne solution ... sauf qu'il ne fonctionnait pas correctement sur les chemins qui ont été supprimés, ce qui a été corrigé dans Git 2.23 (Q3 2019)

Voir commit b932f6a (16 juil.2019 ) par Thomas Gummerer ( tgummerer) .
(Fusionné par Junio ​​C Hamano - gitster- en commit f8aee85 , 25 juil.2019 )

stash: correction de la gestion des fichiers supprimés avec --keep-index

git stash push --keep-index est censé conserver toutes les modifications qui ont été ajoutées à l'index, à la fois dans l'index et sur le disque.

Actuellement, cela ne se comporte pas correctement lorsqu'un fichier est supprimé de l'index.
Au lieu de le conserver supprimé sur le disque, ** - keep-index restaure actuellement le fichier. **

Corrigez ce comportement en utilisant ' git checkout' en mode sans superposition qui peut restaurer fidèlement l'index et l'arborescence de travail.
Cela simplifie également le code.

Notez que cela écrasera les fichiers non suivis si le fichier non suivi porte le même nom qu'un fichier qui a été supprimé dans l'index.

VonC
la source
2

Cacher uniquement l'index (changements échelonnés) dans Git est plus difficile qu'il ne devrait l'être. J'ai trouvé que la réponse de @ Joe fonctionnait bien, et en ai transformé une variante mineure en cet alias:

stash-index = "!f() { \
  git stash push --quiet --keep-index -m \"temp for stash-index\" && \
  git stash push \"$@\" && \
  git stash pop --quiet stash@{1} && \
  git stash show -p | git apply -R; }; f"

Il pousse à la fois la mise en scène et les changements dans Unstaged une planque temporaire, laissant le seul mis en scène des changements. Il pousse ensuite les modifications par étapes dans la cachette, qui est la cachette que nous voulons conserver. Les arguments passés à l'alias, tels que ceux --message "whatever"qui seront ajoutés à cette commande stash. Enfin, il fait apparaître la cachette temporaire pour restaurer l'état d'origine et supprimer la cachette temporaire, puis «supprime» finalement les modifications stockées du répertoire de travail via une application de patch inverse.

Pour le problème opposé de ne cacher que les modifications non mises en scène (alias stash-working), consultez cette réponse .

Raman
la source
1

Est-il absolument nécessaire de travailler sur plusieurs bugs à la fois? Et par «à la fois», je veux dire «avoir des fichiers modifiés pour plusieurs bogues en même temps». Parce que sauf si vous en avez absolument besoin, je ne travaillerais que sur un bogue à la fois dans votre environnement. De cette façon, vous pouvez utiliser les branches locales et rebaser, ce que je trouve beaucoup plus facile que de gérer une stash / stage complexe.

Disons que le maître est au commit B. Maintenant, travaillons sur le bug # 1.

git checkout -b bug1

Vous êtes maintenant sur branch bug1. Apportez quelques modifications, validez, attendez la révision du code. C'est local, donc vous n'affectez personne d'autre, et cela devrait être assez facile de créer un patch à partir de git diffs.

A-B < master
   \
    C < bug1

Vous travaillez maintenant sur bug2. Aller Retour à maître avec git checkout master. Faire une nouvelle branche, git checkout -b bug2. Apportez des modifications, validez, attendez la révision du code.

    D < bug2
   /
A-B < master
   \
    C < bug1

Imaginons que quelqu'un d'autre engage E & F sur master pendant que vous attendez l'examen.

    D < bug2
   /
A-B-E-F < master
   \
    C < bug1

Une fois votre code approuvé, vous pouvez le rebaser sur master en procédant comme suit:

git checkout bug1
git rebase master
git checkout master
git merge bug1

Cela se traduira par les éléments suivants:

    D < bug2
   /
A-B-E-F-C' < master, bug1

Ensuite, vous pouvez pousser, supprimer votre branche locale bug1, et c'est parti. Un bogue à la fois dans votre espace de travail, mais en utilisant des branches locales, votre référentiel peut gérer plusieurs bogues. Et cela évite une danse de scène / stash compliquée.

Réponse à la question de ctote dans les commentaires:

Eh bien, vous pouvez revenir à la sauvegarde pour chaque bogue et ne travailler qu'avec un seul bogue à la fois. Au moins cela vous évite le problème de la mise en scène. Mais après avoir essayé cela, je trouve personnellement cela gênant. Les cachettes sont un peu désordonnées dans un graphique de journal git. Et plus important encore, si vous bousillez quelque chose, vous ne pouvez pas revenir en arrière. Si vous avez un répertoire de travail sale et que vous ouvrez une cachette, vous ne pouvez pas "annuler" cette fenêtre. Il est beaucoup plus difficile de bousiller les commits déjà existants.

Alors git rebase -i.

Lorsque vous rebasez une branche sur une autre, vous pouvez le faire de manière interactive (indicateur -i). Lorsque vous faites cela, vous avez la possibilité de choisir ce que vous voulez faire avec chaque commit. Pro Git est un livre génial qui est également en ligne au format HTML, et a une belle section sur le rebasage et le squash:

http://git-scm.com/book/ch6-4.html

Je vais voler leur exemple textuellement pour plus de commodité. Imaginez que vous avez l'historique de validation suivant et que vous souhaitez rebaser et écraser bug1 sur master:

    F < bug2
   /
A-B-G-H < master
   \
    C-D-E < bug1

Voici ce que vous verrez lorsque vous tapez git rebase -i master bug1

pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
#
# Commands:
#  p, pick = use commit
#  e, edit = use commit, but stop for amending
#  s, 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.
#

Pour écraser toutes les validations d'une branche vers le bas en une seule validation, conservez le premier commit comme "pick" et remplacez toutes les entrées "pick" suivantes par "squash" ou simplement "s". Vous aurez également la possibilité de modifier le message de validation.

pick f7f3f6d changed my name a bit
s 310154e updated README formatting and added blame
s a5f4a0d added cat-file
#
# Commands:
#  p, pick = use commit
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit

Alors oui, écraser est un peu pénible, mais je le recommanderais toujours à une utilisation intensive des cachettes.

Mike Monkiewicz
la source
1
Merci pour le post détaillé! Cela résout à coup sûr beaucoup de mes problèmes - le seul problème que je vois est que notre équipe actuelle a demandé que nous conservions toutes les livraisons à un seul commit. :(
MrDuk
1
S'ils n'ont pas besoin ou ne veulent pas de votre historique de travail dans le référentiel de production, c'est bien: rendez votre maître de suivi sans historique en appliquant des différences plutôt qu'en fusionnant les branches. Vous pouvez déterminer comment conserver une branche maître décorée qui a l'historique fusionné réel et faire votre vrai travail à partir de cela, de cette façon, il sera facile d'automatiser la génération des différences correctes.
jthill
2
Notez que cela git checkout master; git checkout -b bug2peut être raccourci git checkout -b bug2 master. La même chose s'applique à git checkout bug1; git rebase master; git checkout master; git merge bug1, qui est identique à git rebase master bug1; git push . bug1:master(accordé, l' pushastuce n'est pas évidente)
knittl
1
J'ai donné une procédure pas à pas pour ranger ci-dessus dans la réponse principale afin que je puisse utiliser un formatage de fantaisie
Mike Monkiewicz
6
J'ai rétrogradé car cela ne répond pas à la question d'origine. Je suis dans une branche qui travaille sur quelque chose, et je viens de faire un changement qui, je pense, devrait être engagé séparément dans la branche d'intégration. Tout ce que je veux faire, c'est mettre en scène ce changement et le ranger afin que je puisse passer à une autre branche et valider séparément, au lieu de ma branche "work in progress" actuelle. (Attention, git ranting en avant.) Il est absurde que ce soit si difficile à faire; Je dois imaginer que c'est un phénomène courant . (Travailler dans une branche et repérer un changement rapide qui doit être fait et oublier de passer en premier.)
jpmc26
0

D'après vos commentaires à la réponse de Mike Monkiewicz, je suggère d'utiliser un modèle plus simple: utilisez des branches de développement régulières, mais utilisez l'option squash de la fusion pour obtenir un seul commit dans votre branche principale:

git checkout -b bug1    # create the development branch
* hack hack hack *      # do some work
git commit
* hack hack hack *
git commit
* hack hack hack *
git commit
* hack hack hack *
git commit
git checkout master     # go back to the master branch
git merge --squash bug1 # merge the work back
git commit              # commit the merge (don't forget
                        #    to change the default commit message)
git branch -D bug1      # remove the development branch

L'avantage de cette procédure est que vous pouvez utiliser le flux de travail git normal.

Rudi
la source
Je ne vois pas comment cette réponse pourrait aider. Ce n'est pas lié à la question d'origine.
frapen
0

TL; DR ;git stash-staged

Après avoir créé un alias:

git config --global alias.stash-staged '!bash -c "git stash -- \$(git diff --staged --name-only)"'

Ici git diffrenvoie la liste des --stagedfichiers --name-only
Et puis nous passons cette liste comme pathspecà la git stashvirgule.

De man git stash:

git stash [--] [<pathspec>...]

<pathspec>...
   The new stash entry records the modified states only for the files
   that match the pathspec. The index entries and working tree
   files are then rolled back to the state in HEAD only for these
   files, too, leaving files that do not match the pathspec intact.

Eugen Konkov
la source
-1

Pour élaguer une modification accidentelle, en particulier la suppression de plusieurs fichiers, procédez comme suit:

git add <stuff to keep> && git stash --keep-index && git stash drop

en d'autres termes, rangez la merde et jetez-la avec la cachette.

Testé dans git version 2.17.1

wmax
la source
un downvote sans commentaire ne m'aide ni le prochain lecteur ... zaenks anon grincheux. Bien que je puisse imaginer un problème avec ce one-liner: il faut faire très attention de ne pas oublier d'ajouter toutes les modifications souhaitées à l'index, sinon ces modifications importantes seront également supprimées. Mais là encore, une utilisation imprudente de tout outil cli peut être très dangereuse pour le temps et le travail précieux dans le pire des cas.
wmax