Jak wykonać kopię zapasową lokalnego repozytorium Git?

155

Używam gita w stosunkowo małym projekcie i uważam, że spakowanie zawartości katalogu .git może być dobrym sposobem na wykonanie kopii zapasowej projektu. Ale to trochę dziwne, ponieważ kiedy przywracam, pierwszą rzeczą, którą muszę zrobić, jest git reset --hard.

Czy są jakieś problemy z tworzeniem kopii zapasowej repozytorium git w ten sposób? Czy istnieje lepszy sposób, aby to zrobić (np. Przenośny format git lub coś podobnego?)?

Dan Rosenstark
źródło
Dlaczego nikt nie podał oczywistej odpowiedzi na temat używania pakietu git ???
gatopeich
@gatopeich zrobili. Przewiń w dół.
Dan Rosenstark
Wszystkie odpowiedzi za pozytywnymi opiniami zawierają ścianę tekstu o niestandardowych skryptach, nawet ten, który zaczyna o nim wspominaćgit bundle
gatopeich

Odpowiedzi:

23

Zacząłem trochę przerabiać skrypt Yara i rezultat jest na githubie, włączając strony man i skrypt instalacyjny:

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

Instalacja :

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

Witamy wszystkie sugestie i prośby o ściągnięcie na githubie.

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

źródło
1
@Yar Świetny skrypt pakietu, oparty na pakiecie git, za którym opowiadałem się w mojej odpowiedzi poniżej. +1.
VonC,
1
Zainstalowałem już Twoją aplikację w moim lokalnym czystym repozytorium .... jak jej używasz po zainstalowaniu ... w dokumentacji nie ma informacji na ten temat, powinieneś dołączyć sekcję z przykładem tworzenia kopii zapasowej
JAF
Cześć, przepraszam, że nie działa. Zwykle uruchamiasz sudo install.sh, a następnie konfigurujesz (używa systemu git config), aby ustawić katalog docelowy (zobacz plik readme na github). Następnie uruchomisz git backupswoje repozytorium. Na marginesie, był to eksperyment z pakietem git i odpowiedzią na to pytanie, ale pakiet git nigdy nie tworzy absolutnie dokładnej kopii (np. Jeśli dobrze pamiętam, szczególnie w przypadku pilotów git), więc osobiście używam tar do tworzenia kopii zapasowych. katalogi git.
144

Innym oficjalnym sposobem byłoby użycie pakietu git

Spowoduje to utworzenie pliku, który obsługuje git fetchi git pullaktualizuje drugie repozytorium.
Przydatne do przyrostowych kopii zapasowych i przywracania.

Ale jeśli musisz wykonać kopię zapasową wszystkiego (ponieważ nie masz drugiego repozytorium ze starszą zawartością już na miejscu), tworzenie kopii zapasowej jest nieco bardziej skomplikowane, jak wspomniałem w mojej innej odpowiedzi, po komentarzu Kenta Fredrica :

$ 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

(Jest to operacja atomowa , w przeciwieństwie do tworzenia archiwum z .gitfolderu, jak skomentował przez fantabolous )


Ostrzeżenie: Nie zaleca patentowym NOTZ jest rozwiązanie , które jest klonowanie repo.
Tworzenie kopii zapasowych wielu plików jest zawsze trudniejsze niż tworzenie kopii zapasowych lub aktualizowanie ... tylko jednego.

Jeśli spojrzeć na historię edycji na OP Yar odpowiedź , to widać, że Yar wykorzystywane na pierwszy clone --mirror... z edycji:

Używanie tego z Dropbox to totalny bałagan .
Wystąpią błędy synchronizacji i NIE MOŻESZ ZWRÓCIĆ KATALOGU W DROPBOX.
Użyj, git bundlejeśli chcesz utworzyć kopię zapasową w swojej skrzynce referencyjnej.

Obecne rozwiązanie Yar wykorzystuje git bundle.

Odpoczywam moja sprawa.

VonC
źródło
Właśnie to sprawdziłem i jest naprawdę świetne. Będę musiał wypróbować kilka pakietów, unbundling i list-headów, aby się przekonać ... ale bardzo mi się to podoba. Jeszcze raz dziękuję, zwłaszcza za uwagi na temat przełącznika --all.
Dan Rosenstark
W pewnym sensie, czy jest coś złego w skompresowaniu mojego lokalnego repozytorium? Potrzebuję jednego pliku kopii zapasowej, kopiowanie tysięcy plików na dysk zewnętrzny jest niesamowicie powolne. Zastanawiam się tylko, czy jest coś bardziej wydajnego, ponieważ zip musi zarchiwizować tak wiele plików w folderze .git.
@faB: jedyną różnicą jest to, że możesz łatwo tworzyć przyrostowe kopie zapasowe za pomocą git bundle. Nie jest to możliwe w przypadku globalnego zip całego lokalnego repozytorium.
VonC
2
Odpowiadając na stary komentarz, ale kolejną różnicą między pakietem a kompresowaniem katalogu jest pakiet jest atomowy, więc nie będzie bałaganu, jeśli ktoś zdarzy się zaktualizować repozytorium w środku operacji.
fantabolous
1
@fantabolous dobry punkt. Zawarłem to w odpowiedzi dla większej widoczności.
VonC,
62

Sposób, w jaki to robię, polega na utworzeniu zdalnego (gołego) repozytorium (na oddzielnym dysku, kluczu USB, serwerze kopii zapasowych lub nawet githubie), a następnie użycie push --mirrorgo, aby to zdalne repozytorium wyglądało dokładnie tak, jak moje lokalne (z wyjątkiem tego, że zdalne jest puste magazyn).

Spowoduje to przesunięcie wszystkich referencji (gałęzi i tagów), w tym aktualizacji bez szybkiego przewijania do przodu. Używam tego do tworzenia kopii zapasowych mojego lokalnego repozytorium.

Strona podręcznika opisuje to tak:

Zamiast nazywania każdego ref do pchania, określa, że wszystkie pozycje literatury poniżej $GIT_DIR/refs/(co obejmuje, ale nie ogranicza się do refs/heads/, refs/remotes/i refs/tags/) być dublowane do zdalnego repozytorium. Nowo utworzone lokalne referencje zostaną wypchnięte na zdalny koniec, lokalnie zaktualizowane referencje zostaną wymuszone zaktualizowane na zdalnym końcu, a usunięte referencje zostaną usunięte ze zdalnego końca. Jest to ustawienie domyślne, jeśli ustawiono opcję konfiguracji remote.<remote>.mirror.

Zrobiłem alias, aby wykonać push:

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

Następnie po prostu biegam, git bakkiedy chcę zrobić kopię zapasową.

Pat Notz
źródło
+1. Zgoda. Pakiet git jest fajny do przenoszenia kopii zapasowej (jeden plik). Ale z dyskiem, który możesz podłączyć w dowolnym miejscu, samo repozytorium też jest w porządku.
VonC
+1 awesme, przyjrzę się temu. Dziękuję też za przykłady.
Dan Rosenstark
@Pat Notz, w końcu zdecydowałem się pójść za twoim sposobem i zamieściłem tutaj odpowiedź (wynik na stałe utrzymany na poziomie zero :)
Dan Rosenstark
Zwróć uwagę, że w --mirrorrzeczywistości nie przeprowadza żadnej weryfikacji otrzymanych obiektów. Prawdopodobnie powinieneś git fsckw pewnym momencie uruchomić, aby zapobiec korupcji.
docwhat
34

[Zostawiam to tutaj do własnego użytku.]

Mój skrypt pakietu o nazwie git-backupwygląda następująco

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

Czasami używam, git backupa czasami korzystam, git backup different-nameco daje mi większość możliwości, których potrzebuję.

Dan Rosenstark
źródło
2
+1 Ponieważ nie --globalużyłeś opcji ten alias będzie widoczny tylko w Twoim projekcie (jest zdefiniowany w Twoim .git/configpliku) - prawdopodobnie tego chcesz. Dzięki za bardziej szczegółową i ładnie sformatowaną odpowiedź.
Pat Notz
1
@yar: czy wiesz, jak wykonać te zadania bez wiersza poleceń i zamiast tego używać tylko tortoisegit (szukam rozwiązania dla moich użytkowników, którzy nie korzystają z wiersza poleceń)?
pastacool
@pastacool, przepraszam, że w ogóle nie wiem o git bez wiersza poleceń. Może sprawdź odpowiednie IDE, takie jak RubyMine?
Dan Rosenstark
@intuited, możesz cofnąć KATALOGI za pomocą spideroaka lub po prostu pliki (co robi Dropbox i dają 3 GB miejsca)?
Dan Rosenstark,
@Yar: nie wiem, czy rozumiem… czy masz na myśli, że jeśli usunę katalog wspierany przez Dropbox, stracę wszystkie poprzednie wersje zawartych w nim plików? Więcej informacji na temat zasad wersjonowania Spideroak jest tutaj . TBH Tak naprawdę nie korzystałem zbyt często ze SpiderOaka i nie jestem do końca pewien jego ograniczeń. Wygląda na to, że byliby oni rozwiązaniem takich problemów, ale kładą duży nacisk na kompetencje techniczne. Ponadto: czy Dropbox nadal ma 30-dniowy limit przywracania w przypadku bezpłatnych kont?
intuicja
9

Obie odpowiedzi na te pytania są poprawne, ale nadal brakowało mi kompletnego, krótkiego rozwiązania, aby wykonać kopię zapasową repozytorium Github w pliku lokalnym. Istotą jest tutaj, nie krępuj się widelec lub dostosować do swoich potrzeb.

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
źródło
1
Ciekawy. Bardziej precyzyjna niż moja odpowiedź. +1
VonC
Dzięki, jest to przydatne dla Githuba. Zaakceptowaną odpowiedzią jest aktualne pytanie.
Dan Rosenstark
5

Możesz wykonać kopię zapasową repozytorium git za pomocą git-copy . git-copy zapisał nowy projekt jako nagie repozytorium, co oznacza minimalny koszt przechowywania.

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

Następnie możesz przywrócić swój projekt za pomocą git clone

git clone /backup/project.backup project
Quanlong
źródło
Argh! ta odpowiedź sprawiła, że ​​uwierzyłem, że „git copy” jest oficjalnym poleceniem git.
gatopeich
2

Znalazłem prosty oficjalny sposób po przejściu przez ściany tekstu powyżej, który sprawi, że pomyślisz, że go nie ma.

Utwórz kompletny pakiet zawierający:

$ git bundle create <filename> --all

Przywróć go za pomocą:

$ git clone <filename> <folder>

Ta operacja jest atomowa AFAIK. Sprawdź oficjalne dokumenty, aby uzyskać szczegółowe informacje.

Jeśli chodzi o „zip”: pakiety git są skompresowane i zaskakująco małe w porównaniu z rozmiarem folderu .git.

gatopeich
źródło
To nie odpowiada na całe pytanie dotyczące zip, a także zakłada, że ​​przeczytaliśmy inne odpowiedzi. Popraw to tak, aby było atomowe i obsługiwało całe pytanie. Cieszę się, że mogę zaakceptować odpowiedź (10 lat później). Dzięki
Dan Rosenstark
0

przyszedł na to pytanie przez google.

Oto, co zrobiłem w najprostszy sposób.

git checkout branch_to_clone

następnie utwórz nową gałąź git z tej gałęzi

git checkout -b new_cloned_branch
Switched to branch 'new_cloned_branch'

wróć do oryginalnej gałęzi i kontynuuj:

git checkout branch_to_clone

Zakładając, że schrzaniłeś sprawę i musisz przywrócić coś z gałęzi kopii zapasowej:

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

Najlepsza część, jeśli coś jest schrzanione, możesz po prostu usunąć gałąź źródłową i wrócić do gałęzi zapasowej !!

NoobEditor
źródło
1
Podoba mi się to podejście - ale nie jestem pewien, czy jest to najlepsza praktyka? Dość często tworzę „zapasowe” gałęzie git i ostatecznie będę miał wiele zapasowych gałęzi. Nie jestem pewien, czy to jest w porządku, czy nie (mam ~ 20 oddziałów zapasowych z różnych dat). Chyba zawsze mógłbym w końcu usunąć starsze kopie zapasowe - ale jeśli chcę je wszystkie zachować - czy to w porządku? Jak dotąd gra ładnie - ale dobrze byłoby wiedzieć, czy to dobra, czy zła praktyka.
Kyle Vassella,
nie jest to coś, co można by nazwać najlepszą praktyką , zakładam, że jest to bardziej związane z indywidualnymi nawykami robienia rzeczy. Generalnie koduję tylko w jednym oddziale, dopóki praca nie zostanie zakończona, a inny pozostawiam dla żądań ad hoc . Oba mają kopie zapasowe, po zakończeniu usuń główną gałąź! :)
NoobEditor