স্থানীয় গিট সংগ্রহস্থলটির ব্যাকআপ কীভাবে করবেন?


155

আমি অপেক্ষাকৃত ছোট প্রকল্পে গিট ব্যবহার করছি এবং আমি দেখতে পেয়েছি যে .git ডিরেক্টরিটির বিষয়বস্তুগুলিকে জিপ করা প্রকল্পের ব্যাক আপ করার একটি দুর্দান্ত উপায় হতে পারে। তবে এটি এক ধরণের অদ্ভুত কারণ কারণ আমি যখন পুনরুদ্ধার করি তখন প্রথম জিনিসটি আমার করা উচিত git reset --hard

এইভাবে গিট রেপোকে ব্যাক আপ করতে কোনও সমস্যা আছে? এছাড়াও, এটি করার আরও ভাল কোনও উপায় আছে (উদাহরণস্বরূপ, একটি পোর্টেবল গিট ফর্ম্যাট বা অনুরূপ কিছু?)


কেউ গিট বান্ডিল ব্যবহারের সুস্পষ্ট উত্তর কেন দেয়নি ???
গ্যাটোপিচ

পছন্দ করেছেন নিচে নামুন.
ড্যান রোজনস্টার্ক

সমস্ত git bundle
আপভেटेड

উত্তর:


23

আমি ইয়ার স্ক্রিপ্টে কিছুটা দূরে হ্যাক করা শুরু করেছি এবং ফলাফলটি গ্রন্থবলে ম্যান পেজ এবং ইনস্টল স্ক্রিপ্ট সহ রয়েছে:

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

ইনস্টলেশন :

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

সমস্ত পরামর্শ স্বাগত জানাই এবং গিথুব উপর অনুরোধ টান।

#!/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
নীচে আমার উত্তরে আমি যে গিট বান্ডিলের পক্ষে পরামর্শ দিয়েছি তার ভিত্তিতে @ ইয়ার গ্রেট বান্ডিল স্ক্রিপ্ট। +1 টি।
ভোনসি

1
আমি ইতিমধ্যে আপনার অ্যাপ্লিকেশনটি আমার স্থানীয় খালি ভাণ্ডারে ইনস্টল করেছি .... এটি ইনস্টল হয়ে গেলে আপনি কীভাবে এটি ব্যবহার করবেন .... ডকুমেন্টেশনের বিষয়ে কোনও তথ্য নেই, ব্যাকআপ কীভাবে তৈরি করবেন তার একটি উদাহরণ সহ আপনার একটি বিভাগ অন্তর্ভুক্ত করা উচিত
জেএফ

হাই, দুঃখিত আপনি এটি কাজ করতে না। সাধারণত আপনি চালনা করেন sudo install.shতবে গন্তব্য ডিরেক্টরিটি সেট করতে এটি গিফট কনফিগার করুন (এটি গিট কনফিগারেশন সিস্টেম ব্যবহার করে) (গিথুবটিতে রিডমি ফাইলটি দেখুন)। এরপরে আপনি git backupআপনার ভাণ্ডারের ভিতরে চলে যান । সাইডেনোট হিসাবে, এটি গিট বান্ডিল এবং এই প্রশ্নের প্রতিক্রিয়া নিয়ে একটি পরীক্ষা ছিল, তবে গিট বান্ডিল কখনই একটি নির্ভুল সঠিক অনুলিপি তৈরি করে না (উদাহরণস্বরূপ যদি আমি ভালভাবে স্মরণ করি, বিশেষত গিট রিমোটগুলি সম্পর্কিত) তবে ব্যাক্তিগতভাবে আমি ব্যাকআপের জন্য টার ব্যবহার করি tar গিট ডিরেক্টরি

144

অন্যান্য অফিক্যাল উপায়ে গিট বান্ডিল ব্যবহার করা হবে

এটি একটি ফাইল তৈরি করবে যা সমর্থন করে 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ফোল্ডার থেকে সংরক্ষণাগার তৈরির বিপরীতে যেমন কল্পনাপ্রসূত মন্তব্য করেছেন )


সতর্কতা: আমি না করার পরামর্শ দিচ্ছি প্যাট Notz এর সমাধান , যা রেপো ক্লোনিং করা হয়।
ব্যাকআপ করা বা আপডেট করার চেয়ে অনেকগুলি ফাইলের ব্যাকআপ সবসময়ই জটিল ...

আপনি তাকান তাহলে সম্পাদনার ইতিহাস এর ওপি ইয়ার উত্তর , আপনি দেখতে যে ইয়ার প্রথমে একটি ব্যবহৃত clone --mirror, ... সম্পাদনা:

ড্রপবক্সের সাহায্যে এটি ব্যবহার করা মোট জগাখিচুড়ি
আপনার সিঙ্ক ত্রুটি হবে এবং আপনি ড্রপবক্সে একটি সরাসরি ব্যাক রোল করতে পারবেন না। আপনি যদি আপনার ড্রপবক্সে ব্যাক আপ নিতে চান তবে
ব্যবহার করুন git bundle

ইয়ার বর্তমান সমাধান ব্যবহার করে git bundle

আমি আমার মামলা বিশ্রাম।


আমি এটি পরীক্ষা করে দেখেছি এবং এটি আসলে দুর্দান্ত। আমাকে বিশ্বাস করার জন্য কিছু বান্ডিলিং এবং আনবান্ডলিং এবং তালিকা-শিরোনাম চেষ্টা করতে হবে ... তবে আমি এটি বেশ খানিকটা পছন্দ করি। আবার ধন্যবাদ, বিশেষত - সমস্ত স্যুইচ-এ থাকা নোটগুলির জন্য।
ড্যান রোজনস্টার্ক

কিছুটা সম্পর্কিত, কেবল আমার স্থানীয় সংগ্রহস্থলটি জিপ করাতে কোনও ভুল আছে? আমার একটি একক ব্যাকআপ ফাইল দরকার, বাইরের ড্রাইভে হাজার হাজার ফাইল অনুলিপি করা অবিশ্বাস্যরকম ধীর is আমি কেবল ভাবছি যে আরও কার্যকর কিছু আছে কিনা কারণ জিপকে .git ফোল্ডারে অনেকগুলি ফাইল সংরক্ষণাগারভুক্ত করতে হবে।

@faB: শুধুমাত্র পার্থক্য হল যে আপনি সহজেই করতে পারেন ক্রমবর্ধমান সঙ্গে ব্যাকআপ git bundle। সমস্ত স্থানীয় রেপোগুলির একটি বিশ্বব্যাপী জিপ দিয়ে এটি সম্ভব নয়।
ভোনসি

2
একটি পুরানো মন্তব্যে জবাব দেওয়া, তবে বান্ডিল এবং জিপ জিপি করার মধ্যে অন্য একটি পার্থক্য হ'ল পারমাণবিক, তাই যদি কেউ অপারেশনটির মাঝখানে আপনার রেপো আপডেট করার ঘটনা ঘটে তবে তা গণ্ডগোল হবে না।
চমত্কার

1
পছন্দ করুন আরও দৃশ্যমানতার জন্য আমি এটিকে উত্তরে অন্তর্ভুক্ত করেছি।
ভোনসি

62

আমি যেভাবে এটি করি তা হ'ল রিমোট (বেয়ার) সংগ্রহস্থল (একটি পৃথক ড্রাইভ, ইউএসবি কী, ব্যাকআপ সার্ভার বা এমনকি গিথুব) তৈরি করা এবং তারপরে push --mirrorসেই দূরবর্তী রেপোটিকে আমার স্থানীয় দেখতে দেখতে (রিমোট ব্যতীত খালি ছাড়া সংগ্রহস্থলের)।

এটি নন-ফাস্ট-ফরওয়ার্ড আপডেটগুলি সহ সমস্ত রেফ (শাখা এবং ট্যাগ) ঠেলে দেবে। আমি আমার স্থানীয় সংগ্রহস্থলের ব্যাকআপ তৈরি করতে এটি ব্যবহার করি।

Man পৃষ্ঠা এর মতো এটিকে বর্ণনা

ধাক্কা প্রতিটি সুত্র নামকরণ করার পরিবর্তে, নির্দিষ্ট করে অধীনস্থ সকল refs $GIT_DIR/refs/(যার মধ্যে কিন্তু সীমাবদ্ধ নয় refs/heads/, refs/remotes/এবং refs/tags/) দূরবর্তী সংগ্রহস্থল প্রতিবিম্বিত হবে না। নতুন তৈরি স্থানীয় রেফগুলি দূরবর্তী প্রান্তে ঠেলে দেওয়া হবে, স্থানীয়ভাবে আপডেট হওয়া রেফগুলি রিমোট প্রান্তে জোর করে আপডেট করা হবে এবং মুছে ফেলা রিফগুলি দূরবর্তী প্রান্ত থেকে সরানো হবে। কনফিগারেশন বিকল্পটি remote.<remote>.mirrorসেট করা থাকলে এটি ডিফল্ট ।

আমি ধাক্কা দেওয়ার জন্য একটি নাম রাখলাম:

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

তারপরে, আমি git bakযখনই ব্যাকআপ করতে চাই তখনই আমি চালিত ।


+1 টি। একমত। গিট বান্ডিলটি একটি ব্যাকআপ (চারপাশে একটি ফাইল) সরাতে সুন্দর। তবে কোনও ড্রাইভের মাধ্যমে আপনি যে কোনও জায়গায় প্লাগ করতে পারেন, খালি রেপোও বেশ ভাল।
ভোনসি

+1 অবাক, আমি এটি সন্ধান করব। উদাহরণগুলির জন্যও ধন্যবাদ।
ড্যান রোজনস্টার্ক

@ প্যাট নটজ, শেষ পর্যন্ত আমি সিদ্ধান্ত নিয়েছি যে আপনি এটি করার পদ্ধতিটি নিয়ে যাবেন এবং আমি এখানে নীচে একটি উত্তর রেখেছি (স্থায়ীভাবে শূন্যের স্কোর ধরে
রেখেছি

নোট করুন যে --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
@ ইয়ার: আপনি কি জানেন যে কীভাবে কমান্ডলাইন ছাড়াই এই কাজগুলি সম্পাদন করতে হবে এবং পরিবর্তে কেবল কচ্ছপ ব্যবহার করা হবে (আমার নন-কমান্ড-লাইন-উইন্ডোজ ব্যবহারকারীদের জন্য সমাধান অনুসন্ধান করছি)?
পাসটাকুল

@ পাস্তাকুল, দুঃখিত আমি কমান্ড-লাইন ছাড়া গিট সম্পর্কে মোটেই জানি না। সম্ভবত রুবিমাইনের মতো কোনও সম্পর্কিত আইডিই চেক করে দেখুন?
ড্যান রোজনস্টার্ক

@ নিখরচায়, আপনি স্পাইডারোয়াক, বা কেবল ফাইলগুলি (যা ড্রপবক্স দেয় এবং তারা আপনাকে 3 গিগাবাইট স্থান দেয়) দিয়ে DIRECTORIES ফিরে যেতে পারে?
ড্যান রোজনস্টার্ক

@ ইয়ার: আমি বুঝতে পেরেছি তা নিশ্চিত নন .. আপনি কি বোঝাতে চাইছেন যে আমি যদি কোনও ড্রপবক্স-ব্যাক ডিরেক্টরি মুছে ফেলি তবে এতে থাকা ফাইলগুলির সমস্ত পূর্ববর্তী সংস্করণ আমি হারিয়ে ফেলছি? স্পাইডারোকের সংস্করণ নীতি সম্পর্কিত আরও তথ্য এখানে । টিবিএইচ আমি স্পাইডারওককে সত্যিই বেশি ব্যবহার করি নি, এবং এর সীমা সম্পর্কে পুরোপুরি নিশ্চিত নই। দেখে মনে হচ্ছে তারা এ জাতীয় সমস্যার সমাধান দিতে পারত যদিও তারা প্রযুক্তিগত যোগ্যতার উপর অনেক বেশি জোর দেয়। এছাড়াও: ফ্রি অ্যাকাউন্টগুলির জন্য রোলব্যাকগুলিতে ড্রপবক্সের এখনও 30 দিনের সীমা রয়েছে?
intuited

9

এই প্রশ্নের উভয় উত্তরই সঠিক, তবে আমি গিথুব সংগ্রহস্থলের স্থানীয় একটি ফাইলে ব্যাকআপ দেওয়ার একটি সম্পূর্ণ, সংক্ষিপ্ত সমাধানটি অনুপস্থিত ছিল। সারকথা , কাঁটাচামচ বা আপনার প্রয়োজন অনুযায়ী মানিয়ে বিনা দ্বিধায় এখানে পাওয়া যায়।

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
ভোনসি

ধন্যবাদ, এটি গিথুবের জন্য দরকারী। গৃহীত উত্তরটি বর্তমান প্রশ্নের উত্তর to
ড্যান রোজনস্টার্ক

5

গিট-কপির সাহায্যে গিট রেপো ব্যাকআপ করতে পারেন । গিট-অনুলিপি নতুন প্রকল্পটিকে খালি রেপো হিসাবে সংরক্ষণ করা হয়েছে, এর অর্থ সর্বনিম্ন স্টোরেজ ব্যয়।

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

তারপরে আপনি এর সাথে আপনার প্রকল্পটি পুনরুদ্ধার করতে পারেন git clone

git clone /backup/project.backup project

আহা! এই উত্তরটি আমাকে বিশ্বাস করেছে "গিট কপি" একটি অফিসিয়াল গিট কমান্ড was
গাটোপিচ

2

উপরের পাঠ্যের দেয়ালগুলি ভেদ করার পরে সরকারী সরল উপায় খুঁজে পেয়েছে যা আপনাকে ভাবায় যে সেখানে কোনও নেই।

এর সাথে একটি সম্পূর্ণ বান্ডিল তৈরি করুন:

$ git bundle create <filename> --all

এটি দিয়ে পুনরুদ্ধার করুন:

$ git clone <filename> <folder>

এই অপারেশনটি পারমাণবিক আফাইক AI কৌতূহল বিবরণের জন্য অফিসিয়াল ডক্স পরীক্ষা করুন Check

"জিপ" সম্পর্কিত: গিট বান্ডিলগুলি .git ফোল্ডারের আকারের তুলনায় সংকুচিত এবং আশ্চর্যজনকভাবে ছোট।


এটি জিপ সম্পর্কে পুরো প্রশ্নের উত্তর দেয় না এবং ধরে নেওয়া হয় যে আমরা অন্যান্য উত্তরগুলি পড়েছি। দয়া করে এটি ঠিক করুন যাতে এটি পারমাণবিক হয় এবং পুরো প্রশ্নটি পরিচালনা করে এবং আমি এটির উত্তর গৃহীত করে (10 বছর পরে) খুশি হয়েছি। ধন্যবাদ
ড্যান রোজনস্টার্ক

0

গুগলের মাধ্যমে এই প্রশ্নে এসেছিল।

আমি সহজ পদ্ধতিতে যা করেছি তা এখানে।

git checkout branch_to_clone

তারপরে এই শাখা থেকে একটি নতুন গিট শাখা তৈরি করুন

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 "--"

সেরা অংশ যদি কিছু স্ক্রু করা হয় তবে আপনি কেবল উত্স শাখাটি মুছতে পারেন এবং ব্যাকআপ শাখায় ফিরে যেতে পারেন !!


1
আমি এই পদ্ধতির পছন্দ করি - তবে আমি এটির সেরা অনুশীলন কিনা তা সম্পর্কে নিশ্চিত নই? আমি প্রায়শই 'ব্যাকআপ' গিট শাখা তৈরি করি এবং শেষ পর্যন্ত আমার অনেক ব্যাকআপ শাখা থাকবে। আমি নিশ্চিত না এটি ঠিক আছে কি না (বিভিন্ন তারিখ থেকে 20 ডলার ব্যাকআপ শাখা থাকা)। আমার ধারণা আমি শেষ পর্যন্ত পুরানো ব্যাকআপগুলি মুছে ফেলতে পারি - তবে আমি যদি সেগুলি সবই রাখতে চাই - তবে কি ঠিক আছে? এখনও অবধি এটি দুর্দান্ত খেলছে - তবে এটি ভাল বা খারাপ অনুশীলন কিনা তা জানতে পেরে ভাল লাগবে।
কাইল ভ্যাসেলা

এটি এমন কিছু নয় যা সেরা অনুশীলন হিসাবে ডাকা হবে , আমি এটি স্টাফগুলি করার ব্যক্তিগত অভ্যাসের সাথে আরও সম্পর্কিত বলে ধরে নিই। আমি সাধারণত একটি শাখায় কোড করি কেবল ততক্ষণ কাজ শেষ হয় এবং অ্যাডহক অনুরোধের জন্য একটি অন্য শাখা রাখি । দু'জনেরই ব্যাকআপ রয়েছে, একবার হয়ে গেলে মুখ্য শাখাটি মুছুন! :)
নুব এডিটর
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.