danger 8.0.3

Sign up to get free protection for your applications and to get access to all the features.
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 +159 -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