2010-10-09 11 views
143

Estoy convirtiendo todo en Git para mi uso personal y encontré algunas versiones antiguas de un archivo que ya están en el repositorio. ¿Cómo lo aplico al historial en el orden correcto según la "fecha de modificación" del archivo, así que tengo un historial preciso del archivo?¿Cómo puedo hacer una confirmación de Git en el pasado?

me dijeron 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 
+0

respuestas cortas y sencillas: http://stackoverflow.com/a/34639957/2708266 – Yash

+3

Me pregunto si la gente en busca de la respuesta a esto sólo quieren mantener su "Racha de contribución" de GitHub continua de esta manera – ZitRo

Respuesta

152

El aviso que le dieron es erróneo. La configuración incondicional del GIT_AUTHOR_DATE en un --env-filter reescribirá la fecha de cada confirmación. Además, sería inusual usar git commit dentro de --index-filter.

Aquí tiene problemas múltiples e independientes.

especificación de las fechas que no sea “ahora”

cada confirmación tiene dos fechas: la fecha de autor y la fecha confirmador. Puede sobrescribir 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. Ver “Date Formats” in git-commit(1) o el siguiente:

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 comprometerse durante el uso normal es git commit. También tiene una opción --date que le permite especificar directamente la fecha del autor. Su uso anticipado incluye git filter-branch --env-filter también utiliza las variables de entorno mencionados anteriormente (estos son parte de la “env” después de lo cual la opción se llama; ver “Options” in git-filter-branch(1) y el subyacente comando git-commit-tree(1)

Insertar un archivo en un único “cañerías”. ref Historia

Si el depósito es muy simple (es decir, es suficiente con una sola rama, sin etiquetas), entonces es probable que pueda utilizar git rebase para hacer el trabajo.

En los siguientes comandos, el uso el nombre del objeto (SHA-1 hash) 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 se quiere actualizar la A a incluir el nuevo archivo (en lugar de crear un nuevo commit en el que se añadió), a continuación, utilizar git commit --amend en lugar de git commit. El resultado sería el siguiente:

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

lo anterior funciona todo el tiempo que puede nombrar a realizar la confirmación que debe ser el padre de su nuevo compromiso. Si realmente quiere que su nuevo archivo que se añade a través de una nueva raíz cometer (sin padres), entonces usted 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 --orphan es relativamente nuevo (Git 1.7.2), pero hay other ways of doing the same thing que funcionan en versiones anteriores de Git.

Inserción de un archivo en un Multi-ref Historia

Si el depósito es más complejo (es decir, tiene más de un árbitro (ramas, etiquetas, etc.)), entonces es probable que se tenga que utilizar git filter-branch. Antes de usar git filter-branch, debe hacer una copia de seguridad de todo su repositorio. Un simple tar archivo de todo su árbol de trabajo (incluido el directorio .git) es suficiente. git filter-branch hace refs de respaldo, pero a menudo es más fácil recuperarse de un filtrado no del todo correcto simplemente borrando su directorio .git y restaurándolo desde su respaldo.

Nota: Los ejemplos siguientes utilizan el comando de nivel más bajo git update-index --add en lugar de git add. Puede usar git add, pero primero debe copiar el archivo desde una ubicación externa a la ruta esperada (--index-filter ejecuta su comando en un GIT_WORK_TREE temporal que está vacío).

Si usted quiere que su nuevo archivo que se añade a todos los existentes confirma, entonces usted 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 la existente confirmaciones con --env-filter 'GIT_AUTHOR_DATE=…'. Si lo usaste, lo harías condicional para que reescriba la fecha de cada confirmación.

Si usted quiere que su nuevo archivo que aparezca sólo en las confirmaciones después de algún existente comprometerse (“A”), entonces usted 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 que se añade a través de una nueva comprometerse que ha de ser insertado en el centro de su historia, entonces usted necesita para generar la nueva comprometerse antes de usar git filter-branch y añadir a --parent-filtergit 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 podría hacer arreglos para que el archivo se agregue primero en una nueva confirmación raíz: cree su nueva confirmación raíz mediante el método "huérfano" de git rebase sección (capturela en new_commit), use el --index-filter incondicional, y --parent-filter como "sed -e \"s/^$/-p $new_commit/\"".

+0

En el primer ejemplo del caso de uso, "Insertar un archivo en un historial de múltiples refs": ¿Hay alguna manera de tener '--index-filter' aplicado a las confirmaciones devueltas por' git rev-list'? En este momento estoy viendo el filtro de índice aplicado a un subconjunto de la lista de revistas. Apreciar cualquier idea. – Hedgehog

+0

@Hedgehog: todos los ejemplos de "multi-ref" usan '--all' para procesar todos los commits accesibles desde cualquier ref. El último ejemplo muestra cómo cambiar solo ciertos commits (solo prueba GIT_COMMIT para lo que quieras). Para cambiar solo una lista específica de confirmaciones, puede guardar la lista antes de filtrar (por ejemplo, 'git rev-list ...>/tmp/commits_to_rewrite') y luego probar la membresía dentro del filtro (p.'if grep -qF" $ GIT_COMMIT "/ tmp/commits_to_rewrite; luego git update-index ... '). ¿Qué estás tratando de lograr exactamente? Es posible que desee comenzar una nueva pregunta si es demasiado para explicar en los comentarios. –

+0

En mi caso, tengo un pequeño proyecto que quería colocar 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" fue solo una cuestión de duplicar todo el á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 haber usado SCCS, RCS y un feo derivado patentado de RCS que se parece al CVS), así que no se preocupe por hablar conmigo. Me da la impresión de que la respuesta implica una variación de la sección "Insertar un archivo en un solo historial de ref". ¿Correcto? – Steve

86

Puede crear el commit, como de costumbre, pero cuando se comprometa, establecer las variables de entorno y GIT_AUTHOR_DATEGIT_COMMITTER_DATE a los datetimes apropiadas.

Por supuesto, esto hará que la confirmación se realice en la punta de su bifurcación (es decir, delante del compromiso HEAD actual). Si desea llevarlo más lejos en el repositorio, tiene que ser un poco sofisticado. Digamos que usted tiene esta historia:

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

Y usted quiere que su nueva comprometerse (marcado con una "X") para aparecer segundo:

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

La forma más sencilla sería la de pasar de la primera confirmar, agregar su nueva confirmación y luego volver a establecer la base de todas las demás confirmaciones sobre la nueva. De esta manera:

$ 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 
+0

Uno de los archivos debería ser el primero, en mi caso. – unknown

+12

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 'Fri Jul 26 19:32:10 2013 -0400' – MeBigFatGuy

+61

'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

30

En mi caso con el 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 cambiar el nombre de la más antigua a MYFILE git add myfile, entonces git commit --date=(modification date from ls -l) myfile, cambiar el nombre más antiguo junto a MYFILE otra git commit con --date, repetir ...

para automatizar esto de alguna manera, puede utilizar la cáscara-foo para obtener el tiempo de modificación del archivo. Empecé con ls -l y cut, pero stat (1) es más directo

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

+0

Agradable. Ciertamente tuve archivos antes de mi _git days_ por los que quería usar la hora modificada del archivo. Sin embargo, ¿esto no solo establece la fecha de confirmación (no la fecha del autor?) – Xeoncross

+0

de git-scm.com/docs/git-commit: '--date'" Reemplaza la fecha del autor utilizada en la confirmación ". La fecha de 'git log' parece ser AuthorDate,' git log --pretty = fuller' muestra tanto AuthorDate como CommitDate. – skierpage

+8

La opción 'git commit'' --date' solo modificará 'GIT_AUTHOR_DATE', no' GIT_COMMITTER_DATE'. Como el [Pro Git Book] (http://progit.org/book/ch2-3.html) explica: _ "El autor es la persona que originalmente escribió el trabajo, mientras que el autor es la persona que aplicó el trabajo por última vez. . "_ En el contexto de las fechas, el' GIT_AUTHOR_DATE' es la fecha en que se modificó el archivo, mientras que 'GIT_COMMITTER_DATE' es la fecha en que se confirmó. Aquí es importante tener en cuenta que 'git log' muestra las fechas del autor como" Fecha ", pero luego usa las fechas de confirmación para el filtrado cuando se le da una opción' --since'. – Christopher

14

En mi caso, durante el uso de la opción --date, mi proceso de git estrelló. 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 las fechas aprobadas y funcionó esta vez. Gracias por todas las respuestas aquí.

git commit --date="`date --date='2 day ago'`" -am "update" 
+5

Ver [comentario anterior sobre 'git commit --date'] (http://stackoverflow.com/questions/3895453/how-do-i-make- a-git-commit-in-the-past # comment52277002_9778940). Además, su mensaje de confirmación de ejemplo debe ser modificado para desalentar los mensajes de una sola línea como estos @JstRoRR – Christopher

+0

¡la mejor respuesta! muy fácil de llenar algunos gups en las fechas –

21

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

git commit --date="10 day ago" -m "Your commit message" 
+2

Esto funcionó perfectamente. Me sorprende que '--date' también sea compatible con el formato de fecha relativa legible por humanos. – ZitRo

7

Lo siguiente es lo que utilizo para confirmar los cambios en foo a N=1 días 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, dicen 3 días atrás, apenas cambia el date argumento: date -v-3d.

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

ACTUALIZACIÓN: --date también acepta expresiones como --date "3 days ago" o incluso --date "yesterday". Así que podemos reducirlo a una línea de comandos:

git add foo ; git commit --date "yesterday" -m "Update" 
Cuestiones relacionadas