braid 0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,27 @@
1
+ module Braid
2
+ module Commands
3
+ class Remove < Command
4
+ def run(path)
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
+ # will need this in case we decide to remove the .git/config entry also
15
+ # setup_remote(mirror)
16
+
17
+ config.remove(mirror)
18
+ add_config_file
19
+
20
+ commit_message = "Removed mirror '#{mirror.path}'"
21
+ git.commit(commit_message)
22
+ msg commit_message
23
+ end
24
+ end
25
+ end
26
+ end
27
+ 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