Pourquoi Git dit-il que ma branche principale est «déjà à jour» même si ce n'est pas le cas?

118

Problème de base

Je viens de supprimer TOUT le code d'un fichier dans mon projet et de valider la modification dans mon git local (exprès). J'ai fait

git pull upstream master

pour récupérer et fusionner en amont (donc en théorie, le code supprimé devrait être de retour).

Git me dit que tout est à jour.

Tout n'est certainement PAS à jour - tout ce code supprimé est toujours supprimé.

Autres informations pertinentes

Je n'ai qu'une seule branche appelée "master".

J'ai récemment configuré "master" pour suivre en amont comme ceci:

Le maître de branche est configuré pour suivre le maître de branche distant depuis l'amont.

La commande git branch -vvdonne:

* master 7cfcb29 [upstream/master: ahead 9] deletion test

Pourquoi pourquoi pourquoi cela se produit-il? Je suis sur le point d'envoyer par e-mail à mon chef de projet toutes les modifications que j'apporte à notre code.

Mettre à jour

Je pensais que c'était évident, mais de toute façon c'est mon objectif:

Obtenez le code le plus récent sur mon système.

Excusez ma colère ici, mais pourquoi une tâche aussi simple doit-elle être si difficile?

user1971506
la source
1
Il est évident que si vous supprimez votre source, essayez de tirer avec un simple "pull" que git ne remplacera pas vos modifications locales (dans ce cas, vos suppressions) sans vous assurer que vous voulez vraiment remplacer vos modifications locales. La première réponse semble expliquer exactement comment vous pouvez vous y prendre.
CashCow

Réponses:

209

Je pense que votre problème fondamental ici est que vous interprétez et / ou comprenez mal ce que fait git et pourquoi il le fait.

Lorsque vous clonez un autre dépôt, git fait une copie de tout ce qui est "là-bas". Il prend également "leurs" étiquettes de branche, telles que master, et fait une copie de cette étiquette dont le "nom complet" dans votre arbre git est (normalement) remotes/origin/master(mais dans votre cas, remotes/upstream/master). La plupart du temps, vous pouvez également omettre la remotes/partie, de sorte que vous pouvez vous référer à cette copie originale comme upstream/master.

Si vous apportez et validez maintenant des modifications à certains fichiers, vous êtes le seul à avoir ces modifications. Pendant ce temps, d'autres personnes peuvent utiliser le référentiel d'origine (à partir duquel vous avez créé votre clone) pour créer d'autres clones et modifier ces clones. Ils sont les seuls avec leurs changements, bien sûr. Finalement, cependant, quelqu'un peut avoir des changements à renvoyer au propriétaire d'origine (via "push" ou des correctifs ou autre).

La git pullcommande est principalement un raccourci pour git fetchsuivi de git merge. Ceci est important car cela signifie que vous devez comprendre ce que font réellement ces deux opérations.

La git fetchcommande dit de retourner à l'endroit où vous avez cloné (ou que vous avez configuré comme un endroit à récupérer) et de trouver "de nouvelles choses que quelqu'un d'autre a ajoutées, modifiées ou supprimées". Ces modifications sont copiées et appliquées à votre copie de ce que vous en avez obtenu précédemment . Ils ne s'appliquent pas à votre propre travail, uniquement au leur.

La git mergecommande est plus compliquée et c'est là que vous vous trompez. Ce qu'il fait, un peu simplifié à l'extrême, c'est comparer "ce que vous avez changé dans votre copie" aux "changements que vous avez récupérés de quelqu'un d'autre et ainsi ajoutés à votre-copie-du-travail-de-quelqu'un-d'autre". Si vos modifications et leurs modifications ne semblent pas entrer en conflit, l' mergeopération les rassemble et vous donne un «commit de fusion» qui lie votre développement et leur développement ensemble (bien qu'il existe un cas très courant «facile» dans lequel vous n'avez pas change et vous obtenez une "avance rapide").

La situation que vous rencontrez actuellement est celle dans laquelle vous avez apporté des changements et les avez validés - neuf fois, en fait, d'où le «9 à venir» - et ils n'ont fait aucun changement. Donc, fetchconsciencieusement ne récupère rien, puis mergeprend leur absence de changements et ne fait rien non plus.

Ce que vous voulez, c'est regarder, ou peut-être même "réinitialiser", "leur" version du code.

Si vous voulez simplement le regarder, vous pouvez simplement consulter cette version:

git checkout upstream/master

Cela indique à git que vous voulez déplacer le répertoire courant vers la branche dont le nom complet est en fait remotes/upstream/master. Vous verrez leur code à partir de la dernière fois que vous avez exécuté git fetchet obtenu leur dernier code.

Si vous voulez abandonner toutes vos propres modifications, ce que vous devez faire est de changer l'idée de git de la révision que votre étiquette masterdevrait nommer. Actuellement, il nomme votre dernier commit. Si vous revenez sur cette branche:

git checkout master

alors la git resetcommande vous permettra de "déplacer l'étiquette", pour ainsi dire. Le seul problème restant (en supposant que vous êtes vraiment prêt à abandonner tout ce que vous avez fait) est de trouver où l'étiquette doit pointer.

git logvous permettra de trouver les noms numériques - ces choses comme 7cfcb29- qui sont des noms permanents (qui ne changent jamais), et il existe un nombre ridicule d'autres façons de les nommer, mais dans ce cas, vous voulez juste le nom upstream/master.

Pour déplacer l'étiquette, effacer vos propres modifications (celles que vous avez validées sont en fait récupérables pendant un certain temps, mais c'est beaucoup plus difficile après cela, alors soyez très sûr):

git reset --hard upstream/master

Le --harddit à git d'effacer ce que vous avez fait, de déplacer l'étiquette de branche actuelle, puis de vérifier le commit donné.

Il n'est pas très courant de vouloir vraimentgit reset --hard et d'effacer un tas de travail. Une méthode plus sûre (rendant beaucoup plus facile la récupération de ce travail si vous décidez qu'une partie en valait la peine après tout) est de renommer votre branche existante:

git branch -m master bunchofhacks

et ensuite créer une nouvelle branche locale nommée master"pistes" (je n'aime pas vraiment ce terme car je pense qu'il déroute les gens mais c'est le terme git :-)) le master d'origine (ou amont):

git branch -t master upstream/master

que vous pouvez ensuite utiliser:

git checkout master

Ce que font les trois dernières commandes (il y a des raccourcis pour n'en faire que deux commandes) est de changer le nom collé sur l'étiquette existante, puis de créer une nouvelle étiquette, puis de basculer dessus:

avant de faire quoi que ce soit:

C0 -    "remotes/upstream/master"
    \
     \- C1 --- C2 --- C3 --- C4 --- C5 --- C6 --- C7 --- C8 --- C9    "master"

après git branch -m:

C0 -    "remotes/upstream/master"
    \
     \- C1 --- C2 --- C3 --- C4 --- C5 --- C6 --- C7 --- C8 --- C9    "bunchofhacks"

après git branch -t master upstream/master:

C0 -    "remotes/upstream/master", "master"
    \
     \- C1 --- C2 --- C3 --- C4 --- C5 --- C6 --- C7 --- C8 --- C9    "bunchofhacks"

Voici C0le dernier commit (une arborescence des sources complète) que vous avez obtenu lorsque vous avez fait votre git clone. C1 à C9 sont vos commits.

Notez que si vous étiez à git checkout bunchofhackset puis git reset --hard HEAD^^, cela changerait la dernière image en:

C0 -    "remotes/upstream/master", "master"
    \
     \- C1 --- C2 --- C3 --- C4 --- C5 --- C6 --- C7 -    "bunchofhacks"
                                                      \
                                                       \- C8 --- C9

La raison est que HEAD^^nomme la révision deux à partir de la tête de la branche actuelle (ce qui serait juste avant la réinitialisation bunchofhacks), reset --hardpuis déplace l'étiquette. Les commits C8 et C9 sont maintenant pour la plupart invisibles (vous pouvez utiliser des choses comme le reflog et git fsckles trouver mais ce n'est plus trivial). Vos étiquettes sont à vous pour bouger comme vous le souhaitez. La fetchcommande prend en charge ceux qui commencent par remotes/. Il est conventionnel de faire correspondre "le vôtre" avec "le leur" (donc s'ils en ont un, remotes/origin/mauvevous nommez le vôtre mauveaussi), mais vous pouvez taper "le leur" chaque fois que vous voulez nommer / voir les commits que vous avez obtenus "d'eux". (N'oubliez pas que "un commit" est une arborescence source entière. Vous pouvez choisir un fichier spécifique à partir d'un commit, avec git showpar exemple,

Torek
la source
12
J'ai beaucoup appris de votre excellente réponse. Mais je ne comprends toujours pas pourquoi je reçois le message d'état git: "Votre branche est à jour avec 'origin / master'" lorsque le dépôt en amont est plusieurs commits avant mon dépôt actuel. Je peux me mettre à jour avec git pull, mais comment savoir que je dois tirer si le message dit que je suis à jour?
Christopher Werby
@ChristopherWerby: on dirait que vous utilisez git mergeune commande en ligne de commande (ce que je fais moi-même, c'est une méthode raisonnable). Notez, cependant, que cela git pullcommence par l'exécution git fetch, puis par l' exécution git merge(ou git rebasesi vous lui dites de le faire à la place). C'est l' étape d' extraction qui amène en fait les nouveaux commits à fusionner (ou rebasé uniquement).
torek
@torek Existe-t-il une commande, autre que git status, que je peux utiliser pour voir si le dépôt en amont est en avance sur mon dépôt actuel? Le message que je reçois git statusqui dit "Votre branche est à jour avec 'origin / master'" me confond en pensant que j'ai les dernières modifications, alors que ce n'est pas le cas. Parfois, je reçois un message qui dit quelque chose comme "origine / maître a 5 commits avant votre branche" (paraphrase). Mais ce n'est pas cohérent.
Christopher Werby
1
Cela ne répond toujours pas à la raison pour laquelle git statusdit "Votre branche est à jour" alors qu'un immédiat git fetchtire vers le bas plus d'une centaine d'objets et résout près d'une centaine de deltas, mentionne plusieurs nouvelles branches et quelques nouvelles balises et modifie le main (remote tracking) branch head commit - et puis un autre statut git joyeusement, et malhonnêtement, dit toujours "Votre branche est à jour". Il est clair que beaucoup de produits provenaient de l'origine mais la branche était «à jour avec l'origine» avant et après?
NeilG
1
git pulls'exécute en git fetchpremier, puis git merge(ou une autre seconde commande de votre choix). L' git fetchétape met à jour votre mémoire de Git de leur statut de Git, en appelant leur Git et obtenir quelque chose de nouveau d'eux. Votre git statusseul vérifie la mémoire de votre Git de leur Git.
torek
18

J'ai eu le même problème que toi.

Je l'ai fait git status git fetch git pull, mais ma branche était toujours en retard à l'origine. J'avais des dossiers et des fichiers poussés à distance et j'ai vu les fichiers sur le Web, mais sur mon local, ils manquaient.

Enfin, ces commandes ont mis à jour tous les fichiers et dossiers sur mon local:

git fetch --all
git reset --hard origin/master 

ou si vous voulez une succursale

git checkout your_branch_name_here
git reset --hard origin/your_branch_name_here
jturi
la source
1
Merci! cela a vraiment fonctionné. Gardez à l'esprit que les noms de branche sont sensibles à la casse!
André Ramon
4

Toutes les modifications que vous validez, comme la suppression de tous vos fichiers de projet, seront toujours en place après une extraction. Tout ce qu'un pull fait est de fusionner les dernières modifications d'un autre endroit dans votre propre branche, et si votre branche a tout supprimé, vous obtiendrez au mieux des conflits de fusion lorsque les modifications en amont affectent les fichiers que vous avez supprimés. Donc, en bref, oui tout est à jour.

Si vous décrivez le résultat que vous souhaitez obtenir au lieu de "tous les fichiers supprimés", quelqu'un pourra peut-être suggérer une marche à suivre appropriée.

Mettre à jour:

OBTENEZ LE PLUS RÉCENT DU CODE SUR MON SYSTÈME

Ce que vous ne semblez pas comprendre, c'est que vous disposez déjà du code le plus récent, qui est le vôtre. Si vous voulez vraiment voir le travail le plus récent de quelqu'un d'autre sur la branche principale, faites simplement:

git fetch upstream
git checkout upstream/master

Notez que cela ne vous laissera pas en mesure de (re) démarrer immédiatement votre propre travail. Si vous avez besoin de savoir comment annuler quelque chose que vous avez fait ou annuler les modifications que vous ou quelqu'un d'autre avez apportées, veuillez fournir des détails. Pensez également à lire à quoi sert le contrôle de version, car vous semblez mal comprendre son objectif de base.

Ryan Stewart
la source
Je veux en fait la version la plus récente du code de quelqu'un d'autre sur mon système. Cependant, ces deux commandes n'ont pas fait cela. Tout ce que j'obtiens, c'est "déjà sur le maître de branche". Le code dans mes fichiers ne reflète pas le code de l'autre personne.
user1971506
Désolé, aurait dû l'être upstream/master. Mis à jour ma réponse.
Ryan Stewart
3

Comme le disent les autres affiches, pull fusionne les modifications de l'amont dans votre référentiel. Si vous souhaitez remplacer ce qui se trouve dans votre référentiel par ce qui se trouve en amont, vous avez plusieurs options. Tout de suite, j'irais avec

git checkout HEAD^1  # Get off your repo's master.. doesn't matter where you go, so just go back one commit
git branch -d master  # Delete your repo's master branch
git checkout -t upstream/master  # Check out upstream's master into a local tracking branch of the same name
Paul
la source
1

La meilleure réponse est bien meilleure en termes de largeur et de profondeur des informations fournies, mais il semble que si vous vouliez que votre problème soit résolu presque immédiatement, et que cela ne vous dérange pas de marcher sur certains des principes de base du contrôle de version, vous pourriez ...

  1. Passer au maître

    $ git checkout upstream master
    
  2. Supprimez votre branche indésirable. (Remarque: il doit avoir le -D, au lieu de l'indicateur normal -d car votre branche a beaucoup de commits avant le maître.)

    $ git branch -d <branch_name>
    
  3. Créer une nouvelle branche

    $ git checkout -b <new_branch_name>
    
BrotherDonkey
la source
1

Bien qu'aucune de ces réponses ne fonctionne pour moi, j'ai pu résoudre le problème à l'aide de la commande suivante.

git fetch origin

Cela a fait un tour pour moi.

Doctiger
la source
0

Juste un rappel amical si vous avez des fichiers localement qui ne sont pas dans github et que vous git statusdites

Votre succursale est à jour avec «origine / maître». rien à commettre, arbre de travail propre

Cela peut arriver si les fichiers sont .gitignore

Essayez de courir

cat .gitignore 

et voir si ces fichiers apparaissent là-haut. Cela expliquerait pourquoi git ne veut pas les déplacer vers la télécommande.

stevec
la source