auto_tagger 0.1.5 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +4 -1
  2. data/CHANGELOG +26 -2
  3. data/Gemfile +4 -4
  4. data/Gemfile.lock +60 -0
  5. data/README.md +90 -36
  6. data/Rakefile +1 -26
  7. data/VERSION +1 -1
  8. data/auto_tagger.gemspec +24 -14
  9. data/bin/autotag +14 -29
  10. data/features/autotag.feature +43 -2
  11. data/features/deployment.feature +4 -0
  12. data/features/step_definitions/autotag_steps.rb +27 -22
  13. data/features/step_definitions/deployment_steps.rb +41 -33
  14. data/features/support/env.rb +45 -2
  15. data/features/support/step_helpers.rb +36 -12
  16. data/features/templates/deploy.erb +1 -1
  17. data/lib/auto_tagger/base.rb +150 -19
  18. data/lib/auto_tagger/capistrano_helper.rb +38 -17
  19. data/lib/auto_tagger/command_line.rb +65 -0
  20. data/lib/auto_tagger/commander.rb +22 -11
  21. data/lib/auto_tagger/configuration.rb +88 -0
  22. data/lib/auto_tagger/deprecator.rb +11 -0
  23. data/lib/auto_tagger/git/ref.rb +34 -0
  24. data/lib/auto_tagger/git/ref_set.rb +35 -0
  25. data/lib/auto_tagger/git/repo.rb +76 -0
  26. data/lib/auto_tagger/options.rb +170 -0
  27. data/lib/auto_tagger/recipes.rb +67 -27
  28. data/lib/auto_tagger.rb +9 -4
  29. data/spec/auto_tagger/base_spec.rb +236 -52
  30. data/spec/auto_tagger/capistrano_helper_spec.rb +82 -112
  31. data/spec/auto_tagger/command_line_spec.rb +110 -0
  32. data/spec/auto_tagger/commander_spec.rb +33 -7
  33. data/spec/auto_tagger/configuration_spec.rb +275 -0
  34. data/spec/auto_tagger/git/ref_set_spec.rb +61 -0
  35. data/spec/auto_tagger/git/ref_spec.rb +46 -0
  36. data/spec/auto_tagger/git/repo_spec.rb +108 -0
  37. data/spec/auto_tagger/options_spec.rb +157 -0
  38. data/spec/spec_helper.rb +1 -6
  39. metadata +32 -15
  40. data/geminstaller.yml +0 -7
  41. data/lib/auto_tagger/repository.rb +0 -43
  42. data/lib/auto_tagger/stage_manager.rb +0 -23
  43. data/lib/auto_tagger/tag.rb +0 -43
  44. data/spec/auto_tagger/repository_spec.rb +0 -72
  45. data/spec/auto_tagger/stage_manager_spec.rb +0 -34
  46. data/spec/auto_tagger/tag_spec.rb +0 -66
@@ -1,19 +1,30 @@
1
1
  module AutoTagger
2
2
  class Commander
3
- class << self
4
- def execute(path, cmd)
5
- `#{command_in_context(path, cmd)}`
6
- end
7
3
 
8
- def execute?(path, cmd)
9
- system command_in_context(path, cmd)
10
- end
4
+ def initialize(path, verbose)
5
+ @path = path
6
+ @verbose = verbose
7
+ end
8
+
9
+ def print(cmd)
10
+ puts command_in_context(cmd)
11
+ end
11
12
 
12
- private
13
+ def read(cmd)
14
+ puts command_in_context(cmd) if @verbose
15
+ `#{command_in_context(cmd)}`
16
+ end
13
17
 
14
- def command_in_context(path, cmd)
15
- "cd #{path} && #{cmd}"
16
- end
18
+ def execute(cmd)
19
+ puts command_in_context(cmd) if @verbose
20
+ system command_in_context(cmd)
17
21
  end
22
+
23
+ private
24
+
25
+ def command_in_context(cmd)
26
+ "cd #{@path} && #{cmd}"
27
+ end
28
+
18
29
  end
19
30
  end
@@ -0,0 +1,88 @@
1
+ module AutoTagger
2
+
3
+ class Configuration
4
+
5
+ class InvalidRefPath < StandardError
6
+ end
7
+
8
+ def initialize(options = {})
9
+ @options = options
10
+ end
11
+
12
+ def working_directory
13
+ File.expand_path(@options[:path] || Dir.pwd)
14
+ end
15
+
16
+ def opts_file
17
+ file = @options[:opts_file] || ".auto_tagger"
18
+ File.expand_path File.join(working_directory, file)
19
+ end
20
+
21
+ def file_settings
22
+ return {} unless File.exists?(opts_file)
23
+ args = File.read(opts_file).to_s.split("\n").map { |line| line.strip }
24
+ args.reject! { |line| line == "" }
25
+ AutoTagger::Options.from_file(args)
26
+ end
27
+
28
+ def settings
29
+ file_settings.merge(@options)
30
+ end
31
+
32
+ def stages
33
+ stages = settings[:stages] || []
34
+ stages = stages.to_s.split(",").map { |stage| stage.strip } if stages.is_a?(String)
35
+ stages.reject { |stage| stage.to_s == "" }
36
+ end
37
+
38
+ def stage
39
+ settings[:stage] || stages.last
40
+ end
41
+
42
+ def date_separator
43
+ settings[:date_separator] ||= ""
44
+ end
45
+
46
+ def dry_run?
47
+ settings.fetch(:dry_run, false)
48
+ end
49
+
50
+ def verbose?
51
+ settings.fetch(:verbose, false)
52
+ end
53
+
54
+ def offline?
55
+ settings.fetch(:offline, false)
56
+ end
57
+
58
+ def fetch_refs?
59
+ !offline? && settings.fetch(:fetch_refs, true)
60
+ end
61
+
62
+ def push_refs?
63
+ !offline? && settings.fetch(:push_refs, true)
64
+ end
65
+
66
+ def executable
67
+ settings[:executable] || "git"
68
+ end
69
+
70
+ def refs_to_keep
71
+ (settings[:refs_to_keep] || 1).to_i
72
+ end
73
+
74
+ def remote
75
+ settings[:remote] || "origin"
76
+ end
77
+
78
+ def ref_path
79
+ path = settings[:ref_path] || "tags"
80
+ if ["heads", "remotes"].include?(path)
81
+ raise InvalidRefPath, "#{path} is a reserved word in git. Please use something else."
82
+ end
83
+ path
84
+ end
85
+
86
+ end
87
+
88
+ end
@@ -0,0 +1,11 @@
1
+ module AutoTagger
2
+ class Deprecator
3
+ def self.warn(msg)
4
+ puts string(msg)
5
+ end
6
+
7
+ def self.string(msg)
8
+ "AUTO_TAGGER DEPRECATION: #{msg}"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,34 @@
1
+ module AutoTagger
2
+ module Git
3
+ class Ref
4
+ attr_reader :sha, :name
5
+
6
+ # name is refs/autotags/2009292827
7
+ def initialize(repo, sha, name)
8
+ @repo, @sha, @name = repo, sha, name
9
+ end
10
+
11
+ def ==(other)
12
+ other.is_a?(self.class) && other.sha == sha && other.name == name
13
+ end
14
+
15
+ def to_s
16
+ "%s %s" % [sha, name]
17
+ end
18
+
19
+ def delete_locally
20
+ @repo.exec "update-ref -d #{name}"
21
+ end
22
+
23
+ def delete_on_remote(remote = "origin")
24
+ @repo.exec "push #{remote} :#{name}"
25
+ end
26
+
27
+ def save
28
+ @repo.exec "update-ref #{name} #{sha}"
29
+ self
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,35 @@
1
+ module AutoTagger
2
+ module Git
3
+ class RefSet
4
+
5
+ def initialize(repo)
6
+ @repo = repo
7
+ end
8
+
9
+ def all
10
+ @repo.read("show-ref").split(/\n/).map do |line|
11
+ sha, name = line.split
12
+ Ref.new(@repo, sha, name)
13
+ end
14
+ end
15
+
16
+ # name = refs/autotags/2009857463
17
+ # returns a ref
18
+ # should un-cache the refs in refset, or never memoize
19
+ def create(sha, name)
20
+ Ref.new(@repo, sha, name).save
21
+ end
22
+
23
+ # pattern = refs/autotags/*
24
+ def push(pattern, remote = "origin")
25
+ @repo.exec "push #{remote} #{pattern}:#{pattern}"
26
+ end
27
+
28
+ # pattern = refs/auto_tags/*
29
+ def fetch(pattern, remote = "origin")
30
+ @repo.exec "fetch #{remote} #{pattern}:#{pattern}"
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,76 @@
1
+ module AutoTagger
2
+ module Git
3
+
4
+ # A class that represents a git repo
5
+ #
6
+ # repo.refs.create name, sha
7
+ # repo.refs.all
8
+ # repo.refs.push origin, pattern
9
+ # repo.refs.fetch origin, pattern
10
+ #
11
+ class Repo
12
+
13
+ class NoPathProvidedError < StandardError
14
+ end
15
+
16
+ class NoSuchPathError < StandardError
17
+ end
18
+
19
+ class InvalidGitRepositoryError < StandardError
20
+ end
21
+
22
+ class GitCommandFailedError < StandardError
23
+ end
24
+
25
+ def initialize(given_path, options = {})
26
+ @given_path = given_path
27
+ @execute_commands = options.fetch(:execute_commands, true)
28
+ @verbose = options[:verbose]
29
+ @executable = options[:executable] || "git"
30
+ end
31
+
32
+ def path
33
+ return @path if @path
34
+ raise NoPathProvidedError if @given_path.to_s.strip == ""
35
+ raise NoSuchPathError if !File.exists?(@given_path)
36
+ raise InvalidGitRepositoryError if !File.exists?(File.join(@given_path, ".git"))
37
+ @path = @given_path
38
+ end
39
+
40
+ def refs
41
+ RefSet.new(self)
42
+ end
43
+
44
+ def ==(other)
45
+ other.is_a?(AutoTagger::Git::Repo) && other.path == path
46
+ end
47
+
48
+ def latest_commit_sha
49
+ read("rev-parse HEAD").strip
50
+ end
51
+
52
+ def read(cmd)
53
+ commander.read(git_command(cmd))
54
+ end
55
+
56
+ def exec(cmd)
57
+ if @execute_commands
58
+ commander.execute(git_command(cmd)) || raise(GitCommandFailedError)
59
+ else
60
+ commander.print(git_command(cmd))
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def git_command(cmd)
67
+ "%s %s" % [@executable, cmd]
68
+ end
69
+
70
+ def commander
71
+ AutoTagger::Commander.new(path, @verbose)
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,170 @@
1
+ module AutoTagger
2
+ class Options
3
+
4
+ def self.from_command_line(args)
5
+ options = {}
6
+ args.extend(::OptionParser::Arguable)
7
+ args.options do |opts|
8
+ opts.banner = [
9
+ "",
10
+ " USAGE: autotag command [stage] [options]",
11
+ "",
12
+ " Examples:",
13
+ "",
14
+ " autotag help",
15
+ " autotag version",
16
+ " autotag create demo",
17
+ " autotag create demo .",
18
+ " autotag create demo ../",
19
+ " autotag create ci /data/myrepo",
20
+ " autotag create ci /data/myrepo --fetch-refs=false --push-refs=false",
21
+ " autotag create ci /data/myrepo --offline",
22
+ " autotag create ci /data/myrepo --dry-run",
23
+ "",
24
+ " autotag list demo",
25
+ "",
26
+ " autotag cleanup demo --refs-to-keep=2",
27
+ " autotag cleanup demo --refs-to-keep=2",
28
+ " autotag delete_locally demo",
29
+ " autotag delete_on_remote demo",
30
+ "",
31
+ "",
32
+ ].join("\n")
33
+
34
+ common_options(opts, options)
35
+
36
+ opts.on("--opts-file OPTS_FILE",
37
+ "full path to the opts file",
38
+ "Defaults to working directory's .auto_tagger file",
39
+ "Example: /usr/local/.auto_tagger") do |o|
40
+ options[:opts_file] = o
41
+ end
42
+
43
+ opts.on_tail("-h", "--help", "-?", "You're looking at it.") do
44
+ options[:show_help] = true
45
+ options[:command] = :help
46
+ end
47
+
48
+ opts.on_tail("--version", "-v", "Show version") do
49
+ options[:show_version] = true
50
+ options[:command] = :version
51
+ end
52
+
53
+ end.parse!
54
+
55
+ case args.first.to_s.downcase
56
+ when "config"
57
+ options[:command] = :config
58
+ when "version"
59
+ options[:show_version] = true
60
+ options[:command] = :version
61
+ when * ["help", ""]
62
+ options[:show_help] = true
63
+ options[:help_text] = args.options.help
64
+ options[:command] = :help
65
+ when "cleanup"
66
+ options[:command] = :cleanup
67
+ options[:stage] = args[1]
68
+ when "delete_locally"
69
+ options[:command] = :delete_locally
70
+ options[:stage] = args[1]
71
+ when "delete_on_remote"
72
+ options[:command] = :delete_on_remote
73
+ options[:stage] = args[1]
74
+ when "list"
75
+ options[:command] = :list
76
+ options[:stage] = args[1]
77
+ when "create"
78
+ options[:command] = :create
79
+ options[:stage] = args[1]
80
+ options[:path] = args[2]
81
+ else
82
+ if options[:command].nil?
83
+ options[:command] = :create # allow
84
+ options[:deprecated] = true
85
+ options[:stage] = args[0]
86
+ options[:path] = args[1]
87
+ end
88
+ end
89
+
90
+ options
91
+ end
92
+
93
+ def self.from_file(args)
94
+ options = {}
95
+ args.extend(::OptionParser::Arguable)
96
+ args.options do |opts|
97
+ common_options(opts, options)
98
+ end.parse!
99
+ options
100
+ end
101
+
102
+ private
103
+
104
+ def self.common_options(opts, options)
105
+
106
+ opts.on("--date-separator SEPARATOR",
107
+ "Sets the separator of the date part of the ref",
108
+ "Defaults to ''") do |o|
109
+ options[:date_separator] = o
110
+ end
111
+
112
+ opts.on("--fetch-refs FETCH_REFS", TrueClass,
113
+ "Whether or not to fetch tags before creating the tag",
114
+ "Defaults to true") do |o|
115
+ options[:fetch_refs] = o
116
+ end
117
+
118
+ opts.on("--push-refs PUSH_REFS", TrueClass,
119
+ "Whether or not to push tags after creating the tag",
120
+ "Defaults to true") do |o|
121
+ options[:push_refs] = o
122
+ end
123
+
124
+ opts.on("--remote REMOTE",
125
+ "specify the git remote",
126
+ "Defaults to origin") do |o|
127
+ options[:remote] = o
128
+ end
129
+
130
+ opts.on("--ref-path REF_PATH",
131
+ "specify the ref-path",
132
+ "Defaults to auto_tags") do |o|
133
+ options[:ref_path] = o
134
+ end
135
+
136
+ opts.on("--stages STAGES",
137
+ "specify a comma-separated list of stages") do |o|
138
+ options[:stages] = o
139
+ end
140
+
141
+ opts.on("--offline [OFFLINE]", FalseClass,
142
+ "Same as --fetch-refs=false and --push-refs=false") do |o|
143
+ options[:offline] = o
144
+ end
145
+
146
+ opts.on("--dry-run [DRYRUN]", TrueClass,
147
+ "doesn't execute anything, but logs what it would run") do |o|
148
+ options[:dry_run] = o.nil? || (o == true)
149
+ end
150
+
151
+ opts.on("--verbose [VERBOSE]", TrueClass,
152
+ "logs all commands") do |o|
153
+ options[:verbose] = o
154
+ end
155
+
156
+ opts.on("--refs-to-keep REFS_TO_KEEP",
157
+ "logs all commands") do |o|
158
+ options[:refs_to_keep] = (o ? o.to_i : nil)
159
+ end
160
+
161
+ opts.on("--executable EXECUTABLE",
162
+ "the full path to the git executable",
163
+ "Defaults to git (and assumes git is your path)",
164
+ "Example: /usr/local/bin/git") do |o|
165
+ options[:git] = o
166
+ end
167
+ end
168
+
169
+ end
170
+ end
@@ -1,54 +1,94 @@
1
1
  require File.expand_path(File.join(File.dirname(__FILE__), "..", "auto_tagger"))
2
2
 
3
3
  Capistrano::Configuration.instance(:must_exist).load do
4
- namespace :release_tagger do
4
+
5
+ namespace :auto_tagger do
6
+
7
+ def auto_tagger_capistrano_helper
8
+ AutoTagger::CapistranoHelper.new(variables)
9
+ end
10
+
11
+ def auto_tagger
12
+ auto_tagger_capistrano_helper.auto_tagger
13
+ end
14
+
15
+ def log_auto_tagger(message)
16
+ logger.info "AUTO TAGGER: #{message}"
17
+ end
18
+
5
19
  desc %Q{
6
20
  Sets the branch to the latest tag from the previous stage.
7
- Use -Shead=true to set the branch to master, -Stag=<tag> to specify the tag explicitly.
21
+ Use -Shead=true to set the branch to master,
22
+ -Stag=<tag> or -Sref=<ref> to specify the tag explicitly.
8
23
  }
9
24
  task :set_branch do
10
- if branch_name = AutoTagger::CapistranoHelper.new(variables).branch
11
- set :branch, branch_name
12
- logger.info "setting branch to #{branch_name}"
25
+ if ref = auto_tagger_capistrano_helper.ref
26
+ set :branch, ref
27
+ log_auto_tagger "setting branch to #{ref}"
13
28
  else
14
- logger.info "AUTO TAGGER: skipping auto-assignment of branch. Branch will remain the default.}"
29
+ log_auto_tagger "skipping auto-assignment of branch. Branch will remain the default.}"
15
30
  end
16
31
  end
17
32
 
18
- desc %Q{Prints the most current tags from all stages}
19
- task :print_latest_tags, :roles => :app do
20
- logger.info "AUTO TAGGER: release tag history is:"
21
- entries = AutoTagger::CapistranoHelper.new(variables).release_tag_entries
22
- entries.each do |entry|
23
- logger.info entry
33
+ desc %Q{Creates a tag using the stage variable}
34
+ task :create_ref, :roles => :app do
35
+ if variables[:stage]
36
+ ref = auto_tagger.create_ref(real_revision)
37
+ log_auto_tagger "created tag #{ref.name} from #{ref.sha}"
38
+ else
39
+ ref = auto_tagger.create_ref
40
+ log_auto_tagger "created tag #{ref.name}"
24
41
  end
25
42
  end
26
43
 
27
- desc %Q{Reads the text file with the latest tag from the shared directory}
28
- task :read_tag_from_shared, :roles => :app do
29
- logger.info "AUTO TAGGER: latest tag deployed to this environment was:"
44
+ desc %Q{DEPRECATED: Prints the most current tags from all stages}
45
+ task :print_latest_refs, :roles => :app do
46
+ log_auto_tagger "release tag history is:"
47
+ auto_tagger.release_tag_entries.each { |entry| logger.info entry }
48
+ end
49
+
50
+ desc %Q{DEPRECATED: Reads the text file with the latest tag from the shared directory}
51
+ task :read_ref_from_shared, :roles => :app do
52
+ log_auto_tagger "latest tag deployed to this environment was:"
30
53
  run "cat #{shared_path}/released_git_tag.txt"
31
54
  end
32
55
 
33
- desc %Q{Writes the tag name to a file in the shared directory}
34
- task :write_tag_to_shared, :roles => :app do
56
+ desc %Q{DEPRECATED: Writes the tag name to a file in the shared directory}
57
+ task :write_ref_to_shared, :roles => :app do
35
58
  if exists?(:branch)
36
- logger.info "AUTO TAGGER: writing tag to shared text file on remote server"
59
+ log_auto_tagger "writing tag to shared text file on remote server"
37
60
  run "echo '#{branch}' > #{shared_path}/released_git_tag.txt"
38
61
  else
39
- logger.info "AUTO TAGGER: no branch available. Text file was not written to server"
62
+ log_auto_tagger "no branch available. Text file was not written to server"
40
63
  end
41
64
  end
65
+ end
66
+
67
+ namespace :release_tagger do
68
+ desc %Q{DEPRECATED: use auto_tagger:set_branch }
69
+ task :set_branch do
70
+ auto_tagger.set_branch
71
+ end
42
72
 
43
- desc %Q{Creates a tag using the stage variable}
73
+ desc %Q{DEPRECATED: use auto_tagger:create_ref}
44
74
  task :create_tag, :roles => :app do
45
- if variables[:stage]
46
- tag_name = AutoTagger::Runner.new(variables[:stage], variables[:working_directory]).create_tag(real_revision)
47
- logger.info "AUTO TAGGER created tag #{tag_name} from #{real_revision}"
48
- else
49
- tag_name = AutoTagger::Runner.new(:production, variables[:working_directory]).create_tag
50
- logger.info "AUTO TAGGER created tag #{tag_name}"
51
- end
75
+ auto_tagger.create_ref
76
+ end
77
+
78
+ desc %Q{DEPRECATED: use auto_tagger:print_latest_tags}
79
+ task :print_latest_refs, :roles => :app do
80
+ auto_tagger.print_latest_tags
81
+ end
82
+
83
+ desc %Q{DEPRECATED: use auto_tagger:read_ref_from_shared}
84
+ task :read_tag_from_shared, :roles => :app do
85
+ auto_tagger.read_ref_from_shared
86
+ end
87
+
88
+ desc %Q{DEPRECATED: use auto_tagger:write_ref_to_shared}
89
+ task :write_tag_to_shared, :roles => :app do
90
+ auto_tagger.write_ref_to_shared
52
91
  end
53
92
  end
93
+
54
94
  end
data/lib/auto_tagger.rb CHANGED
@@ -1,9 +1,14 @@
1
+ require 'optparse'
1
2
  [
2
- 'commander',
3
- 'repository',
4
- 'tag',
3
+ 'deprecator',
4
+ 'git/ref',
5
+ 'git/ref_set',
6
+ 'git/repo',
5
7
  'base',
6
- 'stage_manager',
8
+ 'command_line',
9
+ 'commander',
10
+ 'configuration',
11
+ 'options',
7
12
  'capistrano_helper'
8
13
  ].each do |file|
9
14
  require File.expand_path(File.join(File.dirname(__FILE__), "auto_tagger", file))