fix TW-58428: make health report sound as recommendation rather than error
[teamcity/git-plugin.git] / git-server / resources / buildServerResources / gitSettings.jsp
1 <%--
2   ~ Copyright 2000-2012 JetBrains s.r.o.
3   ~
4   ~ Licensed under the Apache License, Version 2.0 (the "License");
5   ~ you may not use this file except in compliance with the License.
6   ~ You may obtain a copy of the License at
7   ~
8   ~ http://www.apache.org/licenses/LICENSE-2.0
9   ~
10   ~ Unless required by applicable law or agreed to in writing, software
11   ~ distributed under the License is distributed on an "AS IS" BASIS,
12   ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   ~ See the License for the specific language governing permissions and
14   ~ limitations under the License.
15   --%>
16
17 <%@ page import="jetbrains.buildServer.buildTriggers.vcs.git.Constants" %>
18 <%@ page import="jetbrains.buildServer.buildTriggers.vcs.git.PluginConfigImpl" %>
19 <%@ page import="jetbrains.buildServer.serverSide.TeamCityProperties" %>
20 <%@ page import="jetbrains.buildServer.util.StringUtil" %>
21 <%@ page import="java.io.File" %>
22 <%@include file="/include.jsp" %>
23 <%@ taglib prefix="props" tagdir="/WEB-INF/tags/props" %>
24 <%@ taglib prefix="admin" tagdir="/WEB-INF/tags/admin" %>
25
26 <jsp:useBean id="propertiesBean" scope="request" type="jetbrains.buildServer.controllers.BasePropertiesBean"/>
27 <c:set var="gitPathEnv" value="<%= Constants.TEAMCITY_AGENT_GIT_PATH %>"/>
28 <c:set var="teamcitySshKeysEnabled" value="<%= PluginConfigImpl.isTeamcitySshKeysEnabled() %>"/>
29 <c:set var="showKnownHostsDbOption" value="<%= PluginConfigImpl.showKnownHostsDbOption() %>"/>
30 <c:set var="showCustomClonePath" value="<%= TeamCityProperties.getBoolean(Constants.CUSTOM_CLONE_PATH_ENABLED) &&
31                                             (TeamCityProperties.getBoolean(Constants.SHOW_CUSTOM_CLONE_PATH)
32                                             || !StringUtil.isEmpty(propertiesBean.getProperties().get(Constants.PATH))) %>"/>
33 <style>
34 .gitUsernameStyleHighlight {
35   color: rgb(97, 94, 192);
36 }
37 </style>
38 <script type="text/javascript">
39   uploadedKeySelected = function(encrypted) {
40     if (encrypted) {
41       $j('#gitPassphraseRow').show();
42     } else {
43       $j('#secure\\:passphrase').val('');
44       $j('#gitPassphraseRow').hide();
45     }
46   };
47 </script>
48 <table class="runnerFormTable">
49   <c:set var="userHome"
50          value='<%=new File(System.getProperty("user.home"), ".ssh"+File.separator+"config").getAbsolutePath() %>'/>
51   <l:settingsGroup title="General Settings">
52     <tr>
53       <th><label for="url">Fetch URL: <l:star/></label></th>
54       <td><props:textProperty name="url" className="longField"/>
55         <jsp:include page="/admin/repositoryControls.html?projectId=${parentProject.externalId}&vcsType=git"/>
56         <div class="smallNote" style="margin: 0;">It is used for fetching data from the repository.</div>
57         <div id="fetchUrlCompatNote" class="smallNote error" style="margin: 0; display: none;"></div>
58         <span class="error" id="error_url"></span></td>
59     </tr>
60     <tr>
61       <th><label for="push_url">Push URL:</label></th>
62       <td><props:textProperty name="push_url" className="longField"/>
63         <div class="smallNote" style="margin: 0;">It is used for pushing tags to the remote repository.
64           If blank, the fetch url is used.
65         </div>
66         <div id="pushUrlCompatNote" class="smallNote error" style="margin: 0; display: none;"></div>
67         <span class="error" id="error_push_url"></span>
68       </td>
69     </tr>
70     <tr>
71       <th><label for="branch">Default branch: <l:star/></label></th>
72       <td>
73         <props:textProperty name="branch" className="longField"/>
74         <div class="smallNote" style="margin: 0">The main branch or tag to be monitored</div>
75         <span class="error" id="error_branch"></span>
76       </td>
77     </tr>
78     <bs:branchSpecTableRow/>
79     <tr class="advancedSetting">
80       <th><label for="reportTagRevisions">Use tags as branches:</label></th>
81       <td>
82         <props:checkboxProperty name="reportTagRevisions"/>
83         <label for="reportTagRevisions">Enable to use tags in the branch specification</label>
84       </td>
85     </tr>
86     <tr class="advancedSetting">
87       <th><label for="usernameStyle">Username style:</label></th>
88       <td><props:selectProperty name="usernameStyle" enableFilter="true" className="mediumField">
89         <props:option value="USERID">UserId</props:option>
90         <props:option value="NAME">Author Name</props:option>
91         <props:option value="FULL">Author Name and Email</props:option>
92         <props:option value="EMAIL">Author Email</props:option>
93       </props:selectProperty>
94         <div class="smallNote" style="margin: 0;">
95           Defines a way TeamCity binds VCS changes to the user. With
96           selected style and the following content of ~/.gitconfig:
97           <div style="font-weight: bold">[user]</div>
98           <div style="margin-left: 15px">name = <span id="gitConfigUserName">Joe Coder</span></div>
99               <div style="margin-left: 15px">email = <span id="gitConfigUserId">joe.coder</span><span id="gitConfigEmail">@acme.com</span></div>
100           you should enter <span id="usernameToEnter" class="gitUsernameStyleHighlight"></span> in
101           Version Control Username Settings in your profile.
102           <br/>
103           Changing username style will affect only newly collected
104           changes. Old changes will continue to be stored with the style
105           that was active at the time of collecting changes.
106         </div>
107       </td>
108     </tr>
109     <tr class="advancedSetting">
110       <th><label for="submoduleCheckout">Submodules:</label></th>
111       <td><props:selectProperty name="submoduleCheckout" enableFilter="true" className="mediumField">
112         <props:option value="IGNORE">Ignore</props:option>
113         <props:option value="CHECKOUT">Checkout</props:option>
114       </props:selectProperty>
115         <div class="smallNote" style="margin: 0">
116           Defines whether to checkout submodules
117         </div>
118       </td>
119     </tr>
120     <tr class="advancedSetting">
121       <th><label for="userForTags">Username for tags/merge:</label></th>
122       <td><props:textProperty name="userForTags" className="longField"/>
123         <div class="smallNote" style="margin: 0">Format: Username &lt;email&gt;</div>
124       </td>
125     </tr>
126   </l:settingsGroup>
127   <l:settingsGroup title="Authentication Settings">
128     <tr>
129       <th><label for="authMethod">Authentication method:</label></th>
130       <td>
131         <props:selectProperty name="authMethod" onchange="gitSelectAuthentication(true)" enableFilter="true" className="mediumField">
132           <props:option value="ANONYMOUS">Anonymous</props:option>
133           <props:option value="PASSWORD">Password</props:option>
134           <optgroup label="Private Key">
135             <c:if test="${teamcitySshKeysEnabled}">
136               <props:option value="TEAMCITY_SSH_KEY">Uploaded Key</props:option>
137             </c:if>
138             <props:option value="PRIVATE_KEY_DEFAULT">Default Private Key</props:option>
139             <props:option value="PRIVATE_KEY_FILE">Custom Private Key</props:option>
140           </optgroup>
141         </props:selectProperty>
142         <div id="defaultPrivateKeyNote" class="smallNote auth defaultKey" style="margin: 0">Uses mapping specified in the file
143           ${userHome} if that file exists.
144         </div>
145         <div id="authMethodCompatNote" class="smallNote" style="margin: 0; display: none;"></div>
146       </td>
147     </tr>
148     <tr id="gitUsername" class="auth defaultKey customKey password uploadedKey">
149       <th><label for="username">Username:</label></th>
150       <td><props:textProperty name="username" className="longField"/>
151         <div class="smallNote" style="margin: 0">
152           Specify the username if there is no username in the clone URL. The username specified here overrides the username from the URL.
153         </div>
154       </td>
155     </tr>
156     <tr id="gitPasswordRow" class="auth password">
157       <th><label for="secure:password">Password:</label></th>
158       <td><props:passwordProperty name="secure:password" className="longField"/></td>
159     </tr>
160     <tr id="gitPrivateKeyRow" class="auth customKey">
161       <th><label for="privateKeyPath">Private key path: <l:star/></label></th>
162       <td><props:textProperty name="privateKeyPath" className="longField"/>
163         <div class="smallNote" style="margin: 0;">
164           Specify the path to the private key on the TeamCity server host.
165         </div>
166         <span class="error" id="error_privateKeyPath"></span>
167       </td>
168     </tr>
169     <c:if test="${not empty vcsPropertiesBean.belongsToProject}">
170       <c:set var="projectId" value="${vcsPropertiesBean.belongsToProject.externalId}" scope="request"/>
171     </c:if>
172     <tr id="gitTeamCityKeyRow" class="auth uploadedKey">
173       <th><label for="teamcitySshKey">Uploaded Key: <l:star/></label></th>
174       <td>
175         <admin:sshKeys projectId="${projectId}" keySelectionCallback="uploadedKeySelected"/>
176         <span class="error" id="error_teamcitySshKey"></span>
177       </td>
178     </tr>
179     <tr id="gitPassphraseRow" class="auth customKey">
180       <th><label for="secure:passphrase">Passphrase:</label></th>
181       <td><props:passwordProperty name="secure:passphrase" className="longField"/></td>
182     </tr>
183     <c:choose>
184       <c:when test="${showKnownHostsDbOption or not vcsPropertiesBean.propertiesBean.properties['ignoreKnownHosts']}">
185         <tr id="gitKnownHosts" class="advancedSetting">
186           <div class="auth defaultKey customKey uploadedKey">
187             <th><label for="ignoreKnownHosts">Ignore known hosts database:</label></th>
188             <td><props:checkboxProperty name="ignoreKnownHosts"/>
189               <c:out value="${vcsPropertiesBean.propertiesBean.properties['ignoreKnownHosts']}"/>
190             </td>
191           </div>
192         </tr>
193       </c:when>
194       <c:otherwise>
195         <props:hiddenProperty name="ignoreKnownHosts" value="true"/>
196       </c:otherwise>
197     </c:choose>
198   </l:settingsGroup>
199   <l:settingsGroup title="Server Settings" className="advancedSetting">
200     <tr class="advancedSetting">
201       <td colspan="2">Settings that are used in case of server-side checkout.</td>
202     </tr>
203     <tr class="advancedSetting">
204       <th>
205         <label for="serverSideAutoCrlf">Convert line-endings to CRLF:<bs:help file="Git" anchor="serverAutoCRLF"/></label>
206       </th>
207       <td>
208         <props:checkboxProperty name="serverSideAutoCrlf"/>
209       </td>
210     </tr>
211     <c:if test="${showCustomClonePath}">
212       <tr class="advancedSetting">
213         <th><label for="path">Custom clone directory on server:<bs:help file="Git" anchor="customCloneDir"/></label></th>
214         <td><props:textProperty name="path" className="longField"/>
215           <div class="smallNote" style="margin: 0;">
216             A directory on the TeamCity server where a bare cloned repository is to be created. Leave blank to use the default path.
217           </div>
218         </td>
219       </tr>
220     </c:if>
221   </l:settingsGroup>
222   <l:settingsGroup title="Agent Settings" className="advancedSetting">
223     <tr class="advancedSetting">
224       <td colspan="2">Agent-specific settings that are used in case of agent checkout.<bs:help file="Git" anchor="agentSettings"/></td>
225     </tr>
226     <tr class="advancedSetting">
227       <th><label for="agentGitPath">Path to Git: </label></th>
228       <td><props:textProperty name="agentGitPath" className="longField"/>
229         <div class="smallNote" style="margin: 0;">
230           The path to a git executable on the agent. If blank, the location set up in ${gitPathEnv} environment variable is used.
231         </div>
232       </td>
233     </tr>
234     <tr class="advancedSetting">
235       <th><label for="agentCleanPolicy">Clean policy:</label></th>
236       <td><props:selectProperty name="agentCleanPolicy" enableFilter="true" className="mediumField">
237         <props:option value="ON_BRANCH_CHANGE">On Branch Change</props:option>
238         <props:option value="ALWAYS">Always</props:option>
239         <props:option value="NEVER">Never</props:option>
240       </props:selectProperty>
241         <div class="smallNote" style="margin: 0">
242           This option specifies when the "git clean" command is run on the agent.
243         </div>
244       </td>
245     </tr>
246     <tr class="advancedSetting">
247       <th><label for="agentCleanFilesPolicy">Clean files policy:</label></th>
248       <td><props:selectProperty name="agentCleanFilesPolicy" enableFilter="true" className="mediumField">
249         <props:option value="ALL_UNTRACKED">All untracked files</props:option>
250         <props:option value="IGNORED_ONLY">All ignored untracked files</props:option>
251         <props:option value="NON_IGNORED_ONLY">All non-ignored untracked files</props:option>
252       </props:selectProperty>
253         <div class="smallNote" style="margin: 0">This option specifies which files will be removed when "git
254           clean" command is run on agent.
255         </div>
256       </td>
257     </tr>
258     <tr class="advancedSetting">
259       <th><label for="useAlternates">Use mirrors:</label></th>
260       <td>
261         <props:checkboxProperty name="useAlternates"/>
262         <div class="smallNote" style="margin: 0" >
263           When this option is enabled, TeamCity creates a separate clone of the repository on each agent
264           and uses it in the checkout directory via git alternates.
265         </div>
266       </td>
267     </tr>
268   </l:settingsGroup>
269 </table>
270 <script type="text/javascript">
271   var Git = {
272     compatibleAuthMethods: {//protocol -> compatible auth methods
273       'git' : ['ANONYMOUS'],
274       'http' : ['ANONYMOUS', 'PASSWORD'],
275       'https' : ['ANONYMOUS', 'PASSWORD']
276     },
277
278
279     getProtocol: function($element) {
280       var url = $element.val();
281       if (url) {
282         if (url.startsWith('git://')) {
283           return 'git';
284         } else if (url.startsWith('http://')) {
285           return 'http';
286         } else if (url.startsWith('https://')) {
287           return 'https';
288         } else {
289           return 'other';
290         }
291       } else {
292         return null;
293       }
294     },
295
296
297     getAuthMethodName: function(authMethod) {
298       return $j("#authMethod option[value=" + authMethod + "]").text();
299     },
300
301
302     getCompatibleMethods: function(proto) {
303       if (!proto) {
304         return this.getAllAuthMethods();
305       } else {
306         var compat = this.compatibleAuthMethods[proto];
307         if (compat) {
308           return compat;
309         } else {
310           //no constraints for given protocol
311           return this.getAllAuthMethods();
312         }
313       }
314     },
315
316
317     getAllAuthMethods: function() {
318       var result = [];
319       $j('#authMethod option').each(function() {
320         result.push($j(this).val());
321       });
322       return result;
323     },
324
325
326     contains: function (arr, elem) {
327       return arr.indexOf(elem) != -1;
328     },
329
330
331     disableIncompatible: function (fetchCompatible, pushCompatible, selected) {
332       var that = this;
333       $j('#authMethod option').each(function() {
334         var method = $j(this).val();
335         if (method == selected || that.contains(fetchCompatible, method) && that.contains(pushCompatible, method)) {
336           $j(this).attr("disabled", false);
337         } else {
338           $j(this).attr("disabled", "disabled");
339         }
340       });
341     },
342
343
344     getLimitingProtocols: function (fetchProto, fetchCompatMethods, pushProto, pushCompatMethods) {
345       var allMethods = this.getAllAuthMethods();
346       var limitingProtocols = [];
347       if (fetchCompatMethods.length < allMethods.length) {
348         limitingProtocols.push("'" + fetchProto + "'");
349       }
350       if (pushCompatMethods.length < allMethods.length && pushProto != fetchProto) {
351         limitingProtocols.push("'" + pushProto + "'");
352       }
353       var size = limitingProtocols.length;
354       if (size == 0) {
355         return null;
356       } else {
357         return (size == 1 ? "the " : "") + limitingProtocols.join(" and ") + " protocol" + (size > 1 ? "s" : "");
358       }
359     },
360
361
362     applyAuthConstraints: function () {
363       var selectedAuthMethod = $j('#authMethod').val();
364       var authMethodName = this.getAuthMethodName(selectedAuthMethod);
365       var fetchProto = this.getProtocol($j('#url'));
366       var pushProto = this.getProtocol($j('#push_url'));
367
368       var fetchCompatMethods = this.getCompatibleMethods(fetchProto);
369       var pushCompatMethods = this.getCompatibleMethods(pushProto);
370       var fetchCompatible = true;
371       var pushCompatible = true;
372       if (this.contains(fetchCompatMethods, selectedAuthMethod)) {
373         $j('#fetchUrlCompatNote').hide();
374       } else {
375         fetchCompatible = false;
376         $j('#fetchUrlCompatNote')
377             .text("The '" + authMethodName + "' authentication method is incompatible with the '" + fetchProto + "' protocol")
378             .show();
379       }
380
381       if (this.contains(pushCompatMethods, selectedAuthMethod)) {
382         $j('#pushUrlCompatNote').hide();
383       } else {
384         pushCompatible = false;
385         $j('#pushUrlCompatNote')
386             .text("The '" + authMethodName + "' authentication method is incompatible with the '" + pushProto + "' protocol")
387             .show();
388       }
389
390       this.disableIncompatible(fetchCompatMethods, pushCompatMethods, selectedAuthMethod);
391
392       if (!fetchCompatible || !pushCompatible) {
393         //incompatible method selected, show error
394         var protocol = !fetchCompatible ? fetchProto : pushProto;
395         var usage = !fetchCompatible ? "Fetch URL" : "Push URL";
396         $j('#authMethodCompatNote')
397             .text("Selected authentication method is incompatible with the '" + protocol + "' protocol (" + usage + ")")
398             .addClass('error')
399             .show();
400       } else {
401         var limitingProtocols = this.getLimitingProtocols(fetchProto, fetchCompatMethods, pushProto, pushCompatMethods);
402         if (limitingProtocols) {
403           //there are incompatible methods, show note
404           $j('#authMethodCompatNote').text("Authentication methods incompatible with " + limitingProtocols + " are disabled")
405               .removeClass('error')
406               .show();
407         } else {
408           //all methods are compatible, hide note
409           $j('#authMethodCompatNote').hide();
410         }
411       }
412
413       //refresh ufd
414       $j("#authMethod").ufd("changeOptions");
415     }
416   };
417
418   $j('#url').keyup(function() {Git.applyAuthConstraints();});
419   $j('#push_url').keyup(function() {Git.applyAuthConstraints();});
420
421   gitSelectAuthentication = function(resetHiddenFields) {
422     BS.Util.toggleDependentElements($('authMethod').value, 'auth', resetHiddenFields, {
423       PRIVATE_KEY_DEFAULT : 'defaultKey',
424       PRIVATE_KEY_FILE : 'customKey',
425       PASSWORD : 'password',
426       ANONYMOUS : 'anonymous',
427       TEAMCITY_SSH_KEY : 'uploadedKey'
428     });
429     Git.applyAuthConstraints();
430     BS.VisibilityHandlers.updateVisibility($('vcsRootProperties'));
431   };
432   gitSelectAuthentication(false);
433
434   illustrateUsernameStyle = function() {
435     var style = $j("#usernameStyle").val();
436     $j("#gitConfigUserName").removeClass("gitUsernameStyleHighlight");
437     $j("#gitConfigUserId").removeClass("gitUsernameStyleHighlight");
438     $j("#gitConfigEmail").removeClass("gitUsernameStyleHighlight");
439     if (style === "USERID") {
440       $j("#gitConfigUserId").addClass("gitUsernameStyleHighlight");
441       $j("#usernameToEnter").text("joe.coder");
442     } else if (style === "NAME") {
443       $j("#gitConfigUserName").addClass("gitUsernameStyleHighlight");
444       $j("#usernameToEnter").text("Joe Coder");
445     } else if (style === "FULL") {
446       $j("#gitConfigUserName").addClass("gitUsernameStyleHighlight");
447       $j("#gitConfigUserId").addClass("gitUsernameStyleHighlight");
448       $j("#gitConfigEmail").addClass("gitUsernameStyleHighlight");
449       $j("#usernameToEnter").text("Joe Coder <joe.coder@acme.com>");
450     } else if (style === "EMAIL") {
451       $j("#gitConfigUserId").addClass("gitUsernameStyleHighlight");
452       $j("#gitConfigEmail").addClass("gitUsernameStyleHighlight");
453       $j("#usernameToEnter").text("joe.coder@acme.com");
454     }
455   };
456
457   illustrateUsernameStyle();
458
459   $j("#usernameStyle").change(illustrateUsernameStyle);
460
461   $j(document).ready(function() {
462     if (BS.Repositories != null) {
463       BS.Repositories.installControls($('url'), function (repoInfo, cre) {
464         $('url').value = repoInfo.repositoryUrl;
465         if (cre != null) {
466           $('authMethod').value = 'PASSWORD';
467           BS.jQueryDropdown($('authMethod')).ufd("changeOptions");
468           $('username').value = cre.oauthLogin;
469           gitSelectAuthentication(true);
470         }
471       });
472     } else {
473       $j('.listRepositoriesControls').hide();
474     }
475   });
476 </script>