├── .gitignore ├── CHANGELOG ├── COPYING ├── Gemfile ├── README.md ├── Rakefile ├── bin └── gistore ├── exe └── gistore ├── gistore.gemspec ├── lib ├── gistore.rb └── gistore │ ├── cmd │ ├── add.rb │ ├── checkout.rb │ ├── commit.rb │ ├── config.rb │ ├── export-to-backups.rb │ ├── gc.rb │ ├── git-version.rb │ ├── init.rb │ ├── restore-from-backups.rb │ ├── rm.rb │ ├── safe-commands.rb │ ├── status.rb │ ├── task.rb │ ├── update.rb │ └── version.rb │ ├── config.rb │ ├── config │ └── gistore.yml │ ├── error.rb │ ├── repo.rb │ ├── runner.rb │ ├── templates │ ├── description │ ├── hooks │ │ ├── applypatch-msg.sample │ │ ├── commit-msg.sample │ │ ├── post-update.sample │ │ ├── pre-applypatch.sample │ │ ├── pre-commit.sample │ │ ├── pre-push.sample │ │ ├── pre-rebase.sample │ │ ├── prepare-commit-msg.sample │ │ └── update.sample │ └── info │ │ └── exclude │ ├── utils.rb │ ├── vendor │ ├── open4.rb │ ├── thor.rb │ └── thor │ │ ├── actions.rb │ │ ├── actions │ │ ├── create_file.rb │ │ ├── create_link.rb │ │ ├── directory.rb │ │ ├── empty_directory.rb │ │ ├── file_manipulation.rb │ │ └── inject_into_file.rb │ │ ├── base.rb │ │ ├── command.rb │ │ ├── core_ext │ │ ├── hash_with_indifferent_access.rb │ │ ├── io_binary_read.rb │ │ └── ordered_hash.rb │ │ ├── error.rb │ │ ├── group.rb │ │ ├── invocation.rb │ │ ├── parser.rb │ │ ├── parser │ │ ├── argument.rb │ │ ├── arguments.rb │ │ ├── option.rb │ │ └── options.rb │ │ ├── rake_compat.rb │ │ ├── runner.rb │ │ ├── shell.rb │ │ ├── shell │ │ ├── basic.rb │ │ ├── color.rb │ │ └── html.rb │ │ ├── util.rb │ │ └── version.rb │ └── version.rb └── t ├── .gitignore ├── Makefile ├── README ├── aggregate-results.sh ├── lib-worktree.sh ├── t0000-init.sh ├── t0010-config.sh ├── t0020-version.sh ├── t1000-add-remove.sh ├── t1010-status.sh ├── t1020-commit.sh ├── t1030-commit-and-rotate.sh ├── t2000-task-and-commit-all.sh ├── t3000-checkout.sh ├── t3010-export-and-restore.sh ├── test-binary-1.png ├── test-binary-2.png ├── test-lib-functions.sh └── test-lib.sh /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | pkg 3 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | Release Notes 2 | ============= 3 | 4 | v1.0 (2013/10/23) 5 | ------------------------ 6 | 7 | * Rewrite Gistore in ruby instead of in python. 8 | * Using thor [1] for the command-line interface. 9 | * Do not use mount any more, but use "info/exclude" to exclude unwanted 10 | files and directories. 11 | * Write test cases using the same framework as git.git. 12 | * More subcommands, such as export-to-backups, restore-from-backups. 13 | 14 | v0.1.x (2010/01/23) 15 | ------------------------ 16 | 17 | * Fixed: create /etc/gistore on the fly, and fixed setuptools in-compatible 18 | install. (0.1.1) 19 | * Fixed: force umount failed. (0.1.2) 20 | * New feature: show not backup submodule. (0.1.3) 21 | * More signal handler. (0.1.4) 22 | * New command commit_all. (0.1.5) 23 | * Debianized. (0.1.5) 24 | * Git version belowe 1.6, can not take directory after git init (0.1.6) 25 | 26 | v0.1 (2010/01/23) 27 | ------------------------ 28 | 29 | * Initial version. 30 | 31 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | ## Uncomment and change to another RubyGems mirror, if you are victims of 2 | ## China GFW (http://en.wikipedia.org/wiki/Great_Firewall_of_China). 3 | # 4 | # source 'http://ruby.taobao.org/' 5 | source 'https://rubygems.org' 6 | 7 | # Specify your gem's dependencies in gistore.gemspec 8 | gemspec 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Gistore 2 | ======= 3 | 4 | Gistore is a backup utility using git as a backend, and can be used to 5 | backup arbitrary files and directories. 6 | 7 | Gistore = Git + Store 8 | 9 | Create a gistore repo 10 | --------------------- 11 | 12 | If you want to backup something, you should create a repository to 13 | save the backups first. 14 | 15 | $ gistore init --repo backup.git 16 | 17 | This will create a bare git repository named "backup.git" under the 18 | current directory. 19 | 20 | Prepare a backup list 21 | --------------------- 22 | 23 | When we have a gistore repository used for backup already, we need to 24 | prepare a backup list and tell gistore what you would like to backup. 25 | For example if you want to backup the whole directories such as "/etc", 26 | and "/opt/redmine/files", run: 27 | 28 | $ gistore add --repo backup.git /etc /opt/redmine/files 29 | 30 | Gistore will save the backup list in a yaml file under the gistore repo 31 | ("info/gistore_backups.yml"). Before start to backup, Gistore will chdir 32 | to the root dir ("/") and use it as worktree. Gistore will update the 33 | repo level gitignore file ("info/exclude") as follows to exclude unwanted 34 | files and direcoties according to the backup list. 35 | 36 | * 37 | !/etc 38 | !/opt 39 | !/opt/redmine 40 | !/opt/redmine/files 41 | !/etc/** 42 | !/opt/redmine/files/** 43 | 44 | Start to backup 45 | --------------- 46 | 47 | Run the following command to start to backup: 48 | 49 | $ gistore commit --repo backup.git 50 | 51 | If there are some files you have no privileges to read, backup may fail. 52 | Then you should run backup as root user: 53 | 54 | $ sudo gistore commit --repo backup.git 55 | 56 | You can also provide a custom commit message: 57 | 58 | $ sudo gistore commit --repo backup.git -m "reason to backup" 59 | 60 | Purge old backups 61 | ----------------- 62 | 63 | Gistore will save the latest 360 commits (backups), and it maybe last 64 | for almost one year, if you commit once per day and every day there 65 | are changes. Old backups other than the latest 360 commits will be 66 | purged automatically. You can define how may commits you want to 67 | preserve. 68 | 69 | $ gistore config --repo backup.git increment_backup_number 30 70 | $ gistore config --repo backup.git full_backup_number 12 71 | 72 | These are the default settings. The number of config variable 73 | "increment_backup_number" means that when there are over 30 commits 74 | in master branch, the master branch will be saved as another branch 75 | (e.g. branch "gistore/1") and then the history of master branch will 76 | be purged. When there are 12 such branches (defined in config variable 77 | "full_backup_number"), the oldest one will be deleted and the history 78 | will be purged by calling "git gc". 79 | 80 | You may find out that all the 360 commits can be seen in commit logs of 81 | the master branch, using command: 82 | 83 | $ gistore log --repo backup.git 84 | 85 | This is because all the branches are combined together by "info/grafts", 86 | and this file is updated automatically. 87 | 88 | Installation 89 | ============ 90 | 91 | Install using RubyGems: 92 | 93 | $ gem install gistore 94 | 95 | Or clone this repo [1] from github, and link 'bin/gistore' to your PATH. 96 | 97 | $ git clone git://github.com/jiangxin/gistore 98 | $ ln -s $(pwd)/bin/gistore ~/bin/ 99 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | -------------------------------------------------------------------------------- /bin/gistore: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | GISTORE_FILE_DIRECTORY=$(dirname "$0") 4 | GISTORE_FILE_DIRECTORY=$(cd "$GISTORE_FILE_DIRECTORY" && pwd -P) 5 | 6 | GISTORE_FILENAME=$(basename "$0") 7 | export GISTORE_EXECUTABLE="$GISTORE_FILE_DIRECTORY/$GISTORE_FILENAME" 8 | 9 | GISTORE_SYMLINK=$(readlink $0) 10 | if [ -n "$GISTORE_SYMLINK" ] 11 | then 12 | GISTORE_SYMLINK_DIRECTORY=$(dirname "$GISTORE_SYMLINK") 13 | GISTORE_FILE_DIRECTORY=$(cd "$GISTORE_FILE_DIRECTORY" && 14 | cd "$GISTORE_SYMLINK_DIRECTORY" && pwd -P) 15 | fi 16 | 17 | GISTORE_LIBRARY_DIRECTORY=$(cd "$GISTORE_FILE_DIRECTORY"/../lib && pwd -P) 18 | 19 | GISTORE_SYSTEM=$(uname -s | tr "[:upper:]" "[:lower:]") 20 | if [ "$GISTORE_SYSTEM" = "darwin" ] 21 | then 22 | exec "$GISTORE_LIBRARY_DIRECTORY/gistore.rb" "$@" 23 | else 24 | exec ruby -W0 "$GISTORE_LIBRARY_DIRECTORY/gistore.rb" "$@" 25 | fi 26 | -------------------------------------------------------------------------------- /exe/gistore: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | std_trap = trap("INT") { exit! 130 } # no backtrace thanks 5 | 6 | require 'gistore/runner' 7 | require 'gistore/utils' 8 | 9 | abort "Please install git first" unless git_cmd 10 | if Gistore.git_version_compare('1.6.0') < 0 11 | abort "Git lower than 1.6.0 has not been tested. Please upgrade your git." 12 | end 13 | 14 | $gistore_runner = true 15 | Gistore::Runner.start 16 | -------------------------------------------------------------------------------- /gistore.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'gistore/version' 5 | 6 | Gem::Specification.new do |s| 7 | s.name = "gistore" 8 | s.version = Gistore::VERSION 9 | s.platform = Gem::Platform::RUBY 10 | s.authors = ["Jiang Xin"] 11 | s.email = ["worldhello.net@gmail.com"] 12 | s.description = "A backup utility using git as a backend." 13 | s.summary = "Gistore-#{Gistore::VERSION}" 14 | s.homepage = "https://github.com/jiangxin/gistore" 15 | s.license = "GPL v2" 16 | 17 | s.files = `git ls-files -- lib/`.split($/) 18 | s.files += %w[README.md CHANGELOG COPYING] 19 | s.files.delete_if{ |f| f =~ %r{gistore/vendor/|gistore/cmd/update.rb} } 20 | s.bindir = "exe" 21 | s.executables = `git ls-files -- exe/`.split($/).map{ |f| File.basename(f) } 22 | s.test_files = `git ls-files -- {spec,t}/*`.split($/) 23 | s.require_paths = ["lib"] 24 | 25 | s.add_development_dependency "bundler" 26 | s.add_development_dependency "rake" 27 | 28 | s.add_dependency "open4", "~> 1.3" 29 | s.add_dependency "thor", "~> 0.18" 30 | end 31 | -------------------------------------------------------------------------------- /lib/gistore.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | std_trap = trap("INT") { exit! 130 } # no backtrace thanks 5 | 6 | require 'pathname' 7 | LIBRARY_PATH = Pathname.new(__FILE__).realpath.dirname.to_s 8 | $:.unshift(LIBRARY_PATH + '/gistore/vendor') 9 | $:.unshift(LIBRARY_PATH) 10 | 11 | require 'gistore/runner' 12 | require 'gistore/utils' 13 | 14 | abort "Please install git first" unless git_cmd 15 | if Gistore.git_version_compare('1.6.0') < 0 16 | abort "Git lower than 1.6.0 has not been tested. Please upgrade your git." 17 | end 18 | 19 | $gistore_runner = true 20 | Gistore::Runner.start 21 | -------------------------------------------------------------------------------- /lib/gistore/cmd/add.rb: -------------------------------------------------------------------------------- 1 | module Gistore 2 | class Runner 3 | desc "add ...", "Add path to backup list" 4 | def add(*args) 5 | parse_common_options_and_repo 6 | raise "nothing to add." if args.empty? 7 | args.each do |entry| 8 | gistore.add_entry entry 9 | end 10 | gistore.save_gistore_backups 11 | rescue Exception => e 12 | Tty.die "#{e.message}" 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/gistore/cmd/checkout.rb: -------------------------------------------------------------------------------- 1 | module Gistore 2 | class Runner 3 | desc "checkout [--rev ]", 4 | "Checkout entries to " 5 | option :rev, :aliases => [:r], :desc => "Revision to checkout", :banner => "" 6 | option :to, :required => true, :banner => "", :desc => "a empty directory to save checkout items" 7 | def checkout(*args) 8 | parse_common_options_and_repo 9 | work_tree = options[:to] 10 | if File.exist? work_tree 11 | if not File.directory? work_tree 12 | Tty.die "\"#{work_tree}\" is not a valid directory." 13 | elsif File.file? File.join(work_tree, ".git") 14 | gitfile = File.open(File.join(work_tree, ".git")) {|io| io.read}.strip 15 | if gitfile != "gitdir: #{gistore.repo_path}" 16 | Tty.die "\"#{work_tree}\" not a checkout from #{gistore.repo_path}" 17 | end 18 | elsif Dir.entries(work_tree).size != 2 19 | Tty.die "\"#{work_tree}\" is not a blank directory." 20 | end 21 | else 22 | require 'fileutils' 23 | FileUtils.mkdir_p work_tree 24 | File.open(File.join(work_tree, '.git'), 'w') do |io| 25 | io.puts "gitdir: #{gistore.repo_path}" 26 | end 27 | end 28 | if git_version_compare('1.7.7.1') >= 0 29 | args = args.empty? ? ["."]: args.dup 30 | args << {:work_tree => work_tree} 31 | args.shift if args.first == '--' 32 | cmds = [git_cmd, 33 | "checkout", 34 | options[:rev] || 'HEAD', 35 | "--", 36 | *args] 37 | gistore.safe_system(*cmds) 38 | else 39 | gistore.setup_environment 40 | Dir.chdir(work_tree) do 41 | `#{git_cmd} archive --format=tar #{options[:rev] || 'HEAD'} #{args.map{|e| e.to_s.gsub " ", "\\ "} * " "} | tar xf -` 42 | end 43 | end 44 | 45 | rescue Exception => e 46 | Tty.die "#{e.message}" 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/gistore/cmd/commit.rb: -------------------------------------------------------------------------------- 1 | require 'gistore/error' 2 | 3 | module Gistore 4 | class Runner 5 | map ["ci", "backup"] => :commit 6 | desc "commit [-m ]", "Start commit changes (i.e. backup)" 7 | option :message, :aliases => :m, :desc => "commit log" 8 | def commit(*args) 9 | parse_common_options_and_repo 10 | 11 | # Check if backup needs rotate 12 | gistore.backup_rotate 13 | 14 | # Compare with last backup, and remove unwanted from cache 15 | latest_backups = gistore.get_backups 16 | last_backups = gistore.get_last_backups 17 | if last_backups 18 | last_backups.each do |entry| 19 | if entry and not latest_backups.include? entry 20 | cmds = [git_cmd, 21 | "rm", 22 | "--cached", 23 | "-r", 24 | "-f", 25 | "--ignore-unmatch", 26 | "--quiet", 27 | "--", 28 | entry.sub(/^\/+/, '')] 29 | cmds << {:check_return => false, :without_work_tree => true} 30 | gistore.shellout(*cmds) 31 | end 32 | end 33 | end 34 | 35 | # Add/remove files... 36 | latest_backups.each do |entry| 37 | # entry may be ignored by ".gitignore" under parent dirs. 38 | gistore.shellout git_cmd, "add", "-f", entry.sub(/^\/+/, '') 39 | end 40 | 41 | cmds = [git_cmd, "add", "-A"] 42 | cmds << ":/" if git_version_compare('1.7.6') >= 0 43 | gistore.shellout *cmds 44 | 45 | # Read status 46 | git_status = [] 47 | gistore.shellout git_cmd, "status", "--porcelain" do |stdout| 48 | stdout.readlines.each do |line| 49 | line.strip! 50 | git_status << line unless line.empty? 51 | end 52 | end 53 | 54 | # Add contents of a submodule, not add as a submodule 55 | submodules = gistore.remove_submodules 56 | until submodules.empty? do 57 | Tty.debug "Re-add files in submodules: #{submodules.join(', ')}" 58 | submodules.each do |submod| 59 | git_status += gistore.add_submodule(submod) 60 | end 61 | # new add directories may contain other submodule. 62 | submodules = gistore.remove_submodules 63 | end 64 | 65 | # Format commit messages 66 | message = "" 67 | message << options[:message].strip if options[:message] 68 | message << "\n\n" unless message.empty? 69 | message << commit_summary(git_status) 70 | msgfile = File.join(gistore.repo_path, "COMMIT_EDITMSG") 71 | open(msgfile, "w") do |io| 72 | io.puts message 73 | end 74 | 75 | # Start to commit 76 | committed = nil 77 | output = "" 78 | begin 79 | gistore.shellout(git_cmd, "commit", "-s", "--quiet", "-F", msgfile, 80 | :without_locale => true, 81 | :check_return => true) do |stdout| 82 | output = stdout.read 83 | end 84 | committed = true 85 | rescue CommandReturnError 86 | if output and 87 | (output =~ /no changes added to commit/ or 88 | output =~ /nothing to commit/ or 89 | output =~ /nothing added to commit/) 90 | committed = false 91 | else 92 | raise "Failed to execute git-commit:\n\n#{output.to_s}" 93 | end 94 | end 95 | 96 | # Save backups 97 | gistore.update_gistore_config(:backups => latest_backups) 98 | gistore.save_gistore_config 99 | 100 | display_name = gistore.task_name ? 101 | "#{gistore.task_name} (#{gistore.repo_path})" : 102 | "#{gistore.repo_path}" 103 | 104 | if committed 105 | Tty.info "Successfully backup repo: #{display_name}" 106 | # Run git-gc 107 | gistore.git_gc 108 | else 109 | Tty.info "Nothing changed for repo: #{display_name}" 110 | end 111 | 112 | rescue Exception => e 113 | Tty.die "#{e.message}" 114 | end 115 | 116 | map ["ci_all", "ci-all", "backup_all", "backup-all"] => :commit_all 117 | desc "commit-all [-m ]", "Start backup (commit) all tasks", :hide => true 118 | option :message, :aliases => :m, :desc => "commit log" 119 | def commit_all 120 | messages = [] 121 | Gistore::get_gistore_tasks.each do |task, path| 122 | cmds = ["commit", "--repo", path] 123 | if options[:message] 124 | cmds << "-m" 125 | cmds << options[:message] 126 | end 127 | # invoke run only once? -- invoke :commit, args, opts 128 | begin 129 | Gistore::Runner.start(cmds) 130 | rescue Exception => e 131 | messages << "Failed to execute #{cmds}." 132 | messages << "Error_msg: #{e.message}" 133 | end 134 | end 135 | unless messages.empty? 136 | Tty.die "At lease one task backup failed.\n#{messages * "\n"}" 137 | end 138 | end 139 | 140 | private 141 | def commit_summary(git_status) 142 | sample = 2 143 | statistics = {} 144 | output = [] 145 | git_status.each do |line| 146 | k,v = line.split(" ", 2) 147 | statistics[k] ||= [] 148 | statistics[k] << v 149 | end 150 | 151 | total = git_status.size 152 | detail = statistics.to_a.map{|h| "#{h[0]}: #{h[1].size}"}.sort.join(", ") 153 | output << "Backup #{total} item#{total > 1 ? "s" : ""} (#{detail})" 154 | output << "" 155 | statistics.keys.sort.each do |k| 156 | buffer = [] 157 | if statistics[k].size > sample 158 | step = statistics[k].size / sample 159 | (0...sample).each do |i| 160 | buffer << statistics[k][step * i] 161 | end 162 | buffer << "...#{statistics[k].size - sample} more..." 163 | else 164 | buffer = statistics[k] 165 | end 166 | output << " #{k} => #{buffer.join(", ")}" 167 | end 168 | output.join("\n") 169 | end 170 | end 171 | end 172 | -------------------------------------------------------------------------------- /lib/gistore/cmd/config.rb: -------------------------------------------------------------------------------- 1 | module Gistore 2 | class Runner 3 | map "config" => :cmd_config 4 | desc "config name value", "Read or update gistore config or git config" 5 | option :plan, :desc => "builtin plan: no-gc, no-compress, or normal (default)" 6 | def cmd_config(*args) 7 | parse_common_options_and_repo 8 | if options[:plan] 9 | return gistore.git_config('--plan', options[:plan]) 10 | end 11 | 12 | args << {:check_return => true} 13 | unless gistore.git_config(*args) 14 | exit 1 15 | end 16 | rescue SystemExit 17 | exit 1 18 | rescue Exception => e 19 | Tty.die "#{e.message}" 20 | end 21 | end 22 | end 23 | 24 | -------------------------------------------------------------------------------- /lib/gistore/cmd/export-to-backups.rb: -------------------------------------------------------------------------------- 1 | module Gistore 2 | class Runner 3 | desc "export-to-backups", "Export to a series of full/increment backups" 4 | option :to, :required => true, :banner => "", :desc => "path to save full/increment backups" 5 | def export_to_backups 6 | parse_common_options_and_repo 7 | work_tree = options[:to] 8 | if File.exist? work_tree 9 | if not File.directory? work_tree 10 | raise "\"#{work_tree}\" is not a valid directory." 11 | elsif Dir.entries(work_tree).size != 2 12 | Tty.warning "\"#{work_tree}\" is not a blank directory." 13 | end 14 | else 15 | require 'fileutils' 16 | FileUtils.mkdir_p work_tree 17 | end 18 | 19 | commits = [] 20 | gistore.shellout(git_cmd, "rev-list", "master", 21 | :without_grafts => true) do |stdout| 22 | commits = stdout.readlines 23 | end 24 | 25 | export_commits(commits, work_tree) 26 | rescue Exception => e 27 | Tty.die "#{e.message}" 28 | end 29 | 30 | private 31 | 32 | def export_commits(commits, work_tree) 33 | left = right = nil 34 | n = 1 35 | until commits.empty? 36 | right=commits.pop.strip 37 | export_one_commit(n, left, right, work_tree) 38 | left = right 39 | n += 1 40 | end 41 | end 42 | 43 | def export_one_commit(n, left, right, work_tree) 44 | time = nil 45 | gistore.shellout(git_cmd, "cat-file", "commit", right) do |stdout| 46 | stdout.readlines.each do |line| 47 | if line =~ /^committer .* ([0-9]+)( [+-][0-9]*)?$/ 48 | time = Time.at($1.to_i).strftime("%Y%m%d-%H%M%S") 49 | break 50 | end 51 | end 52 | end 53 | 54 | prefix = "%03d-" % n 55 | prefix << (left ? "incremental" : "full-backup") 56 | prefix << "-#{time}" if time 57 | prefix << "-g#{right[0...7]}" 58 | 59 | if not Dir.glob("#{work_tree}/#{prefix}*.pack").empty? 60 | Tty.info "already export commit #{right}" 61 | return 62 | end 63 | 64 | if left 65 | input_rev = "#{left}..#{right}" 66 | else 67 | input_rev = right 68 | end 69 | 70 | gistore.shellpipe(git_cmd, "pack-objects", "--revs", prefix, 71 | :without_grafts => true, 72 | :work_tree => work_tree) do |stdin, stdout, stderr| 73 | stdin.write input_rev 74 | stdin.close_write 75 | end 76 | Tty.info "export #{n}: #{prefix}" 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/gistore/cmd/gc.rb: -------------------------------------------------------------------------------- 1 | module Gistore 2 | class Runner 3 | desc "gc [--force]", "Run git-gc if gc.auto != 0" 4 | option :force, :type => :boolean, :aliases => [:f], :desc => "run git-gc without --auto option" 5 | def gc(*args) 6 | parse_common_options_and_repo 7 | opts = options.dup 8 | opts.delete :repo 9 | args << opts 10 | gistore.git_gc(*args) 11 | rescue Exception => e 12 | Tty.die "#{e.message}" 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/gistore/cmd/git-version.rb: -------------------------------------------------------------------------------- 1 | require 'gistore/utils' 2 | 3 | module Gistore 4 | class Runner 5 | desc "check-git-version", "Check git version", :hide => true 6 | def check_git_version(v1, v2=nil) 7 | if v2 8 | puts Gistore.git_version_compare(v1, v2) 9 | else 10 | puts Gistore.git_version_compare(v1) 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/gistore/cmd/init.rb: -------------------------------------------------------------------------------- 1 | require 'gistore/repo' 2 | require 'gistore/utils' 3 | require 'gistore/version' 4 | 5 | module Gistore 6 | class Runner 7 | desc "init", "Initialize gistore repository" 8 | long_desc <<-LONGDESC 9 | `gistore init [--repo] ` will create a gistore backup repo. 10 | 11 | The created is a bare git repository, and when excute backup and/or 12 | other commands on , GIT_WORK_TREE will be set as '/', and GIT_DIR 13 | will be set as automatically. 14 | 15 | This bare repo has been set with default settings which are suitable for 16 | backup for text files. But if most of the backups are binaries, you may 17 | like to set with custom settings. You can give specific plan for 18 | when initializing, like: 19 | 20 | > $ gistore init --plan 21 | 22 | Or run `gistore config` command latter, like 23 | 24 | > $ gistore config --repo --plan no-compress 25 | \x5> $ gistore config --repo --plan no-gc 26 | LONGDESC 27 | option :plan, :required => false, :type => :string, 28 | :desc => "no-gc, no-compress, or normal (default)" 29 | def init(name=nil) 30 | parse_common_options 31 | Repo.init(options[:repo] || name || ".", options) 32 | rescue Exception => e 33 | Tty.die "#{e.message}" 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/gistore/cmd/restore-from-backups.rb: -------------------------------------------------------------------------------- 1 | module Gistore 2 | class Runner 3 | desc "restore-from-backups", "Export to a series of full/increment backups" 4 | option :from, :required => true, :desc => "path of backup packs", :banner => "" 5 | option :to, :required => true, :desc => "path of repo.git to restore to", :banner => "" 6 | def restore_from_backups 7 | parse_common_options 8 | from = options[:from] 9 | repo_name = options[:to] 10 | backups = [] 11 | if not File.exist? from 12 | raise "Path \"#{from}\" does not exist." 13 | elsif File.directory? from 14 | backups = Dir.glob("#{from}/*.pack") 15 | elsif from.end_with? ".pack" 16 | backups << from 17 | end 18 | 19 | if not backups or backups.empty? 20 | raise "Can not find valid pack file(s) from \"#{from}\"" 21 | else 22 | backups = backups.map {|p| File.expand_path(p.strip)} 23 | end 24 | 25 | if not File.exist? repo_name 26 | Repo.init repo_name 27 | elsif not Gistore.is_git_repo? repo_name 28 | raise "Path \"#{repo_name}\" is not a valid repo, create one using \"gistore init\"" 29 | end 30 | 31 | self.gistore = Repo.new(repo_name) 32 | output = "" 33 | backups.each do |pack| 34 | begin 35 | gistore.shellpipe(git_cmd, "unpack-objects", "-q", 36 | :work_tree => gistore.repo_path, 37 | :check_return => true 38 | ) do |stdin, stdout, stderr| 39 | File.open(pack, "r") do |io| 40 | stdin.write io.read 41 | end 42 | stdin.close 43 | output << stdout.read 44 | output << "\n" unless output.empty? 45 | output << stderr.read 46 | end 47 | rescue 48 | Tty.warning "failed to unpack #{pack}" 49 | end 50 | end 51 | 52 | danglings = [] 53 | cmds = [git_cmd, "fsck"] 54 | cmds << "--dangling" if Gistore.git_version_compare('1.7.10') >= 0 55 | gistore.shellout(*cmds) do |stdout| 56 | stdout.readlines.each do |line| 57 | if line =~ /^dangling commit (.*)/ 58 | danglings << $1.strip 59 | end 60 | end 61 | end 62 | 63 | unless danglings.empty? 64 | begin 65 | gistore.shellout(git_cmd, "rev-parse", "master", :check_return => true) 66 | rescue 67 | if danglings.size == 1 68 | gistore.shellout(git_cmd, "update-ref", "refs/heads/master", danglings[0]) 69 | else 70 | show_dangling_commits danglings 71 | end 72 | else 73 | show_dangling_commits danglings 74 | end 75 | end 76 | rescue Exception => e 77 | Tty.error "Failed to restore-from-backups.\n#{output}" 78 | Tty.die "#{e.message}" 79 | end 80 | 81 | private 82 | 83 | def show_dangling_commits(danglings) 84 | puts "Found dangling commits after restore backups:" 85 | puts "\n" 86 | puts Tty.show_columns danglings 87 | puts "\n" 88 | puts "You may like to update master branch with it(them) by hands." 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lib/gistore/cmd/rm.rb: -------------------------------------------------------------------------------- 1 | module Gistore 2 | class Runner 3 | desc "rm ...", "Remove entry from backup list" 4 | def rm(*args) 5 | parse_common_options_and_repo 6 | raise "nothing to remove." if args.empty? 7 | args.each do |entry| 8 | gistore.remove_entry entry 9 | end 10 | gistore.save_gistore_backups 11 | rescue Exception => e 12 | Tty.die "#{e.message}" 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/gistore/cmd/safe-commands.rb: -------------------------------------------------------------------------------- 1 | module Gistore 2 | SAFE_GIT_COMMANDS = %w( 3 | annotate blame branch cat-file check-ignore 4 | count-objects describe diff fsck grep 5 | log lost-found ls-files ls-tree name-rev 6 | prune reflog rev-list rev-parse shortlog 7 | show show-ref status tag whatchanged) 8 | 9 | class Runner 10 | desc "repo git-command ...", "Delegate to safe git commands (log, ls-tree, ...)" 11 | option :without_grafts, :type => :boolean, :desc => "not check info/grafts" 12 | option :without_work_tree, :type => :boolean, :desc => "not change work tree" 13 | option :without_locale, :type => :boolean, :desc => "use locale C" 14 | def repo (name, cmd=nil, *args, &block) 15 | parse_common_options 16 | if options[:repo] 17 | args ||= [] 18 | args.unshift cmd if cmd 19 | name, cmd = options[:repo], name 20 | end 21 | cmd = args.shift if cmd == "git" 22 | if Gistore::SAFE_GIT_COMMANDS.include? cmd 23 | opts = options.dup 24 | opts.delete("repo") 25 | args << opts 26 | gistore = Repo.new(name || ".") 27 | gistore.safe_system(git_cmd, cmd, *args) 28 | elsif self.respond_to? cmd 29 | # Because command may have specific options mixed in args, 30 | # can not move options from args easily. So we not call 31 | # invoke here, but call Gistore::Runner.start. 32 | Gistore::Runner.start([cmd, "--repo", name, *args]) 33 | else 34 | raise "Command \"#{cmd}\" is not allowed.\n" 35 | end 36 | rescue Exception => e 37 | Tty.die "#{e.message}" 38 | end 39 | 40 | desc "log args...", "Show gistore backup logs (delegater for git log)" 41 | option :without_grafts, :type => :boolean, :desc => "not check info/grafts" 42 | option :without_work_tree, :type => :boolean, :desc => "not change work tree" 43 | option :without_locale, :type => :boolean, :desc => "use locale C" 44 | def log(*args) 45 | if options[:repo] 46 | repo('log', *args) 47 | else 48 | name = args.shift || "." 49 | repo(name, 'log', *args) 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/gistore/cmd/status.rb: -------------------------------------------------------------------------------- 1 | module Gistore 2 | class Runner 3 | map "st" => :status 4 | desc "status", "Show backup entries list and run git status" 5 | option :config, :type => :boolean, :desc => "Show gistore configurations" 6 | option :backup, :type => :boolean, 7 | :aliases => [:entries, :entry, :backups], 8 | :desc => "Show backup list" 9 | option :git, :type => :boolean, :desc => "Show git status" 10 | def status(*args) 11 | parse_common_options_and_repo 12 | all = (not options.include? "config" and 13 | not options.include? "backup" and 14 | not options.include? "git") 15 | 16 | if all or options[:config] 17 | puts "Task name : #{gistore.task_name || "-"}" 18 | puts "Gistore : #{gistore.repo_path}" 19 | puts "Configurations:" 20 | puts Tty.show_columns gistore.gistore_config.to_a.map {|h| "#{h[0]}: #{h[1].inspect}"} 21 | puts 22 | end 23 | 24 | if all 25 | puts "Backup entries:" 26 | puts Tty.show_columns gistore.gistore_backups 27 | puts 28 | elsif options[:backup] 29 | puts gistore.gistore_backups.join("\n") 30 | end 31 | 32 | if all or options[:git] 33 | puts "#{'-' * 30} Git status #{'-' * 30}" if all 34 | gistore.safe_system(git_cmd, "status", *args) 35 | end 36 | rescue Exception => e 37 | Tty.die "#{e.message}" 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/gistore/cmd/task.rb: -------------------------------------------------------------------------------- 1 | module Gistore 2 | class Task < Thor; end 3 | 4 | class Runner 5 | desc "task SUBCOMMAND ...ARGS", "manage set of tracked repositories" 6 | subcommand "task", Gistore::Task 7 | end 8 | 9 | class Task < Thor 10 | # Use command name "gistore" in help instead of "gistore.rb" 11 | def self.basename; "gistore"; end 12 | 13 | # Show in help screen 14 | package_name "Gistore" 15 | 16 | desc "add []", "Register repo as a task" 17 | option :system, :type => :boolean 18 | def add(task, path=nil) 19 | parse_common_options 20 | path ||= "." 21 | cmds = [git_cmd, "config"] 22 | unless ENV["GISTORE_TEST_GIT_CONFIG"] 23 | ENV.delete "GIT_CONFIG" 24 | if options[:system] 25 | cmds << "--system" 26 | else 27 | cmds << "--global" 28 | end 29 | end 30 | cmds << "gistore.task.#{task}" 31 | cmds << File.expand_path(path) 32 | system(*cmds) 33 | rescue Exception => e 34 | Tty.die "#{e.message}" 35 | end 36 | 37 | desc "rm ", "Remove register of task" 38 | option :system, :type => :boolean 39 | def rm(task) 40 | parse_common_options 41 | cmds = [git_cmd, "config", "--unset"] 42 | unless ENV["GISTORE_TEST_GIT_CONFIG"] 43 | ENV.delete "GIT_CONFIG" 44 | if options[:system] 45 | cmds << "--system" 46 | else 47 | cmds << "--global" 48 | end 49 | end 50 | cmds << "gistore.task.#{task}" 51 | Kernel::system(*cmds) 52 | rescue Exception => e 53 | Tty.die "#{e.message}" 54 | end 55 | 56 | desc "list", "Display task list" 57 | option :system, :type => :boolean 58 | option :global, :type => :boolean 59 | def list(name=nil) 60 | parse_common_options 61 | if name 62 | invoke "gistore:runner:status", [], :repo => name 63 | else 64 | puts "System level Tasks" 65 | tasks = Gistore::get_gistore_tasks(:system => true) 66 | puts Tty.show_columns tasks.to_a.map {|h| "#{h[0]} => #{h[1]}"} 67 | puts 68 | puts "User level Tasks" 69 | tasks = Gistore::get_gistore_tasks(:global => true) 70 | puts Tty.show_columns tasks.to_a.map {|h| "#{h[0]} => #{h[1]}"} 71 | end 72 | end 73 | 74 | private 75 | 76 | def parse_common_options 77 | if options[:verbose] 78 | Tty.options[:verbose] = true 79 | elsif options[:quiet] 80 | Tty.options[:quiet] = true 81 | end 82 | end 83 | 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /lib/gistore/cmd/update.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | 3 | module Gistore 4 | class Runner 5 | map "up" => :update 6 | desc "update", "Update gistore from github.com" 7 | option :url, :type => :string, :desc => "Repository URL" 8 | option :rebase, :type => :boolean, :desc => "Rebase instead of merge" 9 | def update 10 | parse_common_options 11 | report = Gistore::Report.new 12 | gistore_updater = Gistore::Updater.new(options) 13 | Dir.chdir(GISTORE_REPOSITORY) do 14 | # initial git repo if necessary 15 | unless Gistore.is_git_repo? ".git" 16 | gistore_updater.git_init 17 | end 18 | 19 | # git pull 20 | gistore_updater.pull! 21 | report.merge!(gistore_updater.report) 22 | if report.empty? 23 | Tty.info "Already up-to-date." 24 | else 25 | Tty.info "Updated Gistore from #{gistore_updater.initial_revision[0,8]} to #{gistore_updater.current_revision[0,8]}." 26 | report.dump 27 | end 28 | end 29 | rescue Exception => e 30 | Tty.die "#{e.message}" 31 | end 32 | end 33 | 34 | class Updater 35 | attr_reader :initial_revision, :current_revision 36 | 37 | def initialize(options={}) 38 | @url = options[:url] || Gistore::GISTORE_REPOSITORY_URL 39 | @options = options 40 | end 41 | 42 | def git_init 43 | Gistore.safe_system git_cmd, "init" 44 | Gistore.safe_system git_cmd, "config", "core.autocrlf", "false" 45 | Gistore.safe_system git_cmd, "remote", "add", "origin", @url 46 | Gistore.safe_system git_cmd, "fetch", "origin" 47 | Gistore.safe_system git_cmd, "reset", "origin/master" 48 | # Gistore.safe_system git_cmd, "stash", "save" 49 | end 50 | 51 | def pull! 52 | Gistore.safe_system git_cmd, "checkout", "-q", "master" 53 | 54 | @initial_revision = read_current_revision 55 | 56 | # ensure we don't munge line endings on checkout 57 | Gistore.safe_system git_cmd, "config", "core.autocrlf", "false" 58 | 59 | args = [git_cmd, "pull"] 60 | args << "--rebase" if @options[:rebase] 61 | args << "-q" unless @options[:verbose] 62 | args << "origin" 63 | # the refspec ensures that 'origin/master' gets updated 64 | args << "+refs/heads/master:refs/remotes/origin/master" 65 | 66 | reset_on_interrupt { Gistore.safe_system *args } 67 | 68 | @current_revision = read_current_revision 69 | end 70 | 71 | def reset_on_interrupt 72 | ignore_interrupts { yield } 73 | ensure 74 | if $?.signaled? && $?.termsig == 2 # SIGINT 75 | Gistore.safe_system git_cmd, "reset", "--hard", @initial_revision 76 | end 77 | end 78 | 79 | def ignore_interrupts(opt = nil) 80 | std_trap = trap("INT") do 81 | puts "One sec, just cleaning up" unless opt == :quietly 82 | end 83 | yield 84 | ensure 85 | trap("INT", std_trap) 86 | end 87 | 88 | # Matches raw git diff format (see `man git-diff-tree`) 89 | DIFFTREE_RX = /^:[0-7]{6} [0-7]{6} [0-9a-fA-F]{40} [0-9a-fA-F]{40} ([ACDMR])\d{0,3}\t(.+?)(?:\t(.+))?$/ 90 | 91 | def report 92 | map = Hash.new{ |h,k| h[k] = [] } 93 | 94 | if initial_revision && initial_revision != current_revision 95 | `#{git_cmd} diff-tree -r --raw -M85% #{initial_revision} #{current_revision}`.each_line do |line| 96 | DIFFTREE_RX.match line 97 | path = case status = $1.to_sym 98 | when :R then $3 99 | else $2 100 | end 101 | path = Pathname.pwd.join(path).relative_path_from(Pathname.new(GISTORE_REPOSITORY)) 102 | map[status] << path.to_s 103 | end 104 | end 105 | 106 | map 107 | end 108 | 109 | private 110 | 111 | def read_current_revision 112 | `#{git_cmd} rev-parse -q --verify HEAD`.chomp 113 | end 114 | 115 | def `(cmd) 116 | out = Kernel.`(cmd) #` 117 | if $? && !$?.success? 118 | $stderr.puts out 119 | raise CommandReturnError, "Failure while executing: #{cmd}" 120 | end 121 | Tty.debug "Command: #{cmd}.\nOutput: #{out}" 122 | out 123 | end 124 | end 125 | 126 | class Report < Hash 127 | 128 | def dump 129 | # Key Legend: Added (A), Copied (C), Deleted (D), Modified (M), Renamed (R) 130 | dump_report :A, "New" 131 | dump_report :C, "Copy" 132 | dump_report :M, "Modified" 133 | dump_report :D, "Deleted" 134 | dump_report :R, "Renamed" 135 | end 136 | 137 | def dump_report(key, title) 138 | entries = fetch(key, []) 139 | unless entries.empty? 140 | puts "#{Tty.blue}==>#{Tty.white} #{title}#{Tty.reset}" 141 | puts Tty.show_columns entries.uniq 142 | end 143 | end 144 | end 145 | 146 | end 147 | -------------------------------------------------------------------------------- /lib/gistore/cmd/version.rb: -------------------------------------------------------------------------------- 1 | require 'gistore/version' 2 | 3 | module Gistore 4 | class Runner 5 | map ["--version", "-v"] => :version 6 | desc "version", "Show Gistore version and/or repository format version", :hide => true 7 | def version(*args) 8 | parse_common_options 9 | v = Gistore::VERSION 10 | Dir.chdir(GISTORE_REPOSITORY) do 11 | if Gistore.is_git_repo? ".git" 12 | Gistore.shellout(git_cmd, "describe", "--always", "--dirty") do |stdout| 13 | v = stdout.read.strip 14 | v.sub!(/^v/, '') 15 | v << " (#{Gistore::VERSION})" if v != Gistore::VERSION 16 | end 17 | end 18 | end 19 | puts "Gistore version #{v}" 20 | 21 | gistore = Repo.new(options[:repo]) rescue nil if options[:repo] 22 | if gistore 23 | puts "Repository format #{gistore.repo_version}" 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/gistore/config.rb: -------------------------------------------------------------------------------- 1 | module Gistore 2 | 3 | DEFAULT_GISTORE_CONFIG = {"backups" => [], 4 | "plan" => nil, 5 | "increment_backup_number" => 30, 6 | "full_backup_number" => 12, 7 | "user_name" => nil, 8 | "user_email" => nil 9 | } 10 | GISTORE_REPOSITORY_URL = "git://github.com/jiangxin/gistore" 11 | GISTORE_REPOSITORY = File.dirname(File.dirname(File.dirname(__FILE__))) 12 | 13 | end 14 | -------------------------------------------------------------------------------- /lib/gistore/config/gistore.yml: -------------------------------------------------------------------------------- 1 | --- 2 | -------------------------------------------------------------------------------- /lib/gistore/error.rb: -------------------------------------------------------------------------------- 1 | module Gistore 2 | class CommandReturnError < StandardError; end 3 | class CommandExceptionError < StandardError; end 4 | class InvalidRepoError < StandardError; end 5 | class CommandExit < StandardError; end 6 | end 7 | -------------------------------------------------------------------------------- /lib/gistore/runner.rb: -------------------------------------------------------------------------------- 1 | require 'thor' 2 | require 'gistore/error' 3 | 4 | module Gistore 5 | class Runner < Thor 6 | attr_accessor :gistore 7 | # Use command name "gistore" in help instead of "gistore.rb" 8 | def self.basename; "gistore"; end 9 | 10 | # Show in help screen 11 | package_name "Gistore" 12 | 13 | class_option :repo, :banner => "", :desc => "path to gistore repo", :required => false 14 | class_option :help, :type => :boolean, :aliases => [:h], :desc => "Help", :required => false 15 | class_option :verbose, :type => :boolean, :aliases => [:v], :desc => "verbose", :required => false 16 | class_option :quiet, :type => :boolean, :aliases => [:q], :desc => "quiet", :required => false 17 | 18 | private 19 | def parse_common_options 20 | if options[:verbose] 21 | Tty.options[:verbose] = true 22 | elsif options[:quiet] 23 | Tty.options[:quiet] = true 24 | end 25 | end 26 | 27 | def parse_common_options_and_repo 28 | parse_common_options 29 | self.gistore = Repo.new(options[:repo] || ".") 30 | end 31 | 32 | def git_version_compare(version) 33 | Gistore::git_version_compare(version) 34 | end 35 | 36 | def git_cmd; Gistore::git_cmd; end 37 | 38 | def git_version; Gistore::git_version; end 39 | 40 | end 41 | end 42 | 43 | Dir["#{File.dirname(__FILE__)}/cmd/*.rb"].each {|file| require file} 44 | -------------------------------------------------------------------------------- /lib/gistore/templates/description: -------------------------------------------------------------------------------- 1 | Unnamed repository; edit this file 'description' to name the repository. 2 | -------------------------------------------------------------------------------- /lib/gistore/templates/hooks/applypatch-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message taken by 4 | # applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. The hook is 8 | # allowed to edit the commit message file. 9 | # 10 | # To enable this hook, rename this file to "applypatch-msg". 11 | 12 | . git-sh-setup 13 | test -x "$GIT_DIR/hooks/commit-msg" && 14 | exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} 15 | : 16 | -------------------------------------------------------------------------------- /lib/gistore/templates/hooks/commit-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to check the commit log message. 4 | # Called by "git commit" with one argument, the name of the file 5 | # that has the commit message. The hook should exit with non-zero 6 | # status after issuing an appropriate message if it wants to stop the 7 | # commit. The hook is allowed to edit the commit message file. 8 | # 9 | # To enable this hook, rename this file to "commit-msg". 10 | 11 | # Uncomment the below to add a Signed-off-by line to the message. 12 | # Doing this in a hook is a bad idea in general, but the prepare-commit-msg 13 | # hook is more suited to it. 14 | # 15 | # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 16 | # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" 17 | 18 | # This example catches duplicate Signed-off-by lines. 19 | 20 | test "" = "$(grep '^Signed-off-by: ' "$1" | 21 | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { 22 | echo >&2 Duplicate Signed-off-by lines. 23 | exit 1 24 | } 25 | -------------------------------------------------------------------------------- /lib/gistore/templates/hooks/post-update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare a packed repository for use over 4 | # dumb transports. 5 | # 6 | # To enable this hook, rename this file to "post-update". 7 | 8 | exec git update-server-info 9 | -------------------------------------------------------------------------------- /lib/gistore/templates/hooks/pre-applypatch.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed 4 | # by applypatch from an e-mail message. 5 | # 6 | # The hook should exit with non-zero status after issuing an 7 | # appropriate message if it wants to stop the commit. 8 | # 9 | # To enable this hook, rename this file to "pre-applypatch". 10 | 11 | . git-sh-setup 12 | test -x "$GIT_DIR/hooks/pre-commit" && 13 | exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} 14 | : 15 | -------------------------------------------------------------------------------- /lib/gistore/templates/hooks/pre-commit.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed. 4 | # Called by "git commit" with no arguments. The hook should 5 | # exit with non-zero status after issuing an appropriate message if 6 | # it wants to stop the commit. 7 | # 8 | # To enable this hook, rename this file to "pre-commit". 9 | 10 | if git rev-parse --verify HEAD >/dev/null 2>&1 11 | then 12 | against=HEAD 13 | else 14 | # Initial commit: diff against an empty tree object 15 | against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 16 | fi 17 | 18 | # If you want to allow non-ASCII filenames set this variable to true. 19 | allownonascii=$(git config hooks.allownonascii) 20 | 21 | # Redirect output to stderr. 22 | exec 1>&2 23 | 24 | # Cross platform projects tend to avoid non-ASCII filenames; prevent 25 | # them from being added to the repository. We exploit the fact that the 26 | # printable range starts at the space character and ends with tilde. 27 | if [ "$allownonascii" != "true" ] && 28 | # Note that the use of brackets around a tr range is ok here, (it's 29 | # even required, for portability to Solaris 10's /usr/bin/tr), since 30 | # the square bracket bytes happen to fall in the designated range. 31 | test $(git diff --cached --name-only --diff-filter=A -z $against | 32 | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 33 | then 34 | cat <<\EOF 35 | Error: Attempt to add a non-ASCII file name. 36 | 37 | This can cause problems if you want to work with people on other platforms. 38 | 39 | To be portable it is advisable to rename the file. 40 | 41 | If you know what you are doing you can disable this check using: 42 | 43 | git config hooks.allownonascii true 44 | EOF 45 | exit 1 46 | fi 47 | 48 | # If there are whitespace errors, print the offending file names and fail. 49 | exec git diff-index --check --cached $against -- 50 | -------------------------------------------------------------------------------- /lib/gistore/templates/hooks/pre-push.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # An example hook script to verify what is about to be pushed. Called by "git 4 | # push" after it has checked the remote status, but before anything has been 5 | # pushed. If this script exits with a non-zero status nothing will be pushed. 6 | # 7 | # This hook is called with the following parameters: 8 | # 9 | # $1 -- Name of the remote to which the push is being done 10 | # $2 -- URL to which the push is being done 11 | # 12 | # If pushing without using a named remote those arguments will be equal. 13 | # 14 | # Information about the commits which are being pushed is supplied as lines to 15 | # the standard input in the form: 16 | # 17 | # 18 | # 19 | # This sample shows how to prevent push of commits where the log message starts 20 | # with "WIP" (work in progress). 21 | 22 | remote="$1" 23 | url="$2" 24 | 25 | z40=0000000000000000000000000000000000000000 26 | 27 | IFS=' ' 28 | while read local_ref local_sha remote_ref remote_sha 29 | do 30 | if [ "$local_sha" = $z40 ] 31 | then 32 | # Handle delete 33 | : 34 | else 35 | if [ "$remote_sha" = $z40 ] 36 | then 37 | # New branch, examine all commits 38 | range="$local_sha" 39 | else 40 | # Update to existing branch, examine new commits 41 | range="$remote_sha..$local_sha" 42 | fi 43 | 44 | # Check for WIP commit 45 | commit=`git rev-list -n 1 --grep '^WIP' "$range"` 46 | if [ -n "$commit" ] 47 | then 48 | echo "Found WIP commit in $local_ref, not pushing" 49 | exit 1 50 | fi 51 | fi 52 | done 53 | 54 | exit 0 55 | -------------------------------------------------------------------------------- /lib/gistore/templates/hooks/pre-rebase.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2006, 2008 Junio C Hamano 4 | # 5 | # The "pre-rebase" hook is run just before "git rebase" starts doing 6 | # its job, and can prevent the command from running by exiting with 7 | # non-zero status. 8 | # 9 | # The hook is called with the following parameters: 10 | # 11 | # $1 -- the upstream the series was forked from. 12 | # $2 -- the branch being rebased (or empty when rebasing the current branch). 13 | # 14 | # This sample shows how to prevent topic branches that are already 15 | # merged to 'next' branch from getting rebased, because allowing it 16 | # would result in rebasing already published history. 17 | 18 | publish=next 19 | basebranch="$1" 20 | if test "$#" = 2 21 | then 22 | topic="refs/heads/$2" 23 | else 24 | topic=`git symbolic-ref HEAD` || 25 | exit 0 ;# we do not interrupt rebasing detached HEAD 26 | fi 27 | 28 | case "$topic" in 29 | refs/heads/??/*) 30 | ;; 31 | *) 32 | exit 0 ;# we do not interrupt others. 33 | ;; 34 | esac 35 | 36 | # Now we are dealing with a topic branch being rebased 37 | # on top of master. Is it OK to rebase it? 38 | 39 | # Does the topic really exist? 40 | git show-ref -q "$topic" || { 41 | echo >&2 "No such branch $topic" 42 | exit 1 43 | } 44 | 45 | # Is topic fully merged to master? 46 | not_in_master=`git rev-list --pretty=oneline ^master "$topic"` 47 | if test -z "$not_in_master" 48 | then 49 | echo >&2 "$topic is fully merged to master; better remove it." 50 | exit 1 ;# we could allow it, but there is no point. 51 | fi 52 | 53 | # Is topic ever merged to next? If so you should not be rebasing it. 54 | only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` 55 | only_next_2=`git rev-list ^master ${publish} | sort` 56 | if test "$only_next_1" = "$only_next_2" 57 | then 58 | not_in_topic=`git rev-list "^$topic" master` 59 | if test -z "$not_in_topic" 60 | then 61 | echo >&2 "$topic is already up-to-date with master" 62 | exit 1 ;# we could allow it, but there is no point. 63 | else 64 | exit 0 65 | fi 66 | else 67 | not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` 68 | /usr/bin/perl -e ' 69 | my $topic = $ARGV[0]; 70 | my $msg = "* $topic has commits already merged to public branch:\n"; 71 | my (%not_in_next) = map { 72 | /^([0-9a-f]+) /; 73 | ($1 => 1); 74 | } split(/\n/, $ARGV[1]); 75 | for my $elem (map { 76 | /^([0-9a-f]+) (.*)$/; 77 | [$1 => $2]; 78 | } split(/\n/, $ARGV[2])) { 79 | if (!exists $not_in_next{$elem->[0]}) { 80 | if ($msg) { 81 | print STDERR $msg; 82 | undef $msg; 83 | } 84 | print STDERR " $elem->[1]\n"; 85 | } 86 | } 87 | ' "$topic" "$not_in_next" "$not_in_master" 88 | exit 1 89 | fi 90 | 91 | exit 0 92 | 93 | ################################################################ 94 | 95 | This sample hook safeguards topic branches that have been 96 | published from being rewound. 97 | 98 | The workflow assumed here is: 99 | 100 | * Once a topic branch forks from "master", "master" is never 101 | merged into it again (either directly or indirectly). 102 | 103 | * Once a topic branch is fully cooked and merged into "master", 104 | it is deleted. If you need to build on top of it to correct 105 | earlier mistakes, a new topic branch is created by forking at 106 | the tip of the "master". This is not strictly necessary, but 107 | it makes it easier to keep your history simple. 108 | 109 | * Whenever you need to test or publish your changes to topic 110 | branches, merge them into "next" branch. 111 | 112 | The script, being an example, hardcodes the publish branch name 113 | to be "next", but it is trivial to make it configurable via 114 | $GIT_DIR/config mechanism. 115 | 116 | With this workflow, you would want to know: 117 | 118 | (1) ... if a topic branch has ever been merged to "next". Young 119 | topic branches can have stupid mistakes you would rather 120 | clean up before publishing, and things that have not been 121 | merged into other branches can be easily rebased without 122 | affecting other people. But once it is published, you would 123 | not want to rewind it. 124 | 125 | (2) ... if a topic branch has been fully merged to "master". 126 | Then you can delete it. More importantly, you should not 127 | build on top of it -- other people may already want to 128 | change things related to the topic as patches against your 129 | "master", so if you need further changes, it is better to 130 | fork the topic (perhaps with the same name) afresh from the 131 | tip of "master". 132 | 133 | Let's look at this example: 134 | 135 | o---o---o---o---o---o---o---o---o---o "next" 136 | / / / / 137 | / a---a---b A / / 138 | / / / / 139 | / / c---c---c---c B / 140 | / / / \ / 141 | / / / b---b C \ / 142 | / / / / \ / 143 | ---o---o---o---o---o---o---o---o---o---o---o "master" 144 | 145 | 146 | A, B and C are topic branches. 147 | 148 | * A has one fix since it was merged up to "next". 149 | 150 | * B has finished. It has been fully merged up to "master" and "next", 151 | and is ready to be deleted. 152 | 153 | * C has not merged to "next" at all. 154 | 155 | We would want to allow C to be rebased, refuse A, and encourage 156 | B to be deleted. 157 | 158 | To compute (1): 159 | 160 | git rev-list ^master ^topic next 161 | git rev-list ^master next 162 | 163 | if these match, topic has not merged in next at all. 164 | 165 | To compute (2): 166 | 167 | git rev-list master..topic 168 | 169 | if this is empty, it is fully merged to "master". 170 | -------------------------------------------------------------------------------- /lib/gistore/templates/hooks/prepare-commit-msg.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to prepare the commit log message. 4 | # Called by "git commit" with the name of the file that has the 5 | # commit message, followed by the description of the commit 6 | # message's source. The hook's purpose is to edit the commit 7 | # message file. If the hook fails with a non-zero status, 8 | # the commit is aborted. 9 | # 10 | # To enable this hook, rename this file to "prepare-commit-msg". 11 | 12 | # This hook includes three examples. The first comments out the 13 | # "Conflicts:" part of a merge commit. 14 | # 15 | # The second includes the output of "git diff --name-status -r" 16 | # into the message, just before the "git status" output. It is 17 | # commented because it doesn't cope with --amend or with squashed 18 | # commits. 19 | # 20 | # The third example adds a Signed-off-by line to the message, that can 21 | # still be edited. This is rarely a good idea. 22 | 23 | case "$2,$3" in 24 | merge,) 25 | /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; 26 | 27 | # ,|template,) 28 | # /usr/bin/perl -i.bak -pe ' 29 | # print "\n" . `git diff --cached --name-status -r` 30 | # if /^#/ && $first++ == 0' "$1" ;; 31 | 32 | *) ;; 33 | esac 34 | 35 | # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') 36 | # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" 37 | -------------------------------------------------------------------------------- /lib/gistore/templates/hooks/update.sample: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to blocks unannotated tags from entering. 4 | # Called by "git receive-pack" with arguments: refname sha1-old sha1-new 5 | # 6 | # To enable this hook, rename this file to "update". 7 | # 8 | # Config 9 | # ------ 10 | # hooks.allowunannotated 11 | # This boolean sets whether unannotated tags will be allowed into the 12 | # repository. By default they won't be. 13 | # hooks.allowdeletetag 14 | # This boolean sets whether deleting tags will be allowed in the 15 | # repository. By default they won't be. 16 | # hooks.allowmodifytag 17 | # This boolean sets whether a tag may be modified after creation. By default 18 | # it won't be. 19 | # hooks.allowdeletebranch 20 | # This boolean sets whether deleting branches will be allowed in the 21 | # repository. By default they won't be. 22 | # hooks.denycreatebranch 23 | # This boolean sets whether remotely creating branches will be denied 24 | # in the repository. By default this is allowed. 25 | # 26 | 27 | # --- Command line 28 | refname="$1" 29 | oldrev="$2" 30 | newrev="$3" 31 | 32 | # --- Safety check 33 | if [ -z "$GIT_DIR" ]; then 34 | echo "Don't run this script from the command line." >&2 35 | echo " (if you want, you could supply GIT_DIR then run" >&2 36 | echo " $0 )" >&2 37 | exit 1 38 | fi 39 | 40 | if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then 41 | echo "usage: $0 " >&2 42 | exit 1 43 | fi 44 | 45 | # --- Config 46 | allowunannotated=$(git config --bool hooks.allowunannotated) 47 | allowdeletebranch=$(git config --bool hooks.allowdeletebranch) 48 | denycreatebranch=$(git config --bool hooks.denycreatebranch) 49 | allowdeletetag=$(git config --bool hooks.allowdeletetag) 50 | allowmodifytag=$(git config --bool hooks.allowmodifytag) 51 | 52 | # check for no description 53 | projectdesc=$(sed -e '1q' "$GIT_DIR/description") 54 | case "$projectdesc" in 55 | "Unnamed repository"* | "") 56 | echo "*** Project description file hasn't been set" >&2 57 | exit 1 58 | ;; 59 | esac 60 | 61 | # --- Check types 62 | # if $newrev is 0000...0000, it's a commit to delete a ref. 63 | zero="0000000000000000000000000000000000000000" 64 | if [ "$newrev" = "$zero" ]; then 65 | newrev_type=delete 66 | else 67 | newrev_type=$(git cat-file -t $newrev) 68 | fi 69 | 70 | case "$refname","$newrev_type" in 71 | refs/tags/*,commit) 72 | # un-annotated tag 73 | short_refname=${refname##refs/tags/} 74 | if [ "$allowunannotated" != "true" ]; then 75 | echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 76 | echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 77 | exit 1 78 | fi 79 | ;; 80 | refs/tags/*,delete) 81 | # delete tag 82 | if [ "$allowdeletetag" != "true" ]; then 83 | echo "*** Deleting a tag is not allowed in this repository" >&2 84 | exit 1 85 | fi 86 | ;; 87 | refs/tags/*,tag) 88 | # annotated tag 89 | if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 90 | then 91 | echo "*** Tag '$refname' already exists." >&2 92 | echo "*** Modifying a tag is not allowed in this repository." >&2 93 | exit 1 94 | fi 95 | ;; 96 | refs/heads/*,commit) 97 | # branch 98 | if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then 99 | echo "*** Creating a branch is not allowed in this repository" >&2 100 | exit 1 101 | fi 102 | ;; 103 | refs/heads/*,delete) 104 | # delete branch 105 | if [ "$allowdeletebranch" != "true" ]; then 106 | echo "*** Deleting a branch is not allowed in this repository" >&2 107 | exit 1 108 | fi 109 | ;; 110 | refs/remotes/*,commit) 111 | # tracking branch 112 | ;; 113 | refs/remotes/*,delete) 114 | # delete tracking branch 115 | if [ "$allowdeletebranch" != "true" ]; then 116 | echo "*** Deleting a tracking branch is not allowed in this repository" >&2 117 | exit 1 118 | fi 119 | ;; 120 | *) 121 | # Anything else (is there anything else?) 122 | echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 123 | exit 1 124 | ;; 125 | esac 126 | 127 | # --- Finished 128 | exit 0 129 | -------------------------------------------------------------------------------- /lib/gistore/templates/info/exclude: -------------------------------------------------------------------------------- 1 | # git ls-files --others --exclude-from=.git/info/exclude 2 | # Lines that start with '#' are comments. 3 | # For a project mostly in C, the following would be a good set of 4 | # exclude patterns (uncomment them if you want to use them): 5 | # *.[oa] 6 | # *~ 7 | -------------------------------------------------------------------------------- /lib/gistore/utils.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | require 'gistore/error' 3 | 4 | if RUBY_VERSION < '1.9' 5 | require 'open4' 6 | else 7 | require 'open3' 8 | end 9 | 10 | module Gistore 11 | 12 | class < true) do |io| 28 | if io.read.strip =~ /^git version (.*)/ 29 | $1.split('.').map(&:to_i) 30 | end 31 | end 32 | end 33 | end 34 | 35 | def git_version_compare(v1, v2=nil) 36 | if v2 37 | current_version = v1.is_a?(Array) ? v1.dup : v1.split('.').map(&:to_i) 38 | check_version = v2.is_a?(Array) ? v2.dup : v2.split('.').map(&:to_i) 39 | else 40 | current_version = git_version.dup 41 | check_version = v1.is_a?(Array) ? v1.dup : v1.split('.').map(&:to_i) 42 | end 43 | current_version.each do |current| 44 | check = check_version.shift.to_i 45 | result = current <=> check 46 | return result if result != 0 47 | end 48 | check_version.shift ? -1 : 0 49 | end 50 | 51 | def which cmd, path=ENV['PATH'] 52 | dir = path.split(File::PATH_SEPARATOR).find {|p| File.executable? File.join(p, cmd)} 53 | Pathname.new(File.join(dir, cmd)) unless dir.nil? 54 | end 55 | 56 | def shellout(*args, &block) 57 | if Hash === args.last 58 | options = args.pop.dup 59 | else 60 | options = {} 61 | end 62 | options[:stdout_only] = true 63 | args << options 64 | self.popen3(*args, &block) 65 | end 66 | 67 | def shellpipe(*args, &block) 68 | if Hash === args.last 69 | options = args.pop.dup 70 | else 71 | options = {} 72 | end 73 | args << options unless options.empty? 74 | self.popen3(*args, &block) 75 | end 76 | 77 | def system(*args, &block) 78 | fork do 79 | block.call if block_given? 80 | args.map!{|arg| arg.to_s} 81 | exec(*args) rescue nil 82 | # never gets here unless raise some error (exec failed) 83 | exit! 1 84 | end 85 | Process.wait 86 | $?.success? 87 | end 88 | 89 | # Same like system but with exceptions 90 | def safe_system(*args, &block) 91 | unless Gistore.system(*args, &block) 92 | args = args.map{ |arg| arg.to_s.gsub " ", "\\ " } * " " 93 | raise CommandReturnError, "Failure while executing: #{args}" 94 | end 95 | end 96 | 97 | # prints no output 98 | def quiet_system(*args) 99 | Gistore.system(*args) do 100 | # Redirect output streams to `/dev/null` instead of closing as some programs 101 | # will fail to execute if they can't write to an open stream. 102 | $stdout.reopen('/dev/null') 103 | $stderr.reopen('/dev/null') 104 | end 105 | end 106 | 107 | if RUBY_VERSION < '1.9' 108 | 109 | def popen3(*cmd, &block) 110 | if Hash === cmd.last 111 | options = cmd.pop.dup 112 | else 113 | options = {} 114 | end 115 | result = nil 116 | pid, stdin, stdout, stderr = nil 117 | begin 118 | pid, stdin, stdout, stderr = Open4.popen4(*cmd) 119 | if options[:stdout_only] 120 | stdin.close 121 | result = block.call(stdout) if block_given? 122 | else 123 | result = block.call(stdin, stdout, stderr) if block_given? 124 | end 125 | ignored, status = Process::waitpid2 pid 126 | if options[:check_return] and status and status.exitstatus != 0 127 | raise CommandReturnError.new("Command failed (return #{status.exitstatus}).") 128 | end 129 | rescue Exception => e 130 | msg = strip_credential(e.message) 131 | # The command failed, log it and re-raise 132 | logmsg = "Command failed: #{msg}" 133 | logmsg << "\n" 134 | logmsg << " >> #{strip_credential(cmd)}" 135 | if e.is_a? CommandReturnError 136 | raise CommandReturnError.new(logmsg) 137 | else 138 | raise CommandExceptionError.new(logmsg) 139 | end 140 | ensure 141 | [stdin, stdout, stderr].each {|io| io.close unless io.closed?} 142 | end 143 | result 144 | end 145 | 146 | else 147 | 148 | def popen3(*cmd, &block) 149 | if Hash === cmd.last 150 | options = cmd.pop.dup 151 | else 152 | options = {} 153 | end 154 | 155 | result = nil 156 | stdin, stdout, stderr, wait_thr = nil 157 | begin 158 | if options[:merge_stderr] 159 | stdin, stdout, wait_thr = Open3.popen2e(*cmd) 160 | else 161 | stdin, stdout, stderr, wait_thr = Open3.popen3(*cmd) 162 | end 163 | if options[:stdout_only] 164 | stdin.close 165 | result = block.call(stdout) if block_given? 166 | elsif options[:merge_stderr] 167 | result = block.call(stdin, stdout) if block_given? 168 | else 169 | result = block.call(stdin, stdout, stderr) if block_given? 170 | end 171 | wait_thr.join 172 | if (options[:check_return] and 173 | wait_thr and wait_thr.value and 174 | wait_thr.value.exitstatus != 0) 175 | raise CommandReturnError.new("Command failed (return #{wait_thr.value.exitstatus}).") 176 | end 177 | rescue Exception => e 178 | msg = strip_credential(e.message) 179 | # The command failed, log it and re-raise 180 | logmsg = "Command failed: #{msg}" 181 | logmsg << "\n" 182 | logmsg << " >> #{strip_credential(cmd)}" 183 | if e.is_a? CommandReturnError 184 | raise CommandReturnError.new(logmsg) 185 | else 186 | raise CommandExceptionError.new(logmsg) 187 | end 188 | ensure 189 | [stdin, stdout, stderr].each {|io| io.close if io and not io.closed?} 190 | end 191 | result 192 | end 193 | end 194 | 195 | def strip_credential(message) 196 | message 197 | end 198 | 199 | def get_gistore_tasks(options={}) 200 | cmds = [git_cmd, "config"] 201 | unless ENV["GISTORE_TEST_GIT_CONFIG"] 202 | if options[:system] 203 | cmds << "--system" 204 | elsif options[:global] 205 | cmds << "--global" 206 | end 207 | end 208 | cmds << "--get-regexp" 209 | cmds << "gistore.task." 210 | cmds << {:with_git_config => true} if ENV["GISTORE_TEST_GIT_CONFIG"] 211 | tasks = {} 212 | begin 213 | Gistore::shellout(*cmds) do |stdout| 214 | stdout.readlines.each do |line| 215 | if line =~ /^gistore.task.([\S]+) (.*)$/ 216 | tasks[Regexp.last_match(1)] = Regexp.last_match(2) 217 | end 218 | end 219 | end 220 | tasks 221 | rescue 222 | {} 223 | end 224 | end 225 | 226 | def is_git_repo?(name) 227 | File.directory?("#{name}/objects") && 228 | File.directory?("#{name}/refs") && 229 | File.exist?("#{name}/config") 230 | end 231 | end 232 | 233 | def git_cmd; self.class.git_cmd; end 234 | 235 | def git_version; self.class.git_version; end 236 | 237 | def git_version_compare(version) 238 | self.class.git_version_compare(version) 239 | end 240 | 241 | class Tty 242 | class << self 243 | def options 244 | @options ||= {} 245 | end 246 | def blue; bold 34; end 247 | def white; bold 39; end 248 | def red; underline 31; end 249 | def yellow; underline 33 ; end 250 | def reset; escape 0; end 251 | def em; underline 39; end 252 | def green; color 92 end 253 | def gray; bold 30 end 254 | 255 | def width 256 | @width = begin 257 | w = %x{stty size 2>/dev/null}.chomp.split.last.to_i.nonzero? 258 | w ||= %x{tput cols 2>/dev/null}.to_i 259 | w < 1 ? 80 : w 260 | end 261 | end 262 | 263 | def truncate(str) 264 | str.to_s[0, width - 4] 265 | end 266 | 267 | def die(message) 268 | error message 269 | exit 1 270 | end 271 | 272 | def error(message) 273 | lines = message.to_s.split("\n") 274 | if STDERR.tty? 275 | STDERR.puts "#{Tty.red}Error#{Tty.reset}: #{lines.shift}" 276 | else 277 | STDERR.puts "Error: #{lines.shift}" 278 | end 279 | STDERR.puts lines unless lines.empty? 280 | end 281 | 282 | def warning(message) 283 | if STDERR.tty? 284 | STDERR.puts "#{Tty.red}Warning#{Tty.reset}: #{message}" 285 | else 286 | STDERR.puts "Warning: #{message}" 287 | end 288 | end 289 | 290 | def info(message) 291 | unless quiet? 292 | if STDERR.tty? 293 | STDERR.puts "#{Tty.blue}Info#{Tty.reset}: #{message}" 294 | else 295 | STDERR.puts "Info: #{message}" 296 | end 297 | end 298 | end 299 | 300 | def debug(message) 301 | if verbose? 302 | if STDERR.tty? 303 | STDERR.puts "#{Tty.yellow}Debug#{Tty.reset}: #{message}" 304 | else 305 | STDERR.puts "Debug: #{message}" 306 | end 307 | end 308 | end 309 | 310 | private 311 | 312 | def verbose? 313 | options[:verbose] 314 | end 315 | 316 | def quiet? 317 | options[:quiet] 318 | end 319 | 320 | def color n 321 | escape "0;#{n}" 322 | end 323 | def bold n 324 | escape "1;#{n}" 325 | end 326 | def underline n 327 | escape "4;#{n}" 328 | end 329 | def escape n 330 | "\033[#{n}m" if $stdout.tty? 331 | end 332 | 333 | public 334 | 335 | def show_columns(args) 336 | if Hash === args.last 337 | options = args.last.dup 338 | else 339 | options = {} 340 | end 341 | options[:padding] ||= 4 342 | options[:indent] ||= 4 343 | output = '' 344 | 345 | if $stdout.tty? 346 | # determine the best width to display for different console sizes 347 | console_width = width 348 | longest = args.sort_by { |arg| arg.length }.last.length rescue 0 349 | optimal_col_width = ((console_width - options[:indent] + options[:padding]).to_f / 350 | (longest + options[:padding]).to_f).floor rescue 0 351 | cols = optimal_col_width > 1 ? optimal_col_width : 1 352 | 353 | Gistore::shellpipe("/usr/bin/pr", 354 | "-#{cols}", 355 | "-o#{options[:indent]}", 356 | "-t", 357 | "-w#{console_width}") do |stdin, stdout, stderr| 358 | stdin.puts args 359 | stdin.close 360 | output << stdout.read 361 | end 362 | output.rstrip! 363 | output << "\n" unless output.empty? 364 | else 365 | output << args.map{|e| " " * options[:indent] + e.to_s} * "\n" 366 | end 367 | output 368 | end 369 | end 370 | end 371 | end 372 | 373 | def git_cmd 374 | Gistore.git_cmd 375 | end 376 | 377 | class String 378 | def charat(n) 379 | result = self.send "[]", n 380 | RUBY_VERSION < "1.9" ? result.chr : result 381 | end 382 | end 383 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/actions.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'uri' 3 | require 'thor/core_ext/io_binary_read' 4 | require 'thor/actions/create_file' 5 | require 'thor/actions/create_link' 6 | require 'thor/actions/directory' 7 | require 'thor/actions/empty_directory' 8 | require 'thor/actions/file_manipulation' 9 | require 'thor/actions/inject_into_file' 10 | 11 | class Thor 12 | module Actions 13 | attr_accessor :behavior 14 | 15 | def self.included(base) #:nodoc: 16 | base.extend ClassMethods 17 | end 18 | 19 | module ClassMethods 20 | # Hold source paths for one Thor instance. source_paths_for_search is the 21 | # method responsible to gather source_paths from this current class, 22 | # inherited paths and the source root. 23 | # 24 | def source_paths 25 | @_source_paths ||= [] 26 | end 27 | 28 | # Stores and return the source root for this class 29 | def source_root(path=nil) 30 | @_source_root = path if path 31 | @_source_root 32 | end 33 | 34 | # Returns the source paths in the following order: 35 | # 36 | # 1) This class source paths 37 | # 2) Source root 38 | # 3) Parents source paths 39 | # 40 | def source_paths_for_search 41 | paths = [] 42 | paths += self.source_paths 43 | paths << self.source_root if self.source_root 44 | paths += from_superclass(:source_paths, []) 45 | paths 46 | end 47 | 48 | # Add runtime options that help actions execution. 49 | # 50 | def add_runtime_options! 51 | class_option :force, :type => :boolean, :aliases => "-f", :group => :runtime, 52 | :desc => "Overwrite files that already exist" 53 | 54 | class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime, 55 | :desc => "Run but do not make any changes" 56 | 57 | class_option :quiet, :type => :boolean, :aliases => "-q", :group => :runtime, 58 | :desc => "Suppress status output" 59 | 60 | class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime, 61 | :desc => "Skip files that already exist" 62 | end 63 | end 64 | 65 | # Extends initializer to add more configuration options. 66 | # 67 | # ==== Configuration 68 | # behavior:: The actions default behavior. Can be :invoke or :revoke. 69 | # It also accepts :force, :skip and :pretend to set the behavior 70 | # and the respective option. 71 | # 72 | # destination_root:: The root directory needed for some actions. 73 | # 74 | def initialize(args=[], options={}, config={}) 75 | self.behavior = case config[:behavior].to_s 76 | when "force", "skip" 77 | _cleanup_options_and_set(options, config[:behavior]) 78 | :invoke 79 | when "revoke" 80 | :revoke 81 | else 82 | :invoke 83 | end 84 | 85 | super 86 | self.destination_root = config[:destination_root] 87 | end 88 | 89 | # Wraps an action object and call it accordingly to the thor class behavior. 90 | # 91 | def action(instance) #:nodoc: 92 | if behavior == :revoke 93 | instance.revoke! 94 | else 95 | instance.invoke! 96 | end 97 | end 98 | 99 | # Returns the root for this thor class (also aliased as destination root). 100 | # 101 | def destination_root 102 | @destination_stack.last 103 | end 104 | 105 | # Sets the root for this thor class. Relatives path are added to the 106 | # directory where the script was invoked and expanded. 107 | # 108 | def destination_root=(root) 109 | @destination_stack ||= [] 110 | @destination_stack[0] = File.expand_path(root || '') 111 | end 112 | 113 | # Returns the given path relative to the absolute root (ie, root where 114 | # the script started). 115 | # 116 | def relative_to_original_destination_root(path, remove_dot=true) 117 | path = path.dup 118 | if path.gsub!(@destination_stack[0], '.') 119 | remove_dot ? (path[2..-1] || '') : path 120 | else 121 | path 122 | end 123 | end 124 | 125 | # Holds source paths in instance so they can be manipulated. 126 | # 127 | def source_paths 128 | @source_paths ||= self.class.source_paths_for_search 129 | end 130 | 131 | # Receives a file or directory and search for it in the source paths. 132 | # 133 | def find_in_source_paths(file) 134 | relative_root = relative_to_original_destination_root(destination_root, false) 135 | 136 | source_paths.each do |source| 137 | source_file = File.expand_path(file, File.join(source, relative_root)) 138 | return source_file if File.exists?(source_file) 139 | end 140 | 141 | message = "Could not find #{file.inspect} in any of your source paths. " 142 | 143 | unless self.class.source_root 144 | message << "Please invoke #{self.class.name}.source_root(PATH) with the PATH containing your templates. " 145 | end 146 | 147 | if source_paths.empty? 148 | message << "Currently you have no source paths." 149 | else 150 | message << "Your current source paths are: \n#{source_paths.join("\n")}" 151 | end 152 | 153 | raise Error, message 154 | end 155 | 156 | # Do something in the root or on a provided subfolder. If a relative path 157 | # is given it's referenced from the current root. The full path is yielded 158 | # to the block you provide. The path is set back to the previous path when 159 | # the method exits. 160 | # 161 | # ==== Parameters 162 | # dir:: the directory to move to. 163 | # config:: give :verbose => true to log and use padding. 164 | # 165 | def inside(dir='', config={}, &block) 166 | verbose = config.fetch(:verbose, false) 167 | pretend = options[:pretend] 168 | 169 | say_status :inside, dir, verbose 170 | shell.padding += 1 if verbose 171 | @destination_stack.push File.expand_path(dir, destination_root) 172 | 173 | # If the directory doesnt exist and we're not pretending 174 | if !File.exist?(destination_root) && !pretend 175 | FileUtils.mkdir_p(destination_root) 176 | end 177 | 178 | if pretend 179 | # In pretend mode, just yield down to the block 180 | block.arity == 1 ? yield(destination_root) : yield 181 | else 182 | FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield } 183 | end 184 | 185 | @destination_stack.pop 186 | shell.padding -= 1 if verbose 187 | end 188 | 189 | # Goes to the root and execute the given block. 190 | # 191 | def in_root 192 | inside(@destination_stack.first) { yield } 193 | end 194 | 195 | # Loads an external file and execute it in the instance binding. 196 | # 197 | # ==== Parameters 198 | # path:: The path to the file to execute. Can be a web address or 199 | # a relative path from the source root. 200 | # 201 | # ==== Examples 202 | # 203 | # apply "http://gist.github.com/103208" 204 | # 205 | # apply "recipes/jquery.rb" 206 | # 207 | def apply(path, config={}) 208 | verbose = config.fetch(:verbose, true) 209 | is_uri = path =~ /^https?\:\/\// 210 | path = find_in_source_paths(path) unless is_uri 211 | 212 | say_status :apply, path, verbose 213 | shell.padding += 1 if verbose 214 | 215 | if is_uri 216 | contents = open(path, "Accept" => "application/x-thor-template") {|io| io.read } 217 | else 218 | contents = open(path) {|io| io.read } 219 | end 220 | 221 | instance_eval(contents, path) 222 | shell.padding -= 1 if verbose 223 | end 224 | 225 | # Executes a command returning the contents of the command. 226 | # 227 | # ==== Parameters 228 | # command:: the command to be executed. 229 | # config:: give :verbose => false to not log the status, :capture => true to hide to output. Specify :with 230 | # to append an executable to command executation. 231 | # 232 | # ==== Example 233 | # 234 | # inside('vendor') do 235 | # run('ln -s ~/edge rails') 236 | # end 237 | # 238 | def run(command, config={}) 239 | return unless behavior == :invoke 240 | 241 | destination = relative_to_original_destination_root(destination_root, false) 242 | desc = "#{command} from #{destination.inspect}" 243 | 244 | if config[:with] 245 | desc = "#{File.basename(config[:with].to_s)} #{desc}" 246 | command = "#{config[:with]} #{command}" 247 | end 248 | 249 | say_status :run, desc, config.fetch(:verbose, true) 250 | 251 | unless options[:pretend] 252 | config[:capture] ? `#{command}` : system("#{command}") 253 | end 254 | end 255 | 256 | # Executes a ruby script (taking into account WIN32 platform quirks). 257 | # 258 | # ==== Parameters 259 | # command:: the command to be executed. 260 | # config:: give :verbose => false to not log the status. 261 | # 262 | def run_ruby_script(command, config={}) 263 | return unless behavior == :invoke 264 | run command, config.merge(:with => Thor::Util.ruby_command) 265 | end 266 | 267 | # Run a thor command. A hash of options can be given and it's converted to 268 | # switches. 269 | # 270 | # ==== Parameters 271 | # command:: the command to be invoked 272 | # args:: arguments to the command 273 | # config:: give :verbose => false to not log the status, :capture => true to hide to output. 274 | # Other options are given as parameter to Thor. 275 | # 276 | # 277 | # ==== Examples 278 | # 279 | # thor :install, "http://gist.github.com/103208" 280 | # #=> thor install http://gist.github.com/103208 281 | # 282 | # thor :list, :all => true, :substring => 'rails' 283 | # #=> thor list --all --substring=rails 284 | # 285 | def thor(command, *args) 286 | config = args.last.is_a?(Hash) ? args.pop : {} 287 | verbose = config.key?(:verbose) ? config.delete(:verbose) : true 288 | pretend = config.key?(:pretend) ? config.delete(:pretend) : false 289 | capture = config.key?(:capture) ? config.delete(:capture) : false 290 | 291 | args.unshift(command) 292 | args.push Thor::Options.to_switches(config) 293 | command = args.join(' ').strip 294 | 295 | run command, :with => :thor, :verbose => verbose, :pretend => pretend, :capture => capture 296 | end 297 | 298 | protected 299 | 300 | # Allow current root to be shared between invocations. 301 | # 302 | def _shared_configuration #:nodoc: 303 | super.merge!(:destination_root => self.destination_root) 304 | end 305 | 306 | def _cleanup_options_and_set(options, key) #:nodoc: 307 | case options 308 | when Array 309 | %w(--force -f --skip -s).each { |i| options.delete(i) } 310 | options << "--#{key}" 311 | when Hash 312 | [:force, :skip, "force", "skip"].each { |i| options.delete(i) } 313 | options.merge!(key => true) 314 | end 315 | end 316 | 317 | end 318 | end 319 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/actions/create_file.rb: -------------------------------------------------------------------------------- 1 | require 'thor/actions/empty_directory' 2 | 3 | class Thor 4 | module Actions 5 | 6 | # Create a new file relative to the destination root with the given data, 7 | # which is the return value of a block or a data string. 8 | # 9 | # ==== Parameters 10 | # destination:: the relative path to the destination root. 11 | # data:: the data to append to the file. 12 | # config:: give :verbose => false to not log the status. 13 | # 14 | # ==== Examples 15 | # 16 | # create_file "lib/fun_party.rb" do 17 | # hostname = ask("What is the virtual hostname I should use?") 18 | # "vhost.name = #{hostname}" 19 | # end 20 | # 21 | # create_file "config/apache.conf", "your apache config" 22 | # 23 | def create_file(destination, *args, &block) 24 | config = args.last.is_a?(Hash) ? args.pop : {} 25 | data = args.first 26 | action CreateFile.new(self, destination, block || data.to_s, config) 27 | end 28 | alias :add_file :create_file 29 | 30 | # CreateFile is a subset of Template, which instead of rendering a file with 31 | # ERB, it gets the content from the user. 32 | # 33 | class CreateFile < EmptyDirectory #:nodoc: 34 | attr_reader :data 35 | 36 | def initialize(base, destination, data, config={}) 37 | @data = data 38 | super(base, destination, config) 39 | end 40 | 41 | # Checks if the content of the file at the destination is identical to the rendered result. 42 | # 43 | # ==== Returns 44 | # Boolean:: true if it is identical, false otherwise. 45 | # 46 | def identical? 47 | exists? && File.binread(destination) == render 48 | end 49 | 50 | # Holds the content to be added to the file. 51 | # 52 | def render 53 | @render ||= if data.is_a?(Proc) 54 | data.call 55 | else 56 | data 57 | end 58 | end 59 | 60 | def invoke! 61 | invoke_with_conflict_check do 62 | FileUtils.mkdir_p(File.dirname(destination)) 63 | File.open(destination, 'wb') { |f| f.write render } 64 | end 65 | given_destination 66 | end 67 | 68 | protected 69 | 70 | # Now on conflict we check if the file is identical or not. 71 | # 72 | def on_conflict_behavior(&block) 73 | if identical? 74 | say_status :identical, :blue 75 | else 76 | options = base.options.merge(config) 77 | force_or_skip_or_conflict(options[:force], options[:skip], &block) 78 | end 79 | end 80 | 81 | # If force is true, run the action, otherwise check if it's not being 82 | # skipped. If both are false, show the file_collision menu, if the menu 83 | # returns true, force it, otherwise skip. 84 | # 85 | def force_or_skip_or_conflict(force, skip, &block) 86 | if force 87 | say_status :force, :yellow 88 | block.call unless pretend? 89 | elsif skip 90 | say_status :skip, :yellow 91 | else 92 | say_status :conflict, :red 93 | force_or_skip_or_conflict(force_on_collision?, true, &block) 94 | end 95 | end 96 | 97 | # Shows the file collision menu to the user and gets the result. 98 | # 99 | def force_on_collision? 100 | base.shell.file_collision(destination){ render } 101 | end 102 | 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/actions/create_link.rb: -------------------------------------------------------------------------------- 1 | require 'thor/actions/create_file' 2 | 3 | class Thor 4 | module Actions 5 | 6 | # Create a new file relative to the destination root from the given source. 7 | # 8 | # ==== Parameters 9 | # destination:: the relative path to the destination root. 10 | # source:: the relative path to the source root. 11 | # config:: give :verbose => false to not log the status. 12 | # :: give :symbolic => false for hard link. 13 | # 14 | # ==== Examples 15 | # 16 | # create_link "config/apache.conf", "/etc/apache.conf" 17 | # 18 | def create_link(destination, *args, &block) 19 | config = args.last.is_a?(Hash) ? args.pop : {} 20 | source = args.first 21 | action CreateLink.new(self, destination, source, config) 22 | end 23 | alias :add_link :create_link 24 | 25 | # CreateLink is a subset of CreateFile, which instead of taking a block of 26 | # data, just takes a source string from the user. 27 | # 28 | class CreateLink < CreateFile #:nodoc: 29 | attr_reader :data 30 | 31 | # Checks if the content of the file at the destination is identical to the rendered result. 32 | # 33 | # ==== Returns 34 | # Boolean:: true if it is identical, false otherwise. 35 | # 36 | def identical? 37 | exists? && File.identical?(render, destination) 38 | end 39 | 40 | def invoke! 41 | invoke_with_conflict_check do 42 | FileUtils.mkdir_p(File.dirname(destination)) 43 | # Create a symlink by default 44 | config[:symbolic] = true if config[:symbolic].nil? 45 | File.unlink(destination) if exists? 46 | if config[:symbolic] 47 | File.symlink(render, destination) 48 | else 49 | File.link(render, destination) 50 | end 51 | end 52 | given_destination 53 | end 54 | 55 | def exists? 56 | super || File.symlink?(destination) 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/actions/directory.rb: -------------------------------------------------------------------------------- 1 | require 'thor/actions/empty_directory' 2 | 3 | class Thor 4 | module Actions 5 | # Copies recursively the files from source directory to root directory. 6 | # If any of the files finishes with .tt, it's considered to be a template 7 | # and is placed in the destination without the extension .tt. If any 8 | # empty directory is found, it's copied and all .empty_directory files are 9 | # ignored. If any file name is wrapped within % signs, the text within 10 | # the % signs will be executed as a method and replaced with the returned 11 | # value. Let's suppose a doc directory with the following files: 12 | # 13 | # doc/ 14 | # components/.empty_directory 15 | # README 16 | # rdoc.rb.tt 17 | # %app_name%.rb 18 | # 19 | # When invoked as: 20 | # 21 | # directory "doc" 22 | # 23 | # It will create a doc directory in the destination with the following 24 | # files (assuming that the `app_name` method returns the value "blog"): 25 | # 26 | # doc/ 27 | # components/ 28 | # README 29 | # rdoc.rb 30 | # blog.rb 31 | # 32 | # Encoded path note: Since Thor internals use Object#respond_to? to check if it can 33 | # expand %something%, this `something` should be a public method in the class calling 34 | # #directory. If a method is private, Thor stack raises PrivateMethodEncodedError. 35 | # 36 | # ==== Parameters 37 | # source:: the relative path to the source root. 38 | # destination:: the relative path to the destination root. 39 | # config:: give :verbose => false to not log the status. 40 | # If :recursive => false, does not look for paths recursively. 41 | # If :mode => :preserve, preserve the file mode from the source. 42 | # If :exclude_pattern => /regexp/, prevents copying files that match that regexp. 43 | # 44 | # ==== Examples 45 | # 46 | # directory "doc" 47 | # directory "doc", "docs", :recursive => false 48 | # 49 | def directory(source, *args, &block) 50 | config = args.last.is_a?(Hash) ? args.pop : {} 51 | destination = args.first || source 52 | action Directory.new(self, source, destination || source, config, &block) 53 | end 54 | 55 | class Directory < EmptyDirectory #:nodoc: 56 | attr_reader :source 57 | 58 | def initialize(base, source, destination=nil, config={}, &block) 59 | @source = File.expand_path(base.find_in_source_paths(source.to_s)) 60 | @block = block 61 | super(base, destination, { :recursive => true }.merge(config)) 62 | end 63 | 64 | def invoke! 65 | base.empty_directory given_destination, config 66 | execute! 67 | end 68 | 69 | def revoke! 70 | execute! 71 | end 72 | 73 | protected 74 | 75 | def execute! 76 | lookup = Util.escape_globs(source) 77 | lookup = config[:recursive] ? File.join(lookup, '**') : lookup 78 | lookup = file_level_lookup(lookup) 79 | 80 | files(lookup).sort.each do |file_source| 81 | next if File.directory?(file_source) 82 | next if config[:exclude_pattern] && file_source.match(config[:exclude_pattern]) 83 | file_destination = File.join(given_destination, file_source.gsub(source, '.')) 84 | file_destination.gsub!('/./', '/') 85 | 86 | case file_source 87 | when /\.empty_directory$/ 88 | dirname = File.dirname(file_destination).gsub(/\/\.$/, '') 89 | next if dirname == given_destination 90 | base.empty_directory(dirname, config) 91 | when /\.tt$/ 92 | destination = base.template(file_source, file_destination[0..-4], config, &@block) 93 | else 94 | destination = base.copy_file(file_source, file_destination, config, &@block) 95 | end 96 | end 97 | end 98 | 99 | if RUBY_VERSION < '2.0' 100 | def file_level_lookup(previous_lookup) 101 | File.join(previous_lookup, '{*,.[a-z]*}') 102 | end 103 | 104 | def files(lookup) 105 | Dir[lookup] 106 | end 107 | else 108 | def file_level_lookup(previous_lookup) 109 | File.join(previous_lookup, '*') 110 | end 111 | 112 | def files(lookup) 113 | Dir.glob(lookup, File::FNM_DOTMATCH) 114 | end 115 | end 116 | 117 | end 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/actions/empty_directory.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | module Actions 3 | 4 | # Creates an empty directory. 5 | # 6 | # ==== Parameters 7 | # destination:: the relative path to the destination root. 8 | # config:: give :verbose => false to not log the status. 9 | # 10 | # ==== Examples 11 | # 12 | # empty_directory "doc" 13 | # 14 | def empty_directory(destination, config={}) 15 | action EmptyDirectory.new(self, destination, config) 16 | end 17 | 18 | # Class which holds create directory logic. This is the base class for 19 | # other actions like create_file and directory. 20 | # 21 | # This implementation is based in Templater actions, created by Jonas Nicklas 22 | # and Michael S. Klishin under MIT LICENSE. 23 | # 24 | class EmptyDirectory #:nodoc: 25 | attr_reader :base, :destination, :given_destination, :relative_destination, :config 26 | 27 | # Initializes given the source and destination. 28 | # 29 | # ==== Parameters 30 | # base:: A Thor::Base instance 31 | # source:: Relative path to the source of this file 32 | # destination:: Relative path to the destination of this file 33 | # config:: give :verbose => false to not log the status. 34 | # 35 | def initialize(base, destination, config={}) 36 | @base, @config = base, { :verbose => true }.merge(config) 37 | self.destination = destination 38 | end 39 | 40 | # Checks if the destination file already exists. 41 | # 42 | # ==== Returns 43 | # Boolean:: true if the file exists, false otherwise. 44 | # 45 | def exists? 46 | ::File.exists?(destination) 47 | end 48 | 49 | def invoke! 50 | invoke_with_conflict_check do 51 | ::FileUtils.mkdir_p(destination) 52 | end 53 | end 54 | 55 | def revoke! 56 | say_status :remove, :red 57 | ::FileUtils.rm_rf(destination) if !pretend? && exists? 58 | given_destination 59 | end 60 | 61 | protected 62 | 63 | # Shortcut for pretend. 64 | # 65 | def pretend? 66 | base.options[:pretend] 67 | end 68 | 69 | # Sets the absolute destination value from a relative destination value. 70 | # It also stores the given and relative destination. Let's suppose our 71 | # script is being executed on "dest", it sets the destination root to 72 | # "dest". The destination, given_destination and relative_destination 73 | # are related in the following way: 74 | # 75 | # inside "bar" do 76 | # empty_directory "baz" 77 | # end 78 | # 79 | # destination #=> dest/bar/baz 80 | # relative_destination #=> bar/baz 81 | # given_destination #=> baz 82 | # 83 | def destination=(destination) 84 | if destination 85 | @given_destination = convert_encoded_instructions(destination.to_s) 86 | @destination = ::File.expand_path(@given_destination, base.destination_root) 87 | @relative_destination = base.relative_to_original_destination_root(@destination) 88 | end 89 | end 90 | 91 | # Filenames in the encoded form are converted. If you have a file: 92 | # 93 | # %file_name%.rb 94 | # 95 | # It calls #file_name from the base and replaces %-string with the 96 | # return value (should be String) of #file_name: 97 | # 98 | # user.rb 99 | # 100 | # The method referenced can be either public or private. 101 | # 102 | def convert_encoded_instructions(filename) 103 | filename.gsub(/%(.*?)%/) do |initial_string| 104 | method = $1.strip 105 | base.respond_to?(method, true) ? base.send(method) : initial_string 106 | end 107 | end 108 | 109 | # Receives a hash of options and just execute the block if some 110 | # conditions are met. 111 | # 112 | def invoke_with_conflict_check(&block) 113 | if exists? 114 | on_conflict_behavior(&block) 115 | else 116 | say_status :create, :green 117 | block.call unless pretend? 118 | end 119 | 120 | destination 121 | end 122 | 123 | # What to do when the destination file already exists. 124 | # 125 | def on_conflict_behavior(&block) 126 | say_status :exist, :blue 127 | end 128 | 129 | # Shortcut to say_status shell method. 130 | # 131 | def say_status(status, color) 132 | base.shell.say_status status, relative_destination, color if config[:verbose] 133 | end 134 | 135 | end 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/actions/inject_into_file.rb: -------------------------------------------------------------------------------- 1 | require 'thor/actions/empty_directory' 2 | 3 | class Thor 4 | module Actions 5 | 6 | # Injects the given content into a file. Different from gsub_file, this 7 | # method is reversible. 8 | # 9 | # ==== Parameters 10 | # destination:: Relative path to the destination root 11 | # data:: Data to add to the file. Can be given as a block. 12 | # config:: give :verbose => false to not log the status and the flag 13 | # for injection (:after or :before) or :force => true for 14 | # insert two or more times the same content. 15 | # 16 | # ==== Examples 17 | # 18 | # insert_into_file "config/environment.rb", "config.gem :thor", :after => "Rails::Initializer.run do |config|\n" 19 | # 20 | # insert_into_file "config/environment.rb", :after => "Rails::Initializer.run do |config|\n" do 21 | # gems = ask "Which gems would you like to add?" 22 | # gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n") 23 | # end 24 | # 25 | def insert_into_file(destination, *args, &block) 26 | if block_given? 27 | data, config = block, args.shift 28 | else 29 | data, config = args.shift, args.shift 30 | end 31 | action InjectIntoFile.new(self, destination, data, config) 32 | end 33 | alias_method :inject_into_file, :insert_into_file 34 | 35 | class InjectIntoFile < EmptyDirectory #:nodoc: 36 | attr_reader :replacement, :flag, :behavior 37 | 38 | def initialize(base, destination, data, config) 39 | super(base, destination, { :verbose => true }.merge(config)) 40 | 41 | @behavior, @flag = if @config.key?(:after) 42 | [:after, @config.delete(:after)] 43 | else 44 | [:before, @config.delete(:before)] 45 | end 46 | 47 | @replacement = data.is_a?(Proc) ? data.call : data 48 | @flag = Regexp.escape(@flag) unless @flag.is_a?(Regexp) 49 | end 50 | 51 | def invoke! 52 | say_status :invoke 53 | 54 | content = if @behavior == :after 55 | '\0' + replacement 56 | else 57 | replacement + '\0' 58 | end 59 | 60 | replace!(/#{flag}/, content, config[:force]) 61 | end 62 | 63 | def revoke! 64 | say_status :revoke 65 | 66 | regexp = if @behavior == :after 67 | content = '\1\2' 68 | /(#{flag})(.*)(#{Regexp.escape(replacement)})/m 69 | else 70 | content = '\2\3' 71 | /(#{Regexp.escape(replacement)})(.*)(#{flag})/m 72 | end 73 | 74 | replace!(regexp, content, true) 75 | end 76 | 77 | protected 78 | 79 | def say_status(behavior) 80 | status = if behavior == :invoke 81 | if flag == /\A/ 82 | :prepend 83 | elsif flag == /\z/ 84 | :append 85 | else 86 | :insert 87 | end 88 | else 89 | :subtract 90 | end 91 | 92 | super(status, config[:verbose]) 93 | end 94 | 95 | # Adds the content to the file. 96 | # 97 | def replace!(regexp, string, force) 98 | unless base.options[:pretend] 99 | content = File.binread(destination) 100 | if force || !content.include?(replacement) 101 | content.gsub!(regexp, string) 102 | File.open(destination, 'wb') { |file| file.write(content) } 103 | end 104 | end 105 | end 106 | 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/command.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | class Command < Struct.new(:name, :description, :long_description, :usage, :options) 3 | FILE_REGEXP = /^#{Regexp.escape(File.dirname(__FILE__))}/ 4 | 5 | def initialize(name, description, long_description, usage, options=nil) 6 | super(name.to_s, description, long_description, usage, options || {}) 7 | end 8 | 9 | def initialize_copy(other) #:nodoc: 10 | super(other) 11 | self.options = other.options.dup if other.options 12 | end 13 | 14 | def hidden? 15 | false 16 | end 17 | 18 | # By default, a command invokes a method in the thor class. You can change this 19 | # implementation to create custom commands. 20 | def run(instance, args=[]) 21 | arity = nil 22 | 23 | if private_method?(instance) 24 | instance.class.handle_no_command_error(name) 25 | elsif public_method?(instance) 26 | arity = instance.method(name).arity 27 | instance.__send__(name, *args) 28 | elsif local_method?(instance, :method_missing) 29 | instance.__send__(:method_missing, name.to_sym, *args) 30 | else 31 | instance.class.handle_no_command_error(name) 32 | end 33 | rescue ArgumentError => e 34 | handle_argument_error?(instance, e, caller) ? 35 | instance.class.handle_argument_error(self, e, args, arity) : (raise e) 36 | rescue NoMethodError => e 37 | handle_no_method_error?(instance, e, caller) ? 38 | instance.class.handle_no_command_error(name) : (raise e) 39 | end 40 | 41 | # Returns the formatted usage by injecting given required arguments 42 | # and required options into the given usage. 43 | def formatted_usage(klass, namespace = true, subcommand = false) 44 | if namespace 45 | namespace = klass.namespace 46 | formatted = "#{namespace.gsub(/^(default)/,'')}:" 47 | end 48 | formatted = "#{klass.namespace.split(':').last} " if subcommand 49 | 50 | formatted ||= "" 51 | 52 | # Add usage with required arguments 53 | formatted << if klass && !klass.arguments.empty? 54 | usage.to_s.gsub(/^#{name}/) do |match| 55 | match << " " << klass.arguments.map{ |a| a.usage }.compact.join(' ') 56 | end 57 | else 58 | usage.to_s 59 | end 60 | 61 | # Add required options 62 | formatted << " #{required_options}" 63 | 64 | # Strip and go! 65 | formatted.strip 66 | end 67 | 68 | protected 69 | 70 | def not_debugging?(instance) 71 | !(instance.class.respond_to?(:debugging) && instance.class.debugging) 72 | end 73 | 74 | def required_options 75 | @required_options ||= options.map{ |_, o| o.usage if o.required? }.compact.sort.join(" ") 76 | end 77 | 78 | # Given a target, checks if this class name is a public method. 79 | def public_method?(instance) #:nodoc: 80 | !(instance.public_methods & [name.to_s, name.to_sym]).empty? 81 | end 82 | 83 | def private_method?(instance) 84 | !(instance.private_methods & [name.to_s, name.to_sym]).empty? 85 | end 86 | 87 | def local_method?(instance, name) 88 | methods = instance.public_methods(false) + instance.private_methods(false) + instance.protected_methods(false) 89 | !(methods & [name.to_s, name.to_sym]).empty? 90 | end 91 | 92 | def sans_backtrace(backtrace, caller) #:nodoc: 93 | saned = backtrace.reject { |frame| frame =~ FILE_REGEXP || (frame =~ /\.java:/ && RUBY_PLATFORM =~ /java/) } 94 | saned -= caller 95 | end 96 | 97 | def handle_argument_error?(instance, error, caller) 98 | not_debugging?(instance) && error.message =~ /wrong number of arguments/ && begin 99 | saned = sans_backtrace(error.backtrace, caller) 100 | # Ruby 1.9 always include the called method in the backtrace 101 | saned.empty? || (saned.size == 1 && RUBY_VERSION >= "1.9") 102 | end 103 | end 104 | 105 | def handle_no_method_error?(instance, error, caller) 106 | not_debugging?(instance) && 107 | error.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/ 108 | end 109 | end 110 | Task = Command 111 | 112 | # A command that is hidden in help messages but still invocable. 113 | class HiddenCommand < Command 114 | def hidden? 115 | true 116 | end 117 | end 118 | HiddenTask = HiddenCommand 119 | 120 | # A dynamic command that handles method missing scenarios. 121 | class DynamicCommand < Command 122 | def initialize(name, options=nil) 123 | super(name.to_s, "A dynamically-generated command", name.to_s, name.to_s, options) 124 | end 125 | 126 | def run(instance, args=[]) 127 | if (instance.methods & [name.to_s, name.to_sym]).empty? 128 | super 129 | else 130 | instance.class.handle_no_command_error(name) 131 | end 132 | end 133 | end 134 | DynamicTask = DynamicCommand 135 | 136 | end 137 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/core_ext/hash_with_indifferent_access.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | module CoreExt #:nodoc: 3 | 4 | # A hash with indifferent access and magic predicates. 5 | # 6 | # hash = Thor::CoreExt::HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true 7 | # 8 | # hash[:foo] #=> 'bar' 9 | # hash['foo'] #=> 'bar' 10 | # hash.foo? #=> true 11 | # 12 | class HashWithIndifferentAccess < ::Hash #:nodoc: 13 | 14 | def initialize(hash={}) 15 | super() 16 | hash.each do |key, value| 17 | self[convert_key(key)] = value 18 | end 19 | end 20 | 21 | def [](key) 22 | super(convert_key(key)) 23 | end 24 | 25 | def []=(key, value) 26 | super(convert_key(key), value) 27 | end 28 | 29 | def delete(key) 30 | super(convert_key(key)) 31 | end 32 | 33 | def values_at(*indices) 34 | indices.collect { |key| self[convert_key(key)] } 35 | end 36 | 37 | def merge(other) 38 | dup.merge!(other) 39 | end 40 | 41 | def merge!(other) 42 | other.each do |key, value| 43 | self[convert_key(key)] = value 44 | end 45 | self 46 | end 47 | 48 | # Convert to a Hash with String keys. 49 | def to_hash 50 | Hash.new(default).merge!(self) 51 | end 52 | 53 | protected 54 | 55 | def convert_key(key) 56 | key.is_a?(Symbol) ? key.to_s : key 57 | end 58 | 59 | # Magic predicates. For instance: 60 | # 61 | # options.force? # => !!options['force'] 62 | # options.shebang # => "/usr/lib/local/ruby" 63 | # options.test_framework?(:rspec) # => options[:test_framework] == :rspec 64 | # 65 | def method_missing(method, *args, &block) 66 | method = method.to_s 67 | if method =~ /^(\w+)\?$/ 68 | if args.empty? 69 | !!self[$1] 70 | else 71 | self[$1] == args.first 72 | end 73 | else 74 | self[method] 75 | end 76 | end 77 | 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/core_ext/io_binary_read.rb: -------------------------------------------------------------------------------- 1 | class IO #:nodoc: 2 | class << self 3 | 4 | def binread(file, *args) 5 | raise ArgumentError, "wrong number of arguments (#{1 + args.size} for 1..3)" unless args.size < 3 6 | File.open(file, 'rb') do |f| 7 | f.read(*args) 8 | end 9 | end unless method_defined? :binread 10 | 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/core_ext/ordered_hash.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | module CoreExt #:nodoc: 3 | 4 | if RUBY_VERSION >= '1.9' 5 | class OrderedHash < ::Hash 6 | end 7 | else 8 | # This class is based on the Ruby 1.9 ordered hashes. 9 | # 10 | # It keeps the semantics and most of the efficiency of normal hashes 11 | # while also keeping track of the order in which elements were set. 12 | # 13 | class OrderedHash #:nodoc: 14 | include Enumerable 15 | 16 | Node = Struct.new(:key, :value, :next, :prev) 17 | 18 | def initialize 19 | @hash = {} 20 | end 21 | 22 | def [](key) 23 | @hash[key] && @hash[key].value 24 | end 25 | 26 | def []=(key, value) 27 | if node = @hash[key] 28 | node.value = value 29 | else 30 | node = Node.new(key, value) 31 | 32 | if @first.nil? 33 | @first = @last = node 34 | else 35 | node.prev = @last 36 | @last.next = node 37 | @last = node 38 | end 39 | end 40 | 41 | @hash[key] = node 42 | value 43 | end 44 | 45 | def delete(key) 46 | if node = @hash[key] 47 | prev_node = node.prev 48 | next_node = node.next 49 | 50 | next_node.prev = prev_node if next_node 51 | prev_node.next = next_node if prev_node 52 | 53 | @first = next_node if @first == node 54 | @last = prev_node if @last == node 55 | 56 | value = node.value 57 | end 58 | 59 | @hash.delete(key) 60 | value 61 | end 62 | 63 | def keys 64 | self.map { |k, v| k } 65 | end 66 | 67 | def values 68 | self.map { |k, v| v } 69 | end 70 | 71 | def each 72 | return unless @first 73 | yield [@first.key, @first.value] 74 | node = @first 75 | yield [node.key, node.value] while node = node.next 76 | self 77 | end 78 | 79 | def merge(other) 80 | hash = self.class.new 81 | 82 | self.each do |key, value| 83 | hash[key] = value 84 | end 85 | 86 | other.each do |key, value| 87 | hash[key] = value 88 | end 89 | 90 | hash 91 | end 92 | 93 | def empty? 94 | @hash.empty? 95 | end 96 | end 97 | end 98 | 99 | end 100 | end 101 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/error.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | # Thor::Error is raised when it's caused by wrong usage of thor classes. Those 3 | # errors have their backtrace suppressed and are nicely shown to the user. 4 | # 5 | # Errors that are caused by the developer, like declaring a method which 6 | # overwrites a thor keyword, it SHOULD NOT raise a Thor::Error. This way, we 7 | # ensure that developer errors are shown with full backtrace. 8 | class Error < StandardError 9 | end 10 | 11 | # Raised when a command was not found. 12 | class UndefinedCommandError < Error 13 | end 14 | UndefinedTaskError = UndefinedCommandError 15 | 16 | # Raised when a command was found, but not invoked properly. 17 | class InvocationError < Error 18 | end 19 | 20 | class UnknownArgumentError < Error 21 | end 22 | 23 | class RequiredArgumentMissingError < InvocationError 24 | end 25 | 26 | class MalformattedArgumentError < InvocationError 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/group.rb: -------------------------------------------------------------------------------- 1 | require 'thor/base' 2 | 3 | # Thor has a special class called Thor::Group. The main difference to Thor class 4 | # is that it invokes all commands at once. It also include some methods that allows 5 | # invocations to be done at the class method, which are not available to Thor 6 | # commands. 7 | class Thor::Group 8 | class << self 9 | # The description for this Thor::Group. If none is provided, but a source root 10 | # exists, tries to find the USAGE one folder above it, otherwise searches 11 | # in the superclass. 12 | # 13 | # ==== Parameters 14 | # description:: The description for this Thor::Group. 15 | # 16 | def desc(description=nil) 17 | @desc = case description 18 | when nil 19 | @desc || from_superclass(:desc, nil) 20 | else 21 | description 22 | end 23 | end 24 | 25 | # Prints help information. 26 | # 27 | # ==== Options 28 | # short:: When true, shows only usage. 29 | # 30 | def help(shell) 31 | shell.say "Usage:" 32 | shell.say " #{banner}\n" 33 | shell.say 34 | class_options_help(shell) 35 | shell.say self.desc if self.desc 36 | end 37 | 38 | # Stores invocations for this class merging with superclass values. 39 | # 40 | def invocations #:nodoc: 41 | @invocations ||= from_superclass(:invocations, {}) 42 | end 43 | 44 | # Stores invocation blocks used on invoke_from_option. 45 | # 46 | def invocation_blocks #:nodoc: 47 | @invocation_blocks ||= from_superclass(:invocation_blocks, {}) 48 | end 49 | 50 | # Invoke the given namespace or class given. It adds an instance 51 | # method that will invoke the klass and command. You can give a block to 52 | # configure how it will be invoked. 53 | # 54 | # The namespace/class given will have its options showed on the help 55 | # usage. Check invoke_from_option for more information. 56 | # 57 | def invoke(*names, &block) 58 | options = names.last.is_a?(Hash) ? names.pop : {} 59 | verbose = options.fetch(:verbose, true) 60 | 61 | names.each do |name| 62 | invocations[name] = false 63 | invocation_blocks[name] = block if block_given? 64 | 65 | class_eval <<-METHOD, __FILE__, __LINE__ 66 | def _invoke_#{name.to_s.gsub(/\W/, '_')} 67 | klass, command = self.class.prepare_for_invocation(nil, #{name.inspect}) 68 | 69 | if klass 70 | say_status :invoke, #{name.inspect}, #{verbose.inspect} 71 | block = self.class.invocation_blocks[#{name.inspect}] 72 | _invoke_for_class_method klass, command, &block 73 | else 74 | say_status :error, %(#{name.inspect} [not found]), :red 75 | end 76 | end 77 | METHOD 78 | end 79 | end 80 | 81 | # Invoke a thor class based on the value supplied by the user to the 82 | # given option named "name". A class option must be created before this 83 | # method is invoked for each name given. 84 | # 85 | # ==== Examples 86 | # 87 | # class GemGenerator < Thor::Group 88 | # class_option :test_framework, :type => :string 89 | # invoke_from_option :test_framework 90 | # end 91 | # 92 | # ==== Boolean options 93 | # 94 | # In some cases, you want to invoke a thor class if some option is true or 95 | # false. This is automatically handled by invoke_from_option. Then the 96 | # option name is used to invoke the generator. 97 | # 98 | # ==== Preparing for invocation 99 | # 100 | # In some cases you want to customize how a specified hook is going to be 101 | # invoked. You can do that by overwriting the class method 102 | # prepare_for_invocation. The class method must necessarily return a klass 103 | # and an optional command. 104 | # 105 | # ==== Custom invocations 106 | # 107 | # You can also supply a block to customize how the option is going to be 108 | # invoked. The block receives two parameters, an instance of the current 109 | # class and the klass to be invoked. 110 | # 111 | def invoke_from_option(*names, &block) 112 | options = names.last.is_a?(Hash) ? names.pop : {} 113 | verbose = options.fetch(:verbose, :white) 114 | 115 | names.each do |name| 116 | unless class_options.key?(name) 117 | raise ArgumentError, "You have to define the option #{name.inspect} " << 118 | "before setting invoke_from_option." 119 | end 120 | 121 | invocations[name] = true 122 | invocation_blocks[name] = block if block_given? 123 | 124 | class_eval <<-METHOD, __FILE__, __LINE__ 125 | def _invoke_from_option_#{name.to_s.gsub(/\W/, '_')} 126 | return unless options[#{name.inspect}] 127 | 128 | value = options[#{name.inspect}] 129 | value = #{name.inspect} if TrueClass === value 130 | klass, command = self.class.prepare_for_invocation(#{name.inspect}, value) 131 | 132 | if klass 133 | say_status :invoke, value, #{verbose.inspect} 134 | block = self.class.invocation_blocks[#{name.inspect}] 135 | _invoke_for_class_method klass, command, &block 136 | else 137 | say_status :error, %(\#{value} [not found]), :red 138 | end 139 | end 140 | METHOD 141 | end 142 | end 143 | 144 | # Remove a previously added invocation. 145 | # 146 | # ==== Examples 147 | # 148 | # remove_invocation :test_framework 149 | # 150 | def remove_invocation(*names) 151 | names.each do |name| 152 | remove_command(name) 153 | remove_class_option(name) 154 | invocations.delete(name) 155 | invocation_blocks.delete(name) 156 | end 157 | end 158 | 159 | # Overwrite class options help to allow invoked generators options to be 160 | # shown recursively when invoking a generator. 161 | # 162 | def class_options_help(shell, groups={}) #:nodoc: 163 | get_options_from_invocations(groups, class_options) do |klass| 164 | klass.send(:get_options_from_invocations, groups, class_options) 165 | end 166 | super(shell, groups) 167 | end 168 | 169 | # Get invocations array and merge options from invocations. Those 170 | # options are added to group_options hash. Options that already exists 171 | # in base_options are not added twice. 172 | # 173 | def get_options_from_invocations(group_options, base_options) #:nodoc: 174 | invocations.each do |name, from_option| 175 | value = if from_option 176 | option = class_options[name] 177 | option.type == :boolean ? name : option.default 178 | else 179 | name 180 | end 181 | next unless value 182 | 183 | klass, _ = prepare_for_invocation(name, value) 184 | next unless klass && klass.respond_to?(:class_options) 185 | 186 | value = value.to_s 187 | human_name = value.respond_to?(:classify) ? value.classify : value 188 | 189 | group_options[human_name] ||= [] 190 | group_options[human_name] += klass.class_options.values.select do |class_option| 191 | base_options[class_option.name.to_sym].nil? && class_option.group.nil? && 192 | !group_options.values.flatten.any? { |i| i.name == class_option.name } 193 | end 194 | 195 | yield klass if block_given? 196 | end 197 | end 198 | 199 | # Returns commands ready to be printed. 200 | def printable_commands(*) 201 | item = [] 202 | item << banner 203 | item << (desc ? "# #{desc.gsub(/\s+/m,' ')}" : "") 204 | [item] 205 | end 206 | alias printable_tasks printable_commands 207 | 208 | def handle_argument_error(command, error, args, arity) #:nodoc: 209 | msg = "#{basename} #{command.name} takes #{arity} argument" 210 | msg << "s" if arity > 1 211 | msg << ", but it should not." 212 | raise error, msg 213 | end 214 | 215 | protected 216 | 217 | # The method responsible for dispatching given the args. 218 | def dispatch(command, given_args, given_opts, config) #:nodoc: 219 | if Thor::HELP_MAPPINGS.include?(given_args.first) 220 | help(config[:shell]) 221 | return 222 | end 223 | 224 | args, opts = Thor::Options.split(given_args) 225 | opts = given_opts || opts 226 | 227 | instance = new(args, opts, config) 228 | yield instance if block_given? 229 | 230 | if command 231 | instance.invoke_command(all_commands[command]) 232 | else 233 | instance.invoke_all 234 | end 235 | end 236 | 237 | # The banner for this class. You can customize it if you are invoking the 238 | # thor class by another ways which is not the Thor::Runner. 239 | def banner 240 | "#{basename} #{self_command.formatted_usage(self, false)}" 241 | end 242 | 243 | # Represents the whole class as a command. 244 | def self_command #:nodoc: 245 | Thor::DynamicCommand.new(self.namespace, class_options) 246 | end 247 | alias self_task self_command 248 | 249 | def baseclass #:nodoc: 250 | Thor::Group 251 | end 252 | 253 | def create_command(meth) #:nodoc: 254 | commands[meth.to_s] = Thor::Command.new(meth, nil, nil, nil, nil) 255 | true 256 | end 257 | alias create_task create_command 258 | end 259 | 260 | include Thor::Base 261 | 262 | protected 263 | 264 | # Shortcut to invoke with padding and block handling. Use internally by 265 | # invoke and invoke_from_option class methods. 266 | def _invoke_for_class_method(klass, command=nil, *args, &block) #:nodoc: 267 | with_padding do 268 | if block 269 | case block.arity 270 | when 3 271 | block.call(self, klass, command) 272 | when 2 273 | block.call(self, klass) 274 | when 1 275 | instance_exec(klass, &block) 276 | end 277 | else 278 | invoke klass, command, *args 279 | end 280 | end 281 | end 282 | end 283 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/invocation.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | module Invocation 3 | def self.included(base) #:nodoc: 4 | base.extend ClassMethods 5 | end 6 | 7 | module ClassMethods 8 | # This method is responsible for receiving a name and find the proper 9 | # class and command for it. The key is an optional parameter which is 10 | # available only in class methods invocations (i.e. in Thor::Group). 11 | def prepare_for_invocation(key, name) #:nodoc: 12 | case name 13 | when Symbol, String 14 | Thor::Util.find_class_and_command_by_namespace(name.to_s, !key) 15 | else 16 | name 17 | end 18 | end 19 | end 20 | 21 | # Make initializer aware of invocations and the initialization args. 22 | def initialize(args=[], options={}, config={}, &block) #:nodoc: 23 | @_invocations = config[:invocations] || Hash.new { |h,k| h[k] = [] } 24 | @_initializer = [ args, options, config ] 25 | super 26 | end 27 | 28 | # Receives a name and invokes it. The name can be a string (either "command" or 29 | # "namespace:command"), a Thor::Command, a Class or a Thor instance. If the 30 | # command cannot be guessed by name, it can also be supplied as second argument. 31 | # 32 | # You can also supply the arguments, options and configuration values for 33 | # the command to be invoked, if none is given, the same values used to 34 | # initialize the invoker are used to initialize the invoked. 35 | # 36 | # When no name is given, it will invoke the default command of the current class. 37 | # 38 | # ==== Examples 39 | # 40 | # class A < Thor 41 | # def foo 42 | # invoke :bar 43 | # invoke "b:hello", ["José"] 44 | # end 45 | # 46 | # def bar 47 | # invoke "b:hello", ["José"] 48 | # end 49 | # end 50 | # 51 | # class B < Thor 52 | # def hello(name) 53 | # puts "hello #{name}" 54 | # end 55 | # end 56 | # 57 | # You can notice that the method "foo" above invokes two commands: "bar", 58 | # which belongs to the same class and "hello" which belongs to the class B. 59 | # 60 | # By using an invocation system you ensure that a command is invoked only once. 61 | # In the example above, invoking "foo" will invoke "b:hello" just once, even 62 | # if it's invoked later by "bar" method. 63 | # 64 | # When class A invokes class B, all arguments used on A initialization are 65 | # supplied to B. This allows lazy parse of options. Let's suppose you have 66 | # some rspec commands: 67 | # 68 | # class Rspec < Thor::Group 69 | # class_option :mock_framework, :type => :string, :default => :rr 70 | # 71 | # def invoke_mock_framework 72 | # invoke "rspec:#{options[:mock_framework]}" 73 | # end 74 | # end 75 | # 76 | # As you noticed, it invokes the given mock framework, which might have its 77 | # own options: 78 | # 79 | # class Rspec::RR < Thor::Group 80 | # class_option :style, :type => :string, :default => :mock 81 | # end 82 | # 83 | # Since it's not rspec concern to parse mock framework options, when RR 84 | # is invoked all options are parsed again, so RR can extract only the options 85 | # that it's going to use. 86 | # 87 | # If you want Rspec::RR to be initialized with its own set of options, you 88 | # have to do that explicitly: 89 | # 90 | # invoke "rspec:rr", [], :style => :foo 91 | # 92 | # Besides giving an instance, you can also give a class to invoke: 93 | # 94 | # invoke Rspec::RR, [], :style => :foo 95 | # 96 | def invoke(name=nil, *args) 97 | if name.nil? 98 | warn "[Thor] Calling invoke() without argument is deprecated. Please use invoke_all instead.\n#{caller.join("\n")}" 99 | return invoke_all 100 | end 101 | 102 | args.unshift(nil) if Array === args.first || NilClass === args.first 103 | command, args, opts, config = args 104 | 105 | klass, command = _retrieve_class_and_command(name, command) 106 | raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base 107 | 108 | args, opts, config = _parse_initialization_options(args, opts, config) 109 | klass.send(:dispatch, command, args, opts, config) do |instance| 110 | instance.parent_options = options 111 | end 112 | end 113 | 114 | # Invoke the given command if the given args. 115 | def invoke_command(command, *args) #:nodoc: 116 | current = @_invocations[self.class] 117 | 118 | unless current.include?(command.name) 119 | current << command.name 120 | command.run(self, *args) 121 | end 122 | end 123 | alias invoke_task invoke_command 124 | 125 | # Invoke all commands for the current instance. 126 | def invoke_all #:nodoc: 127 | self.class.all_commands.map { |_, command| invoke_command(command) } 128 | end 129 | 130 | # Invokes using shell padding. 131 | def invoke_with_padding(*args) 132 | with_padding { invoke(*args) } 133 | end 134 | 135 | protected 136 | 137 | # Configuration values that are shared between invocations. 138 | def _shared_configuration #:nodoc: 139 | { :invocations => @_invocations } 140 | end 141 | 142 | # This method simply retrieves the class and command to be invoked. 143 | # If the name is nil or the given name is a command in the current class, 144 | # use the given name and return self as class. Otherwise, call 145 | # prepare_for_invocation in the current class. 146 | def _retrieve_class_and_command(name, sent_command=nil) #:nodoc: 147 | case 148 | when name.nil? 149 | [self.class, nil] 150 | when self.class.all_commands[name.to_s] 151 | [self.class, name.to_s] 152 | else 153 | klass, command = self.class.prepare_for_invocation(nil, name) 154 | [klass, command || sent_command] 155 | end 156 | end 157 | alias _retrieve_class_and_task _retrieve_class_and_command 158 | 159 | # Initialize klass using values stored in the @_initializer. 160 | def _parse_initialization_options(args, opts, config) #:nodoc: 161 | stored_args, stored_opts, stored_config = @_initializer 162 | 163 | args ||= stored_args.dup 164 | opts ||= stored_opts.dup 165 | 166 | config ||= {} 167 | config = stored_config.merge(_shared_configuration).merge!(config) 168 | 169 | [ args, opts, config ] 170 | end 171 | end 172 | end 173 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/parser.rb: -------------------------------------------------------------------------------- 1 | require 'thor/parser/argument' 2 | require 'thor/parser/arguments' 3 | require 'thor/parser/option' 4 | require 'thor/parser/options' 5 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/parser/argument.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | class Argument #:nodoc: 3 | VALID_TYPES = [ :numeric, :hash, :array, :string ] 4 | 5 | attr_reader :name, :description, :enum, :required, :type, :default, :banner 6 | alias :human_name :name 7 | 8 | def initialize(name, options={}) 9 | class_name = self.class.name.split("::").last 10 | 11 | type = options[:type] 12 | 13 | raise ArgumentError, "#{class_name} name can't be nil." if name.nil? 14 | raise ArgumentError, "Type :#{type} is not valid for #{class_name.downcase}s." if type && !valid_type?(type) 15 | 16 | @name = name.to_s 17 | @description = options[:desc] 18 | @required = options.key?(:required) ? options[:required] : true 19 | @type = (type || :string).to_sym 20 | @default = options[:default] 21 | @banner = options[:banner] || default_banner 22 | @enum = options[:enum] 23 | 24 | validate! # Trigger specific validations 25 | end 26 | 27 | def usage 28 | required? ? banner : "[#{banner}]" 29 | end 30 | 31 | def required? 32 | required 33 | end 34 | 35 | def show_default? 36 | case default 37 | when Array, String, Hash 38 | !default.empty? 39 | else 40 | default 41 | end 42 | end 43 | 44 | protected 45 | 46 | def validate! 47 | if required? && !default.nil? 48 | raise ArgumentError, "An argument cannot be required and have default value." 49 | elsif @enum && !@enum.is_a?(Array) 50 | raise ArgumentError, "An argument cannot have an enum other than an array." 51 | end 52 | end 53 | 54 | def valid_type?(type) 55 | self.class::VALID_TYPES.include?(type.to_sym) 56 | end 57 | 58 | def default_banner 59 | case type 60 | when :boolean 61 | nil 62 | when :string, :default 63 | human_name.upcase 64 | when :numeric 65 | "N" 66 | when :hash 67 | "key:value" 68 | when :array 69 | "one two three" 70 | end 71 | end 72 | 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/parser/arguments.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | class Arguments #:nodoc: 3 | NUMERIC = /(\d*\.\d+|\d+)/ 4 | 5 | # Receives an array of args and returns two arrays, one with arguments 6 | # and one with switches. 7 | # 8 | def self.split(args) 9 | arguments = [] 10 | 11 | args.each do |item| 12 | break if item =~ /^-/ 13 | arguments << item 14 | end 15 | 16 | return arguments, args[Range.new(arguments.size, -1)] 17 | end 18 | 19 | def self.parse(*args) 20 | to_parse = args.pop 21 | new(*args).parse(to_parse) 22 | end 23 | 24 | # Takes an array of Thor::Argument objects. 25 | # 26 | def initialize(arguments=[]) 27 | @assigns, @non_assigned_required = {}, [] 28 | @switches = arguments 29 | 30 | arguments.each do |argument| 31 | if argument.default != nil 32 | @assigns[argument.human_name] = argument.default 33 | elsif argument.required? 34 | @non_assigned_required << argument 35 | end 36 | end 37 | end 38 | 39 | def parse(args) 40 | @pile = args.dup 41 | 42 | @switches.each do |argument| 43 | break unless peek 44 | @non_assigned_required.delete(argument) 45 | @assigns[argument.human_name] = send(:"parse_#{argument.type}", argument.human_name) 46 | end 47 | 48 | check_requirement! 49 | @assigns 50 | end 51 | 52 | def remaining 53 | @pile 54 | end 55 | 56 | private 57 | 58 | def no_or_skip?(arg) 59 | arg =~ /^--(no|skip)-([-\w]+)$/ 60 | $2 61 | end 62 | 63 | def last? 64 | @pile.empty? 65 | end 66 | 67 | def peek 68 | @pile.first 69 | end 70 | 71 | def shift 72 | @pile.shift 73 | end 74 | 75 | def unshift(arg) 76 | unless arg.kind_of?(Array) 77 | @pile.unshift(arg) 78 | else 79 | @pile = arg + @pile 80 | end 81 | end 82 | 83 | def current_is_value? 84 | peek && peek.to_s !~ /^-/ 85 | end 86 | 87 | # Runs through the argument array getting strings that contains ":" and 88 | # mark it as a hash: 89 | # 90 | # [ "name:string", "age:integer" ] 91 | # 92 | # Becomes: 93 | # 94 | # { "name" => "string", "age" => "integer" } 95 | # 96 | def parse_hash(name) 97 | return shift if peek.is_a?(Hash) 98 | hash = {} 99 | 100 | while current_is_value? && peek.include?(?:) 101 | key, value = shift.split(':',2) 102 | hash[key] = value 103 | end 104 | hash 105 | end 106 | 107 | # Runs through the argument array getting all strings until no string is 108 | # found or a switch is found. 109 | # 110 | # ["a", "b", "c"] 111 | # 112 | # And returns it as an array: 113 | # 114 | # ["a", "b", "c"] 115 | # 116 | def parse_array(name) 117 | return shift if peek.is_a?(Array) 118 | array = [] 119 | 120 | while current_is_value? 121 | array << shift 122 | end 123 | array 124 | end 125 | 126 | # Check if the peek is numeric format and return a Float or Integer. 127 | # Otherwise raises an error. 128 | # 129 | def parse_numeric(name) 130 | return shift if peek.is_a?(Numeric) 131 | 132 | unless peek =~ NUMERIC && $& == peek 133 | raise MalformattedArgumentError, "Expected numeric value for '#{name}'; got #{peek.inspect}" 134 | end 135 | 136 | $&.index('.') ? shift.to_f : shift.to_i 137 | end 138 | 139 | # Parse string: 140 | # for --string-arg, just return the current value in the pile 141 | # for --no-string-arg, nil 142 | # 143 | def parse_string(name) 144 | if no_or_skip?(name) 145 | nil 146 | else 147 | value = shift 148 | if @switches.is_a?(Hash) && switch = @switches[name] 149 | if switch.enum && !switch.enum.include?(value) 150 | raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}" 151 | end 152 | end 153 | value 154 | end 155 | end 156 | 157 | # Raises an error if @non_assigned_required array is not empty. 158 | # 159 | def check_requirement! 160 | unless @non_assigned_required.empty? 161 | names = @non_assigned_required.map do |o| 162 | o.respond_to?(:switch_name) ? o.switch_name : o.human_name 163 | end.join("', '") 164 | 165 | class_name = self.class.name.split('::').last.downcase 166 | raise RequiredArgumentMissingError, "No value provided for required #{class_name} '#{names}'" 167 | end 168 | end 169 | 170 | end 171 | end 172 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/parser/option.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | class Option < Argument #:nodoc: 3 | attr_reader :aliases, :group, :lazy_default, :hide 4 | 5 | VALID_TYPES = [:boolean, :numeric, :hash, :array, :string] 6 | 7 | def initialize(name, options={}) 8 | options[:required] = false unless options.key?(:required) 9 | super 10 | @lazy_default = options[:lazy_default] 11 | @group = options[:group].to_s.capitalize if options[:group] 12 | @aliases = Array(options[:aliases]) 13 | @hide = options[:hide] 14 | end 15 | 16 | # This parse quick options given as method_options. It makes several 17 | # assumptions, but you can be more specific using the option method. 18 | # 19 | # parse :foo => "bar" 20 | # #=> Option foo with default value bar 21 | # 22 | # parse [:foo, :baz] => "bar" 23 | # #=> Option foo with default value bar and alias :baz 24 | # 25 | # parse :foo => :required 26 | # #=> Required option foo without default value 27 | # 28 | # parse :foo => 2 29 | # #=> Option foo with default value 2 and type numeric 30 | # 31 | # parse :foo => :numeric 32 | # #=> Option foo without default value and type numeric 33 | # 34 | # parse :foo => true 35 | # #=> Option foo with default value true and type boolean 36 | # 37 | # The valid types are :boolean, :numeric, :hash, :array and :string. If none 38 | # is given a default type is assumed. This default type accepts arguments as 39 | # string (--foo=value) or booleans (just --foo). 40 | # 41 | # By default all options are optional, unless :required is given. 42 | # 43 | def self.parse(key, value) 44 | if key.is_a?(Array) 45 | name, *aliases = key 46 | else 47 | name, aliases = key, [] 48 | end 49 | 50 | name = name.to_s 51 | default = value 52 | 53 | type = case value 54 | when Symbol 55 | default = nil 56 | if VALID_TYPES.include?(value) 57 | value 58 | elsif required = (value == :required) 59 | :string 60 | end 61 | when TrueClass, FalseClass 62 | :boolean 63 | when Numeric 64 | :numeric 65 | when Hash, Array, String 66 | value.class.name.downcase.to_sym 67 | end 68 | self.new(name.to_s, :required => required, :type => type, :default => default, :aliases => aliases) 69 | end 70 | 71 | def switch_name 72 | @switch_name ||= dasherized? ? name : dasherize(name) 73 | end 74 | 75 | def human_name 76 | @human_name ||= dasherized? ? undasherize(name) : name 77 | end 78 | 79 | def usage(padding=0) 80 | sample = if banner && !banner.to_s.empty? 81 | "#{switch_name}=#{banner}" 82 | else 83 | switch_name 84 | end 85 | 86 | sample = "[#{sample}]" unless required? 87 | 88 | if aliases.empty? 89 | (" " * padding) << sample 90 | else 91 | "#{aliases.join(', ')}, #{sample}" 92 | end 93 | end 94 | 95 | VALID_TYPES.each do |type| 96 | class_eval <<-RUBY, __FILE__, __LINE__ + 1 97 | def #{type}? 98 | self.type == #{type.inspect} 99 | end 100 | RUBY 101 | end 102 | 103 | protected 104 | 105 | def validate! 106 | raise ArgumentError, "An option cannot be boolean and required." if boolean? && required? 107 | end 108 | 109 | def dasherized? 110 | name.index('-') == 0 111 | end 112 | 113 | def undasherize(str) 114 | str.sub(/^-{1,2}/, '') 115 | end 116 | 117 | def dasherize(str) 118 | (str.length > 1 ? "--" : "-") + str.gsub('_', '-') 119 | end 120 | end 121 | end 122 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/parser/options.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | class Options < Arguments #:nodoc: 3 | LONG_RE = /^(--\w+(?:-\w+)*)$/ 4 | SHORT_RE = /^(-[a-z])$/i 5 | EQ_RE = /^(--\w+(?:-\w+)*|-[a-z])=(.*)$/i 6 | SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args 7 | SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i 8 | OPTS_END = '--'.freeze 9 | 10 | # Receives a hash and makes it switches. 11 | def self.to_switches(options) 12 | options.map do |key, value| 13 | case value 14 | when true 15 | "--#{key}" 16 | when Array 17 | "--#{key} #{value.map{ |v| v.inspect }.join(' ')}" 18 | when Hash 19 | "--#{key} #{value.map{ |k,v| "#{k}:#{v}" }.join(' ')}" 20 | when nil, false 21 | "" 22 | else 23 | "--#{key} #{value.inspect}" 24 | end 25 | end.join(" ") 26 | end 27 | 28 | # Takes a hash of Thor::Option and a hash with defaults. 29 | # 30 | # If +stop_on_unknown+ is true, #parse will stop as soon as it encounters 31 | # an unknown option or a regular argument. 32 | def initialize(hash_options={}, defaults={}, stop_on_unknown=false) 33 | @stop_on_unknown = stop_on_unknown 34 | options = hash_options.values 35 | super(options) 36 | 37 | # Add defaults 38 | defaults.each do |key, value| 39 | @assigns[key.to_s] = value 40 | @non_assigned_required.delete(hash_options[key]) 41 | end 42 | 43 | @shorts, @switches, @extra = {}, {}, [] 44 | 45 | options.each do |option| 46 | @switches[option.switch_name] = option 47 | 48 | option.aliases.each do |short| 49 | name = short.to_s.sub(/^(?!\-)/, '-') 50 | @shorts[name] ||= option.switch_name 51 | end 52 | end 53 | end 54 | 55 | def remaining 56 | @extra 57 | end 58 | 59 | def peek 60 | return super unless @parsing_options 61 | 62 | result = super 63 | if result == OPTS_END 64 | shift 65 | @parsing_options = false 66 | super 67 | else 68 | result 69 | end 70 | end 71 | 72 | def parse(args) 73 | @pile = args.dup 74 | @parsing_options = true 75 | 76 | while peek 77 | if parsing_options? 78 | match, is_switch = current_is_switch? 79 | shifted = shift 80 | 81 | if is_switch 82 | case shifted 83 | when SHORT_SQ_RE 84 | unshift($1.split('').map { |f| "-#{f}" }) 85 | next 86 | when EQ_RE, SHORT_NUM 87 | unshift($2) 88 | switch = $1 89 | when LONG_RE, SHORT_RE 90 | switch = $1 91 | end 92 | 93 | switch = normalize_switch(switch) 94 | option = switch_option(switch) 95 | @assigns[option.human_name] = parse_peek(switch, option) 96 | elsif @stop_on_unknown 97 | @parsing_options = false 98 | @extra << shifted 99 | @extra << shift while peek 100 | break 101 | elsif match 102 | @extra << shifted 103 | @extra << shift while peek && peek !~ /^-/ 104 | else 105 | @extra << shifted 106 | end 107 | else 108 | @extra << shift 109 | end 110 | end 111 | 112 | check_requirement! 113 | 114 | assigns = Thor::CoreExt::HashWithIndifferentAccess.new(@assigns) 115 | assigns.freeze 116 | assigns 117 | end 118 | 119 | def check_unknown! 120 | # an unknown option starts with - or -- and has no more --'s afterward. 121 | unknown = @extra.select { |str| str =~ /^--?(?:(?!--).)*$/ } 122 | raise UnknownArgumentError, "Unknown switches '#{unknown.join(', ')}'" unless unknown.empty? 123 | end 124 | 125 | protected 126 | 127 | # Check if the current value in peek is a registered switch. 128 | # 129 | # Two booleans are returned. The first is true if the current value 130 | # starts with a hyphen; the second is true if it is a registered switch. 131 | def current_is_switch? 132 | case peek 133 | when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM 134 | [true, switch?($1)] 135 | when SHORT_SQ_RE 136 | [true, $1.split('').any? { |f| switch?("-#{f}") }] 137 | else 138 | [false, false] 139 | end 140 | end 141 | 142 | def current_is_switch_formatted? 143 | case peek 144 | when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE 145 | true 146 | else 147 | false 148 | end 149 | end 150 | 151 | def current_is_value? 152 | peek && (!parsing_options? || super) 153 | end 154 | 155 | def switch?(arg) 156 | switch_option(normalize_switch(arg)) 157 | end 158 | 159 | def switch_option(arg) 160 | if match = no_or_skip?(arg) 161 | @switches[arg] || @switches["--#{match}"] 162 | else 163 | @switches[arg] 164 | end 165 | end 166 | 167 | # Check if the given argument is actually a shortcut. 168 | # 169 | def normalize_switch(arg) 170 | (@shorts[arg] || arg).tr('_', '-') 171 | end 172 | 173 | def parsing_options? 174 | peek 175 | @parsing_options 176 | end 177 | 178 | # Parse boolean values which can be given as --foo=true, --foo or --no-foo. 179 | # 180 | def parse_boolean(switch) 181 | if current_is_value? 182 | if ["true", "TRUE", "t", "T", true].include?(peek) 183 | shift 184 | true 185 | elsif ["false", "FALSE", "f", "F", false].include?(peek) 186 | shift 187 | false 188 | else 189 | true 190 | end 191 | else 192 | @switches.key?(switch) || !no_or_skip?(switch) 193 | end 194 | end 195 | 196 | # Parse the value at the peek analyzing if it requires an input or not. 197 | # 198 | def parse_peek(switch, option) 199 | if parsing_options? && (current_is_switch_formatted? || last?) 200 | if option.boolean? 201 | # No problem for boolean types 202 | elsif no_or_skip?(switch) 203 | return nil # User set value to nil 204 | elsif option.string? && !option.required? 205 | # Return the default if there is one, else the human name 206 | return option.lazy_default || option.default || option.human_name 207 | elsif option.lazy_default 208 | return option.lazy_default 209 | else 210 | raise MalformattedArgumentError, "No value provided for option '#{switch}'" 211 | end 212 | end 213 | 214 | @non_assigned_required.delete(option) 215 | send(:"parse_#{option.type}", switch) 216 | end 217 | end 218 | end 219 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/rake_compat.rb: -------------------------------------------------------------------------------- 1 | require 'rake' 2 | require 'rake/dsl_definition' 3 | 4 | class Thor 5 | # Adds a compatibility layer to your Thor classes which allows you to use 6 | # rake package tasks. For example, to use rspec rake tasks, one can do: 7 | # 8 | # require 'thor/rake_compat' 9 | # require 'rspec/core/rake_task' 10 | # 11 | # class Default < Thor 12 | # include Thor::RakeCompat 13 | # 14 | # RSpec::Core::RakeTask.new(:spec) do |t| 15 | # t.spec_opts = ['--options', "./.rspec"] 16 | # t.spec_files = FileList['spec/**/*_spec.rb'] 17 | # end 18 | # end 19 | # 20 | module RakeCompat 21 | include Rake::DSL if defined?(Rake::DSL) 22 | 23 | def self.rake_classes 24 | @rake_classes ||= [] 25 | end 26 | 27 | def self.included(base) 28 | # Hack. Make rakefile point to invoker, so rdoc task is generated properly. 29 | rakefile = File.basename(caller[0].match(/(.*):\d+/)[1]) 30 | Rake.application.instance_variable_set(:@rakefile, rakefile) 31 | self.rake_classes << base 32 | end 33 | end 34 | end 35 | 36 | # override task on (main), for compatibility with Rake 0.9 37 | self.instance_eval do 38 | alias rake_namespace namespace 39 | 40 | def task(*) 41 | task = super 42 | 43 | if klass = Thor::RakeCompat.rake_classes.last 44 | non_namespaced_name = task.name.split(':').last 45 | 46 | description = non_namespaced_name 47 | description << task.arg_names.map{ |n| n.to_s.upcase }.join(' ') 48 | description.strip! 49 | 50 | klass.desc description, Rake.application.last_description || non_namespaced_name 51 | Rake.application.last_description = nil 52 | klass.send :define_method, non_namespaced_name do |*args| 53 | Rake::Task[task.name.to_sym].invoke(*args) 54 | end 55 | end 56 | 57 | task 58 | end 59 | 60 | def namespace(name) 61 | if klass = Thor::RakeCompat.rake_classes.last 62 | const_name = Thor::Util.camel_case(name.to_s).to_sym 63 | klass.const_set(const_name, Class.new(Thor)) 64 | new_klass = klass.const_get(const_name) 65 | Thor::RakeCompat.rake_classes << new_klass 66 | end 67 | 68 | super 69 | Thor::RakeCompat.rake_classes.pop 70 | end 71 | end 72 | 73 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/runner.rb: -------------------------------------------------------------------------------- 1 | require 'thor' 2 | require 'thor/group' 3 | require 'thor/core_ext/io_binary_read' 4 | 5 | require 'fileutils' 6 | require 'open-uri' 7 | require 'yaml' 8 | require 'digest/md5' 9 | require 'pathname' 10 | 11 | class Thor::Runner < Thor #:nodoc: 12 | map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version 13 | 14 | # Override Thor#help so it can give information about any class and any method. 15 | # 16 | def help(meth = nil) 17 | if meth && !self.respond_to?(meth) 18 | initialize_thorfiles(meth) 19 | klass, command = Thor::Util.find_class_and_command_by_namespace(meth) 20 | self.class.handle_no_command_error(command, false) if klass.nil? 21 | klass.start(["-h", command].compact, :shell => self.shell) 22 | else 23 | super 24 | end 25 | end 26 | 27 | # If a command is not found on Thor::Runner, method missing is invoked and 28 | # Thor::Runner is then responsible for finding the command in all classes. 29 | # 30 | def method_missing(meth, *args) 31 | meth = meth.to_s 32 | initialize_thorfiles(meth) 33 | klass, command = Thor::Util.find_class_and_command_by_namespace(meth) 34 | self.class.handle_no_command_error(command, false) if klass.nil? 35 | args.unshift(command) if command 36 | klass.start(args, :shell => self.shell) 37 | end 38 | 39 | desc "install NAME", "Install an optionally named Thor file into your system commands" 40 | method_options :as => :string, :relative => :boolean, :force => :boolean 41 | def install(name) 42 | initialize_thorfiles 43 | 44 | # If a directory name is provided as the argument, look for a 'main.thor' 45 | # command in said directory. 46 | begin 47 | if File.directory?(File.expand_path(name)) 48 | base, package = File.join(name, "main.thor"), :directory 49 | contents = open(base) {|input| input.read } 50 | else 51 | base, package = name, :file 52 | contents = open(name) {|input| input.read } 53 | end 54 | rescue OpenURI::HTTPError 55 | raise Error, "Error opening URI '#{name}'" 56 | rescue Errno::ENOENT 57 | raise Error, "Error opening file '#{name}'" 58 | end 59 | 60 | say "Your Thorfile contains:" 61 | say contents 62 | 63 | unless options["force"] 64 | return false if no?("Do you wish to continue [y/N]?") 65 | end 66 | 67 | as = options["as"] || begin 68 | first_line = contents.split("\n")[0] 69 | (match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil 70 | end 71 | 72 | unless as 73 | basename = File.basename(name) 74 | as = ask("Please specify a name for #{name} in the system repository [#{basename}]:") 75 | as = basename if as.empty? 76 | end 77 | 78 | location = if options[:relative] || name =~ /^https?:\/\// 79 | name 80 | else 81 | File.expand_path(name) 82 | end 83 | 84 | thor_yaml[as] = { 85 | :filename => Digest::MD5.hexdigest(name + as), 86 | :location => location, 87 | :namespaces => Thor::Util.namespaces_in_content(contents, base) 88 | } 89 | 90 | save_yaml(thor_yaml) 91 | say "Storing thor file in your system repository" 92 | destination = File.join(thor_root, thor_yaml[as][:filename]) 93 | 94 | if package == :file 95 | File.open(destination, "w") { |f| f.puts contents } 96 | else 97 | FileUtils.cp_r(name, destination) 98 | end 99 | 100 | thor_yaml[as][:filename] # Indicate success 101 | end 102 | 103 | desc "version", "Show Thor version" 104 | def version 105 | require 'thor/version' 106 | say "Thor #{Thor::VERSION}" 107 | end 108 | 109 | desc "uninstall NAME", "Uninstall a named Thor module" 110 | def uninstall(name) 111 | raise Error, "Can't find module '#{name}'" unless thor_yaml[name] 112 | say "Uninstalling #{name}." 113 | FileUtils.rm_rf(File.join(thor_root, "#{thor_yaml[name][:filename]}")) 114 | 115 | thor_yaml.delete(name) 116 | save_yaml(thor_yaml) 117 | 118 | puts "Done." 119 | end 120 | 121 | desc "update NAME", "Update a Thor file from its original location" 122 | def update(name) 123 | raise Error, "Can't find module '#{name}'" if !thor_yaml[name] || !thor_yaml[name][:location] 124 | 125 | say "Updating '#{name}' from #{thor_yaml[name][:location]}" 126 | 127 | old_filename = thor_yaml[name][:filename] 128 | self.options = self.options.merge("as" => name) 129 | 130 | if File.directory? File.expand_path(name) 131 | FileUtils.rm_rf(File.join(thor_root, old_filename)) 132 | 133 | thor_yaml.delete(old_filename) 134 | save_yaml(thor_yaml) 135 | 136 | filename = install(name) 137 | else 138 | filename = install(thor_yaml[name][:location]) 139 | end 140 | 141 | unless filename == old_filename 142 | File.delete(File.join(thor_root, old_filename)) 143 | end 144 | end 145 | 146 | desc "installed", "List the installed Thor modules and commands" 147 | method_options :internal => :boolean 148 | def installed 149 | initialize_thorfiles(nil, true) 150 | display_klasses(true, options["internal"]) 151 | end 152 | 153 | desc "list [SEARCH]", "List the available thor commands (--substring means .*SEARCH)" 154 | method_options :substring => :boolean, :group => :string, :all => :boolean, :debug => :boolean 155 | def list(search="") 156 | initialize_thorfiles 157 | 158 | search = ".*#{search}" if options["substring"] 159 | search = /^#{search}.*/i 160 | group = options[:group] || "standard" 161 | 162 | klasses = Thor::Base.subclasses.select do |k| 163 | (options[:all] || k.group == group) && k.namespace =~ search 164 | end 165 | 166 | display_klasses(false, false, klasses) 167 | end 168 | 169 | private 170 | 171 | def self.banner(command, all = false, subcommand = false) 172 | "thor " + command.formatted_usage(self, all, subcommand) 173 | end 174 | 175 | def thor_root 176 | Thor::Util.thor_root 177 | end 178 | 179 | def thor_yaml 180 | @thor_yaml ||= begin 181 | yaml_file = File.join(thor_root, "thor.yml") 182 | yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file) 183 | yaml || {} 184 | end 185 | end 186 | 187 | # Save the yaml file. If none exists in thor root, creates one. 188 | # 189 | def save_yaml(yaml) 190 | yaml_file = File.join(thor_root, "thor.yml") 191 | 192 | unless File.exists?(yaml_file) 193 | FileUtils.mkdir_p(thor_root) 194 | yaml_file = File.join(thor_root, "thor.yml") 195 | FileUtils.touch(yaml_file) 196 | end 197 | 198 | File.open(yaml_file, "w") { |f| f.puts yaml.to_yaml } 199 | end 200 | 201 | def self.exit_on_failure? 202 | true 203 | end 204 | 205 | # Load the Thorfiles. If relevant_to is supplied, looks for specific files 206 | # in the thor_root instead of loading them all. 207 | # 208 | # By default, it also traverses the current path until find Thor files, as 209 | # described in thorfiles. This look up can be skipped by suppliying 210 | # skip_lookup true. 211 | # 212 | def initialize_thorfiles(relevant_to=nil, skip_lookup=false) 213 | thorfiles(relevant_to, skip_lookup).each do |f| 214 | Thor::Util.load_thorfile(f, nil, options[:debug]) unless Thor::Base.subclass_files.keys.include?(File.expand_path(f)) 215 | end 216 | end 217 | 218 | # Finds Thorfiles by traversing from your current directory down to the root 219 | # directory of your system. If at any time we find a Thor file, we stop. 220 | # 221 | # We also ensure that system-wide Thorfiles are loaded first, so local 222 | # Thorfiles can override them. 223 | # 224 | # ==== Example 225 | # 226 | # If we start at /Users/wycats/dev/thor ... 227 | # 228 | # 1. /Users/wycats/dev/thor 229 | # 2. /Users/wycats/dev 230 | # 3. /Users/wycats <-- we find a Thorfile here, so we stop 231 | # 232 | # Suppose we start at c:\Documents and Settings\james\dev\thor ... 233 | # 234 | # 1. c:\Documents and Settings\james\dev\thor 235 | # 2. c:\Documents and Settings\james\dev 236 | # 3. c:\Documents and Settings\james 237 | # 4. c:\Documents and Settings 238 | # 5. c:\ <-- no Thorfiles found! 239 | # 240 | def thorfiles(relevant_to=nil, skip_lookup=false) 241 | thorfiles = [] 242 | 243 | unless skip_lookup 244 | Pathname.pwd.ascend do |path| 245 | thorfiles = Thor::Util.globs_for(path).map { |g| Dir[g] }.flatten 246 | break unless thorfiles.empty? 247 | end 248 | end 249 | 250 | files = (relevant_to ? thorfiles_relevant_to(relevant_to) : Thor::Util.thor_root_glob) 251 | files += thorfiles 252 | files -= ["#{thor_root}/thor.yml"] 253 | 254 | files.map! do |file| 255 | File.directory?(file) ? File.join(file, "main.thor") : file 256 | end 257 | end 258 | 259 | # Load Thorfiles relevant to the given method. If you provide "foo:bar" it 260 | # will load all thor files in the thor.yaml that has "foo" e "foo:bar" 261 | # namespaces registered. 262 | # 263 | def thorfiles_relevant_to(meth) 264 | lookup = [ meth, meth.split(":")[0...-1].join(":") ] 265 | 266 | files = thor_yaml.select do |k, v| 267 | v[:namespaces] && !(v[:namespaces] & lookup).empty? 268 | end 269 | 270 | files.map { |k, v| File.join(thor_root, "#{v[:filename]}") } 271 | end 272 | 273 | # Display information about the given klasses. If with_module is given, 274 | # it shows a table with information extracted from the yaml file. 275 | # 276 | def display_klasses(with_modules=false, show_internal=false, klasses=Thor::Base.subclasses) 277 | klasses -= [Thor, Thor::Runner, Thor::Group] unless show_internal 278 | 279 | raise Error, "No Thor commands available" if klasses.empty? 280 | show_modules if with_modules && !thor_yaml.empty? 281 | 282 | list = Hash.new { |h,k| h[k] = [] } 283 | groups = klasses.select { |k| k.ancestors.include?(Thor::Group) } 284 | 285 | # Get classes which inherit from Thor 286 | (klasses - groups).each { |k| list[k.namespace.split(":").first] += k.printable_commands(false) } 287 | 288 | # Get classes which inherit from Thor::Base 289 | groups.map! { |k| k.printable_commands(false).first } 290 | list["root"] = groups 291 | 292 | # Order namespaces with default coming first 293 | list = list.sort{ |a,b| a[0].sub(/^default/, '') <=> b[0].sub(/^default/, '') } 294 | list.each { |n, commands| display_commands(n, commands) unless commands.empty? } 295 | end 296 | 297 | def display_commands(namespace, list) #:nodoc: 298 | list.sort!{ |a,b| a[0] <=> b[0] } 299 | 300 | say shell.set_color(namespace, :blue, true) 301 | say "-" * namespace.size 302 | 303 | print_table(list, :truncate => true) 304 | say 305 | end 306 | alias display_tasks display_commands 307 | 308 | def show_modules #:nodoc: 309 | info = [] 310 | labels = ["Modules", "Namespaces"] 311 | 312 | info << labels 313 | info << [ "-" * labels[0].size, "-" * labels[1].size ] 314 | 315 | thor_yaml.each do |name, hash| 316 | info << [ name, hash[:namespaces].join(", ") ] 317 | end 318 | 319 | print_table info 320 | say "" 321 | end 322 | end 323 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/shell.rb: -------------------------------------------------------------------------------- 1 | require 'rbconfig' 2 | 3 | class Thor 4 | module Base 5 | # Returns the shell used in all Thor classes. If you are in a Unix platform 6 | # it will use a colored log, otherwise it will use a basic one without color. 7 | # 8 | def self.shell 9 | @shell ||= if ENV['THOR_SHELL'] && ENV['THOR_SHELL'].size > 0 10 | Thor::Shell.const_get(ENV['THOR_SHELL']) 11 | elsif ((RbConfig::CONFIG['host_os'] =~ /mswin|mingw/) && !(ENV['ANSICON'])) 12 | Thor::Shell::Basic 13 | else 14 | Thor::Shell::Color 15 | end 16 | end 17 | 18 | # Sets the shell used in all Thor classes. 19 | # 20 | def self.shell=(klass) 21 | @shell = klass 22 | end 23 | end 24 | 25 | module Shell 26 | SHELL_DELEGATED_METHODS = [:ask, :error, :set_color, :yes?, :no?, :say, :say_status, :print_in_columns, :print_table, :print_wrapped, :file_collision, :terminal_width] 27 | 28 | autoload :Basic, 'thor/shell/basic' 29 | autoload :Color, 'thor/shell/color' 30 | autoload :HTML, 'thor/shell/html' 31 | 32 | # Add shell to initialize config values. 33 | # 34 | # ==== Configuration 35 | # shell:: An instance of the shell to be used. 36 | # 37 | # ==== Examples 38 | # 39 | # class MyScript < Thor 40 | # argument :first, :type => :numeric 41 | # end 42 | # 43 | # MyScript.new [1.0], { :foo => :bar }, :shell => Thor::Shell::Basic.new 44 | # 45 | def initialize(args=[], options={}, config={}) 46 | super 47 | self.shell = config[:shell] 48 | self.shell.base ||= self if self.shell.respond_to?(:base) 49 | end 50 | 51 | # Holds the shell for the given Thor instance. If no shell is given, 52 | # it gets a default shell from Thor::Base.shell. 53 | def shell 54 | @shell ||= Thor::Base.shell.new 55 | end 56 | 57 | # Sets the shell for this thor class. 58 | def shell=(shell) 59 | @shell = shell 60 | end 61 | 62 | # Common methods that are delegated to the shell. 63 | SHELL_DELEGATED_METHODS.each do |method| 64 | module_eval <<-METHOD, __FILE__, __LINE__ 65 | def #{method}(*args,&block) 66 | shell.#{method}(*args,&block) 67 | end 68 | METHOD 69 | end 70 | 71 | # Yields the given block with padding. 72 | def with_padding 73 | shell.padding += 1 74 | yield 75 | ensure 76 | shell.padding -= 1 77 | end 78 | 79 | protected 80 | 81 | # Allow shell to be shared between invocations. 82 | # 83 | def _shared_configuration #:nodoc: 84 | super.merge!(:shell => self.shell) 85 | end 86 | 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/shell/color.rb: -------------------------------------------------------------------------------- 1 | require 'thor/shell/basic' 2 | 3 | class Thor 4 | module Shell 5 | # Inherit from Thor::Shell::Basic and add set_color behavior. Check 6 | # Thor::Shell::Basic to see all available methods. 7 | # 8 | class Color < Basic 9 | # Embed in a String to clear all previous ANSI sequences. 10 | CLEAR = "\e[0m" 11 | # The start of an ANSI bold sequence. 12 | BOLD = "\e[1m" 13 | 14 | # Set the terminal's foreground ANSI color to black. 15 | BLACK = "\e[30m" 16 | # Set the terminal's foreground ANSI color to red. 17 | RED = "\e[31m" 18 | # Set the terminal's foreground ANSI color to green. 19 | GREEN = "\e[32m" 20 | # Set the terminal's foreground ANSI color to yellow. 21 | YELLOW = "\e[33m" 22 | # Set the terminal's foreground ANSI color to blue. 23 | BLUE = "\e[34m" 24 | # Set the terminal's foreground ANSI color to magenta. 25 | MAGENTA = "\e[35m" 26 | # Set the terminal's foreground ANSI color to cyan. 27 | CYAN = "\e[36m" 28 | # Set the terminal's foreground ANSI color to white. 29 | WHITE = "\e[37m" 30 | 31 | # Set the terminal's background ANSI color to black. 32 | ON_BLACK = "\e[40m" 33 | # Set the terminal's background ANSI color to red. 34 | ON_RED = "\e[41m" 35 | # Set the terminal's background ANSI color to green. 36 | ON_GREEN = "\e[42m" 37 | # Set the terminal's background ANSI color to yellow. 38 | ON_YELLOW = "\e[43m" 39 | # Set the terminal's background ANSI color to blue. 40 | ON_BLUE = "\e[44m" 41 | # Set the terminal's background ANSI color to magenta. 42 | ON_MAGENTA = "\e[45m" 43 | # Set the terminal's background ANSI color to cyan. 44 | ON_CYAN = "\e[46m" 45 | # Set the terminal's background ANSI color to white. 46 | ON_WHITE = "\e[47m" 47 | 48 | # Set color by using a string or one of the defined constants. If a third 49 | # option is set to true, it also adds bold to the string. This is based 50 | # on Highline implementation and it automatically appends CLEAR to the end 51 | # of the returned String. 52 | # 53 | # Pass foreground, background and bold options to this method as 54 | # symbols. 55 | # 56 | # Example: 57 | # 58 | # set_color "Hi!", :red, :on_white, :bold 59 | # 60 | # The available colors are: 61 | # 62 | # :bold 63 | # :black 64 | # :red 65 | # :green 66 | # :yellow 67 | # :blue 68 | # :magenta 69 | # :cyan 70 | # :white 71 | # :on_black 72 | # :on_red 73 | # :on_green 74 | # :on_yellow 75 | # :on_blue 76 | # :on_magenta 77 | # :on_cyan 78 | # :on_white 79 | def set_color(string, *colors) 80 | if colors.all? { |color| color.is_a?(Symbol) || color.is_a?(String) } 81 | ansi_colors = colors.map { |color| lookup_color(color) } 82 | "#{ansi_colors.join}#{string}#{CLEAR}" 83 | else 84 | # The old API was `set_color(color, bold=boolean)`. We 85 | # continue to support the old API because you should never 86 | # break old APIs unnecessarily :P 87 | foreground, bold = colors 88 | foreground = self.class.const_get(foreground.to_s.upcase) if foreground.is_a?(Symbol) 89 | 90 | bold = bold ? BOLD : "" 91 | "#{bold}#{foreground}#{string}#{CLEAR}" 92 | end 93 | end 94 | 95 | protected 96 | 97 | def can_display_colors? 98 | stdout.tty? 99 | end 100 | 101 | # Overwrite show_diff to show diff with colors if Diff::LCS is 102 | # available. 103 | # 104 | def show_diff(destination, content) #:nodoc: 105 | if diff_lcs_loaded? && ENV['THOR_DIFF'].nil? && ENV['RAILS_DIFF'].nil? 106 | actual = File.binread(destination).to_s.split("\n") 107 | content = content.to_s.split("\n") 108 | 109 | Diff::LCS.sdiff(actual, content).each do |diff| 110 | output_diff_line(diff) 111 | end 112 | else 113 | super 114 | end 115 | end 116 | 117 | def output_diff_line(diff) #:nodoc: 118 | case diff.action 119 | when '-' 120 | say "- #{diff.old_element.chomp}", :red, true 121 | when '+' 122 | say "+ #{diff.new_element.chomp}", :green, true 123 | when '!' 124 | say "- #{diff.old_element.chomp}", :red, true 125 | say "+ #{diff.new_element.chomp}", :green, true 126 | else 127 | say " #{diff.old_element.chomp}", nil, true 128 | end 129 | end 130 | 131 | # Check if Diff::LCS is loaded. If it is, use it to create pretty output 132 | # for diff. 133 | # 134 | def diff_lcs_loaded? #:nodoc: 135 | return true if defined?(Diff::LCS) 136 | return @diff_lcs_loaded unless @diff_lcs_loaded.nil? 137 | 138 | @diff_lcs_loaded = begin 139 | require 'diff/lcs' 140 | true 141 | rescue LoadError 142 | false 143 | end 144 | end 145 | 146 | end 147 | end 148 | end 149 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/shell/html.rb: -------------------------------------------------------------------------------- 1 | require 'thor/shell/basic' 2 | 3 | class Thor 4 | module Shell 5 | # Inherit from Thor::Shell::Basic and add set_color behavior. Check 6 | # Thor::Shell::Basic to see all available methods. 7 | # 8 | class HTML < Basic 9 | # The start of an HTML bold sequence. 10 | BOLD = "font-weight: bold" 11 | 12 | # Set the terminal's foreground HTML color to black. 13 | BLACK = 'color: black' 14 | # Set the terminal's foreground HTML color to red. 15 | RED = 'color: red' 16 | # Set the terminal's foreground HTML color to green. 17 | GREEN = 'color: green' 18 | # Set the terminal's foreground HTML color to yellow. 19 | YELLOW = 'color: yellow' 20 | # Set the terminal's foreground HTML color to blue. 21 | BLUE = 'color: blue' 22 | # Set the terminal's foreground HTML color to magenta. 23 | MAGENTA = 'color: magenta' 24 | # Set the terminal's foreground HTML color to cyan. 25 | CYAN = 'color: cyan' 26 | # Set the terminal's foreground HTML color to white. 27 | WHITE = 'color: white' 28 | 29 | # Set the terminal's background HTML color to black. 30 | ON_BLACK = 'background-color: black' 31 | # Set the terminal's background HTML color to red. 32 | ON_RED = 'background-color: red' 33 | # Set the terminal's background HTML color to green. 34 | ON_GREEN = 'background-color: green' 35 | # Set the terminal's background HTML color to yellow. 36 | ON_YELLOW = 'background-color: yellow' 37 | # Set the terminal's background HTML color to blue. 38 | ON_BLUE = 'background-color: blue' 39 | # Set the terminal's background HTML color to magenta. 40 | ON_MAGENTA = 'background-color: magenta' 41 | # Set the terminal's background HTML color to cyan. 42 | ON_CYAN = 'background-color: cyan' 43 | # Set the terminal's background HTML color to white. 44 | ON_WHITE = 'background-color: white' 45 | 46 | # Set color by using a string or one of the defined constants. If a third 47 | # option is set to true, it also adds bold to the string. This is based 48 | # on Highline implementation and it automatically appends CLEAR to the end 49 | # of the returned String. 50 | # 51 | def set_color(string, *colors) 52 | if colors.all? { |color| color.is_a?(Symbol) || color.is_a?(String) } 53 | html_colors = colors.map { |color| lookup_color(color) } 54 | "#{string}" 55 | else 56 | color, bold = colors 57 | html_color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol) 58 | styles = [html_color] 59 | styles << BOLD if bold 60 | "#{string}" 61 | end 62 | end 63 | 64 | # Ask something to the user and receives a response. 65 | # 66 | # ==== Example 67 | # ask("What is your name?") 68 | # 69 | # TODO: Implement #ask for Thor::Shell::HTML 70 | def ask(statement, color=nil) 71 | raise NotImplementedError, "Implement #ask for Thor::Shell::HTML" 72 | end 73 | 74 | protected 75 | 76 | def can_display_colors? 77 | true 78 | end 79 | 80 | # Overwrite show_diff to show diff with colors if Diff::LCS is 81 | # available. 82 | # 83 | def show_diff(destination, content) #:nodoc: 84 | if diff_lcs_loaded? && ENV['THOR_DIFF'].nil? && ENV['RAILS_DIFF'].nil? 85 | actual = File.binread(destination).to_s.split("\n") 86 | content = content.to_s.split("\n") 87 | 88 | Diff::LCS.sdiff(actual, content).each do |diff| 89 | output_diff_line(diff) 90 | end 91 | else 92 | super 93 | end 94 | end 95 | 96 | def output_diff_line(diff) #:nodoc: 97 | case diff.action 98 | when '-' 99 | say "- #{diff.old_element.chomp}", :red, true 100 | when '+' 101 | say "+ #{diff.new_element.chomp}", :green, true 102 | when '!' 103 | say "- #{diff.old_element.chomp}", :red, true 104 | say "+ #{diff.new_element.chomp}", :green, true 105 | else 106 | say " #{diff.old_element.chomp}", nil, true 107 | end 108 | end 109 | 110 | # Check if Diff::LCS is loaded. If it is, use it to create pretty output 111 | # for diff. 112 | # 113 | def diff_lcs_loaded? #:nodoc: 114 | return true if defined?(Diff::LCS) 115 | return @diff_lcs_loaded unless @diff_lcs_loaded.nil? 116 | 117 | @diff_lcs_loaded = begin 118 | require 'diff/lcs' 119 | true 120 | rescue LoadError 121 | false 122 | end 123 | end 124 | 125 | end 126 | end 127 | end 128 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/util.rb: -------------------------------------------------------------------------------- 1 | require 'rbconfig' 2 | 3 | class Thor 4 | module Sandbox #:nodoc: 5 | end 6 | 7 | # This module holds several utilities: 8 | # 9 | # 1) Methods to convert thor namespaces to constants and vice-versa. 10 | # 11 | # Thor::Util.namespace_from_thor_class(Foo::Bar::Baz) #=> "foo:bar:baz" 12 | # 13 | # 2) Loading thor files and sandboxing: 14 | # 15 | # Thor::Util.load_thorfile("~/.thor/foo") 16 | # 17 | module Util 18 | 19 | class << self 20 | 21 | # Receives a namespace and search for it in the Thor::Base subclasses. 22 | # 23 | # ==== Parameters 24 | # namespace:: The namespace to search for. 25 | # 26 | def find_by_namespace(namespace) 27 | namespace = "default#{namespace}" if namespace.empty? || namespace =~ /^:/ 28 | Thor::Base.subclasses.find { |klass| klass.namespace == namespace } 29 | end 30 | 31 | # Receives a constant and converts it to a Thor namespace. Since Thor 32 | # commands can be added to a sandbox, this method is also responsable for 33 | # removing the sandbox namespace. 34 | # 35 | # This method should not be used in general because it's used to deal with 36 | # older versions of Thor. On current versions, if you need to get the 37 | # namespace from a class, just call namespace on it. 38 | # 39 | # ==== Parameters 40 | # constant:: The constant to be converted to the thor path. 41 | # 42 | # ==== Returns 43 | # String:: If we receive Foo::Bar::Baz it returns "foo:bar:baz" 44 | # 45 | def namespace_from_thor_class(constant) 46 | constant = constant.to_s.gsub(/^Thor::Sandbox::/, "") 47 | constant = snake_case(constant).squeeze(":") 48 | constant 49 | end 50 | 51 | # Given the contents, evaluate it inside the sandbox and returns the 52 | # namespaces defined in the sandbox. 53 | # 54 | # ==== Parameters 55 | # contents 56 | # 57 | # ==== Returns 58 | # Array[Object] 59 | # 60 | def namespaces_in_content(contents, file=__FILE__) 61 | old_constants = Thor::Base.subclasses.dup 62 | Thor::Base.subclasses.clear 63 | 64 | load_thorfile(file, contents) 65 | 66 | new_constants = Thor::Base.subclasses.dup 67 | Thor::Base.subclasses.replace(old_constants) 68 | 69 | new_constants.map!{ |c| c.namespace } 70 | new_constants.compact! 71 | new_constants 72 | end 73 | 74 | # Returns the thor classes declared inside the given class. 75 | # 76 | def thor_classes_in(klass) 77 | stringfied_constants = klass.constants.map { |c| c.to_s } 78 | Thor::Base.subclasses.select do |subclass| 79 | next unless subclass.name 80 | stringfied_constants.include?(subclass.name.gsub("#{klass.name}::", '')) 81 | end 82 | end 83 | 84 | # Receives a string and convert it to snake case. SnakeCase returns snake_case. 85 | # 86 | # ==== Parameters 87 | # String 88 | # 89 | # ==== Returns 90 | # String 91 | # 92 | def snake_case(str) 93 | return str.downcase if str =~ /^[A-Z_]+$/ 94 | str.gsub(/\B[A-Z]/, '_\&').squeeze('_') =~ /_*(.*)/ 95 | return $+.downcase 96 | end 97 | 98 | # Receives a string and convert it to camel case. camel_case returns CamelCase. 99 | # 100 | # ==== Parameters 101 | # String 102 | # 103 | # ==== Returns 104 | # String 105 | # 106 | def camel_case(str) 107 | return str if str !~ /_/ && str =~ /[A-Z]+.*/ 108 | str.split('_').map { |i| i.capitalize }.join 109 | end 110 | 111 | # Receives a namespace and tries to retrieve a Thor or Thor::Group class 112 | # from it. It first searches for a class using the all the given namespace, 113 | # if it's not found, removes the highest entry and searches for the class 114 | # again. If found, returns the highest entry as the class name. 115 | # 116 | # ==== Examples 117 | # 118 | # class Foo::Bar < Thor 119 | # def baz 120 | # end 121 | # end 122 | # 123 | # class Baz::Foo < Thor::Group 124 | # end 125 | # 126 | # Thor::Util.namespace_to_thor_class("foo:bar") #=> Foo::Bar, nil # will invoke default command 127 | # Thor::Util.namespace_to_thor_class("baz:foo") #=> Baz::Foo, nil 128 | # Thor::Util.namespace_to_thor_class("foo:bar:baz") #=> Foo::Bar, "baz" 129 | # 130 | # ==== Parameters 131 | # namespace 132 | # 133 | def find_class_and_command_by_namespace(namespace, fallback = true) 134 | if namespace.include?(?:) # look for a namespaced command 135 | pieces = namespace.split(":") 136 | command = pieces.pop 137 | klass = Thor::Util.find_by_namespace(pieces.join(":")) 138 | end 139 | unless klass # look for a Thor::Group with the right name 140 | klass, command = Thor::Util.find_by_namespace(namespace), nil 141 | end 142 | if !klass && fallback # try a command in the default namespace 143 | command = namespace 144 | klass = Thor::Util.find_by_namespace('') 145 | end 146 | return klass, command 147 | end 148 | alias find_class_and_task_by_namespace find_class_and_command_by_namespace 149 | 150 | # Receives a path and load the thor file in the path. The file is evaluated 151 | # inside the sandbox to avoid namespacing conflicts. 152 | # 153 | def load_thorfile(path, content=nil, debug=false) 154 | content ||= File.binread(path) 155 | 156 | begin 157 | Thor::Sandbox.class_eval(content, path) 158 | rescue Exception => e 159 | $stderr.puts("WARNING: unable to load thorfile #{path.inspect}: #{e.message}") 160 | if debug 161 | $stderr.puts(*e.backtrace) 162 | else 163 | $stderr.puts(e.backtrace.first) 164 | end 165 | end 166 | end 167 | 168 | def user_home 169 | @@user_home ||= if ENV["HOME"] 170 | ENV["HOME"] 171 | elsif ENV["USERPROFILE"] 172 | ENV["USERPROFILE"] 173 | elsif ENV["HOMEDRIVE"] && ENV["HOMEPATH"] 174 | File.join(ENV["HOMEDRIVE"], ENV["HOMEPATH"]) 175 | elsif ENV["APPDATA"] 176 | ENV["APPDATA"] 177 | else 178 | begin 179 | File.expand_path("~") 180 | rescue 181 | if File::ALT_SEPARATOR 182 | "C:/" 183 | else 184 | "/" 185 | end 186 | end 187 | end 188 | end 189 | 190 | # Returns the root where thor files are located, depending on the OS. 191 | # 192 | def thor_root 193 | File.join(user_home, ".thor").gsub(/\\/, '/') 194 | end 195 | 196 | # Returns the files in the thor root. On Windows thor_root will be something 197 | # like this: 198 | # 199 | # C:\Documents and Settings\james\.thor 200 | # 201 | # If we don't #gsub the \ character, Dir.glob will fail. 202 | # 203 | def thor_root_glob 204 | files = Dir["#{escape_globs(thor_root)}/*"] 205 | 206 | files.map! do |file| 207 | File.directory?(file) ? File.join(file, "main.thor") : file 208 | end 209 | end 210 | 211 | # Where to look for Thor files. 212 | # 213 | def globs_for(path) 214 | path = escape_globs(path) 215 | ["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"] 216 | end 217 | 218 | # Return the path to the ruby interpreter taking into account multiple 219 | # installations and windows extensions. 220 | # 221 | def ruby_command 222 | @ruby_command ||= begin 223 | ruby_name = RbConfig::CONFIG['ruby_install_name'] 224 | ruby = File.join(RbConfig::CONFIG['bindir'], ruby_name) 225 | ruby << RbConfig::CONFIG['EXEEXT'] 226 | 227 | # avoid using different name than ruby (on platforms supporting links) 228 | if ruby_name != 'ruby' && File.respond_to?(:readlink) 229 | begin 230 | alternate_ruby = File.join(RbConfig::CONFIG['bindir'], 'ruby') 231 | alternate_ruby << RbConfig::CONFIG['EXEEXT'] 232 | 233 | # ruby is a symlink 234 | if File.symlink? alternate_ruby 235 | linked_ruby = File.readlink alternate_ruby 236 | 237 | # symlink points to 'ruby_install_name' 238 | ruby = alternate_ruby if linked_ruby == ruby_name || linked_ruby == ruby 239 | end 240 | rescue NotImplementedError 241 | # just ignore on windows 242 | end 243 | end 244 | 245 | # escape string in case path to ruby executable contain spaces. 246 | ruby.sub!(/.*\s.*/m, '"\&"') 247 | ruby 248 | end 249 | end 250 | 251 | # Returns a string that has had any glob characters escaped. 252 | # The glob characters are `* ? { } [ ]`. 253 | # 254 | # ==== Examples 255 | # 256 | # Thor::Util.escape_globs('[apps]') # => '\[apps\]' 257 | # 258 | # ==== Parameters 259 | # String 260 | # 261 | # ==== Returns 262 | # String 263 | # 264 | def escape_globs(path) 265 | path.to_s.gsub(/[*?{}\[\]]/, '\\\\\\&') 266 | end 267 | 268 | end 269 | end 270 | end 271 | -------------------------------------------------------------------------------- /lib/gistore/vendor/thor/version.rb: -------------------------------------------------------------------------------- 1 | class Thor 2 | VERSION = "0.18.1" 3 | end 4 | -------------------------------------------------------------------------------- /lib/gistore/version.rb: -------------------------------------------------------------------------------- 1 | module Gistore 2 | VERSION = "1.0.0.rc4" 3 | REPO_VERSION = 1 4 | end 5 | -------------------------------------------------------------------------------- /t/.gitignore: -------------------------------------------------------------------------------- 1 | /trash directory* 2 | /test-results 3 | /.prove 4 | -------------------------------------------------------------------------------- /t/Makefile: -------------------------------------------------------------------------------- 1 | # Run tests 2 | # 3 | # Copyright (c) 2005 Junio C Hamano 4 | # 5 | 6 | #GIT_TEST_OPTS = --verbose --debug 7 | SHELL_PATH ?= $(SHELL) 8 | PERL_PATH ?= /usr/bin/perl 9 | TAR ?= $(TAR) 10 | RM ?= rm -f 11 | PROVE ?= prove 12 | DEFAULT_TEST_TARGET ?= test 13 | TEST_LINT ?= test-lint-duplicates test-lint-executable 14 | 15 | ifdef TEST_OUTPUT_DIRECTORY 16 | TEST_RESULTS_DIRECTORY = $(TEST_OUTPUT_DIRECTORY)/test-results 17 | else 18 | TEST_RESULTS_DIRECTORY = test-results 19 | endif 20 | 21 | # Shell quote; 22 | SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) 23 | PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) 24 | TEST_RESULTS_DIRECTORY_SQ = $(subst ','\'',$(TEST_RESULTS_DIRECTORY)) 25 | 26 | T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)) 27 | 28 | all: $(DEFAULT_TEST_TARGET) 29 | 30 | test: pre-clean $(TEST_LINT) 31 | $(MAKE) aggregate-results-and-cleanup 32 | 33 | prove: pre-clean $(TEST_LINT) 34 | @echo "*** prove ***"; $(PROVE) --exec '$(SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS) 35 | $(MAKE) clean-except-prove-cache 36 | 37 | $(T): 38 | @echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS) 39 | 40 | pre-clean: 41 | $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)' 42 | 43 | clean-except-prove-cache: 44 | $(RM) -r 'trash directory'.* '$(TEST_RESULTS_DIRECTORY_SQ)' 45 | $(RM) -r valgrind/bin 46 | 47 | clean: clean-except-prove-cache 48 | $(RM) .prove 49 | 50 | test-lint: test-lint-duplicates test-lint-executable test-lint-shell-syntax 51 | 52 | test-lint-duplicates: 53 | @dups=`echo $(T) | tr ' ' '\n' | sed 's/-.*//' | sort | uniq -d` && \ 54 | test -z "$$dups" || { \ 55 | echo >&2 "duplicate test numbers:" $$dups; exit 1; } 56 | 57 | test-lint-executable: 58 | @bad=`for i in $(T); do test -x "$$i" || echo $$i; done` && \ 59 | test -z "$$bad" || { \ 60 | echo >&2 "non-executable tests:" $$bad; exit 1; } 61 | 62 | test-lint-shell-syntax: 63 | @'$(PERL_PATH_SQ)' check-non-portable-shell.pl $(T) 64 | 65 | aggregate-results-and-cleanup: $(T) 66 | $(MAKE) aggregate-results 67 | $(MAKE) clean 68 | 69 | aggregate-results: 70 | for f in '$(TEST_RESULTS_DIRECTORY_SQ)'/t*-*.counts; do \ 71 | echo "$$f"; \ 72 | done | '$(SHELL_PATH_SQ)' ./aggregate-results.sh 73 | 74 | valgrind: 75 | $(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --valgrind" 76 | 77 | perf: 78 | $(MAKE) -C perf/ all 79 | 80 | .PHONY: pre-clean $(T) aggregate-results clean valgrind perf 81 | -------------------------------------------------------------------------------- /t/aggregate-results.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | failed_tests= 4 | fixed=0 5 | success=0 6 | failed=0 7 | broken=0 8 | total=0 9 | 10 | while read file 11 | do 12 | while read type value 13 | do 14 | case $type in 15 | '') 16 | continue ;; 17 | fixed) 18 | fixed=$(($fixed + $value)) ;; 19 | success) 20 | success=$(($success + $value)) ;; 21 | failed) 22 | failed=$(($failed + $value)) 23 | if test $value != 0 24 | then 25 | testnum=$(expr "$file" : 'test-results/\(t[0-9]*\)-') 26 | failed_tests="$failed_tests $testnum" 27 | fi 28 | ;; 29 | broken) 30 | broken=$(($broken + $value)) ;; 31 | total) 32 | total=$(($total + $value)) ;; 33 | esac 34 | done <"$file" 35 | done 36 | 37 | if test -n "$failed_tests" 38 | then 39 | printf "\nfailed test(s):$failed_tests\n\n" 40 | fi 41 | 42 | printf "%-8s%d\n" fixed $fixed 43 | printf "%-8s%d\n" success $success 44 | printf "%-8s%d\n" failed $failed 45 | printf "%-8s%d\n" broken $broken 46 | printf "%-8s%d\n" total $total 47 | -------------------------------------------------------------------------------- /t/lib-worktree.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2013 Jiang Xin 4 | # 5 | 6 | prepare_work_tree() 7 | { 8 | mkdir -p root/src/lib/a 9 | mkdir -p root/src/lib/b 10 | mkdir -p root/src/lib/empty 11 | mkdir -p root/src/images 12 | mkdir -p root/doc/ 13 | echo "readme" > root/src/README.txt 14 | echo "foo" > root/src/lib/a/foo.c 15 | echo "bar" > root/src/lib/b/bar.o 16 | echo "baz" > root/src/lib/b/baz.a 17 | cp ${TEST_DIRECTORY}/test-binary-1.png root/src/images 18 | cp ${TEST_DIRECTORY}/test-binary-2.png root/src/images 19 | echo "copyright" > root/doc/COPYRIGHT 20 | touch root/.hidden 21 | 22 | mkdir -p root/mod1/ 23 | ( 24 | cd root/mod1 25 | echo "readme" > README.txt 26 | mkdir lib 27 | cd lib 28 | git init 29 | mkdir src doc 30 | echo "foo" > src/foo.c 31 | echo "bar" > doc/bar.txt 32 | git add -A . 33 | git commit -m 'initial submodule mod1' 34 | ) 35 | mkdir -p root/mod2/ 36 | ( 37 | cd root/mod2 38 | git init 39 | echo "readme" > README.txt 40 | mkdir src doc 41 | echo "foo" > src/foo.c 42 | echo "bar" > doc/bar.txt 43 | git add -A . 44 | git commit -m 'initial submodule mod2' 45 | mkdir lib 46 | cd lib 47 | git init 48 | echo "hello" > hello.txt 49 | git add -A . 50 | git commit -m 'initial lib under mod2' 51 | ) 52 | } 53 | 54 | count_git_commits() 55 | { 56 | repo=$1 57 | git --git-dir "$repo" rev-list HEAD 2>/dev/null | wc -l | sed -e 's/ //g' 58 | } 59 | 60 | count_git_objects() 61 | { 62 | repo=$1 63 | num=0 64 | git --git-dir "$repo" count-objects -v | sort | \ 65 | while read line; do 66 | case $line in 67 | count:*) 68 | num=$((num + ${line#count:})) 69 | ;; 70 | in-pack:*) 71 | num=$((num + ${line#in-pack:})) 72 | echo $num 73 | ;; 74 | esac 75 | done 76 | } 77 | -------------------------------------------------------------------------------- /t/t0000-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2013 Jiang Xin 4 | # 5 | 6 | test_description='Test gistore init' 7 | 8 | TEST_NO_CREATE_REPO=NoThanks 9 | . ./test-lib.sh 10 | 11 | test_expect_success 'init with default normal plan' ' 12 | ( 13 | mkdir default && 14 | cd default && 15 | gistore init && 16 | test "$(gistore config plan)" = "normal" 17 | ) 18 | ' 19 | 20 | cat >expect < actual && 26 | test_cmp expect actual 27 | ' 28 | 29 | cat >expect <actual || true ) && 36 | test_cmp expect actual 37 | ' 38 | 39 | test_expect_success 'check default gistore/git configurations' ' 40 | ( 41 | cd default && 42 | test "$(gistore config full_backup_number)" = "12" && 43 | test "$(gistore config increment_backup_number)" = "30" && 44 | test -z "$(gistore config gc.auto)" && 45 | test -z "$(gistore config core.compression)" && 46 | test -z "$(gistore config core.loosecompression)" && 47 | test "$(gistore config --bool core.quotepath)" = "false" && 48 | test "$(gistore config --bool core.autocrlf)" = "false" && 49 | test "$(gistore config --bool core.logAllRefUpdates)" = "true" && 50 | test "$(gistore config core.sharedRepository)" = "group" && 51 | test "$(gistore config core.bigFileThreshold)" = "2m" 52 | ) 53 | ' 54 | 55 | test_expect_success 'init with --no-compress plan' ' 56 | ( 57 | gistore init --repo notz --plan no-compress && 58 | test "$(gistore config --repo notz plan)" = "no-compress" && 59 | test -z "$(gistore config --repo notz gc.auto)" && 60 | test "$(gistore config --repo notz core.compression)" = "0" && 61 | test "$(gistore config --repo notz core.loosecompression)" = "0" 62 | ) 63 | ' 64 | 65 | test_expect_success 'init with --no-gc plan' ' 66 | ( 67 | gistore init --repo notgc --plan no-gc && 68 | test "$(gistore config --repo notgc plan)" = "no-gc" && 69 | test "$(gistore config --repo notgc gc.auto)" = "0" && 70 | test "$(gistore config --repo notgc core.compression)" = "0" && 71 | test "$(gistore config --repo notgc core.loosecompression)" = "0" 72 | ) 73 | ' 74 | 75 | test_done 76 | -------------------------------------------------------------------------------- /t/t0010-config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2013 Jiang Xin 4 | # 5 | 6 | test_description='Test gistore config' 7 | 8 | TEST_NO_CREATE_REPO= 9 | . ./test-lib.sh 10 | 11 | test_expect_success 'default is normal plan' ' 12 | test "$(gistore config plan)" = "normal" 13 | ' 14 | 15 | test_expect_success 'check default gistore configurations' ' 16 | test "$(gistore config full_backup_number)" = "12" && 17 | test "$(gistore config increment_backup_number)" = "30" && 18 | test -z "$(gistore config gc.auto)" && 19 | test -z "$(gistore config core.compression)" && 20 | test -z "$(gistore config core.loosecompression)" && 21 | test "$(gistore config --bool core.quotepath)" = "false" && 22 | test "$(gistore config --bool core.autocrlf)" = "false" && 23 | test "$(gistore config --bool core.logAllRefUpdates)" = "true" && 24 | test "$(gistore config core.sharedRepository)" = "group" && 25 | test "$(gistore config core.bigFileThreshold)" = "2m" 26 | ' 27 | 28 | test_expect_success 'change plan from normal to no-gc' ' 29 | test "$(gistore config plan)" = "normal" && 30 | gistore config --plan no-gc && 31 | test "$(gistore config plan)" = "no-gc" && 32 | test "$(gistore config gc.auto)" = "0" && 33 | test "$(gistore config core.compression)" = "0" && 34 | test "$(gistore config core.loosecompression)" = "0" 35 | ' 36 | 37 | test_expect_success 'change plan from no-gc to no-compress' ' 38 | test "$(gistore config plan)" = "no-gc" && 39 | gistore config --plan no-compress && 40 | test "$(gistore config plan)" = "no-compress" && 41 | test -z "$(gistore config gc.auto)" && 42 | test "$(gistore config core.compression)" = "0" && 43 | test "$(gistore config core.loosecompression)" = "0" 44 | ' 45 | 46 | test_expect_success 'change plan without --plan option' ' 47 | test "$(gistore config plan)" = "no-compress" && 48 | gistore config plan no-gc && 49 | gistore config plan normal && 50 | test "$(gistore config plan)" = "normal" && 51 | test -z "$(gistore config gc.auto)" && 52 | test -z "$(gistore config core.compression)" && 53 | test -z "$(gistore config core.loosecompression)" 54 | ' 55 | 56 | test_expect_success 'read/write gistore configurations' ' 57 | test "$(gistore config full_backup_number)" = "12" && 58 | test "$(gistore config increment_backup_number)" = "30" && 59 | gistore config full_backup_number 5 && 60 | gistore config increment_backup_number 9 && 61 | test "$(gistore config full_backup_number)" = "5" && 62 | test "$(gistore config increment_backup_number)" = "9" && 63 | test_must_fail gistore config non_exist_config value && 64 | test_must_fail gistore config --unset non.exist.config 65 | ' 66 | 67 | test_expect_success 'read/write git configurations' ' 68 | gistore config x.y.z foobar && 69 | test "$(gistore config x.y.z)" = "foobar" && 70 | test "$(git config x.y.z)" = "foobar" && 71 | git config x.y.z baz && 72 | test "$(gistore config x.y.z)" = "baz" 73 | ' 74 | 75 | test_done 76 | -------------------------------------------------------------------------------- /t/t0020-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2013 Jiang Xin 4 | # 5 | 6 | test_description='Test git version compare' 7 | 8 | TEST_NO_CREATE_REPO=NoThanks 9 | . ./test-lib.sh 10 | 11 | test_expect_success 'gistore version' ' 12 | gistore --version | grep -q "Gistore version [0-9]\+" && 13 | test "$(gistore -v)" = "$(gistore --version)" && 14 | test "$(gistore version)" = "$(gistore --version)" 15 | ' 16 | 17 | test_expect_success 'compare two versions' ' 18 | test $(gistore check-git-version 1.8.5 1.8.5) -eq 0 && 19 | test $(gistore check-git-version 1.8.4 1.8.4.1) -eq -1 && 20 | test $(gistore check-git-version 1.7.5 1.7.11) -eq -1 && 21 | test $(gistore check-git-version 1.7.11 1.7.5) -eq 1 && 22 | test $(gistore check-git-version 1.7.11 1.7.5) -eq 1 && 23 | test $(gistore check-git-version 1.7.11 2.0) -eq -1 && 24 | test $(gistore check-git-version 2.0 1.8.5) -eq 1 25 | ' 26 | 27 | test_expect_success 'compare with current version' ' 28 | test $(gistore check-git-version 0.99.1) -eq 1 && 29 | test $(gistore check-git-version 0.99.1.2) -eq 1 30 | ' 31 | 32 | test_done 33 | -------------------------------------------------------------------------------- /t/t1000-add-remove.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2013 Jiang Xin 4 | # 5 | 6 | test_description='Test gistore add and rm' 7 | 8 | TEST_NO_CREATE_REPO=NoThanks 9 | . ./lib-worktree.sh 10 | . ./test-lib.sh 11 | 12 | cwd=$(pwd -P) 13 | 14 | cat >expect << EOF 15 | $cwd/root/doc 16 | $cwd/root/src 17 | EOF 18 | 19 | test_expect_success 'repo initial with blank backup list' ' 20 | gistore init --repo repo.git && 21 | gistore status --repo repo.git --backup | sed -e /^$/d | > actual && 22 | test ! -s actual 23 | ' 24 | 25 | test_expect_success 'add root/src and root/doc' ' 26 | gistore add --repo repo.git root/src && 27 | gistore add --repo repo.git root/doc && 28 | gistore status --repo repo.git --backup >actual && 29 | test_cmp expect actual 30 | ' 31 | 32 | cat >expect << EOF 33 | $cwd/root/src 34 | EOF 35 | 36 | test_expect_success 'remove root/doc' ' 37 | ( 38 | cd repo.git && 39 | gistore rm ../root/doc && 40 | gistore status --backup >../actual 41 | ) && test_cmp expect actual 42 | ' 43 | 44 | cat >expect << EOF 45 | $cwd/root 46 | EOF 47 | 48 | test_expect_success 'root override root/src and root/doc' ' 49 | gistore add --repo repo.git root/src && 50 | gistore add --repo repo.git root && 51 | gistore add --repo repo.git root/doc && 52 | gistore status --repo repo.git --backup > actual && 53 | test_cmp expect actual 54 | ' 55 | 56 | test_expect_success 'not add parent of repo' ' 57 | gistore add --repo repo.git .. && 58 | gistore add --repo repo.git . && 59 | gistore status --repo repo.git --backup > actual && 60 | test_cmp expect actual 61 | ' 62 | 63 | test_expect_success 'not add subdir of repo' ' 64 | gistore add --repo repo.git repo.git && 65 | gistore add --repo repo.git repo.git/objects && 66 | gistore add --repo repo.git repo.git/refs && 67 | gistore status --repo repo.git --backup > actual && 68 | test_cmp expect actual 69 | ' 70 | 71 | cat >expect <actual || true) && 78 | test_cmp expect actual && 79 | test_must_fail gistore rm --repo non-exist-repo.git root/src && 80 | (gistore add --repo non-exist-repo.git 2>actual || true) && 81 | test_cmp expect actual 82 | ' 83 | 84 | test_expect_success 'fail if no argument for add/remove' ' 85 | test_must_fail gistore add --repo repo.git && 86 | test_must_fail gistore rm --repo repo.git 87 | ' 88 | 89 | test_done 90 | -------------------------------------------------------------------------------- /t/t1010-status.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2013 Jiang Xin 4 | # 5 | 6 | test_description='Test gistore status' 7 | 8 | TEST_NO_CREATE_REPO=NoThanks 9 | . ./lib-worktree.sh 10 | . ./test-lib.sh 11 | 12 | cwd=$(pwd -P) 13 | 14 | cat >expect << EOF 15 | root/doc 16 | root/src 17 | EOF 18 | 19 | # Parent .gitignore ignore all this directory, and new file 20 | # will not show in git-status. 21 | test_expect_success 'remove to avoid .gitignore side-effect' ' 22 | if [ -f "$TEST_DIRECTORY/.gitignore" ]; then 23 | mv "$TEST_DIRECTORY/.gitignore" "$TEST_DIRECTORY/.gitignore.save" 24 | fi 25 | ' 26 | 27 | test_expect_success 'status show backup list' ' 28 | prepare_work_tree && 29 | gistore init --repo repo.git && 30 | gistore add --repo repo.git root/src && 31 | gistore add --repo repo.git root/doc && 32 | gistore status --repo repo.git --backup \ 33 | | sed -e "s#^${cwd}/##g" > actual && 34 | test_cmp expect actual 35 | ' 36 | 37 | # Before git v1.7.4, filenames in git-status are NOT quoted. 38 | # So strip double quote before compare with this. 39 | cat >expect << EOF 40 | M root/doc/COPYRIGHT 41 | M root/src/README.txt 42 | D root/src/images/test-binary-1.png 43 | D root/src/lib/b/baz.a 44 | ?? root/src/lib/a/foo.h 45 | EOF 46 | 47 | test_expect_success GIT_CAP_WILDMATCH 'status --git (1)' ' 48 | gistore commit --repo repo.git && \ 49 | echo "hack" >> root/doc/COPYRIGHT && \ 50 | echo "hack" >> root/src/README.txt && \ 51 | touch root/src/lib/a/foo.h && \ 52 | rm root/src/images/test-binary-1.png && \ 53 | rm root/src/lib/b/baz.a && \ 54 | gistore status --repo repo.git --git -s \ 55 | | sed -e "s#${cwd#/}/##g" | sed -e "s/\"//g" > actual && 56 | test_cmp expect actual 57 | ' 58 | 59 | # Rstore parent .gitignore file 60 | test_expect_success 'restore .gitignore' ' 61 | if [ -f "$TEST_DIRECTORY/.gitignore.save" ]; then 62 | mv "$TEST_DIRECTORY/.gitignore.save" "$TEST_DIRECTORY/.gitignore" 63 | fi 64 | ' 65 | 66 | cat >expect <actual || true) && 73 | test_cmp expect actual 74 | ' 75 | 76 | cat >expect <&1 | grep "^Error" | 83 | sed -e "s/ [^ ]*\/git/ git/" > actual || true) && 84 | test_cmp expect actual 85 | ' 86 | 87 | test_done 88 | -------------------------------------------------------------------------------- /t/t1020-commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2013 Jiang Xin 4 | # 5 | 6 | test_description='Test gistore commit' 7 | 8 | TEST_NO_CREATE_REPO=NoThanks 9 | . ./lib-worktree.sh 10 | . ./test-lib.sh 11 | 12 | cwd=$(pwd -P) 13 | 14 | cat >expect << EOF 15 | root/doc/COPYRIGHT 16 | root/src/README.txt 17 | root/src/images/test-binary-1.png 18 | root/src/images/test-binary-2.png 19 | root/src/lib/a/foo.c 20 | root/src/lib/b/bar.o 21 | root/src/lib/b/baz.a 22 | EOF 23 | 24 | test_expect_success 'initialize for commit' ' 25 | prepare_work_tree && 26 | gistore init --repo repo.git && 27 | gistore add --repo repo.git root/src && 28 | gistore add --repo repo.git root/doc && 29 | gistore commit --repo repo.git && 30 | test "$(count_git_commits repo.git)" = "1" && 31 | gistore repo repo.git ls-tree --name-only \ 32 | -r HEAD | sed -e "s#^${cwd#/}/##g" > actual && 33 | test_cmp expect actual 34 | ' 35 | 36 | test_expect_success 'nothing changed, no commit' ' 37 | gistore commit --repo repo.git && 38 | touch root/src/README.txt && 39 | gistore commit --repo repo.git && 40 | test "$(count_git_commits repo.git)" = "1" 41 | ' 42 | 43 | test_expect_success 'commit if something changed' ' 44 | echo "more" >> root/src/README.txt && 45 | gistore commit --repo repo.git && 46 | test "$(count_git_commits repo.git)" = "2" 47 | ' 48 | 49 | cat >expect << EOF 50 | root/doc/COPYRIGHT 51 | root/src/README.txt 52 | root/src/images/test-binary-1.png 53 | root/src/images/test-binary-2.png 54 | root/src/lib/a/foo.c 55 | root/src/lib/b/bar.o 56 | EOF 57 | 58 | test_expect_success 'commit if something removed' ' 59 | rm root/src/lib/b/baz.a && 60 | gistore commit --repo repo.git && 61 | test "$(count_git_commits repo.git)" = "3" && 62 | gistore repo repo.git ls-tree --name-only \ 63 | -r HEAD | sed -e "s#^${cwd#/}/##g" > actual && 64 | test_cmp expect actual 65 | ' 66 | 67 | cat >expect << EOF 68 | root/doc/COPYRIGHT 69 | root/new_src/README.txt 70 | root/new_src/images/test-binary-1.png 71 | root/new_src/images/test-binary-2.png 72 | root/new_src/lib/a/foo.c 73 | root/new_src/lib/b/bar.o 74 | root/src 75 | EOF 76 | 77 | test_expect_success 'commit even for symlink' ' 78 | mv root/src root/new_src && 79 | ln -s new_src root/src && 80 | gistore commit --repo repo.git && 81 | test "$(count_git_commits repo.git)" = "4" && 82 | gistore repo repo.git ls-tree --name-only \ 83 | -r HEAD | sed -e "s#^${cwd#/}/##g" > actual && 84 | test_cmp expect actual 85 | ' 86 | 87 | cat >expect << EOF 88 | root/doc/COPYRIGHT 89 | EOF 90 | 91 | test_expect_success 'not backup root/src any more' ' 92 | gistore rm --repo repo.git root/src && 93 | gistore commit --repo repo.git && 94 | test -L root/src && 95 | test -f root/new_src/README.txt && 96 | test -f root/new_src/images/test-binary-1.png && 97 | test -f root/new_src/lib/a/foo.c && 98 | test -f root/new_src/lib/b/bar.o && 99 | gistore repo repo.git ls-tree --name-only \ 100 | -r HEAD | sed -e "s#^${cwd#/}/##g" > actual && 101 | test_cmp expect actual 102 | ' 103 | 104 | cat >expect << EOF 105 | root/doc/COPYRIGHT 106 | root/mod1/README.txt 107 | root/mod1/lib/doc/bar.txt 108 | root/mod1/lib/src/foo.c 109 | root/mod2/README.txt 110 | root/mod2/doc/bar.txt 111 | root/mod2/lib/hello.txt 112 | root/mod2/src/foo.c 113 | EOF 114 | 115 | test_expect_success 'add real files instead of submodule' ' 116 | gistore add --repo repo.git root/mod1 && 117 | gistore add --repo repo.git root/mod2 && 118 | gistore commit --repo repo.git && 119 | gistore repo repo.git ls-tree --name-only \ 120 | -r HEAD | sed -e "s#^${cwd#/}/##g" > actual && 121 | test_cmp expect actual 122 | ' 123 | 124 | cat >expect <actual || true) && 131 | test_cmp expect actual 132 | ' 133 | 134 | test_done 135 | -------------------------------------------------------------------------------- /t/t1030-commit-and-rotate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2013 Jiang Xin 4 | # 5 | 6 | test_description='Test gistore commit' 7 | 8 | TEST_NO_CREATE_REPO=NoThanks 9 | . ./lib-worktree.sh 10 | . ./test-lib.sh 11 | 12 | do_hack() 13 | { 14 | echo "hack $*" >> root/src/README.txt 15 | echo "hack $*" >> root/doc/COPYRIGHT 16 | } 17 | 18 | cwd=$(pwd -P) 19 | n=0 20 | 21 | test_expect_success 'initialize for commit' ' 22 | n=$((n+1)) && 23 | prepare_work_tree && 24 | gistore init --repo repo.git && 25 | gistore config --repo repo.git full_backup_number 3 && 26 | gistore config --repo repo.git increment_backup_number 5 && 27 | gistore add --repo repo.git root/src && 28 | gistore add --repo repo.git root/doc && 29 | gistore commit --repo repo.git -m "Backup No. $n" && 30 | test "$(count_git_commits repo.git)" = "$n" 31 | ' 32 | 33 | cat >expect << EOF 34 | Backup No. 6 35 | Backup No. 5 36 | Backup No. 4 37 | Backup No. 3 38 | Backup No. 2 39 | Backup No. 1 40 | EOF 41 | 42 | test_expect_success 'no rotate when commit 5 times' ' 43 | i=0 && 44 | while test $i -lt 5; do 45 | i=$((i+1)); 46 | n=$((n+1)); 47 | do_hack $n; 48 | gistore commit --repo repo.git -m "Backup No. $n"; 49 | done && 50 | git_log_only_subject repo.git > actual && 51 | test_cmp expect actual && 52 | echo "* master" > expect && 53 | gistore repo repo.git branch > actual && 54 | test_cmp expect actual 55 | ' 56 | 57 | cat >expect < actual && 66 | test_cmp expect actual 67 | ' 68 | 69 | cat >expect << EOF 70 | Backup No. 7 71 | Full backup of repo.git 72 | Backup No. 5 73 | Backup No. 4 74 | Backup No. 3 75 | Backup No. 2 76 | Backup No. 1 77 | EOF 78 | 79 | test_expect_success 'graft test' ' 80 | git_log_only_subject repo.git > actual && 81 | test_cmp expect actual && 82 | head -2 expect > expect2 && 83 | git_log_only_subject repo.git --without_grafts > actual && 84 | test_cmp expect2 actual && 85 | echo "Backup No. 6" > expect3 && 86 | tail -5 expect >> expect3 && 87 | git_log_only_subject repo.git --without_grafts gistore/1 > actual && 88 | test_cmp expect3 actual 89 | ' 90 | 91 | cat >expect << EOF 92 | Backup No. 12 93 | Full backup of repo.git 94 | Backup No. 10 95 | Backup No. 9 96 | Backup No. 8 97 | Backup No. 7 98 | Full backup of repo.git 99 | Backup No. 5 100 | Backup No. 4 101 | Backup No. 3 102 | Backup No. 2 103 | Backup No. 1 104 | EOF 105 | 106 | test_expect_success 'rotate after commit another 5 times' ' 107 | i=0 && 108 | while test $i -lt 5; do 109 | i=$((i+1)); 110 | n=$((n+1)); 111 | do_hack $n; 112 | gistore commit --repo repo.git -m "Backup No. $n"; 113 | done && 114 | git_log_only_subject repo.git > actual && 115 | test_cmp expect actual && 116 | echo " gistore/1" > expect && 117 | echo " gistore/2" >> expect && 118 | echo "* master" >> expect && 119 | gistore repo repo.git branch > actual && 120 | test_cmp expect actual 121 | ' 122 | 123 | cat >expect << EOF 124 | Backup No. 12 125 | Full backup of repo.git 126 | EOF 127 | 128 | test_expect_success 'commit log of master (no grafts)' ' 129 | git_log_only_subject repo.git --without_grafts > actual && 130 | test_cmp expect actual 131 | ' 132 | 133 | cat >expect << EOF 134 | Backup No. 11 135 | Backup No. 10 136 | Backup No. 9 137 | Backup No. 8 138 | Backup No. 7 139 | Full backup of repo.git 140 | EOF 141 | 142 | test_expect_success 'commit log of gistore/1 (no grafts)' ' 143 | git_log_only_subject repo.git --without_grafts gistore/1 > actual && 144 | test_cmp expect actual 145 | ' 146 | 147 | cat >expect << EOF 148 | Backup No. 6 149 | Backup No. 5 150 | Backup No. 4 151 | Backup No. 3 152 | Backup No. 2 153 | Backup No. 1 154 | EOF 155 | 156 | test_expect_success 'commit log of gistore/2 (no grafts)' ' 157 | git_log_only_subject repo.git --without_grafts gistore/2 > actual && 158 | test_cmp expect actual 159 | ' 160 | 161 | cat >expect << EOF 162 | gistore/1 163 | gistore/2 164 | gistore/3 165 | * master 166 | EOF 167 | 168 | test_expect_success 'after 20 commits' ' 169 | i=0 && 170 | while test $i -lt 20; do 171 | i=$((i+1)); 172 | n=$((n+1)); 173 | do_hack $n; 174 | gistore commit --repo repo.git -m "Backup No. $n"; 175 | done && 176 | gistore repo repo.git branch > actual && 177 | test_cmp expect actual 178 | ' 179 | 180 | cat >expect << EOF 181 | Backup No. 21 182 | Backup No. 20 183 | Backup No. 19 184 | Backup No. 18 185 | Backup No. 17 186 | Full backup of repo.git 187 | EOF 188 | 189 | test_expect_success 'commit log of gistore/3 (no grafts)' ' 190 | git_log_only_subject repo.git --without_grafts gistore/3 > actual && 191 | test_cmp expect actual 192 | ' 193 | 194 | cat >expect << EOF 195 | Backup No. 26 196 | Backup No. 25 197 | Backup No. 24 198 | Backup No. 23 199 | Backup No. 22 200 | Full backup of repo.git 201 | EOF 202 | 203 | test_expect_success 'commit log of gistore/2 (no grafts)' ' 204 | git_log_only_subject repo.git --without_grafts gistore/2 > actual && 205 | test_cmp expect actual 206 | ' 207 | 208 | cat >expect << EOF 209 | Backup No. 31 210 | Backup No. 30 211 | Backup No. 29 212 | Backup No. 28 213 | Backup No. 27 214 | Full backup of repo.git 215 | EOF 216 | 217 | test_expect_success 'commit log of gistore/1 (no grafts)' ' 218 | git_log_only_subject repo.git --without_grafts gistore/1 > actual && 219 | test_cmp expect actual 220 | ' 221 | 222 | cat >expect << EOF 223 | Backup No. 32 224 | Full backup of repo.git 225 | EOF 226 | 227 | test_expect_success 'commit log of master (no grafts)' ' 228 | git_log_only_subject repo.git --without_grafts > actual && 229 | test_cmp expect actual 230 | ' 231 | 232 | cat >expect << EOF 233 | Backup No. 32 234 | Full backup of repo.git 235 | Backup No. 30 236 | Backup No. 29 237 | Backup No. 28 238 | Backup No. 27 239 | Full backup of repo.git 240 | Backup No. 25 241 | Backup No. 24 242 | Backup No. 23 243 | Backup No. 22 244 | Full backup of repo.git 245 | Backup No. 20 246 | Backup No. 19 247 | Backup No. 18 248 | Backup No. 17 249 | Full backup of repo.git 250 | EOF 251 | 252 | test_expect_success 'commit log of master (with grafts)' ' 253 | git_log_only_subject repo.git > actual && 254 | test_cmp expect actual 255 | ' 256 | 257 | test_expect_success 'purge history using gc' ' 258 | count=$(count_git_objects repo.git) && 259 | gistore repo repo.git prune --expire=now && 260 | gistore repo repo.git gc && 261 | test $(count_git_objects repo.git) -eq $count && 262 | gistore gc --repo repo.git --force && 263 | test $(count_git_objects repo.git) -lt $count 264 | ' 265 | 266 | test_done 267 | -------------------------------------------------------------------------------- /t/t2000-task-and-commit-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2013 Jiang Xin 4 | # 5 | 6 | test_description='Test gistore task' 7 | 8 | TEST_NO_CREATE_REPO=NoThanks 9 | . ./lib-worktree.sh 10 | . ./test-lib.sh 11 | 12 | do_hack() 13 | { 14 | echo "hack $*" >> root/src/README.txt 15 | echo "hack $*" >> root/doc/COPYRIGHT 16 | } 17 | 18 | cwd=$(pwd -P) 19 | 20 | HOME=$cwd 21 | unset XDG_CONFIG_HOME 22 | GISTORE_TEST_GIT_CONFIG=Yes 23 | export HOME XDG_CONFIG_HOME GISTORE_TEST_GIT_CONFIG 24 | 25 | touch config_file 26 | GIT_CONFIG=$cwd/config_file 27 | export GIT_CONFIG 28 | 29 | cat >expect << EOF 30 | System level Tasks 31 | User level Tasks 32 | EOF 33 | 34 | test_expect_success 'initial config' ' 35 | prepare_work_tree && 36 | gistore init --repo repo1.git && 37 | gistore init --repo repo2.git && 38 | gistore add --repo repo1.git root/src root/doc && 39 | gistore add --repo repo2.git root/src root/doc && 40 | gistore task list |sed -e "/^$/d" > actual && 41 | test_cmp expect actual 42 | ' 43 | 44 | # Ruby hash to yaml may have different order, so sort before compare. 45 | cat >expect << EOF 46 | hello => repo1.git 47 | world => repo2.git 48 | EOF 49 | 50 | test_expect_success 'task add and task list' ' 51 | gistore task add hello repo1.git && 52 | gistore task add world repo2.git && 53 | gistore task list | grep -q "$cwd" && 54 | gistore task list | sed -e "/^$/d" | \ 55 | sed -e "s#${cwd}/##g" | grep "^ " | sort -u > actual && 56 | test_cmp expect actual 57 | ' 58 | 59 | test_expect_success 'commit specific task' ' 60 | test "$(count_git_commits repo1.git)" = "0" && 61 | test "$(count_git_commits repo2.git)" = "0" && 62 | gistore commit --repo hello -m "task hello, commit no.1" && 63 | test "$(count_git_commits repo1.git)" = "1" && 64 | test "$(count_git_commits repo2.git)" = "0" && 65 | gistore ci --repo world -m "for world, commit no.1" && 66 | test "$(count_git_commits repo1.git)" = "1" && 67 | test "$(count_git_commits repo2.git)" = "1" && 68 | test "$(git_log_only_subject repo1.git -1)" = \ 69 | "task hello, commit no.1" && 70 | test "$(git_log_only_subject repo2.git -1)" = \ 71 | "for world, commit no.1" 72 | ' 73 | 74 | test_expect_success 'commit all task' ' 75 | do_hack && 76 | gistore commit-all && 77 | test "$(count_git_commits repo1.git)" = "2" && 78 | test "$(count_git_commits repo2.git)" = "2" && 79 | do_hack && 80 | gistore ci-all -m "commit invoke by ci-all" && 81 | test "$(count_git_commits repo1.git)" = "3" && 82 | test "$(count_git_commits repo2.git)" = "3" && 83 | test "$(git_log_only_subject repo1.git -1)" = \ 84 | "commit invoke by ci-all" && 85 | test "$(git_log_only_subject repo2.git -1)" = \ 86 | "commit invoke by ci-all" 87 | ' 88 | 89 | cat >expect << EOF 90 | System level Tasks 91 | hello => repo1.git 92 | User level Tasks 93 | hello => repo1.git 94 | EOF 95 | 96 | test_expect_success 'task remove' ' 97 | do_hack && 98 | gistore task rm world && 99 | gistore task list |sed -e "/^$/d" | sed -e "s#${cwd}/##g" > actual && 100 | test_cmp expect actual && 101 | gistore commit-all && 102 | test "$(count_git_commits repo1.git)" = "4" && 103 | test "$(count_git_commits repo2.git)" = "3" 104 | ' 105 | 106 | cat >expect << EOF 107 | hello => repo1.git 108 | world => repo2.git 109 | EOF 110 | 111 | test_expect_success 'commit-all while missing task repo' ' 112 | gistore task add hello repo1.git && 113 | gistore task add world repo2.git && 114 | gistore task list | grep -q "$cwd" && 115 | gistore task list | sed -e "/^$/d" | \ 116 | sed -e "s#${cwd}/##g" | grep "^ " | sort -u > actual && 117 | test_cmp expect actual && 118 | do_hack && 119 | gistore commit-all && 120 | test "$(count_git_commits repo1.git)" = "5" && 121 | test "$(count_git_commits repo2.git)" = "4" && 122 | mv repo1.git repo1.git.moved && 123 | do_hack && 124 | test_must_fail gistore commit-all && 125 | test "$(count_git_commits repo2.git)" = "5" && 126 | mv repo1.git.moved repo1.git && 127 | mv repo2.git repo2.git.moved && 128 | test_must_fail gistore commit-all && 129 | test "$(count_git_commits repo1.git)" = "6" 130 | ' 131 | 132 | test_done 133 | -------------------------------------------------------------------------------- /t/t3000-checkout.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2013 Jiang Xin 4 | # 5 | 6 | test_description='Test gistore checkout' 7 | 8 | TEST_NO_CREATE_REPO=NoThanks 9 | . ./lib-worktree.sh 10 | . ./test-lib.sh 11 | 12 | do_hack() 13 | { 14 | echo "hack $*" >> root/src/README.txt 15 | echo "hack $*" >> root/doc/COPYRIGHT 16 | } 17 | 18 | cwd=$(pwd -P) 19 | 20 | cat >expect << EOF 21 | outdir/.git 22 | outdir/root/doc/COPYRIGHT 23 | EOF 24 | 25 | test_expect_success 'initialize for checkout' ' 26 | prepare_work_tree && 27 | gistore init --repo repo.git && 28 | gistore init --repo repo2.git && 29 | gistore add --repo repo.git root/doc && 30 | gistore add --repo repo2.git root/doc && 31 | gistore commit --repo repo.git -m "initialize for checkout" && 32 | gistore commit --repo repo2.git -m "initialize for checkout" && 33 | test ! -d outdir && 34 | gistore checkout --repo repo.git --to outdir && 35 | find outdir -type f | sed -e "s#${cwd}##g" | LC_COLLATE=C sort > actual && 36 | test_cmp expect actual 37 | ' 38 | 39 | cat >expect <actual || true) && 46 | test_cmp expect actual 47 | ' 48 | 49 | cat >expect <&1 | 57 | sed -e "s/ [^ ]*\/git/ git/" > actual || true) && 58 | test_cmp expect actual 59 | ' 60 | 61 | cat >expect << EOF 62 | outdir/.git 63 | outdir/root/doc/COPYRIGHT 64 | outdir/root/src/README.txt 65 | outdir/root/src/images/test-binary-1.png 66 | outdir/root/src/images/test-binary-2.png 67 | outdir/root/src/lib/a/foo.c 68 | outdir/root/src/lib/b/bar.o 69 | outdir/root/src/lib/b/baz.a 70 | EOF 71 | 72 | test_expect_success 'checkout continue' ' 73 | gistore add --repo repo.git root/src && 74 | gistore commit --repo repo.git -m "checkout continue" && 75 | gistore checkout --repo repo.git --to outdir && 76 | find outdir -type f | sed -e "s#${cwd}##g" | LC_COLLATE=C sort > actual && 77 | test_cmp expect actual 78 | ' 79 | 80 | cat >expect << EOF 81 | partial/.git 82 | partial/root/src/lib/a/foo.c 83 | partial/root/src/lib/b/bar.o 84 | partial/root/src/lib/b/baz.a 85 | EOF 86 | 87 | test_expect_success 'partial checkout' ' 88 | gistore checkout --repo repo.git --to partial "${cwd#/}/root/src/lib" && 89 | find partial -type f | sed -e "s#${cwd}##g" | LC_COLLATE=C sort > actual && 90 | test_cmp expect actual 91 | ' 92 | 93 | cat >expect << EOF 94 | history/.git 95 | history/root/doc/COPYRIGHT 96 | EOF 97 | 98 | test_expect_success 'checkout history' ' 99 | gistore checkout --repo repo.git --to history --rev HEAD^ && 100 | find history -type f | sed -e "s#${cwd}##g" | LC_COLLATE=C sort > actual && 101 | test_cmp expect actual 102 | ' 103 | 104 | test_expect_success 'worktree and gitdir unmatch' ' 105 | test_must_fail gistore checkout --repo repo2.git --to outdir 106 | ' 107 | 108 | test_expect_success 'not checkout to no empty dir' ' 109 | mkdir outdir2 && touch outdir2/.hidden && 110 | test_must_fail gistore checkout --repo repo2.git --to outdir2 && 111 | rm outdir2/.hidden && 112 | gistore checkout --repo repo2.git --to outdir2 113 | ' 114 | 115 | test_done 116 | -------------------------------------------------------------------------------- /t/t3010-export-and-restore.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2013 Jiang Xin 4 | # 5 | 6 | test_description='Test gistore export and restore' 7 | 8 | TEST_NO_CREATE_REPO=NoThanks 9 | . ./lib-worktree.sh 10 | . ./test-lib.sh 11 | 12 | do_hack() 13 | { 14 | echo "hack $*" >> root/src/README.txt 15 | echo "hack $*" >> root/doc/COPYRIGHT 16 | } 17 | 18 | cwd=$(pwd -P) 19 | n=0 20 | 21 | cat >expect <actual || true) && 28 | test_cmp expect actual 29 | ' 30 | 31 | cat >expect <actual || true) && 39 | test_cmp expect actual 40 | ' 41 | 42 | cat >expect << EOF 43 | Backup No. 24 44 | Backup No. 23 45 | Backup No. 22 46 | Full backup of repo.git 47 | Backup No. 20 48 | Backup No. 19 49 | Backup No. 18 50 | Backup No. 17 51 | Full backup of repo.git 52 | Backup No. 15 53 | Backup No. 14 54 | Backup No. 13 55 | Backup No. 12 56 | Full backup of repo.git 57 | Backup No. 10 58 | Backup No. 9 59 | Backup No. 8 60 | Backup No. 7 61 | Full backup of repo.git 62 | EOF 63 | 64 | test_expect_success 'initialize for export' ' 65 | prepare_work_tree && 66 | gistore init --repo repo.git && 67 | gistore config --repo repo.git full_backup_number 3 && 68 | gistore config --repo repo.git increment_backup_number 5 && 69 | gistore add --repo repo.git root/src && 70 | gistore add --repo repo.git root/doc && 71 | i=0 && 72 | while test $i -lt 24; do 73 | i=$((i+1)); 74 | n=$((n+1)); 75 | do_hack $n; 76 | gistore commit --repo repo.git -m "Backup No. $n"; 77 | done && 78 | test "$(count_git_commits repo.git)" = "19" && 79 | git_log_only_subject repo.git > actual && 80 | test_cmp expect actual && 81 | count=$(count_git_objects repo.git) && 82 | gistore gc --repo repo.git --force && 83 | test $(count_git_objects repo.git) -lt $count 84 | ' 85 | 86 | test_expect_success 'export backups' ' 87 | gistore export-to-backups --repo repo.git --to backups && 88 | test $(ls backups/001-full-backup-*.pack | wc -l) -eq 1 && 89 | test $(ls backups/*-incremental-*.pack | wc -l) -eq 3 90 | ' 91 | 92 | cat >expect < actual && 103 | test_cmp expect actual 104 | ' 105 | 106 | test_expect_success 'export another repo' ' 107 | gistore init --repo repo2.git && 108 | gistore config --repo repo2.git full_backup_number 3 && 109 | gistore config --repo repo2.git increment_backup_number 5 && 110 | gistore add --repo repo2.git root/src && 111 | i=0 && n=0 && 112 | while test $i -lt 2; do 113 | i=$((i+1)); 114 | n=$((n+1)); 115 | do_hack $n; 116 | gistore commit --repo repo2.git -m "Repo2 commit No. $n"; 117 | done && 118 | test $(count_git_commits repo2.git) -eq 2 && 119 | gistore export-to-backups --repo repo2.git --to backups2 && 120 | test $(ls backups2/001-full-backup-*.pack | wc -l) -eq 1 && 121 | test $(ls backups2/*-incremental-*.pack | wc -l) -eq 1 122 | ' 123 | 124 | cat >expect <result 2>&1 && 131 | new_commit=$(grep $_x40 result) && 132 | test $(count_git_commits restore.git) -eq 4 && 133 | ( 134 | cd restore.git; 135 | git update-ref refs/heads/master $new_commit; 136 | ) && 137 | git_log_only_subject restore.git > actual && 138 | test_cmp expect actual 139 | ' 140 | 141 | test_done 142 | -------------------------------------------------------------------------------- /t/test-binary-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangxin/gistore/f265013fdc94ab9d09c61de48e6646a4840659cd/t/test-binary-1.png -------------------------------------------------------------------------------- /t/test-binary-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangxin/gistore/f265013fdc94ab9d09c61de48e6646a4840659cd/t/test-binary-2.png --------------------------------------------------------------------------------