Comment sauvegarder un référentiel Git local?

155

J'utilise git sur un projet relativement petit et je trouve que compresser le contenu du répertoire .git pourrait être un bon moyen de sauvegarder le projet. Mais c'est un peu bizarre parce que, lorsque je restaure, la première chose que je dois faire est git reset --hard.

Y a-t-il des problèmes avec la sauvegarde d'un dépôt git de cette façon? De plus, y a-t-il une meilleure façon de le faire (par exemple, un format git portable ou quelque chose de similaire?)?

Dan Rosenstark
la source
Pourquoi personne n'a donné la réponse évidente à l'utilisation de git bundle ???
gatopeich le
@gatopeich ils l'ont fait. Défiler vers le bas.
Dan Rosenstark le
Toutes les réponses avec vote positif contiennent un mur de texte sur les scripts personnalisés, même celui qui commence à être mentionnégit bundle
gatopeich

Réponses:

23

J'ai commencé à pirater un peu le script de Yar et le résultat est sur github, y compris les pages de manuel et le script d'installation:

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

Installation :

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

Accueillant toutes les suggestions et pull request sur 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

la source
1
@Yar Great bundle script, basé sur le bundle git que je préconisais dans ma réponse ci-dessous. +1.
VonC
1
J'ai déjà installé votre application dans mon référentiel nu local .... comment l'utiliser une fois qu'elle est installée ... il n'y a pas d'informations à ce sujet dans la documentation, vous devriez inclure une section avec un exemple sur la façon de faire une sauvegarde
JAF
Salut, désolé, vous ne le faites pas fonctionner. Normalement, vous exécutez sudo install.sh, puis configurez-le (il utilise le système git config) pour définir le répertoire de destination (voir le fichier readme sur github). Ensuite, vous exécutez git backupdans votre référentiel. En guise de note secondaire, c'était une expérience avec git bundle et une réponse à cette question, mais git bundle ne fait jamais de copie exacte absolue (par exemple, si je me souviens bien, en particulier en ce qui concerne les télécommandes git), donc personnellement, j'utilise tar pour sauvegarder. répertoires git.
144

L'autre façon officielle serait d'utiliser git bundle

Cela créera un fichier prenant en charge git fetchet git pullafin de mettre à jour votre deuxième dépôt.
Utile pour la sauvegarde et la restauration incrémentielles.

Mais si vous avez besoin de tout sauvegarder (parce que vous n'avez pas de deuxième dépôt avec du contenu plus ancien déjà en place), la sauvegarde est un peu plus élaborée à faire, comme mentionné dans mon autre réponse, après le commentaire 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

(Il est une opération atomique , plutôt que de faire une archive du .gitdossier, comme commenté par fantabolous )


Attention: je ne recommanderais pas la solution de Pat Notz , qui clonait le dépôt. La sauvegarde de nombreux fichiers est toujours plus délicate que la sauvegarde ou la mise à jour ... un seul.

Si vous regardez l' historique des modifications de la réponse OP Yar , vous verrez que Yar a d'abord utilisé a , ... avec la modification:clone --mirror

Utiliser cela avec Dropbox est un désordre total .
Vous aurez des erreurs de synchronisation et vous NE POUVEZ PAS REMPLIR UN RÉPERTOIRE DANS DROPBOX.
À utiliser git bundlesi vous souhaitez sauvegarder dans votre boîte de dépôt.

La solution actuelle de Yar utilise git bundle.

Je repose mon cas.

VonC
la source
Je viens de vérifier cela et c'est vraiment génial. Je vais devoir essayer quelques regroupements et dégroupages et des têtes de liste pour être convaincu ... mais je l'aime beaucoup. Merci encore, en particulier pour les notes sur le commutateur --all.
Dan Rosenstark
Un peu lié, y a-t-il quelque chose de mal à compresser simplement mon dépôt local? J'ai besoin d'un seul fichier de sauvegarde, la copie de milliers de fichiers sur un lecteur externe est incroyablement lente. Je me demande juste s'il y a quelque chose de plus efficace parce que zip doit archiver autant de fichiers dans le dossier .git.
@faB: la seule différence est que vous pouvez facilement faire une sauvegarde incrémentielle avec git bundle. Ce n'est pas possible avec un zip global de tout le dépôt local.
VonC
2
Répondre à un ancien commentaire, mais une autre différence entre bundle et compresser le répertoire est que bundle est atomique, donc il ne sera pas gâché si quelqu'un met à jour votre dépôt au milieu de l'opération.
fantastique
1
@fantabolous bon point. Je l'ai inclus dans la réponse pour plus de visibilité.
VonC le
62

La façon dont je fais cela est de créer un référentiel distant (nu) (sur un lecteur séparé, une clé USB, un serveur de sauvegarde ou même un github), puis de l'utiliser push --mirrorpour faire en sorte que ce dépôt distant ressemble exactement à mon dépôt local (sauf que le distant est un simple dépôt).

Cela poussera toutes les références (branches et balises), y compris les mises à jour non rapides. J'utilise ceci pour créer des sauvegardes de mon référentiel local.

La page de manuel le décrit comme ceci:

Au lieu de nommer chaque arbitre pour pousser, précise que tous les refs sous $GIT_DIR/refs/(qui comprend , mais sans s'y limiter refs/heads/, refs/remotes/et refs/tags/) être mis en miroir le dépôt distant. Les références locales nouvellement créées seront poussées vers l'extrémité distante, les références mises à jour localement seront mises à jour de force sur l'extrémité distante et les références supprimées seront supprimées de l'extrémité distante. Il s'agit de la valeur par défaut si l'option de configuration remote.<remote>.mirrorest définie.

J'ai fait un alias pour faire le push:

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

Ensuite, je cours git bakchaque fois que je veux faire une sauvegarde.

Pat Notz
la source
+1. D'accord. git bundle est agréable pour déplacer une sauvegarde (un fichier). Mais avec un lecteur que vous pouvez brancher n'importe où, le dépôt nu convient également.
VonC
+1 awesme, je vais regarder ça. Merci aussi pour les exemples.
Dan Rosenstark
@Pat Notz, à la fin j'ai décidé de suivre votre façon de faire, et j'ai mis une réponse ci-dessous ici (score maintenu en permanence à zéro :)
Dan Rosenstark
Notez que cela --mirrorn'exécute aucun type de vérification sur les objets qu'il obtient. Vous devriez probablement exécuter git fsckà un moment donné pour éviter la corruption.
docwhat
34

[Je laisse juste ceci ici pour ma propre référence.]

Mon script de bundle appelé git-backupressemble à ceci

#!/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

Parfois j'utilise git backupet parfois j'utilise git backup different-namece qui me donne la plupart des possibilités dont j'ai besoin.

Dan Rosenstark
la source
2
+1 Parce que vous n'avez pas utilisé l' --globaloption, cet alias ne sera visible que dans votre projet (il est défini dans votre .git/configfichier) - c'est probablement ce que vous voulez. Merci pour la réponse plus détaillée et bien formatée.
Pat Notz
1
@yar: savez-vous comment accomplir ces tâches sans la ligne de commande et utiliser à la place uniquement tortoisegit (je recherche une solution pour mes utilisateurs qui ne sont pas en ligne de commande)?
pastacool
@pastacool, désolé, je ne sais pas du tout pour git sans la ligne de commande. Peut-être consulter un IDE pertinent comme RubyMine?
Dan Rosenstark le
@intuited, vous pouvez restaurer des RÉPERTOIRES avec spideroak, ou simplement des fichiers (ce que fait la Dropbox et vous donne 3 Go d'espace)?
Dan Rosenstark
@Yar: je ne suis pas sûr de comprendre ... voulez-vous dire que si je supprime un répertoire basé sur Dropbox, je perds toutes les révisions précédentes des fichiers qu'il contient? Plus d'informations sur les politiques de gestion des versions de spideroak sont ici . TBH Je n'ai pas vraiment beaucoup utilisé SpiderOak et je ne suis pas totalement sûr de ses limites. Il semble cependant qu'ils auraient fourni une solution à de tels problèmes, mais ils mettent beaucoup l'accent sur la compétence technique. Aussi: Dropbox a-t-il toujours une limite de 30 jours sur les restaurations pour les comptes gratuits?
intuité le
9

Les deux réponses à ces questions sont correctes, mais il me manquait toujours une solution complète et courte pour sauvegarder un référentiel Github dans un fichier local. L' essentiel est disponible ici, n'hésitez pas à bifurquer ou à vous adapter à vos besoins.

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
la source
1
Intéressant. Plus précis que ma réponse. +1
VonC
Merci, c'est utile pour Github. La réponse acceptée est à la question actuelle.
Dan Rosenstark
5

Vous pouvez sauvegarder le repo git avec git-copy . git-copy a enregistré un nouveau projet en tant que dépôt nu, cela signifie un coût de stockage minimum.

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

Ensuite, vous pouvez restaurer votre projet avec git clone

git clone /backup/project.backup project
Quanlong
la source
Argh! cette réponse m'a fait croire que "git copy" était une commande git officielle.
gatopeich le
2

Trouvé le moyen officiel simple après avoir pataugé à travers les murs de texte ci-dessus qui vous ferait penser qu'il n'y en a pas.

Créez un bundle complet avec:

$ git bundle create <filename> --all

Restaurez-le avec:

$ git clone <filename> <folder>

Cette opération est l'AFAIK atomique. Consultez les documents officiels pour les détails granuleux.

Concernant "zip": les bundles git sont compressés et étonnamment petits par rapport à la taille du dossier .git.

Gatopeich
la source
Cela ne répond pas à toute la question sur zip et suppose également que nous avons lu les autres réponses. S'il vous plaît, corrigez-le pour qu'il soit atomique et traite toute la question et je suis heureux de lui faire accepter la réponse (10 ans plus tard). Merci
Dan Rosenstark
0

est venu à cette question via google.

Voici ce que j'ai fait de la manière la plus simple.

git checkout branch_to_clone

puis créez une nouvelle branche git à partir de cette branche

git checkout -b new_cloned_branch
Switched to branch 'new_cloned_branch'

revenez à la branche d'origine et continuez:

git checkout branch_to_clone

En supposant que vous ayez foiré et que vous ayez besoin de restaurer quelque chose à partir de la branche de sauvegarde:

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

La meilleure partie si quelque chose est foiré, vous pouvez simplement supprimer la branche source et revenir à la branche de sauvegarde !!

NoobEditor
la source
1
J'aime cette approche - mais je ne sais pas si c'est la meilleure pratique? Je fais assez souvent des branches git de «sauvegarde», et j'aurai finalement de nombreuses branches de sauvegarde. Je ne sais pas si cela est correct ou non (ayant ~ 20 branches de sauvegarde de différentes dates). Je suppose que je pourrais toujours supprimer les anciennes sauvegardes éventuellement - mais si je veux les conserver toutes - est-ce que ça va? Jusqu'à présent, il joue bien - mais ce serait bien de savoir si c'est une bonne ou une mauvaise pratique.
Kyle Vassella
ce n'est pas quelque chose qui s'appellerait la meilleure pratique , je suppose que c'est plus lié à ses habitudes individuelles de faire des choses. Je code généralement dans une branche seulement jusqu'à ce que le travail soit fait et je garde une autre branche pour les demandes ad hoc . Les deux ont des sauvegardes, une fois terminé, supprimez la branche principale! :)
NoobEditor