danger 8.0.4

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 (121) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +94 -0
  4. data/bin/danger +5 -0
  5. data/lib/assets/DangerfileTemplate +13 -0
  6. data/lib/danger.rb +44 -0
  7. data/lib/danger/ci_source/appcenter.rb +55 -0
  8. data/lib/danger/ci_source/appveyor.rb +60 -0
  9. data/lib/danger/ci_source/azure_pipelines.rb +44 -0
  10. data/lib/danger/ci_source/bamboo.rb +41 -0
  11. data/lib/danger/ci_source/bitbucket_pipelines.rb +37 -0
  12. data/lib/danger/ci_source/bitrise.rb +65 -0
  13. data/lib/danger/ci_source/buddybuild.rb +62 -0
  14. data/lib/danger/ci_source/buildkite.rb +51 -0
  15. data/lib/danger/ci_source/ci_source.rb +37 -0
  16. data/lib/danger/ci_source/circle.rb +94 -0
  17. data/lib/danger/ci_source/circle_api.rb +51 -0
  18. data/lib/danger/ci_source/cirrus.rb +31 -0
  19. data/lib/danger/ci_source/code_build.rb +57 -0
  20. data/lib/danger/ci_source/codefresh.rb +53 -0
  21. data/lib/danger/ci_source/codeship.rb +44 -0
  22. data/lib/danger/ci_source/dotci.rb +52 -0
  23. data/lib/danger/ci_source/drone.rb +71 -0
  24. data/lib/danger/ci_source/github_actions.rb +43 -0
  25. data/lib/danger/ci_source/gitlab_ci.rb +86 -0
  26. data/lib/danger/ci_source/jenkins.rb +149 -0
  27. data/lib/danger/ci_source/local_git_repo.rb +119 -0
  28. data/lib/danger/ci_source/local_only_git_repo.rb +47 -0
  29. data/lib/danger/ci_source/screwdriver.rb +47 -0
  30. data/lib/danger/ci_source/semaphore.rb +37 -0
  31. data/lib/danger/ci_source/support/commits.rb +17 -0
  32. data/lib/danger/ci_source/support/find_repo_info_from_logs.rb +35 -0
  33. data/lib/danger/ci_source/support/find_repo_info_from_url.rb +42 -0
  34. data/lib/danger/ci_source/support/local_pull_request.rb +14 -0
  35. data/lib/danger/ci_source/support/no_pull_request.rb +7 -0
  36. data/lib/danger/ci_source/support/no_repo_info.rb +5 -0
  37. data/lib/danger/ci_source/support/pull_request_finder.rb +179 -0
  38. data/lib/danger/ci_source/support/remote_pull_request.rb +15 -0
  39. data/lib/danger/ci_source/support/repo_info.rb +10 -0
  40. data/lib/danger/ci_source/surf.rb +37 -0
  41. data/lib/danger/ci_source/teamcity.rb +161 -0
  42. data/lib/danger/ci_source/travis.rb +51 -0
  43. data/lib/danger/ci_source/vsts.rb +73 -0
  44. data/lib/danger/ci_source/xcode_server.rb +48 -0
  45. data/lib/danger/clients/rubygems_client.rb +14 -0
  46. data/lib/danger/commands/dangerfile/gem.rb +43 -0
  47. data/lib/danger/commands/dangerfile/init.rb +30 -0
  48. data/lib/danger/commands/dry_run.rb +54 -0
  49. data/lib/danger/commands/init.rb +297 -0
  50. data/lib/danger/commands/init_helpers/interviewer.rb +92 -0
  51. data/lib/danger/commands/local.rb +83 -0
  52. data/lib/danger/commands/local_helpers/http_cache.rb +36 -0
  53. data/lib/danger/commands/local_helpers/local_setup.rb +46 -0
  54. data/lib/danger/commands/local_helpers/pry_setup.rb +31 -0
  55. data/lib/danger/commands/plugins/plugin_json.rb +46 -0
  56. data/lib/danger/commands/plugins/plugin_lint.rb +54 -0
  57. data/lib/danger/commands/plugins/plugin_readme.rb +45 -0
  58. data/lib/danger/commands/pr.rb +92 -0
  59. data/lib/danger/commands/runner.rb +94 -0
  60. data/lib/danger/commands/staging.rb +53 -0
  61. data/lib/danger/commands/systems.rb +43 -0
  62. data/lib/danger/comment_generators/bitbucket_server.md.erb +20 -0
  63. data/lib/danger/comment_generators/bitbucket_server_inline.md.erb +15 -0
  64. data/lib/danger/comment_generators/bitbucket_server_message_group.md.erb +12 -0
  65. data/lib/danger/comment_generators/github.md.erb +55 -0
  66. data/lib/danger/comment_generators/github_inline.md.erb +26 -0
  67. data/lib/danger/comment_generators/gitlab.md.erb +40 -0
  68. data/lib/danger/comment_generators/gitlab_inline.md.erb +26 -0
  69. data/lib/danger/comment_generators/vsts.md.erb +20 -0
  70. data/lib/danger/core_ext/file_list.rb +18 -0
  71. data/lib/danger/core_ext/string.rb +20 -0
  72. data/lib/danger/danger_core/dangerfile.rb +341 -0
  73. data/lib/danger/danger_core/dangerfile_dsl.rb +29 -0
  74. data/lib/danger/danger_core/dangerfile_generator.rb +11 -0
  75. data/lib/danger/danger_core/environment_manager.rb +123 -0
  76. data/lib/danger/danger_core/executor.rb +92 -0
  77. data/lib/danger/danger_core/message_aggregator.rb +49 -0
  78. data/lib/danger/danger_core/message_group.rb +68 -0
  79. data/lib/danger/danger_core/messages/base.rb +56 -0
  80. data/lib/danger/danger_core/messages/markdown.rb +42 -0
  81. data/lib/danger/danger_core/messages/violation.rb +54 -0
  82. data/lib/danger/danger_core/plugins/dangerfile_bitbucket_cloud_plugin.rb +144 -0
  83. data/lib/danger/danger_core/plugins/dangerfile_bitbucket_server_plugin.rb +211 -0
  84. data/lib/danger/danger_core/plugins/dangerfile_danger_plugin.rb +248 -0
  85. data/lib/danger/danger_core/plugins/dangerfile_git_plugin.rb +158 -0
  86. data/lib/danger/danger_core/plugins/dangerfile_github_plugin.rb +254 -0
  87. data/lib/danger/danger_core/plugins/dangerfile_gitlab_plugin.rb +240 -0
  88. data/lib/danger/danger_core/plugins/dangerfile_local_only_plugin.rb +42 -0
  89. data/lib/danger/danger_core/plugins/dangerfile_messaging_plugin.rb +218 -0
  90. data/lib/danger/danger_core/plugins/dangerfile_vsts_plugin.rb +191 -0
  91. data/lib/danger/danger_core/standard_error.rb +143 -0
  92. data/lib/danger/helpers/array_subclass.rb +61 -0
  93. data/lib/danger/helpers/comment.rb +32 -0
  94. data/lib/danger/helpers/comments_helper.rb +178 -0
  95. data/lib/danger/helpers/comments_parsing_helper.rb +70 -0
  96. data/lib/danger/helpers/emoji_mapper.rb +41 -0
  97. data/lib/danger/helpers/find_max_num_violations.rb +31 -0
  98. data/lib/danger/helpers/message_groups_array_helper.rb +31 -0
  99. data/lib/danger/plugin_support/gems_resolver.rb +77 -0
  100. data/lib/danger/plugin_support/plugin.rb +49 -0
  101. data/lib/danger/plugin_support/plugin_file_resolver.rb +30 -0
  102. data/lib/danger/plugin_support/plugin_linter.rb +161 -0
  103. data/lib/danger/plugin_support/plugin_parser.rb +199 -0
  104. data/lib/danger/plugin_support/templates/readme_table.html.erb +26 -0
  105. data/lib/danger/request_sources/bitbucket_cloud.rb +171 -0
  106. data/lib/danger/request_sources/bitbucket_cloud_api.rb +181 -0
  107. data/lib/danger/request_sources/bitbucket_server.rb +105 -0
  108. data/lib/danger/request_sources/bitbucket_server_api.rb +117 -0
  109. data/lib/danger/request_sources/github/github.rb +530 -0
  110. data/lib/danger/request_sources/github/github_review.rb +126 -0
  111. data/lib/danger/request_sources/github/github_review_resolver.rb +19 -0
  112. data/lib/danger/request_sources/github/github_review_unsupported.rb +25 -0
  113. data/lib/danger/request_sources/gitlab.rb +525 -0
  114. data/lib/danger/request_sources/local_only.rb +53 -0
  115. data/lib/danger/request_sources/request_source.rb +85 -0
  116. data/lib/danger/request_sources/support/get_ignored_violation.rb +17 -0
  117. data/lib/danger/request_sources/vsts.rb +118 -0
  118. data/lib/danger/request_sources/vsts_api.rb +138 -0
  119. data/lib/danger/scm_source/git_repo.rb +181 -0
  120. data/lib/danger/version.rb +4 -0
  121. metadata +339 -0
@@ -0,0 +1,53 @@
1
+ # coding: utf-8
2
+
3
+ require "danger/helpers/comments_helper"
4
+ require "danger/helpers/comment"
5
+
6
+ module Danger
7
+ module RequestSources
8
+ class LocalOnly < RequestSource
9
+ include Danger::Helpers::CommentsHelper
10
+ attr_accessor :mr_json, :commits_json
11
+
12
+ def self.env_vars
13
+ ["DANGER_LOCAL_ONLY"]
14
+ end
15
+
16
+ def initialize(ci_source, environment)
17
+ self.ci_source = ci_source
18
+ self.environment = environment
19
+ end
20
+
21
+ def validates_as_ci?
22
+ true
23
+ end
24
+
25
+ def validates_as_api_source?
26
+ true
27
+ end
28
+
29
+ def scm
30
+ @scm ||= GitRepo.new
31
+ end
32
+
33
+ def setup_danger_branches
34
+ # Check that discovered values really exists
35
+ [ci_source.base_commit, ci_source.head_commit].each do |commit|
36
+ raise "Specified commit '#{commit}' not found" if scm.exec("rev-parse --quiet --verify #{commit}").empty?
37
+ end
38
+
39
+ self.scm.exec "branch #{EnvironmentManager.danger_base_branch} #{ci_source.base_commit}"
40
+ self.scm.exec "branch #{EnvironmentManager.danger_head_branch} #{ci_source.head_commit}"
41
+ end
42
+
43
+ def fetch_details; end
44
+
45
+ def update_pull_request!(_hash_needed); end
46
+
47
+ # @return [String] The organisation name, is nil if it can't be detected
48
+ def organisation
49
+ nil
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,85 @@
1
+ module Danger
2
+ module RequestSources
3
+ class RequestSource
4
+ DANGER_REPO_NAME = "danger".freeze
5
+
6
+ attr_accessor :ci_source, :environment, :scm, :host, :ignored_violations
7
+
8
+ def self.env_vars
9
+ raise "Subclass and overwrite self.env_vars"
10
+ end
11
+
12
+ def self.optional_env_vars
13
+ []
14
+ end
15
+
16
+ def self.inherited(child_class)
17
+ available_request_sources.add child_class
18
+ super
19
+ end
20
+
21
+ def self.available_request_sources
22
+ @available_request_sources ||= Set.new
23
+ end
24
+
25
+ def self.source_name
26
+ to_s.sub("Danger::RequestSources::".freeze, "".freeze)
27
+ end
28
+
29
+ def self.available_source_names_and_envs
30
+ available_request_sources.map do |klass|
31
+ " - #{klass.source_name}: #{klass.env_vars.join(', '.freeze).yellow}"
32
+ end
33
+ end
34
+
35
+ def initialize(_ci_source, _environment)
36
+ raise "Subclass and overwrite initialize"
37
+ end
38
+
39
+ # @return [Boolean] whether scm.origins is a valid git repository or not
40
+ def validates_as_ci?
41
+ !!self.scm.origins.match(%r{#{Regexp.escape self.host}(:|/)(.+/.+?)(?:\.git)?$})
42
+ end
43
+
44
+ def validates_as_api_source?
45
+ raise "Subclass and overwrite validates_as_api_source?"
46
+ end
47
+
48
+ def scm
49
+ @scm ||= nil
50
+ end
51
+
52
+ def host
53
+ @host ||= nil
54
+ end
55
+
56
+ def ignored_violations
57
+ @ignored_violations ||= []
58
+ end
59
+
60
+ def update_pull_request!(_warnings: [], _errors: [], _messages: [], _markdowns: [])
61
+ raise "Subclass and overwrite update_pull_request!"
62
+ end
63
+
64
+ def setup_danger_branches
65
+ raise "Subclass and overwrite setup_danger_branches"
66
+ end
67
+
68
+ def fetch_details
69
+ raise "Subclass and overwrite initialize"
70
+ end
71
+
72
+ def organisation
73
+ raise "Subclass and overwrite organisation"
74
+ end
75
+
76
+ def file_url(_organisation: nil, _repository: nil, _branch: "master", _path: nil)
77
+ raise "Subclass and overwrite file_url"
78
+ end
79
+
80
+ def update_build_status(status)
81
+ raise "Subclass and overwrite update_build_status"
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,17 @@
1
+ class GetIgnoredViolation
2
+ IGNORE_REGEXP = />*\s*danger\s*:\s*ignore\s*"(?<error>[^"]*)"/i
3
+
4
+ def initialize(body)
5
+ @body = body
6
+ end
7
+
8
+ def call
9
+ return [] unless body
10
+
11
+ body.chomp.scan(IGNORE_REGEXP).flatten
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :body
17
+ end
@@ -0,0 +1,118 @@
1
+ # coding: utf-8
2
+
3
+ require "danger/helpers/comments_helper"
4
+ require "danger/request_sources/vsts_api"
5
+
6
+ module Danger
7
+ module RequestSources
8
+ class VSTS < RequestSource
9
+ include Danger::Helpers::CommentsHelper
10
+ attr_accessor :pr_json
11
+
12
+ def self.env_vars
13
+ [
14
+ "DANGER_VSTS_API_TOKEN",
15
+ "DANGER_VSTS_HOST"
16
+ ]
17
+ end
18
+
19
+ def self.optional_env_vars
20
+ [
21
+ "DANGER_VSTS_API_VERSION"
22
+ ]
23
+ end
24
+
25
+ def initialize(ci_source, environment)
26
+ self.ci_source = ci_source
27
+ self.environment = environment
28
+
29
+ @is_vsts_git = environment["BUILD_REPOSITORY_PROVIDER"] == "TfsGit"
30
+
31
+ project, slug = ci_source.repo_slug.split("/")
32
+ @api = VSTSAPI.new(project, slug, ci_source.pull_request_id, environment)
33
+ end
34
+
35
+ def validates_as_ci?
36
+ @is_vsts_git
37
+ end
38
+
39
+ def validates_as_api_source?
40
+ @api.credentials_given?
41
+ end
42
+
43
+ def scm
44
+ @scm ||= GitRepo.new
45
+ end
46
+
47
+ def host
48
+ @host ||= @api.host
49
+ end
50
+
51
+ def fetch_details
52
+ self.pr_json = @api.fetch_pr_json
53
+ end
54
+
55
+ def setup_danger_branches
56
+ base_branch = self.pr_json[:targetRefName].sub("refs/heads/", "")
57
+ base_commit = self.pr_json[:lastMergeTargetCommit][:commitId]
58
+ head_branch = self.pr_json[:sourceRefName].sub("refs/heads/", "")
59
+ head_commit = self.pr_json[:lastMergeSourceCommit][:commitId]
60
+
61
+ # Next, we want to ensure that we have a version of the current branch at a known location
62
+ scm.ensure_commitish_exists_on_branch! base_branch, base_commit
63
+ self.scm.exec "branch #{EnvironmentManager.danger_base_branch} #{base_commit}"
64
+
65
+ # OK, so we want to ensure that we have a known head branch, this will always represent
66
+ # the head of the PR ( e.g. the most recent commit that will be merged. )
67
+ scm.ensure_commitish_exists_on_branch! head_branch, head_commit
68
+ self.scm.exec "branch #{EnvironmentManager.danger_head_branch} #{head_commit}"
69
+ end
70
+
71
+ def organisation
72
+ nil
73
+ end
74
+
75
+ def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false, remove_previous_comments: false)
76
+ unless @api.supports_comments?
77
+ return
78
+ end
79
+
80
+ comment = generate_description(warnings: warnings, errors: errors)
81
+ comment += "\n\n"
82
+ comment += generate_comment(warnings: warnings,
83
+ errors: errors,
84
+ messages: messages,
85
+ markdowns: markdowns,
86
+ previous_violations: {},
87
+ danger_id: danger_id,
88
+ template: "vsts")
89
+ if new_comment || remove_previous_comments
90
+ post_new_comment(comment)
91
+ else
92
+ update_old_comment(comment, danger_id: danger_id)
93
+ end
94
+ end
95
+
96
+ def post_new_comment(comment)
97
+ @api.post_comment(comment)
98
+ end
99
+
100
+ def update_old_comment(new_comment, danger_id: "danger")
101
+ comment_updated = false
102
+ @api.fetch_last_comments.each do |c|
103
+ thread_id = c[:id]
104
+ comment = c[:comments].first
105
+ comment_id = comment[:id]
106
+ comment_content = comment[:content].nil? ? "" : comment[:content]
107
+ # Skip the comment if it wasn't posted by danger
108
+ next unless comment_content.include?("generated_by_#{danger_id}")
109
+ # Updated the danger posted comment
110
+ @api.update_comment(thread_id, comment_id, new_comment)
111
+ comment_updated = true
112
+ end
113
+ # If no comment was updated, post a new one
114
+ post_new_comment(new_comment) unless comment_updated
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,138 @@
1
+ # coding: utf-8
2
+
3
+ require "base64"
4
+ require "danger/helpers/comments_helper"
5
+
6
+ module Danger
7
+ module RequestSources
8
+ class VSTSAPI
9
+ attr_accessor :host, :pr_api_endpoint, :min_api_version_for_comments
10
+
11
+ def initialize(_project, slug, pull_request_id, environment)
12
+ self.min_api_version_for_comments = "3.0"
13
+
14
+ user_name = ""
15
+ personal_access_token = environment["DANGER_VSTS_API_TOKEN"]
16
+
17
+ @token = Base64.strict_encode64("#{user_name}:#{personal_access_token}")
18
+ @api_version = environment["DANGER_VSTS_API_VERSION"] ||= self.min_api_version_for_comments
19
+
20
+ self.host = environment["DANGER_VSTS_HOST"]
21
+ if self.host && !(self.host.include? "http://") && !(self.host.include? "https://")
22
+ self.host = "https://" + self.host
23
+ end
24
+
25
+ self.pr_api_endpoint = "#{host}/_apis/git/repositories/#{slug}/pullRequests/#{pull_request_id}"
26
+ end
27
+
28
+ def supports_comments?
29
+ major_version = @api_version.split(".").first.to_i
30
+ minimun_version_for_comments = self.min_api_version_for_comments.split(".").first.to_i
31
+
32
+ major_version >= minimun_version_for_comments
33
+ end
34
+
35
+ def inspect
36
+ inspected = super
37
+
38
+ if @token
39
+ inspected = inspected.sub! @token, "********".freeze
40
+ end
41
+
42
+ inspected
43
+ end
44
+
45
+ def credentials_given?
46
+ @token && !@token.empty?
47
+ end
48
+
49
+ def fetch_pr_json
50
+ uri = URI("#{pr_api_endpoint}?api-version=#{@api_version}")
51
+ fetch_json(uri)
52
+ end
53
+
54
+ def fetch_last_comments
55
+ uri = URI("#{pr_api_endpoint}/threads?api-version=#{@api_version}")
56
+ fetch_json(uri)[:value]
57
+ end
58
+
59
+ def post_comment(text)
60
+ uri = URI("#{pr_api_endpoint}/threads?api-version=#{@api_version}")
61
+ body = {
62
+ "comments" => [
63
+ {
64
+ "parentCommentId" => 0,
65
+ "content" => text,
66
+ "commentType" => 1
67
+ }
68
+ ],
69
+ "properties" => {
70
+ "Microsoft.TeamFoundation.Discussion.SupportsMarkdown" => {
71
+ "type" => "System.Int32",
72
+ "value" => 1
73
+ }
74
+ },
75
+ "status" => 1
76
+ }.to_json
77
+ post(uri, body)
78
+ end
79
+
80
+ def update_comment(thread, id, new_comment)
81
+ uri = URI("#{pr_api_endpoint}/threads/#{thread}/comments/#{id}?api-version=#{@api_version}")
82
+ body = {
83
+ "content" => new_comment
84
+ }.to_json
85
+ patch(uri, body)
86
+ end
87
+
88
+ private
89
+
90
+ def use_ssl
91
+ return self.pr_api_endpoint.include? "https://"
92
+ end
93
+
94
+ def fetch_json(uri)
95
+ req = Net::HTTP::Get.new(uri.request_uri, { "Content-Type" => "application/json", "Authorization" => "Basic #{@token}" })
96
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl) do |http|
97
+ http.request(req)
98
+ end
99
+ JSON.parse(res.body, symbolize_names: true)
100
+ end
101
+
102
+ def post(uri, body)
103
+ req = Net::HTTP::Post.new(uri.request_uri, { "Content-Type" => "application/json", "Authorization" => "Basic #{@token}" })
104
+ req.body = body
105
+
106
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl) do |http|
107
+ http.request(req)
108
+ end
109
+
110
+ # show error to the user when VSTS returned an error
111
+ case res
112
+ when Net::HTTPClientError, Net::HTTPServerError
113
+ # HTTP 4xx - 5xx
114
+ abort "\nError posting comment to VSTS: #{res.code} (#{res.message})\n\n"
115
+ end
116
+ end
117
+
118
+ def patch(uri, body)
119
+ puts uri
120
+ puts body
121
+
122
+ req = Net::HTTP::Patch.new(uri.request_uri, { "Content-Type" => "application/json", "Authorization" => "Basic #{@token}" })
123
+ req.body = body
124
+
125
+ res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl) do |http|
126
+ http.request(req)
127
+ end
128
+
129
+ # show error to the user when VSTS returned an error
130
+ case res
131
+ when Net::HTTPClientError, Net::HTTPServerError
132
+ # HTTP 4xx - 5xx
133
+ abort "\nError updating comment on VSTS: #{res.code} (#{res.message})\n\n"
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,181 @@
1
+ # For more info see: https://github.com/schacon/ruby-git
2
+
3
+ require "git"
4
+
5
+ module Danger
6
+ class GitRepo
7
+ attr_accessor :diff, :log, :folder
8
+
9
+ def diff_for_folder(folder, from: "master", to: "HEAD", lookup_top_level: false)
10
+ self.folder = folder
11
+ git_top_level = folder
12
+ if lookup_top_level
13
+ Dir.chdir(folder) do
14
+ git_top_level = exec("rev-parse --show-toplevel")
15
+ end
16
+ end
17
+ repo = Git.open git_top_level
18
+
19
+ ensure_commitish_exists!(from)
20
+ ensure_commitish_exists!(to)
21
+
22
+ merge_base = find_merge_base(repo, from, to)
23
+ commits_in_branch_count = commits_in_branch_count(from, to)
24
+
25
+ self.diff = repo.diff(merge_base, to)
26
+ self.log = repo.log(commits_in_branch_count).between(from, to)
27
+ end
28
+
29
+ def renamed_files
30
+ # Get raw diff with --find-renames --diff-filter
31
+ # We need to pass --find-renames cause
32
+ # older versions of git don't use this flag as default
33
+ diff = exec(
34
+ "diff #{self.diff.from} #{self.diff.to} --find-renames --diff-filter=R"
35
+ ).lines.map { |line| line.tr("\n", "") }
36
+
37
+ before_name_regexp = /^rename from (.*)$/
38
+ after_name_regexp = /^rename to (.*)$/
39
+
40
+ # Extract old and new paths via regexp
41
+ diff.each_with_index.map do |line, index|
42
+ before_match = line.match(before_name_regexp)
43
+ next unless before_match
44
+
45
+ after_match = diff.fetch(index + 1, "").match(after_name_regexp)
46
+ next unless after_match
47
+
48
+ {
49
+ before: before_match.captures.first,
50
+ after: after_match.captures.first
51
+ }
52
+ end.compact
53
+ end
54
+
55
+ def exec(string)
56
+ require "open3"
57
+ Dir.chdir(self.folder || ".") do
58
+ Open3.popen2(default_env, "git #{string}") do |_stdin, stdout, _wait_thr|
59
+ stdout.read.rstrip
60
+ end
61
+ end
62
+ end
63
+
64
+ def head_commit
65
+ exec("rev-parse HEAD")
66
+ end
67
+
68
+ def tags
69
+ exec("tag")
70
+ end
71
+
72
+ def origins
73
+ exec("remote show origin -n").lines.grep(/Fetch URL/)[0].split(": ", 2)[1].chomp
74
+ end
75
+
76
+ def ensure_commitish_exists!(commitish)
77
+ return ensure_commitish_exists_on_branch!(commitish, commitish) if commit_is_ref?(commitish)
78
+ return if commit_exists?(commitish)
79
+
80
+ git_in_depth_fetch
81
+ raise_if_we_cannot_find_the_commit(commitish) if commit_not_exists?(commitish)
82
+ end
83
+
84
+ def ensure_commitish_exists_on_branch!(branch, commitish)
85
+ return if commit_exists?(commitish)
86
+
87
+ depth = 0
88
+ success =
89
+ (3..6).any? do |factor|
90
+ depth += Math.exp(factor).to_i
91
+
92
+ git_fetch_branch_to_depth(branch, depth)
93
+ commit_exists?(commitish)
94
+ end
95
+
96
+ return if success
97
+
98
+ git_in_depth_fetch
99
+ raise_if_we_cannot_find_the_commit(commitish) if commit_not_exists?(commitish)
100
+ end
101
+
102
+ private
103
+
104
+ def git_in_depth_fetch
105
+ exec("fetch --depth 1000000")
106
+ end
107
+
108
+ def git_fetch_branch_to_depth(branch, depth)
109
+ exec("fetch --depth=#{depth} --prune origin +refs/heads/#{branch}:refs/remotes/origin/#{branch}")
110
+ end
111
+
112
+ def default_env
113
+ { "LANG" => "en_US.UTF-8" }
114
+ end
115
+
116
+ def raise_if_we_cannot_find_the_commit(commitish)
117
+ raise "Commit #{commitish[0..7]} doesn't exist. Are you running `danger local/pr` against the correct repository? Also this usually happens when you rebase/reset and force-pushed."
118
+ end
119
+
120
+ def commit_exists?(sha1)
121
+ !commit_not_exists?(sha1)
122
+ end
123
+
124
+ def commit_not_exists?(sha1)
125
+ exec("rev-parse --quiet --verify #{sha1}^{commit}").empty?
126
+ end
127
+
128
+ def find_merge_base(repo, from, to)
129
+ possible_merge_base = possible_merge_base(repo, from, to)
130
+ return possible_merge_base if possible_merge_base
131
+
132
+ possible_merge_base = find_merge_base_with_incremental_fetch(repo, from, to)
133
+ return possible_merge_base if possible_merge_base
134
+
135
+ git_in_depth_fetch
136
+ possible_merge_base = possible_merge_base(repo, from, to)
137
+
138
+ raise "Cannot find a merge base between #{from} and #{to}. If you are using shallow clone/fetch, try increasing the --depth" unless possible_merge_base
139
+
140
+ possible_merge_base
141
+ end
142
+
143
+ def find_merge_base_with_incremental_fetch(repo, from, to)
144
+ from_is_ref = commit_is_ref?(from)
145
+ to_is_ref = commit_is_ref?(to)
146
+
147
+ return unless from_is_ref || to_is_ref
148
+
149
+ depth = 0
150
+ (3..6).any? do |factor|
151
+ depth += Math.exp(factor).to_i
152
+
153
+ git_fetch_branch_to_depth(from, depth) if from_is_ref
154
+ git_fetch_branch_to_depth(to, depth) if to_is_ref
155
+ possible_merge_base(repo, from, to)
156
+ end
157
+ end
158
+
159
+ def possible_merge_base(repo, from, to)
160
+ [repo.merge_base(from, to)].find { |base| commit_exists?(base) }
161
+ end
162
+
163
+ def commits_in_branch_count(from, to)
164
+ exec("rev-list #{from}..#{to} --count").to_i
165
+ end
166
+
167
+ def commit_is_ref?(commit)
168
+ /[a-f0-9]{5,40}/ !~ commit
169
+ end
170
+ end
171
+ end
172
+
173
+ module Git
174
+ class Base
175
+ # Use git-merge-base https://git-scm.com/docs/git-merge-base to
176
+ # find as good common ancestors as possible for a merge
177
+ def merge_base(commit1, commit2, *other_commits)
178
+ Open3.popen2("git", "merge-base", commit1, commit2, *other_commits) { |_stdin, stdout, _wait_thr| stdout.read.rstrip }
179
+ end
180
+ end
181
+ end