evilchelu-braid 0.4.0 → 0.4.10

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.
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