Je me rends compte que git fonctionne en différenciant le contenu des fichiers. J'ai des fichiers que je veux copier. Pour éviter absolument que git ne se trompe, existe-t-il une commande git qui peut être utilisée pour copier les fichiers dans un répertoire différent (pas mv, mais cp), et mettre en scène les fichiers également?
La réponse courte est simplement "non". Mais il y a plus à savoir; cela nécessite juste quelques informations. (Et comme JDB suggère dans un commentaire , je mentionnerai pourquoi git mv
Existe par commodité.)
Légèrement plus long: vous avez raison que Git diffère les fichiers, mais vous pouvez vous tromper sur quand Git fait ces différences de fichiers.
Le modèle de stockage interne de Git propose que chaque commit soit un instantané indépendant de tous les fichiers de ce commit. La version de chaque fichier qui va dans le nouveau commit, c'est-à-dire les données dans l'instantané pour ce chemin, est tout ce qui est dans l'index sous ce chemin au moment où vous exécutez git commit
.1
L'implémentation réelle, au premier niveau, est que chaque fichier instantané est capturé sous forme compressée en tant qu'objet blob dans la base de données Git. L'objet blob est assez indépendant de toutes les versions précédentes et suivantes de ce fichier, à l'exception d'un cas spécial: si vous effectuez un nouveau commit dans lequel aucune donnée ont changé, vous allez réutiliser l'ancien blob . Ainsi, lorsque vous effectuez deux validations consécutives, chacune contenant 100 fichiers, et qu'un seul fichier est modifié, la deuxième validation réutilise 99 objets blob précédents, et n'a besoin que d'un instantané d'un fichier réel dans un nouvel objet blob.2
Par conséquent, le fait que Git diffère les fichiers n'entre pas du tout dans les commits. Aucun commit ne dépend d'un commit précédent, autre que de stocker l'ID de hachage du commit précédent (et peut-être de réutiliser des blobs correspondant exactement, mais c'est un effet secondaire de leur correspondance exacte, plutôt qu'un calcul sophistiqué au moment où vous exécutez git commit
).
Maintenant, tous ces objets blob indépendants occupent finalement une quantité exorbitante d'espace. À ce stade , Git peut "empaqueter" des objets dans un fichier .pack
. Il comparera chaque objet à un ensemble sélectionné d'autres objets - ils peuvent être antérieurs ou ultérieurs dans l'histoire, et avoir le même nom de fichier ou des noms de fichiers différents, et en théorie Git pourrait même compresser un objet commit contre un objet blob ou vice versa (bien que ce ne soit pas le cas en pratique) - et essayez de trouver un moyen de représenter de nombreux objets blob en utilisant moins d'espace disque. Mais le résultat est toujours, au moins logiquement, une série d'objets indépendants, récupérés complètement intacts dans leur forme d'origine en utilisant leurs identifiants de hachage. Ainsi, même si la quantité d'espace disque utilisé diminue (nous l'espérons!) À ce stade, tous les objets sont exactement les mêmes qu'auparavant.
Alors quand Git compare-t-il les fichiers? La réponse est: Uniquement lorsque vous le lui demandez. Le "temps de demande" est lorsque vous exécutez git diff
, Soit directement:
git diff commit1 commit2
ou indirectement:
git show commit # roughly, `git diff commit^@ commmit`
git log -p # runs `git show commit`, more or less, on each commit
Il y a un tas de subtilités à ce sujet - en particulier, git show
Produira ce que Git appelle diffs combinés lorsqu'il est exécuté sur des validations de fusion, tandis que git log -p
Saute normalement juste à côté des différences pour les validations de fusion, mais celles-ci, ainsi que d'autres cas importants, se produisent lorsque Git exécute git diff
.
C'est lorsque Git exécute git diff
que vous pouvez (parfois) lui demander de trouver, ou de ne pas trouver, de copies. Le drapeau -C
, Également orthographié --find-copies=<number>
, Demande à Git de trouver des copies. Le drapeau --find-copies-harder
(Que la documentation de Git appelle "coûteux en calcul") semble plus difficile à copier que le drapeau ordinaire -C
. L'option -B
(Rompre les appariements inappropriés) affecte -C
. L'option -M
Alias --find-renames=<number>
Affecte également -C
. On peut demander à la commande git merge
D'ajuster son niveau de détection de renommage, mais, au moins actuellement, on ne peut pas lui dire de trouver des copies ni de rompre les appariements inappropriés.
(Une commande, git blame
, Effectue une recherche de copie quelque peu différente et ce qui précède ne s'applique pas entièrement à elle.)
1Si vous exécutez git commit --include <paths>
Ou git commit --only <paths>
Ou git commit <paths>
Ou git commit -a
, Considérez-les comme modifiant l'index avant d'exécuter git commit
. Dans le cas particulier de --only
, Git utilise un index temporaire, ce qui est un peu compliqué, mais il commet toujours à partir de un index - il utilise simplement le temporaire spécial au lieu du normal. Pour créer l'index temporaire, Git copie tous les fichiers de la validation HEAD
, puis les superpose avec les fichiers --only
Que vous avez répertoriés. Pour les autres cas, Git copie simplement les fichiers de l'arborescence de travail dans l'index normal, puis effectue la validation à partir de l'index comme d'habitude.
2En fait, l'instantané réel, stockant le blob dans le référentiel, se produit pendant git add
. Cela rend secrètement git commit
Beaucoup plus rapide, car vous ne remarquez normalement pas le temps supplémentaire qu'il faut pour exécuter git add
Avant de lancer git commit
.
git mv
ExisteCe que git mv old new
Fait, très à peu près:
mv old new
git add new
git add old
La première étape est assez évidente: nous devons renommer la version arborescente du fichier. La deuxième étape est similaire: nous devons mettre en place la version index du fichier. Le troisième, cependant, est bizarre: pourquoi devrions-nous "ajouter" un fichier que nous venons de supprimer? Eh bien, git add
N'ajoute pas toujours un fichier: à la place, dans ce cas, il détecte que le fichier était dans l'index et n'est pas plus.
Nous pourrions également épeler cette troisième étape comme suit:
git rm --cached old
Tout ce que nous faisons, c'est retirer l'ancien nom de l'index.
Mais il y a un problème ici, c'est pourquoi j'ai dit " très à peu près". L'index contient une copie de chaque fichier qui sera validé la prochaine fois que vous exécuterez git commit
. Cette copie pourrait ne pas correspondre à celle de l'arborescence de travail. En fait, elle pourrait même ne pas correspondre à celle de HEAD
, si il y en a un dans HEAD
.
Par exemple, après:
echo I am a foo > foo
git add foo
le fichier foo
existe dans l'arbre de travail et dans l'index. Le contenu de l'arborescence de travail et le contenu de l'index correspondent. Mais maintenant, changeons la version de l'arbre de travail:
echo I am a bar > foo
Maintenant, l'index et l'arbre de travail diffèrent. Supposons que nous voulons déplacer le fichier sous-jacent de foo
vers bar
, mais — pour une raison étrange3—Nous voulons garder le contenu de l'index inchangé . Si nous courons:
mv foo bar
git add bar
nous obtiendrons I am a bar
dans le nouveau fichier d'index. Si nous supprimons ensuite l'ancienne version de foo
de l'index, nous perdons entièrement la version I am a foo
.
Ainsi, git mv foo bar
Ne se déplace pas-et-ajoute-deux vraiment, ni ne se déplace-ajoute-et-supprime. Au lieu de cela, il renomme le fichier d'arbre de travail et renomme la copie dans l'index. Si la copie d'index du fichier d'origine diffère du fichier d'arborescence de travail, la copie d'index renommée diffère toujours de la copie d'arborescence de travail renommée.
Il est très difficile de le faire sans une commande frontale comme git mv
.4 Bien sûr, si vous prévoyez de git add
Tout, vous n'avez pas besoin de tout cela en premier lieu. Et, il convient de noter que si git cp
Existait, il devrait probablement également copier la version d'index, pas la version d'arbre de travail, lorsque faire la copie d'index. Donc git cp
Devrait vraiment exister. Il devrait également y avoir une option git mv --after
, À la Mercurial hg mv --after
. Les deux devraient exister, mais actuellement pas. (À mon avis, il y a moins de demande pour l'un ou l'autre de ces éléments que pour le git mv
Direct.)
3Pour cet exemple, c'est un peu idiot et inutile. Mais si vous utilisez git add -p
Pour préparer soigneusement un correctif pour une validation intermédiaire, puis décidez qu'avec le correctif, vous souhaitez renommer le fichier, il est certainement pratique de pouvoir le faire sans gâcher votre version intermédiaire soigneusement assemblée.
4Ce n'est pas impossible: git ls-index --stage
Vous obtiendra les informations dont vous avez besoin de l'index tel qu'il est actuellement, et git update-index
Vous permet d'apporter des modifications arbitraires à l'index. Vous pouvez combiner ces deux, et certains scripts ou programmes Shell complexes dans un langage plus agréable, pour créer quelque chose qui implémente git mv --after
Et git cp
.