mlightner-braid 0.5.1

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.
@@ -0,0 +1,131 @@
1
+ module Braid
2
+ class Command
3
+ class InvalidRevision < BraidError
4
+ end
5
+
6
+ extend Operations::VersionControl
7
+ include Operations::VersionControl
8
+
9
+ def self.run(command, *args)
10
+ verify_git_version!
11
+
12
+ klass = Commands.const_get(command.to_s.capitalize)
13
+ klass.new.run(*args)
14
+
15
+ rescue BraidError => error
16
+ case error
17
+ when Operations::ShellExecutionError
18
+ msg "Shell error: #{error.message}"
19
+ else
20
+ msg "Error: #{error.message}"
21
+ end
22
+ exit(1)
23
+ end
24
+
25
+ def self.msg(str)
26
+ puts "Braid: #{str}"
27
+ end
28
+
29
+ def msg(str)
30
+ self.class.msg(str)
31
+ end
32
+
33
+ def config
34
+ @config ||= load_and_migrate_config
35
+ end
36
+
37
+ def verbose?
38
+ Braid.verbose
39
+ end
40
+
41
+ private
42
+ def setup_remote(mirror)
43
+ Command.run(:setup, mirror.path)
44
+ end
45
+
46
+ def use_local_cache?
47
+ Braid.use_local_cache
48
+ end
49
+
50
+ def self.verify_git_version!
51
+ git.require_version!(REQUIRED_GIT_VERSION)
52
+ end
53
+
54
+ def bail_on_local_changes!
55
+ git.ensure_clean!
56
+ end
57
+
58
+ def with_reset_on_error
59
+ work_head = git.head
60
+
61
+ begin
62
+ yield
63
+ rescue => error
64
+ msg "Resetting to '#{work_head[0, 7]}'."
65
+ git.reset_hard(work_head)
66
+ raise error
67
+ end
68
+ end
69
+
70
+ def load_and_migrate_config
71
+ config = Config.new
72
+ unless config.valid?
73
+ msg "Configuration is outdated. Migrating."
74
+ bail_on_local_changes!
75
+ config.migrate!
76
+ git.commit("Upgrade .braids", "-- .braids")
77
+ end
78
+ config
79
+ end
80
+
81
+ def add_config_file
82
+ git.add(CONFIG_FILE)
83
+ end
84
+
85
+ def display_revision(mirror, revision = nil)
86
+ revision ||= mirror.revision
87
+ mirror.type == "svn" ? "r#{revision}" : "'#{revision[0, 7]}'"
88
+ end
89
+
90
+ def validate_new_revision(mirror, new_revision)
91
+ unless new_revision
92
+ unless mirror.type == "svn"
93
+ return git.rev_parse(mirror.remote)
94
+ else
95
+ return svn.head_revision(mirror.url)
96
+ end
97
+ end
98
+
99
+ unless mirror.type == "svn"
100
+ new_revision = git.rev_parse(new_revision)
101
+ else
102
+ new_revision = svn.clean_revision(new_revision)
103
+ end
104
+ old_revision = mirror.revision
105
+
106
+ if new_revision == old_revision
107
+ raise InvalidRevision, "mirror is already at requested revision"
108
+ end
109
+
110
+ if mirror.type == "svn"
111
+ if old_revision && new_revision < old_revision
112
+ raise InvalidRevision, "local revision is higher than request revision"
113
+ end
114
+
115
+ if svn.head_revision(mirror.url) < new_revision
116
+ raise InvalidRevision, "requested revision is higher than remote revision"
117
+ end
118
+ end
119
+
120
+ new_revision
121
+ end
122
+
123
+ def determine_target_revision(mirror, new_revision)
124
+ unless mirror.type == "svn"
125
+ git.rev_parse(new_revision)
126
+ else
127
+ git_svn.commit_hash(mirror.remote, new_revision)
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,42 @@
1
+ module Braid
2
+ module Commands
3
+ class Add < Command
4
+ def run(url, options = {})
5
+ bail_on_local_changes!
6
+
7
+ with_reset_on_error do
8
+ mirror = config.add_from_options(url, options)
9
+
10
+ branch_message = (mirror.type == "svn" || mirror.branch == "master") ? "" : " branch '#{mirror.branch}'"
11
+ revision_message = options["revision"] ? " at #{display_revision(mirror, options["revision"])}" : ""
12
+ msg "Adding #{mirror.type} mirror of '#{mirror.url}'#{branch_message}#{revision_message}."
13
+
14
+ # these commands are explained in the subtree merge guide
15
+ # http://www.kernel.org/pub/software/scm/git/docs/howto/using-merge-subtree.html
16
+
17
+ setup_remote(mirror)
18
+ mirror.fetch
19
+
20
+ new_revision = validate_new_revision(mirror, options["revision"])
21
+ target_revision = determine_target_revision(mirror, new_revision)
22
+
23
+ unless mirror.squashed?
24
+ git.merge_ours(target_revision)
25
+ end
26
+ git.read_tree_prefix(target_revision, mirror.path)
27
+
28
+ mirror.revision = new_revision
29
+ mirror.lock = new_revision if options["revision"]
30
+ config.update(mirror)
31
+ add_config_file
32
+
33
+ commit_message = "Added mirror '#{mirror.path}' at #{display_revision(mirror)}"
34
+
35
+ git.commit(commit_message)
36
+ msg commit_message
37
+ end
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,13 @@
1
+ module Braid
2
+ module Commands
3
+ class Diff < Command
4
+ def run(path)
5
+ mirror = config.get!(path)
6
+ setup_remote(mirror)
7
+
8
+ diff = mirror.diff
9
+ puts diff unless diff.empty?
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,32 @@
1
+ module Braid
2
+ module Commands
3
+ class List < Command
4
+ def run(path = nil, options = {})
5
+ with_reset_on_error do
6
+ path ? list_one(path, options) : list_all(options)
7
+ end
8
+ end
9
+
10
+ protected
11
+ def list_all(options = {})
12
+ options.reject! { |k,v| %w(revision head).include?(k) }
13
+ print "\n"
14
+ msg "Listing all mirrors.\n=======================================================\n"
15
+ config.mirrors.each_with_index do |path, i|
16
+ mirror = config.get!(path)
17
+ print " #{i + 1}) #{path.to_s}"
18
+ print " [LOCKED]" if mirror.locked?
19
+ setup_remote(mirror)
20
+ msg "Fetching new commits for '#{mirror.path}'." if verbose?
21
+ mirror.fetch
22
+ new_revision = validate_new_revision(mirror, options["revision"])
23
+ target_revision = determine_target_revision(mirror, new_revision)
24
+ print " !!! UPDATE AVAILABLE !!!" if new_revision.to_s != mirror.base_revision.to_s
25
+ print "\n"
26
+ end
27
+ print "\n"
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ module Braid
2
+ module Commands
3
+ class Remove < Command
4
+ def run(path, options = {})
5
+ mirror = config.get!(path)
6
+
7
+ bail_on_local_changes!
8
+
9
+ with_reset_on_error do
10
+ msg "Removing mirror from '#{mirror.path}'."
11
+
12
+ git.rm_r(mirror.path)
13
+
14
+ config.remove(mirror)
15
+ add_config_file
16
+
17
+ if options[:keep]
18
+ msg "Not removing remote '#{mirror.remote}'" if verbose?
19
+ elsif git.remote_url(mirror.remote)
20
+ msg "Removed remote '#{mirror.path}'" if verbose?
21
+ git.remote_rm mirror.remote
22
+ else
23
+ msg "Remote '#{mirror.remote}' not found, nothing to cleanup" if verbose?
24
+ end
25
+
26
+ commit_message = "Removed mirror '#{mirror.path}'"
27
+ git.commit(commit_message)
28
+ msg commit_message
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,34 @@
1
+ module Braid
2
+ module Commands
3
+ class Setup < Command
4
+ def run(path = nil)
5
+ path ? setup_one(path) : setup_all
6
+ end
7
+
8
+ protected
9
+ def setup_all
10
+ msg "Setting up all mirrors."
11
+ config.mirrors.each do |path|
12
+ setup_one(path)
13
+ end
14
+ end
15
+
16
+ def setup_one(path)
17
+ mirror = config.get!(path)
18
+
19
+ if git.remote_url(mirror.remote)
20
+ msg "Setup: Mirror '#{mirror.path}' already has a remote. Reusing it." if verbose?
21
+ return
22
+ end
23
+
24
+ msg "Setup: Creating remote for '#{mirror.path}'."
25
+ unless mirror.type == "svn"
26
+ url = use_local_cache? ? git_cache.path(mirror.url) : mirror.url
27
+ git.remote_add(mirror.remote, url, mirror.branch)
28
+ else
29
+ git_svn.init(mirror.remote, mirror.url)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,102 @@
1
+ module Braid
2
+ module Commands
3
+ class Update < Command
4
+ def run(path, options = {})
5
+ bail_on_local_changes!
6
+
7
+ with_reset_on_error do
8
+ path ? update_one(path, options) : update_all(options)
9
+ end
10
+ end
11
+
12
+ protected
13
+ def update_all(options = {})
14
+ options.reject! { |k,v| %w(revision head).include?(k) }
15
+ msg "Updating all mirrors."
16
+ config.mirrors.each do |path|
17
+ update_one(path, options)
18
+ end
19
+ end
20
+
21
+ def update_one(path, options = {})
22
+ mirror = config.get!(path)
23
+
24
+ revision_message = options["revision"] ? " to #{display_revision(mirror, options["revision"])}" : ""
25
+ msg "Updating mirror '#{mirror.path}'#{revision_message}."
26
+
27
+ # check options for lock modification
28
+ if mirror.locked?
29
+ if options["head"]
30
+ msg "Unlocking mirror '#{mirror.path}'." if verbose?
31
+ mirror.lock = nil
32
+ elsif !options["revision"]
33
+ msg "Mirror '#{mirror.path}' is locked to #{display_revision(mirror, mirror.lock)}. Use --head to force."
34
+ return
35
+ end
36
+ end
37
+
38
+ setup_remote(mirror)
39
+ msg "Fetching new commits for '#{mirror.path}'." if verbose?
40
+ mirror.fetch
41
+
42
+ new_revision = validate_new_revision(mirror, options["revision"])
43
+ target_revision = determine_target_revision(mirror, new_revision)
44
+
45
+ if mirror.merged?(target_revision)
46
+ msg "Mirror '#{mirror.path}' is already up to date."
47
+ return
48
+ end
49
+
50
+ if mirror.squashed?
51
+ diff = mirror.diff
52
+ base_revision = mirror.base_revision
53
+ end
54
+
55
+ mirror.revision = new_revision
56
+ mirror.lock = new_revision if options["revision"]
57
+
58
+ msg "Merging in mirror '#{mirror.path}'." if verbose?
59
+ begin
60
+ if mirror.squashed?
61
+ local_hash = git.rev_parse("HEAD")
62
+ if diff
63
+ base_hash = generate_tree_hash(mirror, base_revision)
64
+ else
65
+ base_hash = local_hash
66
+ end
67
+ remote_hash = generate_tree_hash(mirror, target_revision)
68
+ ENV["GITHEAD_#{local_hash}"] = "HEAD"
69
+ ENV["GITHEAD_#{remote_hash}"] = target_revision
70
+ git.merge_recursive(base_hash, local_hash, remote_hash)
71
+ else
72
+ git.merge_subtree(target_revision)
73
+ end
74
+ rescue Operations::MergeError => error
75
+ msg "Caught merge error. Breaking."
76
+ end
77
+
78
+ config.update(mirror)
79
+ add_config_file
80
+
81
+ commit_message = "Updated mirror '#{mirror.path}' to #{display_revision(mirror)}"
82
+
83
+ if error
84
+ File.open(".git/MERGE_MSG", 'w') { |f| f.puts(commit_message) }
85
+ return
86
+ end
87
+
88
+ git.commit(commit_message)
89
+ msg commit_message
90
+ end
91
+
92
+ def generate_tree_hash(mirror, revision)
93
+ git.rm_r(mirror.path)
94
+ git.read_tree_prefix(revision, mirror.path)
95
+ success = git.commit("Temporary commit for mirror '#{mirror.path}'")
96
+ hash = git.rev_parse("HEAD")
97
+ git.reset_hard("HEAD^") if success
98
+ hash
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,101 @@
1
+ require 'yaml'
2
+ require 'yaml/store'
3
+
4
+ module Braid
5
+ class Config
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
15
+ end
16
+
17
+ def initialize(config_file = CONFIG_FILE)
18
+ @db = YAML::Store.new(config_file)
19
+ end
20
+
21
+ def add_from_options(url, options)
22
+ mirror = Mirror.new_from_options(url, options)
23
+
24
+ add(mirror)
25
+ mirror
26
+ end
27
+
28
+ def mirrors
29
+ @db.transaction(true) do
30
+ @db.roots
31
+ end
32
+ end
33
+
34
+ def get(path)
35
+ @db.transaction(true) do
36
+ if attributes = @db[path.to_s.sub(/\/$/, '')]
37
+ Mirror.new(path, attributes)
38
+ end
39
+ end
40
+ end
41
+
42
+ def get!(path)
43
+ mirror = get(path)
44
+ raise MirrorDoesNotExist, path unless mirror
45
+ mirror
46
+ end
47
+
48
+ def add(mirror)
49
+ @db.transaction do
50
+ raise PathAlreadyInUse, mirror.path if @db[mirror.path]
51
+ write_mirror(mirror)
52
+ end
53
+ end
54
+
55
+ def remove(mirror)
56
+ @db.transaction do
57
+ @db.delete(mirror.path)
58
+ end
59
+ end
60
+
61
+ def update(mirror)
62
+ @db.transaction do
63
+ raise MirrorDoesNotExist, mirror.path unless @db[mirror.path]
64
+ @db.delete(mirror.path)
65
+ write_mirror(mirror)
66
+ end
67
+ end
68
+
69
+ def valid?
70
+ @db.transaction(true) do
71
+ !@db.roots.any? do |path|
72
+ @db[path]["url"].nil?
73
+ end
74
+ end
75
+ end
76
+
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
89
+ end
90
+ end
91
+
92
+ private
93
+ def write_mirror(mirror)
94
+ @db[mirror.path] = clean_attributes(mirror.attributes)
95
+ end
96
+
97
+ def clean_attributes(hash)
98
+ hash.reject { |k,v| v.nil? }
99
+ end
100
+ end
101
+ end