auto_tagger 0.1.5 → 0.2.0

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 (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))