¿Cómo hago un compromiso de Git en el pasado?

222

Estoy convirtiendo todo a Git para mi uso personal y encontré algunas versiones antiguas de un archivo que ya están en el repositorio. ¿Cómo lo confirmo en el historial en el orden correcto de acuerdo con la "fecha de modificación" del archivo para que tenga un historial preciso del archivo?

Me dijeron que algo como esto funcionaría:

git filter-branch --env-filter="GIT_AUTHOR_DATE=... --index-filter "git commit path/to/file --date " --tag-name-filter cat -- --all  
desconocido
fuente
Respuestas cortas y simples: stackoverflow.com/a/34639957/2708266
Yash
33
Me pregunto si las personas que buscan la respuesta a esto solo quieren mantener su "racha de contribución" de GitHub continua de esta manera
ZitRo
1
@ZitRo sí. Y simplemente establecer git commit --date="xxx day ago" -m "yyy"es suficiente para ese propósito si alguien se pregunta.
Vlas Sokolov
alexpeattie.com/blog/working-with-dates-in-git : si busca una explicación amable
thepurpleowl

Respuestas:

198

El consejo que le dieron es defectuoso. Establecer incondicionalmente GIT_AUTHOR_DATE en un --env-filterreescribe la fecha de cada confirmación. Además, sería inusual usar git commit dentro --index-filter.

Estás lidiando con múltiples problemas independientes aquí.

Especificar fechas distintas a "ahora"

Cada confirmación tiene dos fechas: la fecha del autor y la fecha del confirmador. Puede anular cada uno proporcionando valores a través de las variables de entorno GIT_AUTHOR_DATE y GIT_COMMITTER_DATE para cualquier comando que escriba una nueva confirmación. Consulte "Formatos de fecha" en git-commit (1) o a continuación:

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

El único comando que escribe un nuevo commit durante el uso normal es git commit . También tiene una --dateopción que le permite especificar directamente la fecha del autor. Su uso anticipado incluye git filter-branch --env-filtertambién las variables de entorno mencionadas anteriormente (estas son parte del "env" después del cual se nombra la opción; consulte "Opciones" en git-filter-branch (1) y el comando subyacente "plomería" git-commit -árbol (1) .

Insertar un archivo en un solo historial de referencia

Si su repositorio es muy simple (es decir, solo tiene una sola rama, sin etiquetas), entonces probablemente pueda usar git rebase para hacer el trabajo.

En los siguientes comandos, use el nombre del objeto (hash SHA-1) de la confirmación en lugar de "A". No olvide utilizar uno de los métodos de "anulación de fecha" cuando ejecute 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 desea actualizar A para incluir el nuevo archivo (en lugar de crear una nueva confirmación donde se agregó), use en git commit --amendlugar de git commit. El resultado se vería así:

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

Lo anterior funciona siempre que pueda nombrar el commit que debería ser el padre de su nuevo commit. Si realmente desea que su nuevo archivo se agregue a través de una nueva confirmación raíz (sin padres), entonces necesita algo un poco diferente:

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 --orphanes relativamente nuevo (Git 1.7.2), pero hay otras formas de hacer lo mismo que funcionan en versiones anteriores de Git.

Insertar un archivo en un historial de múltiples referencias

Si su repositorio es más complejo (es decir, tiene más de una referencia (ramas, etiquetas, etc.)), entonces probablemente necesitará usar git filter-branch . Antes de usar git filter-branch , debe hacer una copia de seguridad de todo su repositorio. Un archivo tar simple de todo su árbol de trabajo (incluido el directorio .git) es suficiente. git filter-branch hace referencias de copia de seguridad, pero a menudo es más fácil recuperarse de un filtro no del todo correcto simplemente borrando su .gitdirectorio y restableciéndolo desde su copia de seguridad.

Nota: Los siguientes ejemplos usan el comando de nivel inferior en git update-index --addlugar de git add. Podría usar git add , pero primero necesitaría copiar el archivo desde alguna ubicación externa a la ruta esperada ( --index-filterejecuta su comando en un GIT_WORK_TREE temporal que está vacío).

Si desea que su nuevo archivo se agregue a cada confirmación existente, puede hacer esto:

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

Realmente no veo ninguna razón para cambiar las fechas de las confirmaciones existentes con --env-filter 'GIT_AUTHOR_DATE=…'. Si lo usó, lo haría condicional para que reescribiera la fecha de cada confirmación.

Si desea que su nuevo archivo aparezca solo en las confirmaciones después de alguna confirmación existente ("A"), puede hacer esto:

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 desea que el archivo se agregue a través de una nueva confirmación que se insertará en la mitad de su historial, deberá generar la nueva confirmación antes de usar git filter-branch y agregar --parent-filtera 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

También puede hacer arreglos para que el archivo se agregue primero en una nueva confirmación de raíz: cree su nueva confirmación de raíz a través del método "huérfano" de la sección git rebase (capturarla new_commit), use el incondicional --index-filtery cosas --parent-filtersimilares "sed -e \"s/^$/-p $new_commit/\"".

Chris Johnsen
fuente
En el primer ejemplo del caso de uso, "Insertar un archivo en un historial de múltiples referencias": ¿Hay alguna forma de haber --index-filteraplicado a los commits devueltos por git rev-list? Por el momento estoy viendo el filtro de índice aplicado a un subconjunto de rev-list. Agradezco cualquier idea.
Erizo
@Hedgehog: Todos los ejemplos de "referencia múltiple" se utilizan -- --allpara procesar todos los commits accesibles desde cualquier referencia. El último ejemplo muestra cómo cambiar solo ciertas confirmaciones (solo pruebe GIT_COMMIT para lo que quiera). Para cambiar solo una lista específica de confirmaciones, puede guardar la lista antes de filtrar (p git rev-list … >/tmp/commits_to_rewrite. Ej. ), Luego probar la pertenencia al filtro (p if grep -qF "$GIT_COMMIT" /tmp/commits_to_rewrite; then git update-index …. Ej .). ¿Qué estás tratando de lograr exactamente? Es posible que desee comenzar una nueva pregunta si es demasiado para explicar en los comentarios.
Chris Johnsen
En mi caso, tengo un pequeño proyecto que tenía la intención de poner bajo control de fuente. (No lo había hecho, porque es un proyecto en solitario y no había decidido qué sistema usar). Mi "control de origen" era solo una cuestión de duplicar todo mi árbol de proyectos en un nuevo directorio y cambiar el nombre del directorio de la versión anterior. Soy nuevo en Git (después de usar previamente SCCS, RCS y un feo derivado de RCS patentado que se asemeja a CVS), así que no se preocupe por hablar conmigo. Tengo la impresión de que la respuesta implica una variación de la sección "Insertar un archivo en un solo historial de referencia". ¿Correcto?
Steve
1
@Steve: el escenario de "referencia única" se aplica si su historial proviene de una sola línea de desarrollo (es decir, tendría sentido si todas las instantáneas se vieran como puntos sucesivos de una sola rama lineal). El escenario de "referencia múltiple" se aplica si tiene (o ha tenido) varias ramas en su historial. A menos que su historial sea complicado (versiones "bifurcadas" para diferentes clientes, mantuvieron una rama de "corrección de errores" mientras se realizaba un nuevo trabajo en "desarrollo", etc.), entonces probablemente esté viendo una situación de "referencia única". Sin embargo , parece que su situación difiere de la de la pregunta ...
Chris Johnsen
1
@Steve: Dado que tiene una serie de instantáneas de directorio históricas (y aún no tiene un repositorio de Git), probablemente pueda usarlas import-directories.perldesde Git contrib/(o import-tars.perl, o import-zips.py...) para crear un nuevo repositorio de Git a partir de sus instantáneas (incluso con Marcas de tiempo "antiguas"). Las técnicas rebase/ filter-branchen mi respuesta solo son necesarias si desea insertar un archivo que "quedó fuera" del historial de un repositorio existente.
Chris Johnsen
119

Puede crear el compromiso como de costumbre, pero cuando lo haga, configure las variables de entorno GIT_AUTHOR_DATEy GIT_COMMITTER_DATElas fechas y horas apropiadas.

Por supuesto, esto hará la confirmación en la punta de su rama (es decir, frente a la confirmación HEAD actual). Si quieres retrasarlo más en el repositorio, debes ponerte un poco elegante. Digamos que tienes esta historia:

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

Y desea que su nueva confirmación (marcada como "X") aparezca en segundo lugar :

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

La forma más fácil sería bifurcarse desde la primera confirmación, agregar su nueva confirmación y luego cambiar el resto de las confirmaciones por encima de la nueva. Al igual que:

$ 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
fuente
25
Siempre encuentro esta buena respuesta, pero luego tengo que escabullirme para encontrar el formato de fecha, así que aquí está para la próxima vez 'Vie 26 de julio 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
Hay más, por ejemplo, el más bien nemotécnico 2013-07-26T19:32:10: kernel.org/pub/software/scm/git/docs/…
Marian
3
Por cierto, la parte '-0400' especifica el desplazamiento de la zona horaria. Tenga cuidado al seleccionarlo correctamente ... porque si no, su tiempo cambiará en función de eso. Por ejemplo, en el caso de mí, que vivo en Chicago, tuve que elegir '-0600' (CST de América del Norte). Puede encontrar los códigos aquí: timeanddate.com/time/zones
evaldeslacasa
2
Como la fecha debe repetirse, me resultó más fácil crear otra 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

Sé que esta pregunta es bastante antigua, pero eso es lo que realmente funcionó para mí:

git commit --date="10 day ago" -m "Your commit message" 
Hyder B.
fuente
16
Esto funcionó perfectamente. Me sorprende que --datetambién sea compatible con el formato de fecha relativa legible por humanos.
ZitRo
@ZitRo ¿por qué no 10 días?
Alex78191
10
Advertencia: git --datesolo modificará el $ GIT_AUTHOR_DATE ... por lo que, según las circunstancias, verá la fecha actual adjunta al compromiso ($ GIT_COMMITTER_DATE)
Guido U. Draheim el
3
Aferrándose a lo que dijo @ GuidoU.Draheim. Puede verificar los detalles completos de una confirmación usando git show <commit-hash> --format=fuller. Allí verá que AuthorDatees la fecha que especificó, pero CommitDatees la fecha real de la confirmación.
d4nyll
Entonces, ¿no hay forma de cambiar el CommitDate o hacer un commit con fecha pasada? @ d4nyll
Rahul
35

En mi caso lo largo del tiempo que había ahorrado un montón de versiones de mi_archivo como myfile_bak, myfile_old, myfile_2010, copias de seguridad / mi_archivo etc. quería poner la historia de mi_archivo en git usando sus fechas de modificación. Así que cambie el nombre del más antiguo a mi archivo, git add myfileluego git commit --date=(modification date from ls -l) myfile, cambie el nombre del más antiguo a mi archivo, otro git commit con --date, repita ...

Para automatizar esto, puede usar shell-foo para obtener el tiempo de modificación del archivo. Empecé con ls -ly cut, pero stat (1) es más directo

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

esquiador
fuente
1
Agradable. Ciertamente he tenido archivos de antes de mis días de git para los que quería usar el tiempo de modificación del archivo. Sin embargo, esto no solo establece la fecha de confirmación (¿no la fecha del autor?)
Xeoncross
De git-scm.com/docs/git-commit: --date"Anular la fecha de autor utilizada en la confirmación". git log'Date parece ser AuthorDate, git log --pretty=fullermuestra AuthorDate y CommitDate.
skierpage
16
La git commitopción --datesolo modificará el GIT_AUTHOR_DATE, no GIT_COMMITTER_DATE. Como explica el Libro Pro Git : "El autor es la persona que originalmente escribió el trabajo, mientras que el responsable es la última persona que aplicó el trabajo". En el contexto de las fechas, GIT_AUTHOR_DATEes la fecha en que se modificó el archivo, mientras que GIT_COMMITTER_DATEes la fecha en que se confirmó. Es importante tener en cuenta que, de forma predeterminada, git logmuestra las fechas de autor como "Fecha", pero luego utiliza fechas de confirmación para filtrar cuando se le ofrece una --sinceopción.
Christopher
No hay opción -c para stat en OS X 10.11.1
Thinsoldier
El equivalente para stat -c %yen macOS (y otras variantes de BSD) es stat -f %m.
vencedor el
21

Lo siguiente es lo que utilizo para confirmar los cambios en fooal N=1día en el pasado:

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

Si desea comprometerse a una fecha aún más antigua, por ejemplo 3 días atrás, apenas cambia el dateargumento: date -v-3d.

Eso es realmente útil cuando olvidas cometer algo ayer, por ejemplo.

ACTUALIZACIÓN : --datetambién acepta expresiones como --date "3 days ago"o incluso --date "yesterday". Entonces podemos reducirlo a un comando de línea:

git add foo ; git commit --date "yesterday" -m "Update"
Vilson Vieira
fuente
66
Advertencia: git --datesólo se modificará el $ GIT_AUTHOR_DATE ... así que dependiendo de las circunstancias verá la fecha actual adjunto a la confirmación ($ GIT_COMMITTER_DATE)
Guido U. Draheim
mezclar 2 enfoques juntos hace un ajuste casi perfecto:git commit --amend --date="$(stat -c %y fileToCopyMTimeFrom)"
andrej
¡Increíble! El mismo comando con una fecha legible para humanosgit commit --amend --date="$(date -R -d '2020-06-15 16:31')"
pixelbrackets
16

En mi caso, al usar la opción --date, mi proceso git se bloqueó. Puede ser que hice algo terrible. Y como resultado apareció un archivo index.lock. Así que eliminé manualmente los archivos .lock de la carpeta .git y los ejecuté, para que todos los archivos modificados se confirmaran en fechas pasadas y esta vez funcionó. Gracias por todas las respuestas aquí.

git commit --date="`date --date='2 day ago'`" -am "update"
JstRoRR
fuente
55
Ver comentario arriba engit commit --date . Además, su mensaje de confirmación de ejemplo debe modificarse para desalentar los mensajes de una línea deficientes como estos @JstRoRR
Christopher
44
Advertencia: git --datesolo modificará el $ GIT_AUTHOR_DATE ... por lo que, según las circunstancias, verá la fecha actual adjunta al compromiso ($ GIT_COMMITTER_DATE)
Guido U. Draheim el
9

Para realizar una confirmación que parece que se hizo en el pasado, debe establecer ambas GIT_AUTHOR_DATEy GIT_COMMITTER_DATE:

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

donde date -d'...'puede ser la fecha exacta como 2019-01-01 12:00:00o relativa como 5 months ago 24 days ago.

Para ver ambas fechas en git log use:

git log --pretty=fuller

Esto también funciona para confirmaciones de fusión:

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

Siempre puede cambiar una fecha en su computadora, hacer una confirmación, luego cambiar la fecha y presionar.

Nik
fuente
1
Creo que esa no es la práctica correcta, puede crear algunos problemas desconocidos
Vino