-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Expand file tree
/
Copy pathBranchSpec.java
More file actions
254 lines (219 loc) · 8.27 KB
/
Copy pathBranchSpec.java
File metadata and controls
254 lines (219 loc) · 8.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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
215
216
217
218
219
220
221
222
223
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
package hudson.plugins.git;
import hudson.EnvVars;
import hudson.Extension;
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import org.kohsuke.stapler.DataBoundConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.Whitelisted;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import edu.umd.cs.findbugs.annotations.NonNull;
/**
* A specification of branches to build. Rather like a refspec.
*
* eg:
* <pre>
* master
* origin/master
* origin/*
* origin/*/thing
* </pre>
*/
@ExportedBean
public class BranchSpec extends AbstractDescribableImpl<BranchSpec> implements Serializable {
@Serial
private static final long serialVersionUID = -6177158367915899356L;
private String name;
@Exported
@Whitelisted
public String getName() {
return name;
}
public void setName(String name) {
if(name == null)
throw new IllegalArgumentException();
else if(name.length() == 0)
this.name = "**";
else
this.name = name.trim();
}
@DataBoundConstructor
public BranchSpec(String name) {
setName(name);
}
public String toString() {
return name;
}
public boolean matches(String item) {
EnvVars env = new EnvVars();
return matches(item, env);
}
/**
* Compare a git branch reference to configured pattern.
* <p>
* reference uses normalized format `ref/(heads|tags)/xx`
* pattern do support
* <ul>
* <li>ref/heads/branch</li>
* <li>(remote-name)?/branch</li>
* <li>ref/remotes/branch</li>
* <li>tag</li>
* <li>(commit sha1)</li>
* </ul>
* @param ref branch reference to compare
* @param env environment variables to use in comparison
* @return true if ref matches configured pattern
*/
public boolean matches(String ref, EnvVars env) {
return getPattern(env).matcher(ref).matches();
}
/**
* Compare the configured pattern to a git branch defined by the repository name and branch name.
* @param repositoryName git repository name
* @param branchName git branch name
* @return true if repositoryName/branchName matches this BranchSpec
*/
public boolean matchesRepositoryBranch(String repositoryName, String branchName) {
if (branchName == null) {
return false;
}
Pattern pattern = getPattern(new EnvVars(), repositoryName);
String branchWithoutRefs = cutRefs(branchName);
return pattern.matcher(branchWithoutRefs).matches() || pattern.matcher(join(repositoryName, branchWithoutRefs)).matches();
}
/**
* @deprecated use {@link #filterMatching(Collection, EnvVars)}
* @param branches source branch list to be filtered by configured branch specification using a newly constructed EnvVars
* @return branch names which match
*/
@Deprecated
public List<String> filterMatching(Collection<String> branches) {
EnvVars env = new EnvVars();
return filterMatching(branches, env);
}
public List<String> filterMatching(Collection<String> branches, EnvVars env) {
List<String> items = new ArrayList<>();
for(String b : branches) {
if(matches(b, env))
items.add(b);
}
return items;
}
public List<Branch> filterMatchingBranches(Collection<Branch> branches) {
EnvVars env = new EnvVars();
return filterMatchingBranches(branches, env);
}
public List<Branch> filterMatchingBranches(Collection<Branch> branches, EnvVars env) {
List<Branch> items = new ArrayList<>();
for(Branch b : branches) {
if(matches(b.getName(), env))
items.add(b);
}
return items;
}
private String getExpandedName(EnvVars env) {
String expandedName = env.expand(name);
if (expandedName.length() == 0) {
return "**";
}
return expandedName;
}
private Pattern getPattern(EnvVars env) {
return getPattern(env, null);
}
private Pattern getPattern(EnvVars env, String repositoryName) {
String expandedName = getExpandedName(env);
// use regex syntax directly if name starts with colon
if (expandedName.startsWith(":") && expandedName.length() > 1) {
String regexSubstring = expandedName.substring(1, expandedName.length());
return Pattern.compile(regexSubstring);
}
if (expandedName.startsWith("refs/changes/")) {
return Pattern.compile(Pattern.quote(expandedName));
}
if (repositoryName != null) {
// remove the "refs/.../" stuff from the branch-spec if necessary
String pattern = cutRefs(expandedName)
// remove a leading "remotes/" from the branch spec
.replaceAll("^remotes/", "");
pattern = convertWildcardStringToRegex(pattern);
return Pattern.compile(pattern);
}
// build a pattern into this builder
StringBuilder builder = new StringBuilder();
// for legacy reasons (sic) we do support various branch spec format to declare remotes / branches
builder.append("(refs/heads/");
// if an unqualified branch was given, consider all remotes (with various possible syntaxes)
// so it will match branches from any remote repositories as the user probably intended
if (!expandedName.contains("**") && !expandedName.contains("/")) {
builder.append("|refs/remotes/[^/]+/|remotes/[^/]+/|[^/]+/");
} else {
builder.append("|refs/remotes/|remotes/");
}
builder.append(")?");
builder.append(convertWildcardStringToRegex(expandedName));
return Pattern.compile(builder.toString());
}
private String convertWildcardStringToRegex(String expandedName) {
StringBuilder builder = new StringBuilder();
// was the last token a wildcard?
boolean foundWildcard = false;
// split the string at the wildcards
StringTokenizer tokenizer = new StringTokenizer(expandedName, "*", true);
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
// is this token is a wildcard?
if (token.equals("*")) {
// yes, was the previous token a wildcard?
if (foundWildcard) {
// yes, we found "**"
// match over any number of characters
builder.append(".*");
foundWildcard = false;
}
else {
// no, set foundWildcard to true and go on
foundWildcard = true;
}
}
else {
// no, was the previous token a wildcard?
if (foundWildcard) {
// yes, we found "*" followed by a non-wildcard
// match any number of characters other than a "/"
builder.append("[^/]*");
foundWildcard = false;
}
// quote the non-wildcard token before adding it to the phrase
builder.append(Pattern.quote(token));
}
}
// if the string ended with a wildcard add it now
if (foundWildcard) {
builder.append("[^/]*");
}
return builder.toString();
}
private String cutRefs(@NonNull String name) {
Matcher matcher = GitSCM.GIT_REF.matcher(name);
return matcher.matches() ? matcher.group(2) : name;
}
private String join(String repositoryName, String branchWithoutRefs) {
return String.join("/", repositoryName, branchWithoutRefs);
}
@Extension
public static class DescriptorImpl extends Descriptor<BranchSpec> {
@Override
public String getDisplayName() {
return "Branch Spec";
}
}
}