¿Cómo hacer una copia de seguridad de un repositorio Git local?

155

Estoy usando git en un proyecto relativamente pequeño y encuentro que comprimir el contenido del directorio .git podría ser una buena manera de hacer una copia de seguridad del proyecto. Pero esto es un poco extraño porque, cuando restauro, lo primero que tengo que hacer es git reset --hard.

¿Hay algún problema al hacer una copia de seguridad de un repositorio git de esta manera? Además, ¿hay alguna manera mejor de hacerlo (por ejemplo, un formato portátil de git o algo similar?)?

Dan Rosenstark
fuente
¿Por qué nadie dio la respuesta obvia de usar git bundle ?
gatopeich
@gatopeich lo hicieron. Desplácese hacia abajo.
Dan Rosenstark
Todas las respuestas votadas contienen un muro de texto sobre guiones personalizados, incluso el que comienza a mencionargit bundle
gatopeich

Respuestas:

23

Comencé a piratear un poco el script de Yar y el resultado está en github, incluidas las páginas de manual y el script de instalación:

https://github.com/najamelan/git-backup

Instalación :

git clone "https://github.com/najamelan/git-backup.git"
cd git-backup
sudo ./install.sh

Acogiendo con beneplácito todas las sugerencias y solicitud de extracción en github.

#!/usr/bin/env ruby
#
# For documentation please sea man git-backup(1)
#
# TODO:
# - make it a class rather than a function
# - check the standard format of git warnings to be conform
# - do better checking for git repo than calling git status
# - if multiple entries found in config file, specify which file
# - make it work with submodules
# - propose to make backup directory if it does not exists
# - depth feature in git config (eg. only keep 3 backups for a repo - like rotate...)
# - TESTING



# allow calling from other scripts
def git_backup


# constants:
git_dir_name    = '.git'          # just to avoid magic "strings"
filename_suffix = ".git.bundle"   # will be added to the filename of the created backup


# Test if we are inside a git repo
`git status 2>&1`

if $?.exitstatus != 0

   puts 'fatal: Not a git repository: .git or at least cannot get zero exit status from "git status"'
   exit 2


else # git status success

   until        File::directory?( Dir.pwd + '/' + git_dir_name )             \
            or  File::directory?( Dir.pwd                      ) == '/'


         Dir.chdir( '..' )
   end


   unless File::directory?( Dir.pwd + '/.git' )

      raise( 'fatal: Directory still not a git repo: ' + Dir.pwd )

   end

end


# git-config --get of version 1.7.10 does:
#
# if the key does not exist git config exits with 1
# if the key exists twice in the same file   with 2
# if the key exists exactly once             with 0
#
# if the key does not exist       , an empty string is send to stdin
# if the key exists multiple times, the last value  is send to stdin
# if exaclty one key is found once, it's value      is send to stdin
#


# get the setting for the backup directory
# ----------------------------------------

directory = `git config --get backup.directory`


# git config adds a newline, so remove it
directory.chomp!


# check exit status of git config
case $?.exitstatus

   when 1 : directory = Dir.pwd[ /(.+)\/[^\/]+/, 1]

            puts 'Warning: Could not find backup.directory in your git config file. Please set it. See "man git config" for more details on git configuration files. Defaulting to the same directroy your git repo is in: ' + directory

   when 2 : puts 'Warning: Multiple entries of backup.directory found in your git config file. Will use the last one: ' + directory

   else     unless $?.exitstatus == 0 then raise( 'fatal: unknown exit status from git-config: ' + $?.exitstatus ) end

end


# verify directory exists
unless File::directory?( directory )

   raise( 'fatal: backup directory does not exists: ' + directory )

end


# The date and time prefix
# ------------------------

prefix           = ''
prefix_date      = Time.now.strftime( '%F'       ) + ' - ' # %F = YYYY-MM-DD
prefix_time      = Time.now.strftime( '%H:%M:%S' ) + ' - '
add_date_default = true
add_time_default = false

prefix += prefix_date if git_config_bool( 'backup.prefix-date', add_date_default )
prefix += prefix_time if git_config_bool( 'backup.prefix-time', add_time_default )



# default bundle name is the name of the repo
bundle_name = Dir.pwd.split('/').last

# set the name of the file to the first command line argument if given
bundle_name = ARGV[0] if( ARGV[0] )


bundle_name = File::join( directory, prefix + bundle_name + filename_suffix )


puts "Backing up to bundle #{bundle_name.inspect}"


# git bundle will print it's own error messages if it fails
`git bundle create #{bundle_name.inspect} --all --remotes`


end # def git_backup



# helper function to call git config to retrieve a boolean setting
def git_config_bool( option, default_value )

   # get the setting for the prefix-time from git config
   config_value = `git config --get #{option.inspect}`

   # check exit status of git config
   case $?.exitstatus

      # when not set take default
      when 1 : return default_value

      when 0 : return true unless config_value =~ /(false|no|0)/i

      when 2 : puts 'Warning: Multiple entries of #{option.inspect} found in your git config file. Will use the last one: ' + config_value
               return true unless config_value =~ /(false|no|0)/i

      else     raise( 'fatal: unknown exit status from git-config: ' + $?.exitstatus )

   end
end

# function needs to be called if we are not included in another script
git_backup if __FILE__ == $0

fuente
1
@Yar Gran script de paquete, basado en el paquete git que defendí en mi respuesta a continuación. +1.
VonC
1
Ya instalé su aplicación en mi repositorio local ... cómo la usa una vez que está instalada ... no hay información al respecto en la documentación, debe incluir una sección con un ejemplo sobre cómo hacer una copia de seguridad
JAF
Hola, lo siento, no lo haces funcionar. Normalmente ejecuta sudo install.sh, luego configúrelo (usa el sistema de configuración git) para establecer el directorio de destino (vea el archivo readme en github). Luego corres git backupdentro de tu repositorio. Como nota al margen, este fue un experimento con git bundle y una respuesta a esta pregunta, pero git bundle nunca hace una copia exacta absoluta (por ejemplo, si recuerdo bien, especialmente con respecto a los controles remotos git), así que personalmente realmente uso tar para hacer copias de seguridad. directorios git.
144

La otra forma oficial sería usar git bundle

Eso creará un archivo que admita git fetchy git pullpara actualizar su segundo repositorio.
Útil para copia de seguridad incremental y restauración.

Pero si necesita hacer una copia de seguridad de todo (porque no tiene un segundo repositorio con algún contenido anterior ya en su lugar), la copia de seguridad es un poco más complicada de hacer, como se menciona en mi otra respuesta, después del comentario de Kent Fredric :

$ git bundle create /tmp/foo master
$ git bundle create /tmp/foo-all --all
$ git bundle list-heads /tmp/foo
$ git bundle list-heads /tmp/foo-all

(Se trata de una operación atómica , en vez de hacer un archivo de la .gitcarpeta, como se ha comentado por fantabolous )


Advertencia: no recomendaría la solución de Pat Notz , que es clonar el repositorio. Hacer copias de seguridad de muchos archivos siempre es más complicado que hacer una copia de seguridad o actualizar ... solo uno.

Si miras el historial de ediciones de la respuesta de OP Yar , verás que Yar usó al principio un ... con la edición:clone --mirror

Usar esto con Dropbox es un desastre total .
Tendrá errores de sincronización y NO PUEDE ENROLLAR UN DIRECTORIO DE VUELTA A DROPBOX.
Úselo git bundlesi desea hacer una copia de seguridad en su Dropbox.

La solución actual de Yar utiliza git bundle.

Yo descanso mi caso.

VonC
fuente
Acabo de comprobar esto y es realmente genial. Tendré que probar algunos agrupamientos y desgloses y encabezados de listas para convencerme ... pero me gusta bastante. Gracias de nuevo, especialmente por las notas en el interruptor --todos.
Dan Rosenstark
Algo relacionado, ¿hay algo malo en simplemente comprimir mi repositorio local? Necesito un solo archivo de copia de seguridad, copiar miles de archivos en un disco externo es increíblemente lento. Me pregunto si hay algo más eficiente porque zip tiene que archivar tantos archivos en la carpeta .git.
@faB: la única diferencia es que puedes hacer fácilmente copias de seguridad incrementales con git bundle. No es posible con un zip global del repositorio local.
VonC
2
En respuesta a un comentario anterior, pero otra diferencia entre el paquete y comprimir el directorio es que el paquete es atómico, por lo que no se estropeará si alguien actualiza su repositorio en el medio de la operación.
fantabolous
1
@fantabolous buen punto. Lo he incluido en la respuesta para mayor visibilidad.
VonC
62

La forma en que hago esto es crear un repositorio remoto (desnudo) (en una unidad separada, llave USB, servidor de respaldo o incluso github) y luego usarlo push --mirrorpara hacer que ese repositorio remoto se vea exactamente como mi local (excepto que el remoto es un simple repositorio).

Esto empujará todas las referencias (ramas y etiquetas), incluidas las actualizaciones que no sean de avance rápido. Lo uso para crear copias de seguridad de mi repositorio local.

La página del manual lo describe así:

En lugar de nombrar cada árbitro de empuje, especifica que todas las refs bajo $GIT_DIR/refs/(que incluye pero no se limita a refs/heads/, refs/remotes/y refs/tags/) pueden reflejar al repositorio remoto. Las referencias locales recién creadas se enviarán al extremo remoto, las referencias actualizadas localmente se actualizarán a la fuerza en el extremo remoto y las referencias eliminadas se eliminarán del extremo remoto. Este es el valor predeterminado si se establece la opción de remote.<remote>.mirrorconfiguración.

Hice un alias para hacer el empuje:

git config --add alias.bak "push --mirror github"

Luego, solo corro git bakcuando quiero hacer una copia de seguridad.

Pat Notz
fuente
+1. Convenido. git bundle es bueno para mover una copia de seguridad (un archivo). Pero con una unidad que puede enchufar en cualquier lugar, el repositorio simple también está bien.
VonC
+1 awesme, lo investigaré. Gracias por los ejemplos también.
Dan Rosenstark
@Pat Notz, al final decidí seguir tu forma de hacerlo, y puse una respuesta a continuación aquí (puntaje permanentemente en cero :)
Dan Rosenstark
Tenga en cuenta que en --mirrorrealidad no ejecuta ningún tipo de verificación en los objetos que obtiene. Probablemente deberías correr git fscken algún momento para evitar la corrupción.
docwhat
34

[Solo dejo esto aquí para mi propia referencia.]

Mi script de paquete llamado se git-backupve así

#!/usr/bin/env ruby
if __FILE__ == $0
        bundle_name = ARGV[0] if (ARGV[0])
        bundle_name = `pwd`.split('/').last.chomp if bundle_name.nil? 
        bundle_name += ".git.bundle"
        puts "Backing up to bundle #{bundle_name}"
        `git bundle create /data/Dropbox/backup/git-repos/#{bundle_name} --all`
end

A veces uso git backupy a veces uso, lo git backup different-nameque me da la mayoría de las posibilidades que necesito.

Dan Rosenstark
fuente
2
+1 Debido a que no --globalusaste la opción, este alias solo se verá en tu proyecto (está definido en tu .git/configarchivo); eso es probablemente lo que deseas. Gracias por la respuesta más detallada y bien formateada.
Pat Notz
1
@yar: ¿sabe cómo realizar estas tareas sin la línea de comandos y en su lugar solo usa tortoisegit (estoy buscando una solución para mis usuarios de ventanas que no son de línea de comandos)?
pastacool
@pastacool, lo siento, no sé nada sobre git sin la línea de comandos. ¿Quizás revise un IDE relevante como RubyMine?
Dan Rosenstark
@intuited, puede revertir DIRECTORIOS con spideroak, o solo archivos (¿qué hace Dropbox y le dan 3 GB de espacio)?
Dan Rosenstark
@Yar: no estoy seguro de entender ... ¿quiere decir que si elimino un directorio respaldado por Dropbox, pierdo todas las revisiones anteriores de los archivos que contiene? Más información sobre las políticas de versiones de spideroak está aquí . TBH Realmente no he usado mucho SpiderOak, y no estoy totalmente seguro de sus límites. Parece que habrían proporcionado una solución para tales problemas, sin embargo, ponen mucho énfasis en la competencia técnica. Además: ¿Dropbox todavía tiene un límite de 30 días para las reversiones de cuentas gratuitas?
intuido
9

Ambas respuestas a estas preguntas son correctas, pero todavía me faltaba una solución completa y corta para hacer una copia de seguridad de un repositorio de Github en un archivo local. La esencia está disponible aquí, no dude en bifurcar o adaptarse a sus necesidades.

backup.sh:

#!/bin/bash
# Backup the repositories indicated in the command line
# Example:
# bin/backup user1/repo1 user1/repo2
set -e
for i in $@; do
  FILENAME=$(echo $i | sed 's/\//-/g')
  echo "== Backing up $i to $FILENAME.bak"
  git clone [email protected]:$i $FILENAME.git --mirror
  cd "$FILENAME.git"
  git bundle create ../$FILENAME.bak --all
  cd ..
  rm -rf $i.git
  echo "== Repository saved as $FILENAME.bak"
done

restore.sh:

#!/bin/bash
# Restore the repository indicated in the command line
# Example:
# bin/restore filename.bak
set -e

FOLDER_NAME=$(echo $1 | sed 's/.bak//')
git clone --bare $1 $FOLDER_NAME.git
Nacho Coloma
fuente
1
Interesante. Más preciso que mi respuesta. +1
VonC
Gracias, esto es útil para Github. La respuesta aceptada es a la pregunta actual.
Dan Rosenstark
5

Puede hacer una copia de seguridad del repositorio de git con git-copy . git-copy guardó el nuevo proyecto como un repositorio simple, significa un costo mínimo de almacenamiento.

git copy /path/to/project /backup/project.backup

Entonces puedes restaurar tu proyecto con git clone

git clone /backup/project.backup project
Quanlong
fuente
Argh! Esta respuesta me hizo creer que "git copy" era un comando oficial de git.
gatopeich
2

Encontré la forma oficial simple después de atravesar los muros de texto de arriba que te haría pensar que no hay ninguno.

Crea un paquete completo con:

$ git bundle create <filename> --all

Restaurar con:

$ git clone <filename> <folder>

Esta operación es atómica AFAIK. Consulte los documentos oficiales para conocer los detalles arenosos.

Con respecto a "zip": los paquetes git están comprimidos y son sorprendentemente pequeños en comparación con el tamaño de la carpeta .git.

gatopeich
fuente
Esto no responde a toda la pregunta sobre zip y también supone que hemos leído las otras respuestas. Por favor, corríjalo para que sea atómico y maneje toda la pregunta, y me alegra que se acepte la respuesta (10 años después). Gracias
Dan Rosenstark
0

llegó a esta pregunta a través de google.

Esto es lo que hice de la manera más simple.

git checkout branch_to_clone

luego crea una nueva rama git desde esta rama

git checkout -b new_cloned_branch
Switched to branch 'new_cloned_branch'

regrese a la rama original y continúe:

git checkout branch_to_clone

Suponiendo que lo arruinaste y necesitas restaurar algo de la rama de respaldo:

git checkout new_cloned_branch -- <filepath>  #notice the space before and after "--"

¡Lo mejor es que si algo está jodido, puede eliminar la rama de origen y volver a la rama de respaldo!

NoobEditor
fuente
1
Me gusta este enfoque, pero no estoy seguro de si es la mejor práctica. Hago ramas de 'git' de respaldo con bastante frecuencia, y eventualmente tendré muchas ramas de respaldo. No estoy seguro de si esto está bien o no (tener ~ 20 ramas de respaldo de diferentes fechas). Supongo que siempre podría eliminar las copias de seguridad anteriores, pero si quiero conservarlas todas, ¿está bien? Hasta ahora está jugando muy bien, pero sería bueno saber si es una buena o mala práctica.
Kyle Vassella
no es algo que se llamaría la mejor práctica , supongo que está más relacionado con los hábitos individuales de hacer cosas. Generalmente codifico en una rama solo hasta que el trabajo esté hecho y mantengo otra rama para solicitudes adhoc . Ambos tienen copias de seguridad, una vez hecho, ¡elimine la rama principal! :)
NoobEditor