summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorTimothy Andrew <mail@timothyandrew.net>2016-06-15 11:15:01 +0530
committerTimothy Andrew <mail@timothyandrew.net>2016-07-05 10:50:34 +0530
commitf51af496769f2fe181d4633f810b85103efd181e (patch)
treeb95805a0f52389d752174948434f0196d2194ba6 /app
parentba9ef7f3935cfaa42fcdb2317567cc383c7e9c22 (diff)
downloadgitlab-ce-f51af496769f2fe181d4633f810b85103efd181e.tar.gz
Support wildcard matches for protected branches at the model level.
1. The main implementation is in the `ProtectedBranch` model. The wildcard is converted to a Regex and compared. This has been tested thoroughly. - While `Project#protected_branch?` is the main entry point, `project#open_branches` and `project#developers_can_push_to_protected_branch?` have also been modified to work with wildcard protected branches. - The regex is memoized (within the `ProtectedBranch` instance) 2. Improve the performance of `Project#protected_branch?` - This method is called from `Project#open_branches` once _per branch_ in the project, to check if that branch is protected or not. - Before, `#protected_branch?` was making a database call every time it was invoked (in the above case, that amounts to once per branch), which is expensive. - This commit caches the list of protected branches in memory, which reduces the number of database calls down to 1. - A downside to this approach is that `#protected_branch?` _could_ return a stale value (due to the caching), but this is an acceptable tradeoff. 3. Remove the (now) unused `Project#protected_branch_names` method. - This was previously used to check for protected branch status.
Diffstat (limited to 'app')
-rw-r--r--app/models/project.rb17
-rw-r--r--app/models/protected_branch.rb36
2 files changed, 40 insertions, 13 deletions
diff --git a/app/models/project.rb b/app/models/project.rb
index ae96f00a705..d5d57bafb98 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -803,17 +803,7 @@ class Project < ActiveRecord::Base
end
def open_branches
- # We're using a Set here as checking values in a large Set is faster than
- # checking values in a large Array.
- protected_set = Set.new(protected_branch_names)
-
- repository.branches.reject do |branch|
- protected_set.include?(branch.name)
- end
- end
-
- def protected_branch_names
- @protected_branch_names ||= protected_branches.pluck(:name)
+ repository.branches.reject { |branch| self.protected_branch?(branch.name) }
end
def root_ref?(branch)
@@ -830,11 +820,12 @@ class Project < ActiveRecord::Base
# Check if current branch name is marked as protected in the system
def protected_branch?(branch_name)
- protected_branch_names.include?(branch_name)
+ @protected_branches ||= self.protected_branches.to_a
+ ProtectedBranch.matching(branch_name, protected_branches: @protected_branches).present?
end
def developers_can_push_to_protected_branch?(branch_name)
- protected_branches.any? { |pb| pb.name == branch_name && pb.developers_can_push }
+ protected_branches.matching(branch_name).any?(&:developers_can_push)
end
def forked?
diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb
index 33cf046fa75..3db1ab0e5f9 100644
--- a/app/models/protected_branch.rb
+++ b/app/models/protected_branch.rb
@@ -8,4 +8,40 @@ class ProtectedBranch < ActiveRecord::Base
def commit
project.commit(self.name)
end
+
+ # Returns all protected branches that match the given branch name.
+ # This realizes all records from the scope built up so far, and does
+ # _not_ return a relation.
+ #
+ # This method optionally takes in a list of `protected_branches` to search
+ # through, to avoid calling out to the database.
+ def self.matching(branch_name, protected_branches: nil)
+ (protected_branches || all).select { |protected_branch| protected_branch.matches?(branch_name) }
+ end
+
+ # Checks if the protected branch matches the given branch name.
+ def matches?(branch_name)
+ return false if self.name.blank?
+
+ exact_match?(branch_name) || wildcard_match?(branch_name)
+ end
+
+ protected
+
+ def exact_match?(branch_name)
+ self.name == branch_name
+ end
+
+ def wildcard_match?(branch_name)
+ wildcard_regex === branch_name
+ end
+
+ def wildcard_regex
+ @wildcard_regex ||= begin
+ name = self.name.gsub('*', 'STAR_DONT_ESCAPE')
+ quoted_name = Regexp.quote(name)
+ regex_string = quoted_name.gsub('STAR_DONT_ESCAPE', '.*?')
+ /\A#{regex_string}\z/
+ end
+ end
end