mist 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +18 -0
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/.travis.yml +13 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +192 -0
- data/README.md +108 -0
- data/Rakefile +9 -0
- data/app/assets/images/mist/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- data/app/assets/images/mist/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- data/app/assets/images/mist/ui-bg_flat_10_000000_40x100.png +0 -0
- data/app/assets/images/mist/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- data/app/assets/images/mist/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- data/app/assets/images/mist/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/app/assets/images/mist/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- data/app/assets/images/mist/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- data/app/assets/images/mist/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- data/app/assets/images/mist/ui-icons_222222_256x240.png +0 -0
- data/app/assets/images/mist/ui-icons_228ef1_256x240.png +0 -0
- data/app/assets/images/mist/ui-icons_ef8c08_256x240.png +0 -0
- data/app/assets/images/mist/ui-icons_ffd27a_256x240.png +0 -0
- data/app/assets/images/mist/ui-icons_ffffff_256x240.png +0 -0
- data/app/assets/javascripts/mist/jquery-ui-1.8.17.custom.min.js +356 -0
- data/app/assets/javascripts/mist/posts.js.coffee +3 -0
- data/app/assets/javascripts/mist_core.js +3 -0
- data/app/assets/stylesheets/mist/posts.css.sass +97 -0
- data/app/assets/stylesheets/mist/scaffolds.css.scss +56 -0
- data/app/assets/stylesheets/mist/ui-lightness/jquery-ui-1.8.17.custom.css +565 -0
- data/app/assets/stylesheets/mist_core.css +4 -0
- data/app/controllers/application_controller.rb +3 -0
- data/app/controllers/mist/posts_controller.rb +130 -0
- data/app/helpers/mist/posts_helper.rb +61 -0
- data/app/mailers/.gitkeep +0 -0
- data/app/models/.gitkeep +0 -0
- data/app/models/mist/post.rb +240 -0
- data/app/models/mist/post_sweeper.rb +63 -0
- data/app/views/layouts/mist/posts.html.erb +66 -0
- data/app/views/mist/posts/_form.html.erb +55 -0
- data/app/views/mist/posts/_post.html.erb +19 -0
- data/app/views/mist/posts/_sidebar_title.html.erb +5 -0
- data/app/views/mist/posts/edit.html.erb +16 -0
- data/app/views/mist/posts/feed.atom.builder +17 -0
- data/app/views/mist/posts/index.html.erb +31 -0
- data/app/views/mist/posts/new.html.erb +15 -0
- data/app/views/mist/posts/show.html.erb +3 -0
- data/config/cucumber.yml +8 -0
- data/config/environment.rb +1 -0
- data/config/routes.rb +7 -0
- data/features/atom_feed.feature +20 -0
- data/features/authorize_create_post.feature +14 -0
- data/features/authorize_destroy_post.feature +27 -0
- data/features/authorize_update_post.feature +28 -0
- data/features/authorize_view_unpublished.feature +22 -0
- data/features/create_post.feature +13 -0
- data/features/destroy_post.feature +13 -0
- data/features/format_with_markdown.feature +13 -0
- data/features/popular_posts.feature +35 -0
- data/features/recent_posts.feature +28 -0
- data/features/similar_posts.feature +43 -0
- data/features/step_definitions/authorization_steps.rb +17 -0
- data/features/step_definitions/filesystem_steps.rb +13 -0
- data/features/step_definitions/page_steps.rb +43 -0
- data/features/step_definitions/post_steps.rb +58 -0
- data/features/step_definitions/sidebar_steps.rb +21 -0
- data/features/support/env.rb +58 -0
- data/features/support/setup.rb +15 -0
- data/features/update_post.feature +19 -0
- data/gemfiles/common +20 -0
- data/gemfiles/rails-3.1.3 +4 -0
- data/gemfiles/rails-3.1.3.lock +195 -0
- data/gemfiles/rails-3.2.0 +4 -0
- data/lib/generators/mist/USAGE +13 -0
- data/lib/generators/mist/setup_generator.rb +21 -0
- data/lib/generators/mist/templates/initializer.rb +17 -0
- data/lib/generators/mist/templates/mist.css.scss +2 -0
- data/lib/generators/mist/templates/mist.js.coffee +1 -0
- data/lib/generators/mist/views_generator.rb +11 -0
- data/lib/mist.rb +26 -0
- data/lib/mist/code_example_parser.rb +86 -0
- data/lib/mist/configuration.rb +68 -0
- data/lib/mist/configuration/author.rb +8 -0
- data/lib/mist/engine.rb +12 -0
- data/lib/mist/git_file_system_history.rb +66 -0
- data/lib/mist/git_model.rb +154 -0
- data/lib/mist/git_model/attributes.rb +21 -0
- data/lib/mist/git_model/class_methods.rb +141 -0
- data/lib/mist/permalink.rb +5 -0
- data/lib/mist/repository.rb +15 -0
- data/lib/mist/version.rb +8 -0
- data/mist.gemspec +33 -0
- data/spec/controllers/posts_controller_spec.rb +263 -0
- data/spec/dummy_rails_app/app/assets/javascripts/mist.js.coffee +1 -0
- data/spec/dummy_rails_app/app/assets/stylesheets/mist.css.scss +2 -0
- data/spec/dummy_rails_app/app/views/layouts/mist/posts.html.erb +60 -0
- data/spec/dummy_rails_app/config.ru +4 -0
- data/spec/dummy_rails_app/config/application.rb +48 -0
- data/spec/dummy_rails_app/config/boot.rb +7 -0
- data/spec/dummy_rails_app/config/database.yml +28 -0
- data/spec/dummy_rails_app/config/environment.rb +5 -0
- data/spec/dummy_rails_app/config/environments/development.rb +32 -0
- data/spec/dummy_rails_app/config/environments/production.rb +60 -0
- data/spec/dummy_rails_app/config/environments/test.rb +42 -0
- data/spec/dummy_rails_app/config/initializers/active_gist_credentials.rb +8 -0
- data/spec/dummy_rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy_rails_app/config/initializers/inflections.rb +10 -0
- data/spec/dummy_rails_app/config/initializers/mime_types.rb +5 -0
- data/spec/dummy_rails_app/config/initializers/mist.rb +17 -0
- data/spec/dummy_rails_app/config/initializers/secret_token.rb +7 -0
- data/spec/dummy_rails_app/config/initializers/session_store.rb +8 -0
- data/spec/dummy_rails_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy_rails_app/config/locales/en.yml +5 -0
- data/spec/dummy_rails_app/config/routes.rb +3 -0
- data/spec/dummy_rails_app/db/schema.rb +22 -0
- data/spec/dummy_rails_app/db/seeds.rb +7 -0
- data/spec/dummy_rails_app/doc/README_FOR_APP +2 -0
- data/spec/dummy_rails_app/lib/assets/.gitkeep +0 -0
- data/spec/dummy_rails_app/lib/tasks/.gitkeep +0 -0
- data/spec/dummy_rails_app/lib/tasks/cucumber.rake +65 -0
- data/spec/dummy_rails_app/public/404.html +26 -0
- data/spec/dummy_rails_app/public/422.html +26 -0
- data/spec/dummy_rails_app/public/500.html +26 -0
- data/spec/dummy_rails_app/public/favicon.ico +0 -0
- data/spec/dummy_rails_app/public/robots.txt +5 -0
- data/spec/dummy_rails_app/script/cucumber +10 -0
- data/spec/dummy_rails_app/script/rails +6 -0
- data/spec/dummy_rails_app/vendor/assets/stylesheets/.gitkeep +0 -0
- data/spec/dummy_rails_app/vendor/plugins/.gitkeep +0 -0
- data/spec/factories/posts.rb +8 -0
- data/spec/fixtures/gist_404 +6 -0
- data/spec/fixtures/gist_with_1_code_example +66 -0
- data/spec/helpers/posts_helper_spec.rb +64 -0
- data/spec/lib/mist/code_example_parser_spec.rb +135 -0
- data/spec/lib/mist/configuration_spec.rb +88 -0
- data/spec/lib/mist/permalink_spec.rb +17 -0
- data/spec/lib/mist/repository_spec.rb +20 -0
- data/spec/models/mist/git_model_spec.rb +260 -0
- data/spec/models/mist/post_spec.rb +575 -0
- data/spec/requests/posts_caching_spec.rb +152 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/cache_helpers.rb +71 -0
- data/spec/support/config.rb +42 -0
- data/spec/support/fakeweb.rb +3 -0
- data/spec/views/mist/posts/edit.html.erb_spec.rb +11 -0
- data/spec/views/mist/posts/index.html.erb_spec.rb +27 -0
- data/spec/views/mist/posts/new.html.erb_spec.rb +15 -0
- data/spec/views/mist/posts/show.html.erb_spec.rb +11 -0
- metadata +371 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Installs Mist into your Rails application.
|
|
3
|
+
|
|
4
|
+
Next Steps:
|
|
5
|
+
After running this generator, edit config/initializers/mist.rb
|
|
6
|
+
and hook up your authentication so that you can control who is
|
|
7
|
+
or isn't allowed to change posts.
|
|
8
|
+
|
|
9
|
+
Mist extracts its views and assets into your app/ directory.
|
|
10
|
+
You should personalize the look and feel of the blog by editing
|
|
11
|
+
those views.
|
|
12
|
+
|
|
13
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class Mist::SetupGenerator < Rails::Generators::Base
|
|
2
|
+
source_root File.expand_path('../../..', File.dirname(__FILE__))
|
|
3
|
+
|
|
4
|
+
def copy_views
|
|
5
|
+
copy_file 'app/views/layouts/mist/posts.html.erb'
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def install_assets
|
|
9
|
+
copy_file template_path('mist.js.coffee'), 'app/assets/javascripts/mist.js.coffee'
|
|
10
|
+
copy_file template_path('mist.css.scss'), 'app/assets/stylesheets/mist.css.scss'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create_initializer
|
|
14
|
+
copy_file template_path('initializer.rb'), 'config/initializers/mist.rb'
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
def template_path(relative_path)
|
|
19
|
+
File.expand_path(File.join('templates', relative_path), File.dirname(__FILE__))
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# This hook is called whenever Mist needs to know if a user
|
|
2
|
+
# is allowed to do something. You can also pass in specific
|
|
3
|
+
# actions such as :create_post, :edit_post and so on. See
|
|
4
|
+
# documentation for details.
|
|
5
|
+
Mist.authorize :all do |controller|
|
|
6
|
+
true
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
Mist.title = "Blog Title"
|
|
11
|
+
Mist.author.name = "John Doe"
|
|
12
|
+
Mist.author.email = "john@example.com"
|
|
13
|
+
|
|
14
|
+
# GitHub credentails -- without these you can't edit Gists
|
|
15
|
+
# because they're anonymous
|
|
16
|
+
ActiveGist::API.username = ENV['GITHUB_USERNAME']
|
|
17
|
+
ActiveGist::API.password = ENV['GITHUB_PASSWORD']
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# You can place customized Mist JavaScripts here.
|
data/lib/mist.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'git'
|
|
2
|
+
require 'github/markup'
|
|
3
|
+
require 'active_gist'
|
|
4
|
+
|
|
5
|
+
module Mist
|
|
6
|
+
require 'mist/engine'
|
|
7
|
+
require 'mist/configuration'
|
|
8
|
+
require 'mist/repository'
|
|
9
|
+
require 'mist/version'
|
|
10
|
+
require 'mist/permalink'
|
|
11
|
+
require 'mist/git_model'
|
|
12
|
+
require "mist/code_example_parser"
|
|
13
|
+
|
|
14
|
+
extend Mist::Configuration
|
|
15
|
+
extend Mist::Repository
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
delegate :log, :to => :repository
|
|
19
|
+
|
|
20
|
+
# Returns an array of all possible actions which Mist will authorize against
|
|
21
|
+
# at one point or another
|
|
22
|
+
def authorized_actions
|
|
23
|
+
[ :create_post, :edit_post, :destroy_post, :view_drafts ]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
class Mist::CodeExampleParser
|
|
2
|
+
attr_reader :examples, :content
|
|
3
|
+
|
|
4
|
+
class Example < String
|
|
5
|
+
attr_accessor :start_offset
|
|
6
|
+
attr_accessor :whitespace_skipped
|
|
7
|
+
attr_writer :filename
|
|
8
|
+
FILENAME_REGEXP = /\A[\s\t\n]*file: (.*?)[\n\r]+/
|
|
9
|
+
|
|
10
|
+
def initialize(start_offset)
|
|
11
|
+
@whitespace_skipped = 0
|
|
12
|
+
@start_offset = start_offset
|
|
13
|
+
super()
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def explicit_filename
|
|
17
|
+
@explicit_filename ||= if self =~ FILENAME_REGEXP
|
|
18
|
+
# omit filename from self
|
|
19
|
+
match = $~
|
|
20
|
+
@whitespace_skipped += match.offset(0)[1] - match.offset(0)[0]
|
|
21
|
+
sub! match[0], ''
|
|
22
|
+
match[1]
|
|
23
|
+
else
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def filename
|
|
29
|
+
@filename || explicit_filename
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def offset
|
|
33
|
+
start_offset...(start_offset+length+whitespace_skipped)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def initialize(content)
|
|
38
|
+
@content = content.to_s.dup
|
|
39
|
+
@examples = parse
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def parse
|
|
43
|
+
in_example = false
|
|
44
|
+
offset = 0
|
|
45
|
+
|
|
46
|
+
[].tap do |examples|
|
|
47
|
+
while match = /^ ([^\n]+(\n|\z))/m.match(content[offset..-1])
|
|
48
|
+
in_example = examples.last && match.offset(0)[0] == 0 && offset > 0
|
|
49
|
+
examples << Example.new(match.offset(0)[0]+offset) unless in_example
|
|
50
|
+
line = match[1]
|
|
51
|
+
examples.last.whitespace_skipped += 4
|
|
52
|
+
examples.last.concat match[1]
|
|
53
|
+
|
|
54
|
+
offset += match.offset(0)[1]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# detect multiple examples separated only by white space
|
|
58
|
+
# -- they are part of the same example
|
|
59
|
+
pass = proc do
|
|
60
|
+
call_again = false
|
|
61
|
+
examples.length.times do |index|
|
|
62
|
+
current = examples[index]
|
|
63
|
+
next if index == 0 or current.explicit_filename
|
|
64
|
+
|
|
65
|
+
previous = examples[index-1]
|
|
66
|
+
min = previous.offset.max
|
|
67
|
+
max = current.offset.min
|
|
68
|
+
|
|
69
|
+
if content[(min+1)...max] =~ /\A[\s\t\n]*\z/
|
|
70
|
+
previous.whitespace_skipped += current.whitespace_skipped
|
|
71
|
+
previous.concat $~[0] + current
|
|
72
|
+
examples.delete current
|
|
73
|
+
call_again = true
|
|
74
|
+
break
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
pass.call if call_again
|
|
79
|
+
end
|
|
80
|
+
pass.call
|
|
81
|
+
|
|
82
|
+
# Last pass: assign default filenames as needed
|
|
83
|
+
examples.each_with_index { |example, index| example.filename ||= "Example #{index+1}" }
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module Mist::Configuration
|
|
2
|
+
require 'mist/configuration/author'
|
|
3
|
+
|
|
4
|
+
def repository_location
|
|
5
|
+
@respository_location ||= default_repository_location
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def repository_location=(dir)
|
|
9
|
+
@respository_location = dir
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def default_repository_location
|
|
13
|
+
Rails.root.join("db/mist.repo.#{Rails.env}")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def reset_authorizations!
|
|
17
|
+
authorizations.clear
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def authorizations
|
|
21
|
+
@authorizations ||= {}.with_indifferent_access
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def commit_meta_data
|
|
25
|
+
@commit_meta_data = true if @commit_meta_data.nil?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def commit_meta_data=(a)
|
|
29
|
+
@commit_meta_data = a
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def author
|
|
33
|
+
@author ||= Mist::Configuration::Author.new
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def title=(title)
|
|
37
|
+
@title = title
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def title
|
|
41
|
+
@title ||= "Blog Title"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Register a block to be invoked whenever Mist needs to know if a user is allowed
|
|
45
|
+
# to perform some action, such as :create_post, :edit_post, :view_post, :destroy_post,
|
|
46
|
+
# or :all.
|
|
47
|
+
#
|
|
48
|
+
# Mist.authorize { |controller| ... } # invoke for any action not otherwise registered
|
|
49
|
+
# Mist.authorize(:all) { |controller| ... } # same as above
|
|
50
|
+
# Mist.authorize(:create_post) { |controller| ... } # use this block only for :create_post
|
|
51
|
+
#
|
|
52
|
+
# By default, all authorization requests will return false (denied).
|
|
53
|
+
#
|
|
54
|
+
def authorize(*types, &block)
|
|
55
|
+
raise ArgumentError, "Expected a block which will be evaluated at runtime" unless block_given?
|
|
56
|
+
types.push :all if types.empty?
|
|
57
|
+
types.each { |type| authorizations[type] = block }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Invokes the blocks registered by Mist::Configuration#authorize to see if the specified
|
|
61
|
+
# action is authorized. Passes *args into the block.
|
|
62
|
+
def authorized?(action, *args)
|
|
63
|
+
if authorizations.key?(action) then authorizations[action].call *args
|
|
64
|
+
elsif authorizations.key?(:all) then authorizations[:all].call *args
|
|
65
|
+
else nil
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
data/lib/mist/engine.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
require 'rails'
|
|
2
|
+
|
|
3
|
+
module Mist
|
|
4
|
+
class Engine < Rails::Engine
|
|
5
|
+
engine_name 'mist'
|
|
6
|
+
isolate_namespace Mist
|
|
7
|
+
|
|
8
|
+
initializer "mist.assets" do |app|
|
|
9
|
+
app.config.assets.precompile += ['mist_core.js', 'mist_core.css', 'mist.js', 'mist.css']
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
class Mist::GitFileSystemHistory
|
|
2
|
+
def initialize(path_or_repo)
|
|
3
|
+
@files = ActiveSupport::OrderedHash.new
|
|
4
|
+
@repo = path_or_repo.kind_of?(Git::Base) ? path_or_repo : Git.open(path_or_repo)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Returns files in order of discovery. Since they are discovered
|
|
8
|
+
# by traveling up the tree and backward in time, this is the
|
|
9
|
+
# REVERSE of the order of their actual creation.
|
|
10
|
+
def files
|
|
11
|
+
@files.keys.select { |filename| @files[filename] > 0 }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def mark_file(path, mode)
|
|
15
|
+
@files[path] ||= 0
|
|
16
|
+
case mode
|
|
17
|
+
when :created then @files[path] += 1
|
|
18
|
+
when :deleted then @files[path] -= 1
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def mark_tree(tree, mode, path = /./)
|
|
23
|
+
tree.full_tree.each do |entry|
|
|
24
|
+
file = entry.split(/\t/)[1]
|
|
25
|
+
next unless file[path]
|
|
26
|
+
mark_file file, mode
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Finds and returns `count` files, in the order of creation,
|
|
31
|
+
# in descending order (most recent files first),
|
|
32
|
+
# after accounting for deletion.
|
|
33
|
+
#
|
|
34
|
+
# If count is nil, all matches will be returned.
|
|
35
|
+
def find(count, path = /./)
|
|
36
|
+
begin
|
|
37
|
+
commit = @repo.log(1).first
|
|
38
|
+
rescue Git::GitExecuteError => err
|
|
39
|
+
# empty repository, no commits = no matches
|
|
40
|
+
return [] if err.message =~ /bad default revision 'HEAD'/
|
|
41
|
+
raise err
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
while commit.parent
|
|
45
|
+
commit.diff_parent.each do |diff|
|
|
46
|
+
next if diff.type == 'modified' # only care about new or deleted files
|
|
47
|
+
next unless diff.path[path]
|
|
48
|
+
|
|
49
|
+
case diff.type # because parent is B, changes are reversed.
|
|
50
|
+
when 'deleted' then mark_file diff.path, :created # file was created in this commit
|
|
51
|
+
when 'new' then mark_file diff.path, :deleted # file was deleted in this commit
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
return files if count and files.length == count
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
commit = commit.parent
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# if we made it this far then commit is the initial commit
|
|
61
|
+
# so at this stage, mark each file in the tree.
|
|
62
|
+
mark_tree commit.gtree, :created, path
|
|
63
|
+
|
|
64
|
+
count && files.length > count ? files[0...count] : files
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
class Mist::GitModel
|
|
2
|
+
require 'mist/git_model/attributes'
|
|
3
|
+
require 'mist/git_model/class_methods'
|
|
4
|
+
require 'mist/git_file_system_history'
|
|
5
|
+
|
|
6
|
+
extend ActiveModel::Callbacks
|
|
7
|
+
extend ActiveModel::Naming
|
|
8
|
+
include ActiveModel::Validations
|
|
9
|
+
include ActiveModel::Validations::Callbacks
|
|
10
|
+
include ActiveModel::Dirty
|
|
11
|
+
include ActiveModel::Conversion
|
|
12
|
+
include ActiveModel::Observing
|
|
13
|
+
extend Mist::GitModel::ClassMethods
|
|
14
|
+
|
|
15
|
+
define_model_callbacks :save, :create, :update, :initialize, :destroy
|
|
16
|
+
|
|
17
|
+
delegate :table_name, :record_path, :default_attributes, :to => 'self.class'
|
|
18
|
+
attribute :id, :default => proc { (self.class.count + 1).to_s }
|
|
19
|
+
attribute :id_on_record
|
|
20
|
+
|
|
21
|
+
validate do |record|
|
|
22
|
+
unless record.id.blank?
|
|
23
|
+
if record.id_changed? and self.class.find(record.id)
|
|
24
|
+
record.errors.add :id, "has already been taken"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def initialize(attributes = {})
|
|
30
|
+
run_callbacks :initialize do
|
|
31
|
+
default_attributes!
|
|
32
|
+
attributes.each do |key, value|
|
|
33
|
+
self.send(:"#{key}=", value)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def class_name
|
|
39
|
+
self.class.name
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def ==(other)
|
|
43
|
+
id == (other.kind_of?(self.class) ? other.id : other)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def commit_message
|
|
47
|
+
if new_record?
|
|
48
|
+
"Post created"
|
|
49
|
+
else
|
|
50
|
+
"Post updated"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def commit(commit_message = self.commit_message)
|
|
55
|
+
Mist.repository.add path
|
|
56
|
+
Mist.repository.commit commit_message
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def path
|
|
60
|
+
record_path id
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def path_was
|
|
64
|
+
record_path id_was
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def destroy
|
|
68
|
+
return if new_record?
|
|
69
|
+
run_callbacks :destroy do
|
|
70
|
+
Mist.repository.remove path, :recursive => true
|
|
71
|
+
Mist.repository.commit "Destroyed #{class_name} #{id.inspect}"
|
|
72
|
+
FileUtils.rm_rf path
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def update_attributes(attributes)
|
|
77
|
+
attributes.each { |k,v| self.send(:"#{k}=", v) }
|
|
78
|
+
save
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def attributes
|
|
82
|
+
@attributes ||= Mist::GitModel::Attributes.new(self)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def default_attributes!
|
|
86
|
+
default_attributes.each do |key, value|
|
|
87
|
+
if value.kind_of?(Proc)
|
|
88
|
+
attributes[key] = instance_eval &value
|
|
89
|
+
else
|
|
90
|
+
attributes[key] = value
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
changed_attributes.clear
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def persisted?
|
|
98
|
+
!changed? && !new_record?
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def new_record?
|
|
102
|
+
id_was.blank? || !File.file?(path_was)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def save
|
|
106
|
+
return false unless valid?
|
|
107
|
+
|
|
108
|
+
if new_record? || changed?
|
|
109
|
+
create_or_update_callback = new_record? ? :create : :update
|
|
110
|
+
run_callbacks create_or_update_callback do
|
|
111
|
+
run_callbacks :save do
|
|
112
|
+
Mist.repository.lib.mv path_was, path if !new_record? && id_changed?
|
|
113
|
+
save_record_file
|
|
114
|
+
commit
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
if !(files = Mist.repository.lib.diff_files).empty?
|
|
120
|
+
# if false, there's nothing we can reliably do to sync up unless we
|
|
121
|
+
# forcefully commit all changes, which the user may not want. However,
|
|
122
|
+
# if true, then this record was probably renamed from id_on_record
|
|
123
|
+
# to id. So, commit the deletion.
|
|
124
|
+
old_path = record_path(id_on_record).to_s.sub(/^#{Regexp::escape Mist.repository_location.to_s}\/?/, '')
|
|
125
|
+
if files.keys.include?(old_path) && files[old_path][:type] == 'D'
|
|
126
|
+
save_record_file
|
|
127
|
+
Mist.repository.lib.remove old_path
|
|
128
|
+
Mist.repository.lib.add path
|
|
129
|
+
Mist.repository.commit "Syncing to external filesystem changes"
|
|
130
|
+
else
|
|
131
|
+
Rails.logger.warn "Found uncommitted changes in git repo but didn't recognize them, so didn't commit them"
|
|
132
|
+
# no need to show the diff in the log, as the Git library has done that for us
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
@previously_changed = changes
|
|
137
|
+
changed_attributes.clear
|
|
138
|
+
true
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def save_record_file
|
|
142
|
+
self.id_on_record = id
|
|
143
|
+
FileUtils.mkdir_p File.dirname(path)
|
|
144
|
+
File.open(path, "w") { |f| f.print attributes.to_yaml }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def save!
|
|
148
|
+
raise "Record not saved: #{errors.full_messages.join('; ')}" unless save
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def inspect
|
|
152
|
+
"#<#{self.class.name} #{attributes.collect { |a| [a[0], a[1].inspect].join('=') }.join(' ')}>"
|
|
153
|
+
end
|
|
154
|
+
end
|