Comment faire un commit Git dans le passé?

222

Je suis en train de tout convertir en Git pour mon usage personnel et j'ai trouvé quelques anciennes versions d'un fichier déjà dans le référentiel. Comment puis-je le valider dans l'historique dans le bon ordre en fonction de la "date de modification" du fichier afin d'avoir un historique précis du fichier?

On m'a dit que quelque chose comme ça marcherait:

git filter-branch --env-filter="GIT_AUTHOR_DATE=... --index-filter "git commit path/to/file --date " --tag-name-filter cat -- --all  
inconnue
la source
Réponses courtes et simples: stackoverflow.com/a/34639957/2708266
Yash
33
Je me demande si les gens qui recherchent la réponse à cette question veulent simplement maintenir leur "séquence de contributions" GitHub continue de cette façon
ZitRo
1
@ZitRo oui. Et un simple réglage git commit --date="xxx day ago" -m "yyy"suffit à cet effet si quelqu'un se le demande.
Vlas Sokolov
alexpeattie.com/blog/working-with-dates-in-git : si vous cherchez une explication douce
thepurpleowl

Réponses:

198

Les conseils que vous avez reçus sont viciés. La définition inconditionnelle de GIT_AUTHOR_DATE dans an --env-filterréécrirait la date de chaque validation. De plus, il serait inhabituel d'utiliser git commit à l' intérieur --index-filter.

Vous traitez ici de multiples problèmes indépendants.

Spécification de dates autres que «maintenant»

Chaque commit a deux dates: la date de l'auteur et la date du committer. Vous pouvez les remplacer en fournissant des valeurs via les variables d'environnement GIT_AUTHOR_DATE et GIT_COMMITTER_DATE pour toute commande qui écrit un nouveau commit. Voir «Formats de date» dans git-commit (1) ou ci-dessous:

Git internal format = <unix timestamp> <time zone offset>, e.g.  1112926393 +0200
RFC 2822            = e.g. Thu, 07 Apr 2005 22:13:13 +0200
ISO 8601            = e.g. 2005-04-07T22:13:13

La seule commande qui écrit un nouveau commit pendant une utilisation normale est git commit . Il dispose également d'une --dateoption qui vous permet de spécifier directement la date de l'auteur. Votre utilisation prévue comprend git filter-branch --env-filterégalement les variables d'environnement mentionnées ci-dessus (elles font partie de «env» après lequel l'option est nommée; voir «Options» dans git-filter-branch (1) et la commande sous-jacente «plumbing» git-commit -arbre (1) .

Insertion d'un fichier dans un historique de références unique

Si votre référentiel est très simple (c'est-à-dire que vous n'avez qu'une seule branche, pas de balises), vous pouvez probablement utiliser git rebase pour faire le travail.

Dans les commandes suivantes, utilisez le nom d'objet (hachage SHA-1) de la validation au lieu de «A». N'oubliez pas d'utiliser l'une des méthodes de «remplacement de date» lorsque vous exécutez git commit .

---A---B---C---o---o---o   master

git checkout master
git checkout A~0
git add path/to/file
git commit --date='whenever'
git tag ,new-commit -m'delete me later'
git checkout -
git rebase --onto ,new-commit A
git tag -d ,new-commit

---A---N                      (was ",new-commit", but we delete the tag)
        \
         B'---C'---o---o---o   master

Si vous souhaitez mettre à jour A pour inclure le nouveau fichier (au lieu de créer un nouveau commit là où il a été ajouté), utilisez git commit --amendplutôt à la place de git commit. Le résultat ressemblerait à ceci:

---A'---B'---C'---o---o---o   master

Ce qui précède fonctionne tant que vous pouvez nommer le commit qui devrait être le parent de votre nouveau commit. Si vous voulez réellement que votre nouveau fichier soit ajouté via un nouveau commit racine (pas de parents), alors vous avez besoin de quelque chose d'un peu différent:

B---C---o---o---o   master

git checkout master
git checkout --orphan new-root
git rm -rf .
git add path/to/file
GIT_AUTHOR_DATE='whenever' git commit
git checkout -
git rebase --root --onto new-root
git branch -d new-root

N                       (was new-root, but we deleted it)
 \
  B'---C'---o---o---o   master

git checkout --orphanest relativement nouveau (Git 1.7.2), mais il existe d' autres façons de faire la même chose qui fonctionnent sur les anciennes versions de Git.

Insertion d'un fichier dans un historique multi- références

Si votre référentiel est plus complexe (c'est-à-dire qu'il a plus d'une référence (branches, balises, etc.)), alors vous devrez probablement utiliser git filter-branch . Avant d'utiliser git filter-branch , vous devez faire une copie de sauvegarde de l'ensemble de votre référentiel. Une simple archive tar de l'ensemble de votre arborescence de travail (y compris le répertoire .git) est suffisante. git filter-branch crée des références de sauvegarde, mais il est souvent plus facile de récupérer à partir d'un filtrage pas tout à fait correct en supprimant simplement votre .gitrépertoire et en le restaurant à partir de votre sauvegarde.

Remarque: Les exemples ci-dessous utilisent la commande de niveau inférieur git update-index --addau lieu de git add. Vous pouvez utiliser git add , mais vous devez d'abord copier le fichier d'un emplacement externe vers le chemin prévu ( --index-filterexécute sa commande dans un GIT_WORK_TREE temporaire qui est vide).

Si vous souhaitez que votre nouveau fichier soit ajouté à chaque commit existant, vous pouvez le faire:

new_file=$(git hash-object -w path/to/file)
git filter-branch \
  --index-filter \
    'git update-index --add --cacheinfo 100644 '"$new_file"' path/to/file' \
  --tag-name-filter cat \
  -- --all
git reset --hard

Je ne vois pas vraiment de raison de changer les dates des commits existants avec --env-filter 'GIT_AUTHOR_DATE=…'. Si vous l'utilisiez, vous l'auriez rendu conditionnel pour qu'il réécrive la date de chaque commit.

Si vous souhaitez que votre nouveau fichier n'apparaisse que dans les validations après une validation existante («A»), vous pouvez le faire:

file_path=path/to/file
before_commit=$(git rev-parse --verify A)
file_blob=$(git hash-object -w "$file_path")
git filter-branch \
  --index-filter '

    if x=$(git rev-list -1 "$GIT_COMMIT" --not '"$before_commit"') &&
       test -n "$x"; then
         git update-index --add --cacheinfo 100644 '"$file_blob $file_path"'
    fi

  ' \
  --tag-name-filter cat \
  -- --all
git reset --hard

Si vous souhaitez que le fichier soit ajouté via un nouveau commit qui doit être inséré au milieu de votre historique, vous devrez générer le nouveau commit avant d'utiliser git filter-branch et l'ajouter --parent-filterà git filter-branch :

file_path=path/to/file
before_commit=$(git rev-parse --verify A)

git checkout master
git checkout "$before_commit"
git add "$file_path"
git commit --date='whenever'
new_commit=$(git rev-parse --verify HEAD)
file_blob=$(git rev-parse --verify HEAD:"$file_path")
git checkout -

git filter-branch \
  --parent-filter "sed -e s/$before_commit/$new_commit/g" \
  --index-filter '

    if x=$(git rev-list -1 "$GIT_COMMIT" --not '"$new_commit"') &&
       test -n "$x"; then
         git update-index --add --cacheinfo 100644 '"$file_blob $file_path"'
    fi

  ' \
  --tag-name-filter cat \
  -- --all
git reset --hard

Vous pouvez également prendre des dispositions pour que le fichier soit d' abord ajouté dans un commit racine: créer votre nouvelle racine engagement via la méthode « orphelin » de la rebasage git section (capture en new_commit), utiliser l'inconditionnel --index-filter, et --parent-filtercomme "sed -e \"s/^$/-p $new_commit/\"".

Chris Johnsen
la source
Dans le premier exemple du cas d'utilisation, "Insertion d'un fichier dans un historique multi-références": Existe-t-il un moyen d'avoir --index-filterappliqué les validations renvoyées par git rev-list? Pour le moment, je vois un filtre d'index appliqué à un sous-ensemble de rev-list. Appréciez tout aperçu.
Hérisson
@Hedgehog: Tous les exemples «multi-ref» permettent -- --allde traiter tous les commits accessibles depuis n'importe quelle ref. Le dernier exemple montre comment modifier uniquement certaines validations (testez simplement GIT_COMMIT pour ce que vous voulez). Pour modifier uniquement une liste spécifique de validations, vous pouvez enregistrer la liste avant de filtrer (par exemple git rev-list … >/tmp/commits_to_rewrite), puis tester l'appartenance à l'intérieur du filtre (par exemple if grep -qF "$GIT_COMMIT" /tmp/commits_to_rewrite; then git update-index …). Qu'essayez-vous exactement d'accomplir? Vous voudrez peut-être commencer une nouvelle question si elle est trop à expliquer dans les commentaires.
Chris Johnsen
Dans mon cas, j'ai un petit projet que je voulais placer sous contrôle de source. (Je ne l'avais pas fait, car c'est un projet solo et je n'avais pas décidé quel système utiliser). Mon "contrôle de source" consistait simplement à dupliquer l'intégralité de mon arborescence de projet dans un nouveau répertoire et à renommer le répertoire de la version précédente. Je suis nouveau sur Git (après avoir précédemment utilisé SCCS, RCS et un laid dérivé de RCS qui ressemble à CVS), alors ne vous inquiétez pas de me parler. J'ai l'impression que la réponse implique une variation de la section "Insertion d'un fichier dans un historique de référence unique". Correct?
Steve
1
@Steve: Le scénario «référence unique» s'applique si votre historique provient d'une seule ligne de développement (c'est-à-dire qu'il serait logique que tous les instantanés soient considérés comme des points successifs d'une seule branche linéaire). Le scénario «multi-ref» s'applique si vous avez (ou avez eu) plusieurs branches dans votre historique. À moins que votre historique ne soit compliqué (versions «fourchues» pour différents clients, maintien d'une branche «correction de bogues» pendant que de nouveaux travaux se déroulaient dans le «développement», etc.), alors vous envisagez probablement une situation de «référence unique». Cependant , il semble que votre situation diffère de celle de la question…
Chris Johnsen
1
@Steve: Étant donné que vous disposez d'une série d'instantanés de répertoires historiques et que vous n'avez pas encore de référentiel Git, vous pouvez probablement simplement utiliser à import-directories.perlpartir de Git contrib/(ou import-tars.perl, ou import-zips.py…) pour créer un nouveau référentiel Git à partir de vos instantanés (même avec "Anciens" horodatages). Les techniques rebase/ filter-branchdans ma réponse ne sont nécessaires que si vous souhaitez insérer un fichier qui a été «laissé de côté» de l'historique d'un référentiel existant.
Chris Johnsen
119

Vous pouvez créer la validation comme d'habitude, mais lorsque vous la validez, définissez les variables d'environnement GIT_AUTHOR_DATEet GIT_COMMITTER_DATEles horaires appropriés.

Bien sûr, cela fera le commit à la pointe de votre branche (c'est-à-dire devant le commit HEAD actuel). Si vous voulez le repousser plus loin dans le repo, vous devez faire un peu de fantaisie. Disons que vous avez cette histoire:

o--o--o--o--o

Et vous voulez que votre nouveau commit (marqué comme "X") apparaisse en second :

o--X--o--o--o--o

Le moyen le plus simple serait de créer une branche à partir du premier commit, d'ajouter votre nouveau commit, puis de rebaser toutes les autres commits au-dessus du nouveau. Ainsi:

$ git checkout -b new_commit $desired_parent_of_new_commit
$ git add new_file
$ GIT_AUTHOR_DATE='your date' GIT_COMMITTER_DATE='your date' git commit -m 'new (old) files'
$ git checkout master
$ git rebase new_commit
$ git branch -d new_commit
mipadi
la source
25
je trouve toujours cette bonne réponse, mais je dois ensuite me précipiter pour trouver le format de la date, alors voici pour la prochaine fois 'Ven 26 juillet 19:32:10 2013 -0400'
MeBigFatGuy
94
GIT_AUTHOR_DATE='Fri Jul 26 19:32:10 2013 -0400' GIT_COMMITTER_DATE='Fri Jul 26 19:32:10 2013 -0400' git commit
Xeoncross
15
Il y a plus, par exemple le plutôt mnémonique 2013-07-26T19:32:10: kernel.org/pub/software/scm/git/docs/…
Marian
3
Au fait, la partie '-0400' spécifie le décalage du fuseau horaire. Soyez conscient de le sélectionner correctement ... sinon, votre temps changera en fonction de cela. Par exemple, dans le cas de moi-même, qui habite à Chicago, j'ai dû choisir «-0600» (CST Amérique du Nord). Vous pouvez trouver les codes ici: timeanddate.com/time/zones
evaldeslacasa
2
comme la date doit être répétée, j'ai trouvé plus facile de créer une autre variable:THE_TIME='2019-03-30T8:20:00 -0500' GIT_AUTHOR_DATE=$THE_TIME GIT_COMMITTER_DATE=$THE_TIME git commit -m 'commit message here'
Frank Henard
118

Je sais que cette question est assez ancienne, mais c'est ce qui a fonctionné pour moi:

git commit --date="10 day ago" -m "Your commit message" 
Hyder B.
la source
16
Cela a parfaitement fonctionné. Je suis surpris que le --dateformat de date relative soit lisible par l'homme.
ZitRo
@ZitRo pourquoi pas 10 jours?
Alex78191
10
Avertissement: git --datene modifiera que le $ GIT_AUTHOR_DATE ... donc selon les circonstances, vous verrez la date actuelle attachée au commit ($ GIT_COMMITTER_DATE)
Guido U. Draheim
3
Marquer ce que @ GuidoU.Draheim a dit. Vous pouvez vérifier tous les détails d'un commit à l'aide de git show <commit-hash> --format=fuller. Là, vous verrez AuthorDatela date que vous avez spécifiée, mais la CommitDatedate réelle du commit.
d4nyll
Il n'y a donc aucun moyen de modifier la date de validation ou de faire une validation complètement dépassée? @ d4nyll
Rahul
35

Dans mon cas, au fil du temps, j'avais enregistré un tas de versions de monfichier en tant que myfile_bak, myfile_old, myfile_2010, sauvegardes / myfile etc. Je voulais mettre l'historique de myfile en git en utilisant leurs dates de modification. Renommez donc le plus ancien en monfichier, git add myfilepuis git commit --date=(modification date from ls -l) myfile, renommez le plus ancien en monfichier, un autre git commit avec --date, répétez ...

Pour automatiser quelque peu cela, vous pouvez utiliser shell-foo pour obtenir l'heure de modification du fichier. J'ai commencé avec ls -let cut, mais stat (1) est plus direct

git commit --date = "` stat -c% y myfile `" monfichier

skierpage
la source
1
Agréable. J'ai certainement eu des fichiers d'avant mes jours git pour lesquels je voulais utiliser l'heure de modification du fichier. Cependant, cela ne fixe-t-il pas seulement la date de validation (pas la date de l'auteur?)
Xeoncross
De git-scm.com/docs/git-commit: --date"Remplacez la date de l'auteur utilisée dans le commit." git logLa date de semble être la AuthorDate, git log --pretty=fulleraffiche à la fois AuthorDate et CommitDate.
skierpage
16
L' git commitoption --datene modifiera que le GIT_AUTHOR_DATE, pas GIT_COMMITTER_DATE. Comme l'explique le Pro Git Book : "L'auteur est la personne qui a écrit l'œuvre à l'origine, tandis que le committer est la dernière personne à avoir appliqué l'œuvre." Dans le contexte des dates, GIT_AUTHOR_DATEc'est la date à laquelle le fichier a été modifié, tandis que GIT_COMMITTER_DATEc'est la date à laquelle il a été validé. Il est important ici de noter que par défaut, git logaffiche les dates de l'auteur en tant que «Date» mais utilise ensuite les dates de validation pour le filtrage lorsqu'une --sinceoption est donnée .
Christopher
Il n'y a pas d'option -c pour stat dans OS X 10.11.1
thinsoldier
L'équivalent de stat -c %ydans macOS (et d'autres variantes BSD) est stat -f %m.
victor
21

Ce qui suit est ce que j'utilise pour valider des modifications sur foodes N=1jours dans le passé:

git add foo
git commit -m "Update foo"
git commit --amend --date="$(date -v-1d)"

Si vous voulez engager à une date encore plus ancienne, disons 3 jours en arrière, il suffit de changer l' dateargument: date -v-3d.

C'est vraiment utile lorsque vous oubliez de commettre quelque chose hier, par exemple.

UPDATE : --dateaccepte également les expressions comme --date "3 days ago"ou même --date "yesterday". Nous pouvons donc le réduire à une commande de ligne:

git add foo ; git commit --date "yesterday" -m "Update"
Vilson Vieira
la source
6
Avertissement: git --datene modifiera que le $ GIT_AUTHOR_DATE ... donc selon les circonstances, vous verrez la date actuelle attachée au commit ($ GIT_COMMITTER_DATE)
Guido U. Draheim
mélanger 2 approches ensemble rend l'ajustement presque parfait:git commit --amend --date="$(stat -c %y fileToCopyMTimeFrom)"
andrej
Impressionnant! Même commande avec une date lisible par l'hommegit commit --amend --date="$(date -R -d '2020-06-15 16:31')"
pixelbrackets
16

Dans mon cas, lors de l'utilisation de l'option --date, mon processus git s'est planté. Peut-être que j'ai fait quelque chose de terrible. Et en conséquence, un fichier index.lock est apparu. J'ai donc supprimé manuellement les fichiers .lock du dossier .git et exécuté, pour que tous les fichiers modifiés soient validés dans les dates passées et cela a fonctionné cette fois. Merci pour toutes les réponses ici.

git commit --date="`date --date='2 day ago'`" -am "update"
JstRoRR
la source
5
Voir le commentaire ci-dessus surgit commit --date . De plus, votre exemple de message de validation devrait être modifié pour décourager les messages pauvres d'une seule ligne comme ceux-ci @JstRoRR
Christopher
4
Attention: git --datene modifiera que le $ GIT_AUTHOR_DATE ... donc selon les circonstances, vous verrez la date actuelle attachée au commit ($ GIT_COMMITTER_DATE)
Guido U. Draheim
9

Pour faire un commit qui semble avoir été fait dans le passé, vous devez définir les deux GIT_AUTHOR_DATEet GIT_COMMITTER_DATE:

GIT_AUTHOR_DATE=$(date -d'...') GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" git commit -m '...'

date -d'...'peut être la date exacte comme 2019-01-01 12:00:00ou relative comme 5 months ago 24 days ago.

Pour voir les deux dates dans git log, utilisez:

git log --pretty=fuller

Cela fonctionne également pour les validations de fusion:

GIT_AUTHOR_DATE=$(date -d'...') GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" git merge <branchname> --no-ff
mx0
la source
2

Vous pouvez toujours changer une date sur votre ordinateur, faire un commit, puis changer la date et pousser.

Nik
la source
1
Je pense que ce n'est pas la bonne pratique, cela peut créer des problèmes inconnus
Vino