Module: RightDevelop::Utility::Git
- Defined in:
- lib/right_develop/utility/git.rb
Defined Under Namespace
Classes: VerifyError
Constant Summary collapse
- DEFAULT_REPO_OPTIONS =
{ :shell => ::RightDevelop::Utility::Shell }.freeze
Class Method Summary collapse
-
.branch_exists?(branch_name, options = {}) ⇒ TrueClass|FalseClass
Determine if the branch given by name exists.
-
.checkout_revision(revision, options = {}) ⇒ TrueClass
Checks out the given revision (tag, branch or SHA) and optionally creates a new branch from it.
-
.clone_to(repo, destination) ⇒ TrueClass
Clones the repo given by URL to the given destination (if any).
-
.current_revision(hint = nil, options = {}) ⇒ String
Attempts to determine which branch, tag or SHA to which the current directory is pointing.
-
.default_repository ⇒ RightGit::Git::Repository
Factory method for a default repository object from the current working directory.
-
.diff_files_from(commit) ⇒ String
Generates a difference from the current workspace to the given commit on the same branch as a sorted list of relative file paths.
-
.is_sha?(revision) ⇒ TrueClass|FalseClass
True if the given revision is a commit SHA.
-
.setup ⇒ TrueClass
Performs setup of the working directory repository for automation or development.
-
.tag_exists?(tag_name, options = {}) ⇒ TrueClass|FalseClass
Determine if the tag given by name exists.
-
.tags_for_sha(sha, options = {}) ⇒ Array
Generates a list of tags pointing to the given SHA, if any.
-
.verify_revision(revision = nil) ⇒ TrueClass
Verifies that the local repository and all submodules match the expected revision (branch, tag or SHA).
Class Method Details
.branch_exists?(branch_name, options = {}) ⇒ TrueClass|FalseClass
Determine if the branch given by name exists.
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/right_develop/utility/git.rb', line 69 def branch_exists?(branch_name, = {}) = { :remote => true, :local => true, :repo => nil }.merge() remote = [:remote] local = [:local] repo = [:repo] || default_repository unless local || remote raise ::ArgumentError, 'Either remote or local must be true' end both = local && remote repo.branches(:all => remote).any? do |branch| branch.name == branch_name && (both || remote == branch.remote?) end end |
.checkout_revision(revision, options = {}) ⇒ TrueClass
Checks out the given revision (tag, branch or SHA) and optionally creates a new branch from it.
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/right_develop/utility/git.rb', line 140 def checkout_revision(revision, = {}) = { :new_branch_name => nil, :force => true, :recursive => true }.merge() # check parameters. new_branch_name = [:new_branch_name] if new_branch_name && new_branch_name == revision raise ::ArgumentError, "revision cannot be same as new_branch_name: #{revision}" end unless [TrueClass, FalseClass, NilClass].include?([:force].class) raise ::ArgumentError, "force must be a boolean" end force = !![:force] # hard reset any local changes before attempting checkout, if forced. repo = default_repository logger = repo.logger logger.info("Performing checkout in #{repo.repo_dir.inspect}") repo.hard_reset_to(nil) if force # fetch to ensure revision is known and most up-to-date. repo.fetch_all # do full checkout of revision with submodule update before any attempt to # create a new branch. this handles some wierd git failures where submodules # are changing between major/minor versions of the code. repo.checkout_to(revision, :force => true) # note that the checkout-to-a-branch will simply switch to a local copy of # the branch which may or may not by synchronized with its remote origin. to # ensure the branch is synchronized, perform a pull. is_sha = is_sha?(revision) needs_pull = ( !is_sha && branch_exists?(revision, :remote => true, :local => false, :repo => repo) ) if needs_pull # hard reset to remote origin to overcome any local branch divergence. repo.hard_reset_to("origin/#{revision}") if force # a pull is not needed at this point if we forced hard reset but it is # always nice to see it succeed in the output. repo.spit_output('pull', 'origin', revision) end # perform a localized hard reset to revision just to prove that revision is # now known to the local git database. repo.hard_reset_to(revision) # note that the submodule update is non-recursive for tags and branches in # case the submodule needs to checkout to a specific branch before updating # its own submodules. it would be strange to recursively update submodules # from the parent and then have the recursively checked-out child revision # (branch or tag) introduce a different set of submodules. repo.update_submodules(:recursive => is_sha && [:recursive]) # recursively checkout submodules, if requested and unless we determine the # revision is a SHA (in which case recursive+SHA is ignored). if !is_sha && [:recursive] repo.submodule_paths(:recursive => false).each do |submodule_path| # note that recursion will use the current directory and create a new # repo by calling default_repository. ::Dir.chdir(submodule_path) do checkout_revision(revision, ) end end end # create a new branch from fully resolved directory, if requested. repo.spit_output('checkout', '-b', new_branch_name) if new_branch_name true end |
.clone_to(repo, destination) ⇒ TrueClass
Clones the repo given by URL to the given destination (if any).
111 112 113 114 |
# File 'lib/right_develop/utility/git.rb', line 111 def clone_to(repo, destination) ::RightGit::Git::Repository.clone_to(repo, destination, DEFAULT_REPO_OPTIONS) true end |
.current_revision(hint = nil, options = {}) ⇒ String
Attempts to determine which branch, tag or SHA to which the current directory is pointing. A directory can be pointing at both a branch and multiple tags at the same time it so uses the hint to pick one or the other, if given. if all else fails, the current SHA is returned.
note that current revision is localized and so not directly related to the current state of remote branches or tags. do a fetch to ensure those.
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 |
# File 'lib/right_develop/utility/git.rb', line 310 def current_revision(hint = nil, = {}) = { :repo => nil }.merge() repo = [:repo] || default_repository # SHA logic actual_sha = repo.sha_for(nil) return actual_sha if is_sha?(hint) # branch logic branch_hint = ( hint.nil? || branch_exists?(hint, :remote => true, :local => true, :repo => repo) ) if branch_hint branch = repo.git_output('rev-parse --abbrev-ref HEAD').strip return branch if branch != 'HEAD' end # tag logic if hint && tag_exists?(hint, :repo => repo) hint_sha = repo.sha_for(hint) return hint if hint_sha == actual_sha end # lookup tags for actual SHA, if any. if first_tag = (actual_sha, :repo => repo).first return first_tag end # detached HEAD state, no matching branches or tags. actual_sha end |
.default_repository ⇒ RightGit::Git::Repository
Factory method for a default repository object from the current working directory. The working directory can change but the repo directory is preserved by the object.
42 43 44 |
# File 'lib/right_develop/utility/git.rb', line 42 def default_repository ::RightGit::Git::Repository.new('.', DEFAULT_REPO_OPTIONS) end |
.diff_files_from(commit) ⇒ String
Generates a difference from the current workspace to the given commit on the same branch as a sorted list of relative file paths. This is useful for creating a list of files to patch, etc.
122 123 124 125 126 127 128 |
# File 'lib/right_develop/utility/git.rb', line 122 def diff_files_from(commit) git_args = ['diff', '--stat', '--name-only', commit] result = default_repository.git_output(git_args).lines.map { |line| line.strip }.sort # not sure if git would ever mention directories in a diff, but ignore them. result.delete_if { |item| ::File.directory?(item) } return result end |
.is_sha?(revision) ⇒ TrueClass|FalseClass
Returns true if the given revision is a commit SHA.
56 57 58 |
# File 'lib/right_develop/utility/git.rb', line 56 def is_sha?(revision) ::RightGit::Git::Commit.sha?(revision) end |
.setup ⇒ TrueClass
Performs setup of the working directory repository for automation or development. Currently this only involves initializing/updating submodules.
50 51 52 53 |
# File 'lib/right_develop/utility/git.rb', line 50 def setup default_repository.update_submodules(:recursive => true) true end |
.tag_exists?(tag_name, options = {}) ⇒ TrueClass|FalseClass
Determine if the tag given by name exists.
94 95 96 97 98 99 100 101 102 103 |
# File 'lib/right_develop/utility/git.rb', line 94 def tag_exists?(tag_name, = {}) = { :repo => nil }.merge() repo = [:repo] || default_repository # note that remote tags cannot be queried directly; use git fetch --tags to # import them first. repo..any? { |tag| tag.name == tag_name } end |
.tags_for_sha(sha, options = {}) ⇒ Array
Generates a list of tags pointing to the given SHA, if any. When the revision is a tag, only one tag is returned regardless of whether other tags reference the same SHA.
354 355 356 357 358 359 360 361 |
# File 'lib/right_develop/utility/git.rb', line 354 def (sha, = {}) = { :repo => nil }.merge() repo = [:repo] || default_repository git_args = ['tag', '--contains', sha] repo.git_output(git_args).lines.map { |line| line.strip } end |
.verify_revision(revision = nil) ⇒ TrueClass
Verifies that the local repository and all submodules match the expected revision (branch, tag or SHA).
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/right_develop/utility/git.rb', line 224 def verify_revision(revision = nil) repo = default_repository logger = repo.logger if revision # check current directory against revision. actual_revision = current_revision(revision, :repo => repo) if revision != actual_revision = 'Base directory is in an inconsistent state' + " (#{revision} != #{actual_revision}): #{repo.repo_dir.inspect}" raise VerifyError, end else # determine revision to check from local HEAD state if not given. at best # this will be a branch or tag, at worst a SHA. logger.info("Resolving the default branch, tag or SHA to use for verification in #{repo.repo_dir.inspect}") revision = current_revision(nil, :repo => repo) end # start verify. logger.info("Verifying consistency of revision=#{revision} in #{repo.repo_dir.inspect}") if is_sha?(revision) revision_type = :sha elsif branch_exists?(revision, :remote => false, :local => true, :repo => repo) revision_type = :branch else revision_type = :tag end # for SHAs and tags, verify that expected submodule commits are checked-out # by looking for +,- in the submodule status. any that are out of sync will # not have a blank space on the left-hand side. if revision_type != :branch repo.git_output('submodule status --recursive').lines.each do |line| data = line.chomp if matched = ::RightGit::Git::Repository::SUBMODULE_STATUS_REGEX.match(data) if matched[1] != ' ' = 'At least one submodule is in an inconsistent state:' + " #{::File.(matched[3])}" raise VerifyError, end else raise VerifyError, "Unexpected output from submodule status: #{data.inspect}" end end end # branches are not required to have up-to-date submodule commits for ad-hoc # building purposes but all submodules should be checked-out to the same # branch (and that branch must exist for all submodules). # # for the tag case (i.e. release candidate), we peform a double-check # inside each submodule to verify that what the submodule thinks is tagged # is the same as the submodule SHA from the parent. the same tag must exist # and must be consistent for all submodules. if revision_type != :sha repo.submodule_paths(:recursive => true).each do |submodule_path| sub_repo = ::RightGit::Git::Repository.new(submodule_path, DEFAULT_REPO_OPTIONS) logger.info("Inspecting #{sub_repo.repo_dir.inspect}") actual_revision = current_revision(revision, :repo => sub_repo) if revision != actual_revision = 'At least one submodule is in an inconsistent state' + " (#{revision} != #{actual_revision}): #{sub_repo.repo_dir.inspect}" raise VerifyError, end end end true end |