evilchelu-braid 0.4.0 → 0.4.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/{License.txt → LICENSE} +1 -1
  2. data/README.rdoc +27 -0
  3. data/Rakefile +17 -4
  4. data/bin/braid +29 -24
  5. data/braid.gemspec +7 -7
  6. data/lib/braid.rb +15 -18
  7. data/lib/braid/command.rb +94 -44
  8. data/lib/braid/commands/add.rb +20 -31
  9. data/lib/braid/commands/diff.rb +4 -3
  10. data/lib/braid/commands/remove.rb +8 -12
  11. data/lib/braid/commands/setup.rb +13 -18
  12. data/lib/braid/commands/update.rb +47 -48
  13. data/lib/braid/config.rb +54 -101
  14. data/lib/braid/mirror.rb +181 -0
  15. data/lib/braid/operations.rb +229 -204
  16. data/{spec/braid_spec.rb → test/braid_test.rb} +1 -1
  17. data/test/config_test.rb +62 -0
  18. data/test/fixtures/shiny/README +3 -0
  19. data/test/fixtures/skit1.1/layouts/layout.liquid +219 -0
  20. data/test/fixtures/skit1.2/layouts/layout.liquid +221 -0
  21. data/test/fixtures/skit1/layouts/layout.liquid +219 -0
  22. data/test/fixtures/skit1/preview.png +0 -0
  23. data/test/integration/adding_test.rb +80 -0
  24. data/test/integration/updating_test.rb +87 -0
  25. data/test/integration_helper.rb +69 -0
  26. data/test/mirror_test.rb +118 -0
  27. data/test/operations_test.rb +74 -0
  28. data/test/test_helper.rb +15 -0
  29. metadata +30 -33
  30. data/History.txt +0 -4
  31. data/Manifest.txt +0 -32
  32. data/README.txt +0 -53
  33. data/config/hoe.rb +0 -68
  34. data/config/requirements.rb +0 -17
  35. data/lib/braid/exceptions.rb +0 -33
  36. data/lib/braid/version.rb +0 -9
  37. data/script/destroy +0 -14
  38. data/script/generate +0 -14
  39. data/setup.rb +0 -1585
  40. data/spec/config_spec.rb +0 -117
  41. data/spec/operations_spec.rb +0 -48
  42. data/spec/spec.opts +0 -3
  43. data/spec/spec_helper.rb +0 -11
  44. data/tasks/deployment.rake +0 -27
  45. data/tasks/environment.rake +0 -7
  46. data/tasks/rspec.rake +0 -32
  47. data/tasks/website.rake +0 -9
@@ -1,9 +1,10 @@
1
1
  module Braid
2
2
  module Commands
3
3
  class Diff < Command
4
- def run(mirror)
5
- # easiest call, liek, evar.
6
- system("git diff #{WORK_BRANCH} HEAD #{mirror}")
4
+ def run(path)
5
+ mirror = config.get!(path)
6
+ diff = mirror.diff
7
+ puts diff unless diff.empty?
7
8
  end
8
9
  end
9
10
  end
@@ -1,25 +1,21 @@
1
1
  module Braid
2
2
  module Commands
3
3
  class Remove < Command
4
- def run(mirror)
5
- raise Braid::Git::LocalChangesPresent if invoke(:local_changes?)
4
+ def run(path)
5
+ mirror = config.get!(path)
6
6
 
7
- in_work_branch do
8
- params = config.get(mirror)
9
- unless params
10
- msg "Mirror '#{mirror}/' does not exist."
11
- return
12
- end
7
+ bail_on_local_changes!
13
8
 
14
- msg "Removing #{params["type"]} mirror from '#{mirror}/'."
9
+ with_reset_on_error do
10
+ msg "Removing mirror from '#{mirror.path}/'."
15
11
 
16
- invoke(:git_rm_r, mirror)
12
+ git.rm_r(mirror.path)
17
13
 
18
14
  config.remove(mirror)
19
15
  add_config_file
20
16
 
21
- commit_message = "Remove mirror '#{mirror}/'."
22
- invoke(:git_commit, commit_message)
17
+ commit_message = "Remove mirror '#{mirror.path}/'"
18
+ git.commit(commit_message)
23
19
  end
24
20
  end
25
21
  end
@@ -1,36 +1,31 @@
1
1
  module Braid
2
2
  module Commands
3
3
  class Setup < Command
4
- def run(mirror)
5
- mirror ? setup_one(mirror) : setup_all
4
+ def run(path = nil)
5
+ path ? setup_one(path) : setup_all
6
6
  end
7
7
 
8
8
  protected
9
9
  def setup_all
10
10
  msg "Setting up all mirrors."
11
- config.mirrors.each do |mirror|
12
- setup_one(mirror)
11
+ config.mirrors.each do |path|
12
+ setup_one(path)
13
13
  end
14
14
  end
15
15
 
16
- def setup_one(mirror)
17
- params = config.get(mirror)
18
- unless params
19
- msg "Mirror '#{mirror}/' does not exist. Skipping."
20
- return
21
- end
16
+ def setup_one(path)
17
+ mirror = config.get!(path)
22
18
 
23
- if find_remote(params["local_branch"])
24
- msg "Mirror '#{mirror}/' already has a remote. Skipping."
19
+ if git.remote_exists?(mirror.remote)
20
+ msg "Mirror '#{mirror.path}/' already has a remote. Skipping."
25
21
  return
26
22
  end
27
23
 
28
- msg "Setting up remote for '#{mirror}/'."
29
- case params["type"]
30
- when "git"
31
- invoke(:git_remote_add, params["local_branch"], params["remote"], params["branch"])
32
- when "svn"
33
- invoke(:git_svn_init, params["local_branch"], params["remote"])
24
+ msg "Setting up remote for '#{mirror.path}/'."
25
+ unless mirror.type == "svn"
26
+ git.remote_add(mirror.remote, mirror.cached_url, mirror.branch)
27
+ else
28
+ git_svn.init(mirror.remote, mirror.url)
34
29
  end
35
30
  end
36
31
  end
@@ -1,78 +1,77 @@
1
1
  module Braid
2
2
  module Commands
3
3
  class Update < Command
4
- def run(mirror, options = {})
5
- raise Braid::Git::LocalChangesPresent if invoke(:local_changes?)
4
+ def run(path, options = {})
5
+ bail_on_local_changes!
6
6
 
7
- in_work_branch do
8
- mirror ? update_one(mirror, options) : update_all
7
+ with_reset_on_error do
8
+ path ? update_one(path, options) : update_all(options)
9
9
  end
10
10
  end
11
11
 
12
12
  protected
13
- def update_all
13
+ def update_all(options = {})
14
+ options.reject! { |k,v| %w(revision head).include?(k) }
14
15
  msg "Updating all mirrors."
15
- config.mirrors.each do |mirror|
16
- update_one(mirror)
16
+ config.mirrors.each do |path|
17
+ update_one(path, options)
17
18
  end
18
19
  end
19
20
 
20
- def update_one(mirror, options = {})
21
- params = config.get(mirror)
22
- unless params
23
- msg "Mirror '#{mirror}/' does not exist. Skipping."
24
- return
25
- end
26
- local_branch = params["local_branch"]
27
-
28
- if check_for_lock(params, options)
29
- msg "Mirror '#{mirror}/' is locked to #{display_revision(params["type"], params["revision"])}. Skipping."
30
- return
31
- end
21
+ def update_one(path, options = {})
22
+ mirror = config.get!(path)
32
23
 
33
- # unlock
34
- if params["revision"] && options["head"]
35
- msg "Unlocking mirror '#{mirror}/'."
36
- options["revision"] = nil
24
+ # check options for lock modification
25
+ if mirror.locked?
26
+ if options["head"]
27
+ msg "Unlocking mirror '#{mirror.path}/'."
28
+ mirror.lock = nil
29
+ elsif !options["revision"]
30
+ msg "Mirror '#{mirror.path}/' is locked to #{display_revision(mirror, mirror.lock)}. Skipping."
31
+ return
32
+ end
37
33
  end
38
34
 
39
- begin
40
- fetch_remote(params["type"], local_branch)
35
+ mirror.fetch
41
36
 
42
- validate_revision_option(params, options)
43
- target = determine_target_commit(params, options)
37
+ new_revision = validate_new_revision(mirror, options["revision"])
38
+ target_hash = determine_target_commit(mirror, new_revision)
44
39
 
45
- check_merge_status(target)
46
- rescue Braid::Commands::MirrorAlreadyUpToDate
47
- msg "Mirror '#{mirror}/' is already up to date. Skipping."
48
- update_revision(mirror, options["revision"])
40
+ if mirror.merged?(target_hash)
41
+ msg "Mirror '#{mirror.path}/' is already up to date. Skipping."
49
42
  return
50
43
  end
51
44
 
52
- msg "Updating #{params["type"]} mirror '#{mirror}/'."
45
+ diff = mirror.diff if mirror.squashed? # get diff before setting revision
46
+
47
+ mirror.revision = new_revision
48
+ mirror.lock = new_revision if options["revision"]
49
+ config.update(mirror)
53
50
 
54
- if params["squash"]
55
- invoke(:git_rm_r, mirror)
56
- invoke(:git_read_tree, target, mirror)
51
+ msg "Updating mirror '#{mirror.path}/'."
52
+ if mirror.squashed?
53
+ git.rm_r(mirror.path)
54
+ git.read_tree(target_hash, mirror.path)
55
+ unless diff.empty?
56
+ git.apply(diff, *(options["safe"] ? ["--reject"] : []))
57
+ end
57
58
  else
58
- invoke(:git_merge_subtree, target)
59
+ git.merge_subtree(target_hash)
59
60
  end
60
61
 
61
- update_revision(mirror, options["revision"])
62
62
  add_config_file
63
63
 
64
- revision_message = " to " + (options["revision"] ? display_revision(params["type"], options["revision"]) : "HEAD")
65
- commit_message = "Update mirror '#{mirror}/'#{revision_message}."
66
- invoke(:git_commit, commit_message)
67
- end
68
-
69
- private
70
- def check_for_lock(params, options)
71
- params["revision"] && !options["revision"] && !options["head"]
72
- end
64
+ revision_message = " to " + (options["revision"] ? display_revision(mirror) : "HEAD")
65
+ commit_message = "Update mirror '#{mirror.path}/'#{revision_message}"
66
+ git.commit(commit_message)
73
67
 
74
- def update_revision(mirror, revision)
75
- config.update(mirror, { "revision" => revision })
68
+ rescue Operations::ShellExecutionError => error
69
+ if options["safe"]
70
+ msg "Caught shell error. Breaking."
71
+ exit(0)
72
+ else
73
+ raise error
74
+ end
76
75
  end
77
76
  end
78
77
  end
@@ -3,26 +3,26 @@ require 'yaml/store'
3
3
 
4
4
  module Braid
5
5
  class Config
6
- attr_accessor :db
7
-
8
- def initialize(config_file = nil)
9
- config_file ||= CONFIG_FILE
10
- @db = YAML::Store.new(config_file)
6
+ class PathAlreadyInUse < BraidError
7
+ def message
8
+ "path already in use: #{super}"
9
+ end
10
+ end
11
+ class MirrorDoesNotExist < BraidError
12
+ def message
13
+ "mirror does not exist: #{super}"
14
+ end
11
15
  end
12
16
 
13
- def add_from_options(remote, options)
14
- mirror, params = self.class.options_to_mirror(remote, options)
17
+ def initialize(config_file = CONFIG_FILE)
18
+ @db = YAML::Store.new(config_file)
19
+ end
15
20
 
16
- raise Braid::Config::RemoteIsRequired unless params["remote"]
17
- raise Braid::Config::MirrorTypeIsRequired unless params["type"]
18
- raise Braid::Config::BranchIsRequired unless params["type"] == "svn" || params["branch"]
19
- raise Braid::Config::MirrorNameIsRequired unless mirror
20
- raise Braid::Config::UnknownMirrorType unless MIRROR_TYPES.include?(params["type"])
21
+ def add_from_options(url, options)
22
+ mirror = Mirror.new_from_options(url, options)
21
23
 
22
- params.delete("rails_plugin")
23
- params.delete("branch") if params["type"] == "svn"
24
- add(mirror, params)
25
- [mirror, get(mirror)]
24
+ add(mirror)
25
+ mirror
26
26
  end
27
27
 
28
28
  def mirrors
@@ -31,118 +31,71 @@ module Braid
31
31
  end
32
32
  end
33
33
 
34
- def add(mirror, params)
35
- mirror = remove_trailing_slash(mirror)
36
- @db.transaction do
37
- raise Braid::Config::MirrorNameAlreadyInUse if @db[mirror]
38
- @db[mirror] = params.merge("remote" => remove_trailing_slash(params["remote"]))
39
- end
40
- end
41
-
42
- def get(mirror)
43
- mirror = remove_trailing_slash(mirror)
34
+ def get(path)
44
35
  @db.transaction(true) do
45
- @db[mirror]
36
+ if attributes = @db[path.to_s.sub(/\/$/, '')]
37
+ Mirror.new(path, attributes)
38
+ end
46
39
  end
47
40
  end
48
41
 
49
- def get!(mirror)
50
- params = get(mirror)
51
- raise Braid::Config::MirrorDoesNotExist unless params
52
- params
42
+ def get!(path)
43
+ mirror = get(path)
44
+ raise MirrorDoesNotExist, path unless mirror
45
+ mirror
53
46
  end
54
47
 
55
- def get_by_remote(remote)
56
- remote = remove_trailing_slash(remote)
57
- mirror = nil
58
- @db.transaction(true) do
59
- mirror = @db.roots.detect { |mirror| @db[mirror]["remote"] == remote }
48
+ def add(mirror)
49
+ @db.transaction do
50
+ raise PathAlreadyInUse, mirror.path if @db[mirror.path]
51
+ write_mirror(mirror)
60
52
  end
61
- [mirror, get(mirror)]
62
53
  end
63
54
 
64
55
  def remove(mirror)
65
- mirror = remove_trailing_slash(mirror)
66
56
  @db.transaction do
67
- @db.delete(mirror)
57
+ @db.delete(mirror.path)
68
58
  end
69
59
  end
70
60
 
71
- def update(mirror, params)
72
- mirror = remove_trailing_slash(mirror)
61
+ def update(mirror)
73
62
  @db.transaction do
74
- raise Braid::Config::MirrorDoesNotExist unless @db[mirror]
75
- tmp = @db[mirror].merge(params)
76
- @db[mirror] = tmp.merge("remote" => remove_trailing_slash(tmp["remote"]))
63
+ raise MirrorDoesNotExist, mirror.path unless @db[mirror.path]
64
+ @db.delete(mirror.path)
65
+ write_mirror(mirror)
77
66
  end
78
67
  end
79
68
 
80
- def replace(mirror, params)
81
- mirror = remove_trailing_slash(mirror)
82
- @db.transaction do
83
- raise Braid::Config::MirrorDoesNotExist unless @db[mirror]
84
- params["remote"] = remove_trailing_slash(params["remote"]) if params["remote"]
85
- @db[mirror] = params
69
+ def valid?
70
+ @db.transaction(true) do
71
+ !@db.roots.any? do |path|
72
+ @db[path]["url"].nil?
73
+ end
86
74
  end
87
75
  end
88
76
 
89
- def self.options_to_mirror(remote, options = {})
90
- remote = remove_trailing_slash(remote)
91
- branch = options["branch"] || "master"
92
-
93
- if options["type"]
94
- type = options["type"]
95
- else
96
- type = extract_type_from_path(remote)
97
- raise Braid::Config::CannotGuessMirrorType unless type
98
- end
99
-
100
- mirror = options["mirror"] || extract_mirror_from_path(remote)
101
-
102
- if options["rails_plugin"]
103
- mirror = "vendor/plugins/#{mirror}"
77
+ def migrate!
78
+ @db.transaction do
79
+ @db.roots.each do |path|
80
+ attributes = @db[path]
81
+ if attributes["local_branch"]
82
+ attributes["url"] = attributes.delete("remote")
83
+ attributes["remote"] = attributes.delete("local_branch")
84
+ attributes["squashed"] = attributes.delete("squash")
85
+ attributes["lock"] = attributes["revision"] # so far this has always been true
86
+ end
87
+ @db[path] = clean_attributes(attributes)
88
+ end
104
89
  end
105
-
106
- squash = !options["full"]
107
-
108
- [remove_trailing_slash(mirror), { "type" => type, "remote" => remote, "branch" => branch, "squash" => squash }]
109
90
  end
110
91
 
111
92
  private
112
- def remove_trailing_slash(path)
113
- self.class.send(:remove_trailing_slash, path)
93
+ def write_mirror(mirror)
94
+ @db[mirror.path] = clean_attributes(mirror.attributes)
114
95
  end
115
96
 
116
- def self.remove_trailing_slash(path)
117
- # bluh.
118
- path.sub(/\/$/, '')
119
- end
120
-
121
- def self.extract_type_from_path(path)
122
- return nil unless path
123
- path = remove_trailing_slash(path)
124
-
125
- # check for git:// and svn:// URLs
126
- path_scheme = path.split(":").first
127
- return path_scheme if %w[git svn].include?(path_scheme)
128
-
129
- return "svn" if path[-6..-1] == "/trunk"
130
- return "git" if path[-4..-1] == ".git"
131
- end
132
-
133
- def self.extract_mirror_from_path(path)
134
- return nil unless path
135
- name = File.basename(path)
136
-
137
- if File.extname(name) == ".git"
138
- # strip .git
139
- name[0..-5]
140
- elsif name == "trunk"
141
- # use parent
142
- File.basename(File.dirname(path))
143
- else
144
- name
145
- end
97
+ def clean_attributes(hash)
98
+ hash.reject { |k,v| v.nil? }
146
99
  end
147
100
  end
148
101
  end
@@ -0,0 +1,181 @@
1
+ module Braid
2
+ class Mirror
3
+ TYPES = %w(git svn)
4
+ ATTRIBUTES = %w(url remote type branch squashed revision lock)
5
+
6
+ class UnknownType < BraidError
7
+ def message
8
+ "unknown type: #{super}"
9
+ end
10
+ end
11
+ class CannotGuessType < BraidError
12
+ def message
13
+ "cannot guess type: #{super}"
14
+ end
15
+ end
16
+ class PathRequired < BraidError
17
+ def message
18
+ "path is required"
19
+ end
20
+ end
21
+
22
+ include Operations::VersionControl
23
+
24
+ attr_reader :path, :attributes
25
+
26
+ def initialize(path, attributes = {})
27
+ @path = path.sub(/\/$/, '')
28
+ @attributes = attributes
29
+ end
30
+
31
+ def self.new_from_options(url, options = {})
32
+ url.sub!(/\/$/, '')
33
+
34
+ branch = options["branch"] || "master"
35
+
36
+ if type = options["type"] || extract_type_from_url(url)
37
+ raise UnknownType, type unless TYPES.include?(type)
38
+ else
39
+ raise CannotGuessType, url
40
+ end
41
+
42
+ unless path = options["path"] || extract_path_from_url(url)
43
+ raise PathRequired
44
+ end
45
+
46
+ if options["rails_plugin"]
47
+ path = "vendor/plugins/#{path}"
48
+ end
49
+
50
+ remote = "braid/#{path}".gsub("_", '-') # stupid git svn changes all _ to ., weird
51
+ squashed = !options["full"]
52
+ branch = nil if type == "svn"
53
+
54
+ attributes = { "url" => url, "remote" => remote, "type" => type, "branch" => branch, "squashed" => squashed }
55
+ self.new(path, attributes)
56
+ end
57
+
58
+ def ==(comparison)
59
+ path == comparison.path && attributes == comparison.attributes
60
+ end
61
+
62
+ def type
63
+ # override Object#type
64
+ attributes["type"]
65
+ end
66
+
67
+ def locked?
68
+ !!lock
69
+ end
70
+
71
+ def squashed?
72
+ !!squashed
73
+ end
74
+
75
+ def merged?(commit)
76
+ # tip from spearce in #git:
77
+ # `test z$(git merge-base A B) = z$(git rev-parse --verify A)`
78
+ commit = git.rev_parse(commit)
79
+ if squashed?
80
+ !!base_revision && git.merge_base(commit, base_revision) == commit
81
+ else
82
+ git.merge_base(commit, "HEAD") == commit
83
+ end
84
+ end
85
+
86
+ def diff
87
+ remote_hash = git.rev_parse("#{base_revision}:")
88
+ local_hash = git.tree_hash(path)
89
+ remote_hash != local_hash ? git.diff_tree(remote_hash, local_hash, path) : ""
90
+ end
91
+
92
+ def fetch
93
+ unless type == "svn"
94
+ init_or_fetch_local_cache
95
+ git.fetch(remote)
96
+ else
97
+ git_svn.fetch(remote)
98
+ end
99
+ end
100
+
101
+ def cached_url
102
+ if Braid::USE_LOCAL_CACHE
103
+ File.join(Braid::LOCAL_CACHE_DIR, url.gsub(/[\/:@]/, "_"))
104
+ else
105
+ url
106
+ end
107
+ end
108
+
109
+ def init_or_fetch_local_cache
110
+ git_cache.init_or_fetch(url, cached_url)
111
+ end
112
+
113
+ private
114
+ def method_missing(name, *args)
115
+ if ATTRIBUTES.find { |attribute| name.to_s =~ /^(#{attribute})(=)?$/ }
116
+ unless $2
117
+ attributes[$1]
118
+ else
119
+ attributes[$1] = args[0]
120
+ end
121
+ else
122
+ raise NameError, "unknown attribute `#{name}'"
123
+ end
124
+ end
125
+
126
+ def base_revision
127
+ if revision
128
+ unless type == "svn"
129
+ git.rev_parse(revision)
130
+ else
131
+ git_svn.commit_hash(remote, revision)
132
+ end
133
+ else
134
+ inferred_revision
135
+ end
136
+ end
137
+
138
+ def inferred_revision
139
+ local_commits = git.rev_list("HEAD", "-- #{path}").split("\n")
140
+ remote_hashes = git.rev_list("--pretty=format:\"%T\"", remote).split("commit ").map do |chunk|
141
+ chunk.split("\n", 2).map { |value| value.strip }
142
+ end
143
+ hash = nil
144
+ local_commits.each do |local_commit|
145
+ local_tree = git.tree_hash(path, local_commit)
146
+ if match = remote_hashes.find { |_, remote_tree| local_tree == remote_tree }
147
+ hash = match[0]
148
+ break
149
+ end
150
+ end
151
+ hash
152
+ end
153
+
154
+ def self.extract_type_from_url(url)
155
+ return nil unless url
156
+ url.sub!(/\/$/, '')
157
+
158
+ # check for git:// and svn:// URLs
159
+ url_scheme = url.split(":").first
160
+ return url_scheme if TYPES.include?(url_scheme)
161
+
162
+ return "svn" if url[-6..-1] == "/trunk"
163
+ return "git" if url[-4..-1] == ".git"
164
+ end
165
+
166
+ def self.extract_path_from_url(url)
167
+ return nil unless url
168
+ name = File.basename(url)
169
+
170
+ if File.extname(name) == ".git"
171
+ # strip .git
172
+ name[0..-5]
173
+ elsif name == "trunk"
174
+ # use parent
175
+ File.basename(File.dirname(url))
176
+ else
177
+ name
178
+ end
179
+ end
180
+ end
181
+ end