summaryrefslogtreecommitdiff
path: root/knife/lib/chef/knife/core/cookbook_scm_repo.rb
diff options
context:
space:
mode:
Diffstat (limited to 'knife/lib/chef/knife/core/cookbook_scm_repo.rb')
-rw-r--r--knife/lib/chef/knife/core/cookbook_scm_repo.rb159
1 files changed, 159 insertions, 0 deletions
diff --git a/knife/lib/chef/knife/core/cookbook_scm_repo.rb b/knife/lib/chef/knife/core/cookbook_scm_repo.rb
new file mode 100644
index 0000000000..921dadad8b
--- /dev/null
+++ b/knife/lib/chef/knife/core/cookbook_scm_repo.rb
@@ -0,0 +1,159 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef/mixin/shell_out" unless defined?(Chef::Mixin::ShellOut)
+
+class Chef
+ class Knife
+ class CookbookSCMRepo
+
+ DIRTY_REPO = /^\s+M/.freeze
+
+ include Chef::Mixin::ShellOut
+
+ attr_reader :repo_path
+ attr_reader :default_branch
+ attr_reader :use_current_branch
+ attr_reader :ui
+
+ def initialize(repo_path, ui, opts = {})
+ @repo_path = repo_path
+ @ui = ui
+ @default_branch = "master"
+ @use_current_branch = false
+ apply_opts(opts)
+ end
+
+ def sanity_check
+ unless ::File.directory?(repo_path)
+ ui.error("The cookbook repo path #{repo_path} does not exist or is not a directory")
+ exit 1
+ end
+ unless git_repo?(repo_path)
+ ui.error "The cookbook repo #{repo_path} is not a git repository."
+ ui.info("Use `git init` to initialize a git repo")
+ exit 1
+ end
+ if use_current_branch
+ @default_branch = get_current_branch
+ end
+ unless branch_exists?(default_branch)
+ ui.error "The default branch '#{default_branch}' does not exist"
+ ui.info "If this is a new git repo, make sure you have at least one commit before installing cookbooks"
+ exit 1
+ end
+ cmd = git("status --porcelain")
+ if DIRTY_REPO.match?(cmd.stdout)
+ ui.error "You have uncommitted changes to your cookbook repo (#{repo_path}):"
+ ui.msg cmd.stdout
+ ui.info "Commit or stash your changes before importing cookbooks"
+ exit 1
+ end
+ # TODO: any untracked files in the cookbook directory will get nuked later
+ # make this an error condition also.
+ true
+ end
+
+ def reset_to_default_state
+ ui.info("Checking out the #{default_branch} branch.")
+ git("checkout #{default_branch}")
+ end
+
+ def prepare_to_import(cookbook_name)
+ branch = "chef-vendor-#{cookbook_name}"
+ if branch_exists?(branch)
+ ui.info("Pristine copy branch (#{branch}) exists, switching to it.")
+ git("checkout #{branch}")
+ else
+ ui.info("Creating pristine copy branch #{branch}")
+ git("checkout -b #{branch}")
+ end
+ end
+
+ def finalize_updates_to(cookbook_name, version)
+ if update_count = updated?(cookbook_name)
+ ui.info "#{update_count} files updated, committing changes"
+ git("add #{cookbook_name}")
+ git("commit -m \"Import #{cookbook_name} version #{version}\" -- #{cookbook_name}")
+ ui.info("Creating tag cookbook-site-imported-#{cookbook_name}-#{version}")
+ git("tag -f cookbook-site-imported-#{cookbook_name}-#{version}")
+ true
+ else
+ ui.info("No changes made to #{cookbook_name}")
+ false
+ end
+ end
+
+ def merge_updates_from(cookbook_name, version)
+ branch = "chef-vendor-#{cookbook_name}"
+ Dir.chdir(repo_path) do
+ if system("git merge #{branch}")
+ ui.info("Cookbook #{cookbook_name} version #{version} successfully installed")
+ else
+ ui.error("You have merge conflicts - please resolve manually")
+ ui.info("Merge status (cd #{repo_path}; git status):")
+ system("git status")
+ exit 3
+ end
+ end
+ end
+
+ def updated?(cookbook_name)
+ update_count = git("status --porcelain -- #{cookbook_name}").stdout.strip.lines.count
+ update_count == 0 ? nil : update_count
+ end
+
+ def branch_exists?(branch_name)
+ git("branch --no-color").stdout.lines.any? { |l| l =~ /\s#{Regexp.escape(branch_name)}(?:\s|$)/ }
+ end
+
+ def get_current_branch
+ ref = git("symbolic-ref HEAD").stdout
+ ref.chomp.split("/")[2]
+ end
+
+ private
+
+ def git_repo?(directory)
+ if File.directory?(File.join(directory, ".git"))
+ true
+ elsif File.dirname(directory) == directory
+ false
+ else
+ git_repo?(File.dirname(directory))
+ end
+ end
+
+ def apply_opts(opts)
+ opts.each do |option, value|
+ case option.to_s
+ when "default_branch"
+ @default_branch = value
+ when "use_current_branch"
+ @use_current_branch = value
+ end
+ end
+ end
+
+ def git(command)
+ shell_out!("git #{command}", cwd: repo_path)
+ end
+
+ end
+ end
+end