Как сделать резервную копию локального репозитория Git?

155

Я использую git в относительно небольшом проекте и обнаружил, что архивирование содержимого каталога .git может быть хорошим способом для резервного копирования проекта. Но это немного странно, потому что, когда я восстанавливаюсь, первое, что мне нужно сделать, это git reset --hard.

Есть ли проблемы с резервным копированием git-репо таким образом? Кроме того, есть ли лучший способ сделать это (например, портативный формат Git или что-то подобное?)?

Дэн Розенстарк
источник
Почему никто не дал очевидного ответа об использовании git bundle ???
gatopeich
@gatopeich они сделали. Прокрутить вниз.
Дэн Розенстарк
Все ответы с голосованием содержат стену текста о пользовательских сценариях, даже тот, который начинает упоминатьgit bundle
gatopeich

Ответы:

23

Я начал немного взламывать скрипт Yar, и в результате получился github, включая man-страницы и скрипт установки:

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

Установка :

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

Приветствую все предложения и тяну запрос на 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

источник
1
@Yar Отличный скрипт комплекта, основанный на пакете git, который я отстаивал в своем ответе ниже. +1.
VonC
1
Я уже установил ваше приложение в моем локальном голом репозитории .... как вы используете его после его установки .... в документации нет информации об этом, вы должны включить раздел с примером того, как сделать резервную копию
JAF
Привет, извините, вы не заставили его работать. Обычно вы запускаете sudo install.sh, а затем настраиваете его (он использует систему git config), чтобы установить каталог назначения (см. Файл readme на github). Затем вы запускаете git backupвнутри своего хранилища. Как примечание, это был эксперимент с git bundle и ответ на этот вопрос, но git bundle никогда не делает абсолютно точную копию (например, если я хорошо помню, особенно о git remotes), поэтому лично я на самом деле использую tar для резервного копирования. Git каталоги.
144

Другой официальный способ - использовать git bundle.

Это создаст файл, который поддерживает git fetchи git pullдля того, чтобы обновить ваш второй репо.
Полезно для инкрементного резервного копирования и восстановления.

Но если вам нужно сделать резервную копию всего (потому что у вас нет второго репо с уже существующим содержимым), резервное копирование будет более сложным, как упоминалось в моем другом ответе, после комментария Кента Фредрика :

$ 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

(Это атомарная операция , в отличие от создания архива из .gitпапки, а комментировал по fantabolous )


Предупреждение: Я бы не рекомендовал Pat Notz «s решение , которое клонирование репо.
Резервное копирование многих файлов всегда сложнее, чем резервное копирование или обновление ... только один.

Если вы посмотрите на историю правок в OP Яра ответ , вы бы увидели , что ЯР используется сначала clone --mirror, ... с редактированием:

Использование этого с Dropbox - полный беспорядок .
У вас будут ошибки синхронизации, и вы НЕ МОЖЕТЕ ЗАВЕРШИТЬ СПРАВОЧНИК В DROPBOX.
Используйте, git bundleесли вы хотите выполнить резервное копирование в свой дропбокс.

Текущее решение Яр использует git bundle.

Я считаю так.

VonC
источник
Я только что проверил это, и это действительно здорово. Я должен попробовать некоторые связывания и разделения и списки глав, чтобы убедиться ... но мне это очень нравится. Еще раз спасибо, особенно за заметки на переключателе --all.
Дэн Розенстарк
Что-то связанное, есть ли что-то не так с просто заархивировать мой локальный репозиторий? Мне нужен один резервный файл, копирование тысяч файлов на внешний диск происходит невероятно медленно. Мне просто интересно, есть ли что-то более эффективное, потому что zip должен архивировать так много файлов в папке .git.
@faB: единственное отличие состоит в том, что вы можете легко создавать инкрементные резервные копии git bundle. Это невозможно с глобальным почтовым индексом всего локального репо.
VonC
2
Отвечая на старый комментарий, но еще одно различие между bundle и zip в dir - это bundle, он атомарный, поэтому он не испортится, если кто-нибудь обновит ваш репо в середине операции.
Фантабол
1
@ fantabolous хороший момент. Я включил его в ответ для большей наглядности.
VonC
62

Как я сделать это, чтобы создать удаленный (обнажить) хранилище (на отдельном диске, USB, сервер резервного копирования или даже GitHub) , а затем использовать , push --mirrorчтобы сделать этот вид удаленного репо точно , как мой локальный ( за исключением удаленного является голой хранилище).

Это подтолкнет все ссылки (ветки и теги), включая обновления без ускоренной перемотки. Я использую это для создания резервных копий моего локального хранилища.

Страница людей описывает это следующим образом :

Вместо того , чтобы называть каждый реф толкать, указывает , что все рефов под $GIT_DIR/refs/(которая включает в себя , но не ограничиваясь ими refs/heads/, refs/remotes/и refs/tags/) быть зеркальным в удаленном хранилище. Вновь созданные локальные ссылки будут отправлены на удаленный конец, локально обновленные ссылки будут принудительно обновлены на удаленном конце, а удаленные ссылки будут удалены с удаленного конца. Это значение по умолчанию, если remote.<remote>.mirrorзадан параметр конфигурации .

Я сделал псевдоним, чтобы сделать push:

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

Затем я просто запускаю git bakвсякий раз, когда хочу сделать резервную копию.

Пэт Нотц
источник
+1. Согласовано. Git Bundle хорош для перемещения резервной копии (один файл). Но с приводом, который вы можете подключить куда угодно, репо тоже хорошо.
VonC
+1 офигенно, я посмотрю на это. Спасибо за примеры тоже.
Дэн Розенстарк
@Pat Notz, в конце концов я решил пойти по вашему пути, и я поставил ответ ниже (счет постоянно держится на нуле :)
Дэн Розенстарк
Обратите внимание, что на --mirrorсамом деле не выполняется никакой проверки объектов, которые он получает. Вы должны, вероятно, бежать git fsckв какой-то момент, чтобы предотвратить коррупцию.
что
34

[Просто оставив это здесь для моей справки.]

Мой скрипт пакета называется git-backupтак

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

Иногда я использую, git backupа иногда я использую, git backup different-nameчто дает мне большинство возможностей, которые мне нужны.

Дэн Розенстарк
источник
2
+1 Поскольку вы не использовали --globalопцию, этот псевдоним будет виден только в вашем проекте (он определен в вашем .git/configфайле) - это, вероятно, то, что вы хотите. Спасибо за более подробный и красиво отформатированный ответ.
Пэт Нотц
1
@yar: знаете ли вы, как выполнять эти задачи без командной строки и вместо этого использовать только tortoisegit (я ищу решение для моих пользователей, не использующих командную строку)?
pastacool
@pastacool, извини, я вообще не знаю про git без командной строки. Возможно проверить соответствующую IDE как RubyMine?
Дэн Розенстарк
@intuited, вы можете откатить каталоги с помощью spideroak, или просто файлы (что делает Dropbox, и они дают вам 3 ГБ пространства)?
Дэн Розенстарк
@ Яр: не уверен, что я понимаю .. Вы имеете в виду, что если я удаляю каталог с поддержкой Dropbox, я теряю все предыдущие версии файлов, содержащихся в нем? Больше информации о политиках версий spideroak здесь . TBH Я действительно не использовал SpiderOak много, и я не совсем уверен в его пределах. Похоже, что они бы предоставили решение таких проблем, хотя они уделяют большое внимание технической компетентности. Кроме того: есть ли у Dropbox 30-дневный лимит отката для бесплатных аккаунтов?
интуитивно
9

Оба ответа на эти вопросы верны, но мне все еще не хватает полного, короткого решения для резервного копирования Github-репозитория в локальный файл. Суть доступна здесь, не стесняйтесь раскошелиться или адаптироваться к вашим потребностям.

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 git@github.com:$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
Начо Колома
источник
1
Интересный. Точнее, чем мой ответ. +1
VonC
Спасибо, это полезно для Github. Принятый ответ на текущий вопрос.
Дэн Розенстарк
5

Вы можете сделать резервную копию git-репо с помощью git-copy . git-copy сохранил новый проект как голое хранилище, это означает минимальную стоимость хранения.

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

Затем вы можете восстановить свой проект с git clone

git clone /backup/project.backup project
Quanlong
источник
Argh! этот ответ заставил меня поверить, что «git copy» была официальной командой git.
gatopeich
2

Нашел простой официальный путь после того, как пробирался сквозь стены текста выше, который заставил бы вас думать, что его нет.

Создайте полный комплект с:

$ git bundle create <filename> --all

Восстановите его с помощью:

$ git clone <filename> <folder>

Эта операция является атомной AFAIK. Проверьте официальные документы для мельчайших деталей.

Что касается "zip": пакеты git сжаты и удивительно малы по сравнению с размером папки .git.

gatopeich
источник
Это не отвечает на весь вопрос о zip, а также предполагает, что мы прочитали другие ответы. Пожалуйста, исправьте это так, чтобы оно было атомарным и обрабатывало весь вопрос, и я рад, что он принял ответ (10 лет спустя). Спасибо
Дэн Розенстарк
0

пришел к этому вопросу через гугл.

Вот что я сделал самым простым способом.

git checkout branch_to_clone

затем создайте новую ветку git из этой ветки

git checkout -b new_cloned_branch
Switched to branch 'new_cloned_branch'

вернитесь в исходную ветку и продолжайте:

git checkout branch_to_clone

Предполагая, что вы облажались и нужно что-то восстановить из резервной ветки:

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

Лучшая часть, если что-то напортачило, вы можете просто удалить исходную ветку и вернуться в резервную ветку !!

NoobEditor
источник
1
Мне нравится этот подход - но я не уверен, что это лучший метод? Я делаю 'резервные' ветки git довольно часто, и в итоге у меня будет много резервных веток. Я не уверен, что это нормально или нет (имея ~ 20 резервных веток на разные даты). Я думаю, что я всегда мог бы удалить старые резервные копии в конечном итоге - но если я хочу сохранить их все - это нормально? Пока что игра хороша, но было бы неплохо узнать, хорошо это или плохо.
Кайл Васселла
это не то, что можно назвать лучшей практикой , я предполагаю, что это больше связано с индивидуальными привычками делать вещи. Я обычно код в одной отрасли только до тех пор пока работа сделана и сохранить другую ветвь для AdHoc запросов. У обоих есть резервные копии, однажды сделав, удалите основную ветку! :)
NoobEditor