Skip to content

Commit f5a2086

Browse files
committed
first commit
0 parents  commit f5a2086

File tree

18 files changed

+699
-0
lines changed

18 files changed

+699
-0
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.gem
2+
*.swp
3+
.fixi
4+
.bundle
5+
Gemfile.lock
6+
pkg/*

Gemfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
source "http://rubygems.org"
2+
3+
# Specify your gem's dependencies in fixi.gemspec
4+
gemspec

Rakefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
require "bundler/gem_tasks"

bin/fixi

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env ruby
2+
3+
require 'rubygems'
4+
require 'trollop'
5+
require 'fixi'
6+
7+
name = ARGV.shift
8+
if name == "--help" || name == "-h" || name == "help"
9+
name = ARGV.shift
10+
if name.nil?
11+
puts <<-EOS
12+
usage: fixi [--version] [--help] <command> [<options>] [path]
13+
14+
All commands are scoped to the current directory or the given path, if specified.
15+
16+
Commands:
17+
add: #{Fixi::Command::Add.synopsis}
18+
check: #{Fixi::Command::Check.synopsis}
19+
commit: #{Fixi::Command::Commit.synopsis}
20+
info: #{Fixi::Command::Info.synopsis}
21+
init: #{Fixi::Command::Init.synopsis}
22+
ls: #{Fixi::Command::Ls.synopsis}
23+
rm: #{Fixi::Command::Rm.synopsis}
24+
sum: #{Fixi::Command::Sum.synopsis}
25+
26+
See 'fixi help <command>' for more information on a specific command.
27+
EOS
28+
exit 0
29+
else
30+
ARGV.insert(0, "--help")
31+
end
32+
elsif name == "--version" || name == "-v"
33+
puts "fixi version #{Fixi::VERSION}"
34+
exit 0
35+
end
36+
37+
command = Fixi::command name
38+
if command.nil?
39+
puts "Error: No such command: #{name}"
40+
exit 1
41+
end
42+
begin
43+
command.execute ARGV
44+
rescue RuntimeError => msg
45+
puts "Error: #{msg}"
46+
exit 1
47+
#rescue => msg
48+
# puts "Error: #{msg}"
49+
# exit 1
50+
end

fixi.gemspec

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# -*- encoding: utf-8 -*-
2+
$:.push File.expand_path("../lib", __FILE__)
3+
require "fixi/version"
4+
5+
Gem::Specification.new do |s|
6+
s.name = "fixi"
7+
s.version = Fixi::VERSION
8+
s.authors = ["Chris Wilper"]
9+
s.email = ["[email protected]"]
10+
s.homepage = ""
11+
s.summary = "A fixity tracker utility"
12+
s.description = "Keeps an index of checksums and lets you update and verify them"
13+
14+
s.rubyforge_project = "fixi"
15+
16+
s.files = `git ls-files`.split("\n")
17+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19+
s.require_paths = ["lib"]
20+
21+
# specify any dependencies here; for example:
22+
# s.add_development_dependency "rspec"
23+
# s.add_runtime_dependency "rest-client"
24+
25+
s.add_runtime_dependency "trollop"
26+
end

lib/fixi.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
require "digest"
2+
require "fixi/version"
3+
require "fixi/patch/string_pack"
4+
require "fixi/command"
5+
6+
module Fixi
7+
# Get an instance of the command with the given name
8+
def self.command(name)
9+
return nil unless Command.const_defined? name.capitalize
10+
Command.const_get(name.capitalize).new
11+
end
12+
13+
# Validate the given comma-separated list of checksum algorithms
14+
# and return and array of matching Digest implementations
15+
def self.digests(checksums)
16+
digests = []
17+
checksums.split(",").each do |checksum|
18+
begin
19+
digests << Digest(checksum.upcase).new
20+
rescue LoadError
21+
raise "No such algorithm: #{checksum}"
22+
end
23+
end
24+
digests
25+
end
26+
27+
# Read the file once while computing any number of digests
28+
def self.hexdigests(digests, file)
29+
File.open(file, "rb") {|f|
30+
buf = ""
31+
while f.read(16384, buf)
32+
digests.each {|digest| digest.update buf}
33+
end
34+
}
35+
hds = []
36+
digests.each {|digest|
37+
hd = digest.hexdigest
38+
hds << hd
39+
}
40+
hds
41+
end
42+
end

lib/fixi/command.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module Fixi::Command
2+
def self.banner(name)
3+
"fixi-#{name}: #{const_get(name.capitalize).synopsis}\n\n" +
4+
"usage: fixi #{name} [<options>] [path]\n\n" +
5+
"#{const_get(name.capitalize).details}\n\n" +
6+
"Options:"
7+
end
8+
end
9+
10+
Dir.glob(File.dirname(__FILE__) + '/command/*') {|file| require file}

lib/fixi/command/add.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
require 'trollop'
2+
require 'fixi/index'
3+
4+
class Fixi::Command::Add
5+
def self.synopsis
6+
"Add new files to the index"
7+
end
8+
9+
def self.details
10+
"This command is scoped to the current directory or the given path,
11+
if specified.".pack
12+
end
13+
14+
def execute args
15+
opts = Trollop::options args do
16+
banner Fixi::Command.banner "add"
17+
opt :absolute, "Show absolute paths. By default, paths are reported
18+
relative to the index root.".pack
19+
opt :dry_run, "Don't do anything; just report what would be done"
20+
end
21+
path = File.expand_path(args[0] || ".")
22+
index = Fixi::Index.new(path)
23+
24+
index.find(path) do |abspath|
25+
relpath = index.relpath(abspath)
26+
unless index.contains?(relpath)
27+
puts "A #{opts[:absolute] ? abspath : relpath}"
28+
index.add(relpath) unless opts[:dry_run]
29+
end
30+
end
31+
32+
end
33+
end

lib/fixi/command/check.rb

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
require 'trollop'
2+
3+
class Fixi::Command::Check
4+
def self.synopsis
5+
"Verify the fixity of files in the index"
6+
end
7+
8+
def self.details
9+
"This command is scoped to the current directory or the given path,
10+
if specified.".pack
11+
end
12+
13+
def execute args
14+
opts = Trollop::options args do
15+
banner Fixi::Command.banner "check"
16+
opt :absolute, "Show absolute paths. By default, paths are reported
17+
relative to the index root.".pack
18+
opt :shallow, "Do shallow comparisons when determining which files have
19+
changed. If specified, only file sizes and mtimes will be used. By
20+
default, checksums will also be computed and compared if necessary.".pack
21+
opt :verbose, "For modified files, show which attribute changed.
22+
By default, only the path is shown.".pack
23+
end
24+
path = File.expand_path(args[0] || ".")
25+
index = Fixi::Index.new(path)
26+
27+
index.each(args[0]) do |hash|
28+
relpath = hash['relpath']
29+
abspath = index.rootpath + '/' + relpath
30+
if index.file_in_scope(relpath)
31+
if File.exists?(abspath)
32+
size = File.size(abspath)
33+
mtime = File.mtime(abspath).to_i
34+
if size != hash['size']
35+
detail = opts[:verbose] ? "size=#{size} " : ""
36+
puts "M #{detail}#{opts[:absolute] ? abspath : relpath}"
37+
elsif File.mtime(abspath).to_i != hash['mtime']
38+
detail = opts[:verbose] ? "mtime=#{Time.at(mtime).utc.iso8601} " : ""
39+
puts "M #{detail}#{opts[:absolute] ? abspath : relpath}"
40+
elsif not opts[:shallow]
41+
hexdigests = Fixi::hexdigests(Fixi::digests(index.algorithms), abspath)
42+
i = 0
43+
index.algorithms.split(',').each do |algorithm|
44+
if hexdigests[i] != hash[algorithm]
45+
detail = opts[:verbose] ? "#{algorithm}=#{hexdigests[i]} " : ""
46+
puts "M #{detail}#{opts[:absolute] ? abspath : relpath}"
47+
end
48+
i += 1
49+
end
50+
end
51+
else
52+
puts "D #{opts[:absolute] ? abspath : relpath}"
53+
end
54+
else
55+
puts "X #{opts[:absolute] ? abspath : relpath}"
56+
end
57+
end
58+
59+
index.find(path) do |abspath|
60+
relpath = index.relpath(abspath)
61+
unless index.contains?(relpath)
62+
puts "A #{opts[:absolute] ? abspath : relpath}"
63+
end
64+
end
65+
end
66+
end

lib/fixi/command/commit.rb

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
require 'trollop'
2+
require 'find'
3+
require 'fixi/index'
4+
5+
class Fixi::Command::Commit
6+
def self.synopsis
7+
"Commit modified files to the index"
8+
end
9+
10+
def self.details
11+
"This command is scoped to the current directory or the given path,
12+
if specified.".pack
13+
end
14+
15+
def execute args
16+
opts = Trollop::options args do
17+
banner Fixi::Command.banner "commit"
18+
opt :absolute, "Show absolute paths. By default, paths are reported
19+
relative to the index root.".pack
20+
opt :dry_run, "Don't do anything; just report what would be done"
21+
opt :shallow, "Do shallow comparisons when determining which files have
22+
changed. If specified, only file sizes and mtimes will be used. By
23+
default, checksums will also be computed and compared if necessary.".pack
24+
opt :verbose, "For modified files, show which attribute changed.
25+
By default, only the path is shown.".pack
26+
end
27+
path = File.expand_path(args[0] || ".")
28+
index = Fixi::Index.new(path)
29+
30+
index.each(args[0]) do |hash|
31+
relpath = hash['relpath']
32+
abspath = index.rootpath + '/' + relpath
33+
if index.file_in_scope(relpath) && File.exists?(abspath)
34+
size = File.size(abspath)
35+
mtime = File.mtime(abspath).to_i
36+
if size != hash['size']
37+
detail = opts[:verbose] ? "size=#{size} " : ""
38+
puts "M #{detail}#{opts[:absolute] ? abspath : relpath}"
39+
index.update(relpath) unless opts[:dry_run]
40+
elsif mtime != hash['mtime']
41+
detail = opts[:verbose] ? "mtime=#{Time.at(mtime).utc.iso8601} " : ""
42+
puts "M #{detail}#{opts[:absolute] ? abspath : relpath}"
43+
index.update(relpath) unless opts[:dry_run]
44+
elsif not opts[:shallow]
45+
hexdigests = Fixi::hexdigests(Fixi::digests(index.algorithms), abspath)
46+
i = 0
47+
need_update = false
48+
index.algorithms.split(',').each do |algorithm|
49+
if not(need_update) && (hexdigests[i] != hash[algorithm])
50+
need_update = true
51+
detail = opts[:verbose] ? "#{algorithm}=#{hexdigests[i]} " : ""
52+
puts "M #{detail}#{opts[:absolute] ? abspath : relpath}"
53+
end
54+
hash[algorithm] = hexdigests[i]
55+
i += 1
56+
end
57+
index.update(relpath, hash) if need_update && not(opts[:dry_run])
58+
end
59+
end
60+
end
61+
end
62+
end

0 commit comments

Comments
 (0)