class Confgit::Repo

Constants

ROOT_KEY

Public Class Methods

define_command(command, *opts) click to toggle source

外部コマンドを定義する

# File lib/confgit/repo.rb, line 117
def self.define_command(command, *opts)
        define_method "confgit_#{command}" do |options, *args|
                args = getargs(args)

                Dir.chdir(@repo_path) { |path|
                        begin
                                args = opts + args
                                args.push(options)
                                system_(command, *args)
                        rescue => e
                                abort e.to_s
                        end
                }
        end
end
new(path = '~/.etc/confgit') click to toggle source
# File lib/confgit/repo.rb, line 21
def initialize(path = '~/.etc/confgit')
        @base_path = File.expand_path(path)
        @repos_path = File.join(@base_path, 'repos')

        FileUtils.mkpath(@repos_path)

        @config = read_config(File.join(@base_path, 'confgit.conf'))
        @repo_path = File.expand_path('current', @repos_path)

        valid_repo unless File.symlink?(@repo_path)
end

Public Instance Methods

arg_last_options(args) click to toggle source

引数の最後が Hash ならオプションとして取出す

# File lib/confgit/repo.rb, line 172
def arg_last_options(args)
        if args.last && args.last.kind_of?(Hash)
                args.pop
        else
                {}
        end
end
chrepo(repo) click to toggle source

リポジトリの変更

# File lib/confgit/repo.rb, line 51
def chrepo(repo)
        Dir.chdir(@repos_path) { |path|
                begin
                        if File.symlink?('current')
                                return if File.readlink('current') == repo
                                File.unlink('current')
                        end

                        unless File.exist?(repo)
                                FileUtils.mkpath(repo)

                                Dir.chdir(repo) { |path|
                                        begin
                                                out, err, status = Open3.capture3('git', 'init')
                                                $stderr.puts err unless err.empty?
                                        rescue => e
                                                FileUtils.remove_entry_secure(repo)
                                                abort e.to_s
                                        end
                                }
                        end

                        File.symlink(repo, 'current') 
                rescue => e
                        abort e.to_s
                end
        }
end
confgit_add(options, *files) click to toggle source

ファイルを管理対象に追加

# File lib/confgit/repo.rb, line 503
def confgit_add(options, *files)
        confgit_init unless File.exist?(@repo_path)
        repo = File.realpath(@repo_path)

        files.each { |path|
                path = expand_path(path)

                if relative_path(path) =~ /^[.]{2}/
                        $stderr.puts "'#{path}' is outside directory"
                        next
                end

                if file_directory?(path)
                        dir_each(path) { |file|
                                next if File.directory?(file)

                                from = File.join(path, file)
                                rel = relative_path(from)
                                to = File.join(repo, rel)

                                if filecopy(from, to)
                                        git('add', rel)
                                end
                        }
                else
                        from = path
                        rel = relative_path(from)
                        to = File.join(repo, rel)

                        if filecopy(from, to)
                                git('add', rel)
                        end
                end
        }
end
confgit_backup(options, *args) click to toggle source

バックアップする

# File lib/confgit/repo.rb, line 554
def confgit_backup(options, *args)
        git_each(*args) { |file, hash|
                next if file_directory?(file)

                from = File.join(root, file)
                to = File.join(@repo_path, file)

                unless file_exist?(from)
                        with_color(:fg_red) { print "[?] #{file}" }
                        puts
                        next
                end

                if options[:force] || modfile?(from, to)
                        with_color(:fg_blue) { print "--> #{file}" }
                        write = options[:yes]

                        if write == nil
                                # 書込みが決定していない場合
                                write = yes?(nil, false)
                        else
                                puts
                        end

                        filecopy(from, to) if write
                end
        }

        git('status', :interactive => false)
end
confgit_init(options) click to toggle source

リポジトリの初期化

# File lib/confgit/repo.rb, line 497
def confgit_init(options)
        FileUtils.mkpath(@repo_path)
        git('init')
end
confgit_list(options, *args) click to toggle source

一覧表示する

# File lib/confgit/repo.rb, line 617
def confgit_list(options, *args)
        git_each(*args) { |file, hash|
                next if file_directory?(file)

                from = File.join(root, file)
                to = File.join(@repo_path, file)

                if File.symlink?(from)
                        mode = options[:octal] ? '120000' : 'l---------'
                        user = '-'
                        group = '-'
                elsif File.exist?(from)
                        stat = File.stat(from)
                        mode = options[:octal] ? stat.mode.to_s(8) : mode2str(stat.mode)
                        user = Etc.getpwuid(stat.uid).name
                        group = Etc.getgrgid(stat.gid).name
                else
                        mode = ' ' * (options[:octal] ? 6 : 10)
                        user = '-'
                        group = '-'
                end

                print "#{mode}\t#{user}\t#{group}\t#{from}\n"
        }
end
confgit_path(options, subdir = '.') click to toggle source

リポジトリのパスを表示

# File lib/confgit/repo.rb, line 644
def confgit_path(options, subdir = '.')
        path = File.realpath(File.expand_path(subdir, @repo_path))
        print path, "\n"
end
confgit_repo(options, repo = nil) click to toggle source

カレントリポジトリの表示・変更

# File lib/confgit/repo.rb, line 465
def confgit_repo(options, repo = nil)
        if repo
                # 変更
                if options[:remove]
                        rmrepo(repo, options[:force])
                else
                        chrepo(repo)
                end
        else
                # 表示
                repo_each { |file, is_current|
                        mark = is_current ? '*' : ' '
                        print "#{mark} #{file}\n"
                }
        end
end
confgit_restore(options, *args) click to toggle source

リストアする

# File lib/confgit/repo.rb, line 586
def confgit_restore(options, *args)
        git_each(*args) { |file, hash|
                next if file_directory?(file)

                from = File.join(@repo_path, file)
                to = File.join(root, file)

                unless file_exist?(from)
                        with_color(:fg_red) { print "[?] #{file}" }
                        puts
                        next
                end

                if options[:force] || modfile?(from, to)
                        color = file_writable_real?(to) ? :fg_blue : :fg_magenta
                        with_color(color) { print "<-- #{file}" }
                        write = options[:yes]

                        if write == nil
                                # 書込みが決定していない場合
                                write = yes?(nil, false)
                        else
                                puts
                        end

                        filecopy(from, to) if write
                end
        }
end
confgit_rm(options, *args) click to toggle source

ファイルを管理対象から削除

# File lib/confgit/repo.rb, line 540
def confgit_rm(options, *args)
        return unless File.exist?(@repo_path)

        options = getopts(args)
        repo = File.realpath(@repo_path)

        files = args.collect { |from|
                relative_path(expand_path(from))
        }

        git('rm', *(options + files), :interactive => false)
end
confgit_root(options, value = nil) click to toggle source

ルートの表示・変更

# File lib/confgit/repo.rb, line 483
def confgit_root(options, value = nil)
        if options[:remove]
                # 削除
                self.root = nil
        elsif value
                # 変更
                self.root = value
        else
                # 表示
                puts root
        end
end
default_config() click to toggle source

設定の初期値

# File lib/confgit/repo.rb, line 99
def default_config
        {}
end
dir_each(subdir = '.') { |file| ... } click to toggle source

ディレクトリ内のファイルを繰返す

# File lib/confgit/repo.rb, line 331
def dir_each(subdir = '.')
        Dir.chdir(File.expand_path(subdir, @repo_path)) { |path|
                Dir.foreach('.') { |file|
                        next if /^(\.git|\.$|\.\.$)/ =~ file

                        yield(file)

                        if File.directory?(file)
                                Dir.glob("#{file}/**/*", File::FNM_DOTMATCH) { |file|
                                        if /(^|\/)(\.git|\.|\.\.)$/ !~ file
                                                yield(file)
                                        end
                                }
                        end
                }
        }
end
expand_path(path, dir = nil) click to toggle source

パスを展開する

# File lib/confgit/repo.rb, line 395
def expand_path(path, dir = nil)
        File.expand_path(path, dir).gsub(%r|^/private(/[^/]+)|) { |m|
                begin
                        subdir = $1
                        m = subdir if File.realpath(subdir) == m
                rescue
                end

                m
        }
end
file_directory?(path) click to toggle source

ファイルがディレクトリか?(シンボリックリンクの場合は対象外)

# File lib/confgit/repo.rb, line 285
def file_directory?(path)
        File.directory?(path) && ! File.symlink?(path)
end
file_exist?(path) click to toggle source

ファイルが存在するか?(シンボリックリンクの場合も対象にする)

# File lib/confgit/repo.rb, line 280
def file_exist?(path)
        File.exist?(path) || File.symlink?(path)
end
file_writable_real?(path) click to toggle source

ファイルが書込み可能か?(シンボリックリンクの場合はわからないのでとりあえず true を返す)

# File lib/confgit/repo.rb, line 290
def file_writable_real?(path)
        return true if File.symlink?(path)
        unless File.exist?(path)
                dir = File.dirname(path)
                dir = File.dirname(dir) until File.exist?(dir)
                return File.writable_real?(dir)
        end

        File.writable_real?(path)
end
filecopy(from, to, exiting = false) click to toggle source

ファイルのコピー(属性は維持する)

# File lib/confgit/repo.rb, line 302
def filecopy(from, to, exiting = false)
        begin
                to_dir = File.dirname(to)
                FileUtils.mkpath(to_dir)

                if File.symlink?(to)
                        # シンボリックリンクの場合は削除する
                        File.unlink(to)
                elsif File.exist?(to) && ! File.writable_real?(to)
                        # 書込みできない場合は削除を試みる
                        File.unlink(to)
                end

                FileUtils.copy_entry(from, to)

                unless File.symlink?(from) || File.symlink?(to)
                        stat = File.stat(from)
                        File.utime(stat.atime, stat.mtime, to)
                        File.chmod(stat.mode, to)
                end

                return true
        rescue => e
                abort e.to_s if exiting
                $stderr.puts e.to_s
        end
end
getargs(args, force = false) click to toggle source

引数を利用可能にする

# File lib/confgit/repo.rb, line 149
def getargs(args, force = false)
        args.collect { |x|
                run = false

                case x
                when /^-/
                when /\//
                        run = true
                else
                        run = force
                end

                if run
                        repo = File.realpath(@repo_path)
                        path = File.join(repo, x)
                        x = Pathname(path).relative_path_from(Pathname(repo)).to_s
                end

                x
        }
end
getopts(args) click to toggle source

オプションを取出す

# File lib/confgit/repo.rb, line 408
def getopts(args)
        options = []
        args.each { |opt|
                break unless /^-/ =~ opt
                options << args.shift
        }

        options
end
git(*args) click to toggle source

git を呼出す

# File lib/confgit/repo.rb, line 199
def git(*args)
        Dir.chdir(@repo_path) { |path|
                begin
                        system_('git', *args)
                rescue => e
                        abort e.to_s
                end
        }
end
git_args(args) click to toggle source

git コマンドの引数を生成する

# File lib/confgit/repo.rb, line 210
def git_args(args)
        args.collect { |item|
                item = $' if item.kind_of?(String) && %r|^/| =~ item
                item
        }
end
git_each(*args) { |file, hash| ... } click to toggle source

git に管理されているファイルを繰返す

# File lib/confgit/repo.rb, line 350
        def git_each(*args)
                args = getargs(args, true)
                files = args.collect { |f| f.shellescape }

                Dir.chdir(@repo_path) { |path|
                        open("| git ls-files --stage --full-name " + files.join(' ')) {|f|
                                while line = f.gets
                                        mode, hash, stage, file = line.split

#                                       file = line.chomp
                                        next if /^\.git/ =~ file
                                        next if file_directory?(file)

                                        yield(file, hash)
                                end
                        }
                }
        end
hash_object(file) click to toggle source

ファイルの hash値を求める

# File lib/confgit/repo.rb, line 247
def hash_object(file)
        path = File.expand_path(file)

        if File.symlink?(path)
                mode = '120000'
                hash = `readlink "#{path}" | git hash-object --stdin`.chomp
        else
                mode = File.executable?(path) ? '100755' : '100644'
                hash = `git hash-object "#{path}"`.chomp
        end

        "#{mode} #{hash}"
end
hostname() click to toggle source

ホスト名

# File lib/confgit/repo.rb, line 34
def hostname
        `hostname`.chomp
end
method_missing(name, *args, &block) click to toggle source

メソッドがない場合

Calls superclass method
# File lib/confgit/repo.rb, line 134
        def method_missing(name, *args, &block)
                if name.to_s =~ /^confgit_(.+)$/
                        options = args.shift
                        args = git_args(args).push(options)

                        command = $1.gsub(/_/, '-')
                        git(command, *args)

#                       abort "#{CMD} '#{$'}' is not a git command. See '#{CMD} --help'.\n"
                else
                        super
                end
        end
mode2str(bits) click to toggle source

ファイル属性を文字列にする

# File lib/confgit/repo.rb, line 424
def mode2str(bits)
        case bits & 0170000   # S_IFMT
        when 0010000  # S_IFIFO        パイプ
                mode = 'p'
        when 0020000  # S_IFCHR        キャラクタ・デバイス
                mode = 'c'
        when 0040000  # S_IFDIR        ディレクトリ
                mode = 'd'
        when 0060000  # S_IFBLK        ブロック・デバイス
                mode = 'b'
        when 0100000  # S_IFREG        通常ファイル
                mode = '-'
        when 0120000  # S_IFLNK        シンボリックリンク
                mode = 'l'
        when 0140000  # S_IFSOCK       ソケット
                mode = 's'
        when 0160000  # S_IFWHT        BSD空白ファイル
                mode = 'w'
        end

        mode += 'rwx'*3

        (0..8).each { |i| 
                mask = 1<<i
                mode[-(i+1)] = '-' if (bits & mask) == 0
        }

        if (bits & 0001000) != 0      # S_ISVTX    スティッキービット
                if mode[-1] == '-'
                        mode[-1] = 'T'
                else
                        mode[-1] = 't'
                end
        end

        mode
end
modfile?(from, to) click to toggle source

ファイルの更新チェック

# File lib/confgit/repo.rb, line 419
def modfile?(from, to)
        ! file_exist?(to) || hash_object(from) != hash_object(to)
end
read_config(file) click to toggle source

設定の読込み

# File lib/confgit/repo.rb, line 104
def read_config(file)
        if File.exist?(file)
                config = JSON.parse(File.read(file))
                config = default_config.merge(config)
        else
                config = default_config
                File.write(file, JSON.pretty_generate(config)+"\n")
        end

        return config
end
relative_path(path) click to toggle source

ルートからの相対パス

# File lib/confgit/repo.rb, line 241
def relative_path(path)
        root_path = Pathname.new(root)
        Pathname.new(path).relative_path_from(root_path).to_s
end
repo_each() { |file, is_current| ... } click to toggle source

リポジトリを繰返す

# File lib/confgit/repo.rb, line 370
def repo_each
        Dir.chdir(@repos_path) { |path|
                begin
                        current = File.expand_path(File.readlink('current'))
                rescue
                end

                Dir.glob('*') { |file|
                        next if /^current$/ =~ file

                        if current && File.realpath(file) == current
                                is_current = true
                                current = nil
                        else
                                is_current = false
                        end

                        yield(file, is_current)
                }

                yield(File.readlink('current'), true) if current
        }
end
rmrepo(repo, force = false) click to toggle source

リポジトリの削除

# File lib/confgit/repo.rb, line 81
def rmrepo(repo, force = false)
        Dir.chdir(@repos_path) { |path|
                begin
                        if File.symlink?('current') && File.readlink('current') == repo
                                abort "'#{repo}' is current repository!" unless force
                                File.unlink('current')
                        end

                        FileUtils.remove_entry_secure(repo)

                        valid_repo unless File.symlink?('current')
                rescue => e
                        abort e.to_s
                end
        }
end
root() click to toggle source

ルートのパスを取得する

# File lib/confgit/repo.rb, line 218
def root
        return @root if @root

        # 表示
        out, err, status = git('config', '--path', '--local', ROOT_KEY, :capture => true)
        out.chomp!
        out = '/' if out.empty?

        @root = out
end
root=(value) click to toggle source

ルートのパスを設定する

# File lib/confgit/repo.rb, line 230
def root=(value)
        if value && ! value.empty?
                git('config', '--path', '--local', ROOT_KEY, value)
        else
                git('config', '--unset', '--local', ROOT_KEY)
        end

        @root = nil
end
system_(command, *args) click to toggle source

オプションに応じて外部呼出しを行う

# File lib/confgit/repo.rb, line 181
def system_(command, *args)
        options = arg_last_options(args)

        if options[:interactive] == false
                out, err, status = Open3.capture3(command, *args)

                $stdout.print out unless out.empty?
                $stderr.print err unless err.empty?

                status
        elsif options[:capture]
                Open3.capture3(command, *args)
        else
                system(command, *args)
        end
end
valid_repo() click to toggle source

カレントリポジトリがない場合の処理

# File lib/confgit/repo.rb, line 39
def valid_repo
        repo = nil

        repo_each { |file, is_current|
                repo = file
                break
        }

        chrepo(repo || hostname)
end
yes?(prompt, y = true) click to toggle source

確認プロンプトを表示する

# File lib/confgit/repo.rb, line 262
def yes?(prompt, y = true)
        yn = y ? 'Yn' : 'yN'
        print "#{prompt} [#{yn}]: "

        result = $stdin.gets.chomp

        return y if result.empty?

        if /^(y|yes)$/i =~ result
                y = true
        else
                y = false
        end

        y
end