changeset 1029:9123ad25b082

Trying to solve HG problem with multiple heads
author nikolai.kulakov@DESKTOP-Q4QCGIH
date Thu, 06 Aug 2020 12:15:36 +0300
parents 0699b46a3d2a (diff) 62fe3e69cee6 (current diff)
children 1c65ee703a92
files mercurial-server/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsSupport.java
diffstat 999 files changed, 143422 insertions(+), 8468 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Fri Sep 24 19:45:30 2010 +0400
+++ b/.hgignore	Thu Aug 06 12:15:36 2020 +0300
@@ -2,3 +2,8 @@
 test-output
 .iws
 dist
+mercurial.properties
+syntax: glob
+.idea/workspace.xml
+out
+.DS_Store
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgtags	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,10 @@
+9e60b6d1e5fd8ed9a0c4b9582facc727a16bdcdb build-snapshot-124
+b454327bcb66c35f17ae171a794d540768fc32b4 build-snapshot-125
+0b54319af42cd1e74c1a658d61ecf1c3e6fc91a1 build-snapshot-126
+3a41cf31c90e299237fb1258f9ab363e7e0f9454 build-snapshot-127
+9c29eab7a27c80045948efb1cc094b96b4d82ea5 build-snapshot-128
+45a4b3169272f8d21ff802b25a28e16b46177d26 build-snapshot-129
+ff587c27a2d07607242cc02919c3db8cd4808ca2 build-snapshot-130
+9a52e7a891ce7bbb0f5df0f94483f1636359e73d build-snapshot-131
+df6ed5ed6c105ba2c65a3df1e3dc556f7882681b build-snapshot-133
+9998e944a4f2b19d10621d06c230144c5a68204b build-snapshot-134
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/ant.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="AntConfiguration">
+    <buildFile url="file://$PROJECT_DIR$/build/ant.build.xml" />
+  </component>
+</project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/artifacts/mercurial_agent_jar.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,14 @@
+<component name="ArtifactManager">
+  <artifact type="jar" name="mercurial-agent.jar">
+    <output-path>$PROJECT_DIR$/out/artifacts/mercurial_agent_jar</output-path>
+    <properties id="ant-postprocessing">
+      <options enabled="true">
+        <file>file://$PROJECT_DIR$/build/ant.build.xml</file>
+        <target>replace-tokens-in-teamcity-plugin-xml</target>
+      </options>
+    </properties>
+    <root id="archive" name="mercurial-agent.jar">
+      <element id="module-output" name="mercurial-agent" />
+    </root>
+  </artifact>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/artifacts/mercurial_common_jar.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,11 @@
+<component name="ArtifactManager">
+  <artifact type="jar" name="mercurial-common.jar">
+    <output-path>$PROJECT_DIR$/out/artifacts/mercurial_common_jar</output-path>
+    <root id="archive" name="mercurial-common.jar">
+      <element id="module-output" name="mercurial-common" />
+      <element id="directory" name="python">
+        <element id="dir-copy" path="$PROJECT_DIR$/mercurial-common/src/python" />
+      </element>
+    </root>
+  </artifact>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/artifacts/mercurial_server_jar.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,8 @@
+<component name="ArtifactManager">
+  <artifact type="jar" name="mercurial-server.jar">
+    <output-path>$PROJECT_DIR$/out/artifacts/mercurial_server_jar</output-path>
+    <root id="archive" name="mercurial-server.jar">
+      <element id="module-output" name="mercurial-server" />
+    </root>
+  </artifact>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/artifacts/mercurial_server_tc_jar.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,8 @@
+<component name="ArtifactManager">
+  <artifact type="jar" name="mercurial-server-tc.jar">
+    <output-path>$PROJECT_DIR$/out/artifacts/mercurial_server_tc_jar</output-path>
+    <root id="archive" name="mercurial-server-tc.jar">
+      <element id="module-output" name="mercurial-server-tc" />
+    </root>
+  </artifact>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/artifacts/mercurial_vcs_worker.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,21 @@
+<component name="ArtifactManager">
+  <artifact name="mercurial-vcs-worker">
+    <output-path>$PROJECT_DIR$/out/artifacts/mercurial_vcs_worker</output-path>
+    <properties id="ant-postprocessing">
+      <options enabled="true">
+        <file>file://$PROJECT_DIR$/build/ant.build.xml</file>
+        <target>replace-tokens-in-teamcity-plugin-xml</target>
+      </options>
+    </properties>
+    <root id="root">
+      <element id="directory" name="lib">
+        <element id="artifact" artifact-name="mercurial-common.jar" />
+        <element id="artifact" artifact-name="mercurial-server.jar" />
+        <element id="library" level="project" name="quartz-1.6.0" />
+        <element id="library" level="project" name="commons-compress-1.5" />
+        <element id="library" level="project" name="commons-codec-1.4" />
+      </element>
+      <element id="file-copy" path="$PROJECT_DIR$/teamcity-plugin.xml" />
+    </root>
+  </artifact>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/artifacts/plugin.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,42 @@
+<component name="ArtifactManager">
+  <artifact name="plugin">
+    <output-path>$PROJECT_DIR$/out/artifacts/plugin</output-path>
+    <properties id="ant-postprocessing">
+      <options enabled="true">
+        <file>file://$PROJECT_DIR$/build/ant.build.xml</file>
+        <target>replace-tokens-in-teamcity-plugin-xml</target>
+      </options>
+    </properties>
+    <root id="root">
+      <element id="directory" name="server">
+        <element id="artifact" artifact-name="mercurial-server.jar" />
+        <element id="artifact" artifact-name="mercurial-common.jar" />
+        <element id="artifact" artifact-name="mercurial-server-tc.jar" />
+        <element id="library" level="project" name="quartz-1.6.0" />
+        <element id="library" level="project" name="commons-compress-1.5" />
+        <element id="library" level="project" name="commons-codec-1.4" />
+      </element>
+      <element id="directory" name="agent">
+        <element id="archive" name="mercurial.zip">
+          <element id="directory" name="mercurial">
+            <element id="directory" name="lib">
+              <element id="artifact" artifact-name="mercurial-common.jar" />
+              <element id="artifact" artifact-name="mercurial-agent.jar" />
+              <element id="library" level="project" name="commons-codec-1.4" />
+              <element id="library" level="project" name="commons-compress-1.5" />
+            </element>
+          </element>
+        </element>
+      </element>
+      <element id="file-copy" path="$PROJECT_DIR$/teamcity-plugin.xml" />
+      <element id="directory" name="vcs-worker">
+        <element id="archive" name="mercurial.zip">
+          <element id="artifact" artifact-name="mercurial-vcs-worker" />
+        </element>
+      </element>
+      <element id="directory" name="kotlin-dsl">
+        <element id="file-copy" path="$PROJECT_DIR$/mercurial-dsl/HgVcsRoot.xml" />
+      </element>
+    </root>
+  </artifact>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/artifacts/zip.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,10 @@
+<component name="ArtifactManager">
+  <artifact name="zip">
+    <output-path>$PROJECT_DIR$/dist</output-path>
+    <root id="root">
+      <element id="archive" name="mercurial.zip">
+        <element id="artifact" artifact-name="plugin" />
+      </element>
+    </root>
+  </artifact>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/codeStyleSettings.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectCodeStyleSettingsManager">
+    <option name="PER_PROJECT_SETTINGS">
+      <value>
+        <option name="USE_SAME_INDENTS" value="true" />
+        <option name="IGNORE_SAME_INDENTS_FOR_LANGUAGES" value="true" />
+        <option name="OTHER_INDENT_OPTIONS">
+          <value>
+            <option name="INDENT_SIZE" value="2" />
+            <option name="CONTINUATION_INDENT_SIZE" value="8" />
+            <option name="TAB_SIZE" value="2" />
+            <option name="USE_TAB_CHARACTER" value="false" />
+            <option name="SMART_TABS" value="false" />
+            <option name="LABEL_INDENT_SIZE" value="0" />
+            <option name="LABEL_INDENT_ABSOLUTE" value="false" />
+            <option name="USE_RELATIVE_INDENTS" value="false" />
+          </value>
+        </option>
+        <option name="LINE_SEPARATOR" value="&#13;&#10;" />
+        <option name="FIELD_NAME_PREFIX" value="my" />
+        <option name="STATIC_FIELD_NAME_PREFIX" value="our" />
+        <option name="RIGHT_MARGIN" value="140" />
+        <XML>
+          <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
+        </XML>
+        <codeStyleSettings language="CSS">
+          <indentOptions>
+            <option name="INDENT_SIZE" value="2" />
+            <option name="TAB_SIZE" value="2" />
+          </indentOptions>
+        </codeStyleSettings>
+        <codeStyleSettings language="CoffeeScript">
+          <indentOptions>
+            <option name="CONTINUATION_INDENT_SIZE" value="8" />
+          </indentOptions>
+        </codeStyleSettings>
+        <codeStyleSettings language="Groovy">
+          <indentOptions>
+            <option name="INDENT_SIZE" value="2" />
+            <option name="TAB_SIZE" value="2" />
+          </indentOptions>
+        </codeStyleSettings>
+        <codeStyleSettings language="JAVA">
+          <indentOptions>
+            <option name="INDENT_SIZE" value="2" />
+            <option name="TAB_SIZE" value="2" />
+          </indentOptions>
+        </codeStyleSettings>
+        <codeStyleSettings language="JSP">
+          <indentOptions>
+            <option name="INDENT_SIZE" value="2" />
+            <option name="TAB_SIZE" value="2" />
+          </indentOptions>
+        </codeStyleSettings>
+        <codeStyleSettings language="JavaScript">
+          <indentOptions>
+            <option name="INDENT_SIZE" value="2" />
+            <option name="CONTINUATION_INDENT_SIZE" value="8" />
+            <option name="TAB_SIZE" value="2" />
+          </indentOptions>
+        </codeStyleSettings>
+        <codeStyleSettings language="XML">
+          <indentOptions>
+            <option name="INDENT_SIZE" value="2" />
+            <option name="TAB_SIZE" value="2" />
+          </indentOptions>
+        </codeStyleSettings>
+        <codeStyleSettings language="jet">
+          <indentOptions>
+            <option name="INDENT_SIZE" value="2" />
+            <option name="TAB_SIZE" value="2" />
+          </indentOptions>
+        </codeStyleSettings>
+        <codeStyleSettings language="yaml">
+          <indentOptions>
+            <option name="TAB_SIZE" value="2" />
+          </indentOptions>
+        </codeStyleSettings>
+      </value>
+    </option>
+    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+  </component>
+</project>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/compiler.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <option name="DEFAULT_COMPILER" value="Javac" />
+    <resourceExtensions>
+      <entry name=".+\.(properties|xml|html|dtd|tld)" />
+      <entry name=".+\.(gif|png|jpeg|jpg)" />
+    </resourceExtensions>
+    <wildcardResourcePatterns>
+      <entry name="?*.properties" />
+      <entry name="?*.xml" />
+      <entry name="?*.gif" />
+      <entry name="?*.png" />
+      <entry name="?*.jpeg" />
+      <entry name="?*.jpg" />
+      <entry name="?*.html" />
+      <entry name="?*.dtd" />
+      <entry name="?*.tld" />
+      <entry name="?*.jsp" />
+      <entry name="?*.tag" />
+      <entry name="?*.template" />
+      <entry name="do-not-load-in-vcs-mode" />
+      <entry name="?*.py" />
+    </wildcardResourcePatterns>
+    <annotationProcessing>
+      <profile default="true" name="Default" enabled="false">
+        <processorPath useClasspath="true" />
+      </profile>
+    </annotationProcessing>
+  </component>
+</project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/copyright/open_source.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,9 @@
+<component name="CopyrightManager">
+  <copyright>
+    <option name="notice" value="Copyright 2000-&amp;#36;today.year JetBrains s.r.o.&#10;&#10;Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10;you may not use this file except in compliance with the License.&#10;You may obtain a copy of the License at&#10;&#10;http://www.apache.org/licenses/LICENSE-2.0&#10;&#10;Unless required by applicable law or agreed to in writing, software&#10;distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10;WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10;See the License for the specific language governing permissions and&#10;limitations under the License." />
+    <option name="keyword" value="Copyright" />
+    <option name="allowReplaceKeyword" value="" />
+    <option name="myName" value="open-source" />
+    <option name="myLocal" value="true" />
+  </copyright>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/copyright/profiles_settings.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,7 @@
+<component name="CopyrightManager">
+  <settings default="">
+    <module2copyright>
+      <element module="All" copyright="open-source" />
+    </module2copyright>
+  </settings>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/encodings.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false">
+    <file url="PROJECT" charset="UTF-8" />
+  </component>
+</project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/inspectionProfiles/Project_Default.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,11 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0" is_locked="false">
+    <option name="myName" value="Project Default" />
+    <option name="myLocal" value="false" />
+    <inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
+      <option name="processCode" value="true" />
+      <option name="processLiterals" value="true" />
+      <option name="processComments" value="true" />
+    </inspection_tool>
+  </profile>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/inspectionProfiles/profiles_settings.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,15 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="PROJECT_PROFILE" value="Project Default" />
+    <option name="USE_PROJECT_PROFILE" value="true" />
+    <version value="1.0" />
+    <list size="6">
+      <item index="0" class="java.lang.String" itemvalue="TYPO" />
+      <item index="1" class="java.lang.String" itemvalue="WEAK WARNING" />
+      <item index="2" class="java.lang.String" itemvalue="INFO" />
+      <item index="3" class="java.lang.String" itemvalue="WARNING" />
+      <item index="4" class="java.lang.String" itemvalue="ERROR" />
+      <item index="5" class="java.lang.String" itemvalue="SERVER PROBLEM" />
+    </list>
+  </settings>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/libraries/IDEA_openapi.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,12 @@
+<component name="libraryTable">
+  <library name="IDEA-openapi">
+    <CLASSES>
+      <root url="jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/resources_en.jar!/" />
+      <root url="jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/util.jar!/" />
+      <root url="jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/openapi.jar!/" />
+      <root url="jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/annotations.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES />
+  </library>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/libraries/JMock.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,15 @@
+<component name="libraryTable">
+  <library name="JMock">
+    <CLASSES>
+      <root url="jar://$PROJECT_DIR$/mercurial-tests/lib/hamcrest-library-1.1.jar!/" />
+      <root url="jar://$PROJECT_DIR$/mercurial-tests/lib/hamcrest-core-1.1.jar!/" />
+      <root url="jar://$PROJECT_DIR$/mercurial-tests/lib/jmock-2.5.1.jar!/" />
+      <root url="jar://$PROJECT_DIR$/mercurial-tests/lib/jmock-SNAPSHOT.jar!/" />
+      <root url="jar://$PROJECT_DIR$/mercurial-tests/lib/hamcrest-integration-1.1.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES>
+      <root url="jar://$PROJECT_DIR$/mercurial-tests/lib/hamcrest-integration-1.1.jar!/" />
+    </SOURCES>
+  </library>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/libraries/JUnit.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,9 @@
+<component name="libraryTable">
+  <library name="JUnit">
+    <CLASSES>
+      <root url="jar://$PROJECT_DIR$/mercurial-tests/lib/junit-3.8.1.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES />
+  </library>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/libraries/Log4j.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,9 @@
+<component name="libraryTable">
+  <library name="Log4j">
+    <CLASSES>
+      <root url="jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/log4j-1.2.12.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES />
+  </library>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/libraries/TeamCityAPI_agent.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,12 @@
+<component name="libraryTable">
+  <library name="TeamCityAPI-agent">
+    <CLASSES>
+      <root url="jar://$TeamCityDistribution$/devPackage/agent-api.jar!/" />
+      <root url="jar://$TeamCityDistribution$/buildAgent/lib/agent.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES>
+      <root url="jar://$TeamCityDistribution$/devPackage/src/openApi-source.jar!/" />
+    </SOURCES>
+  </library>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/libraries/TeamCityAPI_common.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,14 @@
+<component name="libraryTable">
+  <library name="TeamCityAPI-common">
+    <CLASSES>
+      <root url="jar://$TeamCityDistribution$/devPackage/common-api.jar!/" />
+      <root url="jar://$TeamCityDistribution$/devPackage/serviceMessages.jar!/" />
+      <root url="jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/commons-httpclient-3.1.jar!/" />
+      <root url="jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/commons-logging.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES>
+      <root url="jar://$TeamCityDistribution$/devPackage/src/openApi-source.jar!/" />
+    </SOURCES>
+  </library>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/libraries/TeamCityAPI_server.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,12 @@
+<component name="libraryTable">
+  <library name="TeamCityAPI-server">
+    <CLASSES>
+      <root url="jar://$TeamCityDistribution$/devPackage/server-api.jar!/" />
+      <root url="jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/server.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES>
+      <root url="jar://$TeamCityDistribution$/devPackage/src/openApi-source.jar!/" />
+    </SOURCES>
+  </library>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/libraries/TeamCity_TestsAPI.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,10 @@
+<component name="libraryTable">
+  <library name="TeamCity-TestsAPI">
+    <CLASSES>
+      <root url="jar://$TeamCityDistribution$/devPackage/tests/patches-test.jar!/" />
+      <root url="jar://$TeamCityDistribution$/devPackage/tests/tests-support.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES />
+  </library>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/libraries/TeamCity_Vcs_Api.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,12 @@
+<component name="libraryTable">
+  <library name="TeamCity Vcs Api">
+    <CLASSES>
+      <root url="jar://$TeamCityDistribution$/devPackage/server-common-api.jar!/" />
+      <root url="jar://$TeamCityDistribution$/devPackage/server-vcs-api.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES>
+      <root url="jar://$TeamCityDistribution$/devPackage/src/openApi-source.jar!/" />
+    </SOURCES>
+  </library>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/libraries/TeamCity_impl.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,11 @@
+<component name="libraryTable">
+  <library name="TeamCity-impl">
+    <CLASSES>
+      <root url="jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/patches-impl.jar!/" />
+      <root url="jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/trove4j.jar!/" />
+      <root url="jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/trove-3.0.3.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES />
+  </library>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/libraries/TestNG.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,9 @@
+<component name="libraryTable">
+  <library name="TestNG">
+    <CLASSES>
+      <root url="jar://$PROJECT_DIR$/mercurial-tests/lib/testng-6.8.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES />
+  </library>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/libraries/commons_codec_1_4.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,9 @@
+<component name="libraryTable">
+  <library name="commons-codec-1.4">
+    <CLASSES>
+      <root url="jar://$PROJECT_DIR$/lib/commons-codec-1.4.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES />
+  </library>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/libraries/commons_compress_1_5.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,9 @@
+<component name="libraryTable">
+  <library name="commons-compress-1.5">
+    <CLASSES>
+      <root url="jar://$PROJECT_DIR$/lib/commons-compress-1.5.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES />
+  </library>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/libraries/jdom.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,9 @@
+<component name="libraryTable">
+  <library name="jdom">
+    <CLASSES>
+      <root url="jar://$TeamCityDistribution$/webapps/ROOT/WEB-INF/lib/jdom.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES />
+  </library>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/libraries/quartz_1_6_0.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,9 @@
+<component name="libraryTable">
+  <library name="quartz-1.6.0">
+    <CLASSES>
+      <root url="jar://$PROJECT_DIR$/lib/quartz-1.6.0.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES />
+  </library>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/libraries/trove4j.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,9 @@
+<component name="libraryTable">
+  <library name="trove4j">
+    <CLASSES>
+      <root url="jar://$PROJECT_DIR$/lib/trove4j.jar!/" />
+    </CLASSES>
+    <JAVADOC />
+    <SOURCES />
+  </library>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/misc.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="EntryPointsManager">
+    <entry_points version="2.0" />
+  </component>
+  <component name="IdProvider" IDEtalkID="C52C76224CD45BEC1DC62428B699D800" />
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK" />
+</project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/modules.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/main.iml" filepath="$PROJECT_DIR$/main.iml" />
+      <module fileurl="file://$PROJECT_DIR$/mercurial-agent/mercurial-agent.iml" filepath="$PROJECT_DIR$/mercurial-agent/mercurial-agent.iml" />
+      <module fileurl="file://$PROJECT_DIR$/mercurial-common/mercurial-common.iml" filepath="$PROJECT_DIR$/mercurial-common/mercurial-common.iml" />
+      <module fileurl="file://$PROJECT_DIR$/mercurial-server/mercurial-server.iml" filepath="$PROJECT_DIR$/mercurial-server/mercurial-server.iml" />
+      <module fileurl="file://$PROJECT_DIR$/mercurial-server-tc/mercurial-server-tc.iml" filepath="$PROJECT_DIR$/mercurial-server-tc/mercurial-server-tc.iml" />
+      <module fileurl="file://$PROJECT_DIR$/mercurial-tests/mercurial-tests.iml" filepath="$PROJECT_DIR$/mercurial-tests/mercurial-tests.iml" />
+    </modules>
+  </component>
+</project>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/runConfigurations/tests.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,38 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="tests" type="TestNG" factoryName="TestNG">
+    <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
+    <module name="mercurial-tests" />
+    <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+    <option name="ALTERNATIVE_JRE_PATH" value="" />
+    <option name="SUITE_NAME" value="$PROJECT_DIR$/mercurial-tests/src/testng.xml" />
+    <option name="PACKAGE_NAME" value="" />
+    <option name="MAIN_CLASS_NAME" value="" />
+    <option name="METHOD_NAME" value="" />
+    <option name="GROUP_NAME" value="" />
+    <option name="TEST_OBJECT" value="SUITE" />
+    <option name="VM_PARAMETERS" value="-ea" />
+    <option name="PARAMETERS" value="" />
+    <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
+    <option name="OUTPUT_DIRECTORY" value="" />
+    <option name="ANNOTATION_TYPE" />
+    <option name="ENV_VARIABLES" />
+    <option name="PASS_PARENT_ENVS" value="true" />
+    <option name="TEST_SEARCH_SCOPE">
+      <value defaultName="moduleWithDependencies" />
+    </option>
+    <option name="USE_DEFAULT_REPORTERS" value="false" />
+    <option name="PROPERTIES_FILE" value="" />
+    <envs />
+    <properties />
+    <listeners />
+    <RunnerSettings RunnerId="Debug">
+      <option name="DEBUG_PORT" value="51735" />
+      <option name="TRANSPORT" value="0" />
+      <option name="LOCAL" value="true" />
+    </RunnerSettings>
+    <RunnerSettings RunnerId="Run" />
+    <ConfigurationWrapper RunnerId="Debug" />
+    <ConfigurationWrapper RunnerId="Run" />
+    <method />
+  </configuration>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/runConfigurations/tests_via_file.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,32 @@
+<component name="ProjectRunConfigurationManager">
+  <configuration default="false" name="tests via file" type="TestNG" factoryName="TestNG">
+    <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
+    <module name="mercurial-tests" />
+    <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+    <option name="ALTERNATIVE_JRE_PATH" value="" />
+    <option name="SUITE_NAME" value="$PROJECT_DIR$/mercurial-tests/src/testng-via-cmd.xml" />
+    <option name="PACKAGE_NAME" value="" />
+    <option name="MAIN_CLASS_NAME" value="" />
+    <option name="METHOD_NAME" value="" />
+    <option name="GROUP_NAME" value="" />
+    <option name="TEST_OBJECT" value="SUITE" />
+    <option name="VM_PARAMETERS" value="-ea -Dteamcity.mercurial.use.commandline.via.file.wrapper=true" />
+    <option name="PARAMETERS" value="" />
+    <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" />
+    <option name="OUTPUT_DIRECTORY" value="" />
+    <option name="ANNOTATION_TYPE" />
+    <option name="ENV_VARIABLES" />
+    <option name="PASS_PARENT_ENVS" value="true" />
+    <option name="TEST_SEARCH_SCOPE">
+      <value defaultName="moduleWithDependencies" />
+    </option>
+    <option name="USE_DEFAULT_REPORTERS" value="false" />
+    <option name="PROPERTIES_FILE" value="" />
+    <envs />
+    <properties />
+    <listeners />
+    <RunnerSettings RunnerId="Run" />
+    <ConfigurationWrapper RunnerId="Run" />
+    <method />
+  </configuration>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/scopes/scope_settings.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,5 @@
+<component name="DependencyValidationManager">
+  <state>
+    <option name="SKIP_IMPORT_STATEMENTS" value="false" />
+  </state>
+</component>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/uiDesigner.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Palette2">
+    <group name="Swing">
+      <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
+      </item>
+      <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
+      </item>
+      <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
+        <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
+        <initial-values>
+          <property name="text" value="Button" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="RadioButton" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="CheckBox" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="Label" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+          <preferred-size width="200" height="200" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+          <preferred-size width="200" height="200" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
+      </item>
+      <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
+          <preferred-size width="-1" height="20" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
+      </item>
+      <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
+      </item>
+    </group>
+  </component>
+</project>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.idea/vcs.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="hg4idea" />
+  </component>
+</project>
+
--- a/README.txt	Fri Sep 24 19:45:30 2010 +0400
+++ b/README.txt	Thu Aug 06 12:15:36 2020 +0300
@@ -3,6 +3,8 @@
 Installation
 =================================
 
-TeamCity 4.x/5.x
+TeamCity 4.x and later
 
 Download mercurial-server.zip and put it into the .BuildServer/plugins folder. Restart server.
+
+
--- a/build.xml	Fri Sep 24 19:45:30 2010 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,57 +0,0 @@
-<project name="Mercurial VCS Support" default="dist" basedir=".">
-  <property file="mercurial.properties"/>
-  <import file="mercurial.xml"/>
-
-  <property name="distPath" value="${basedir}/dist"/>
-
-  <property name="plugin.name" value="mercurial"/>
-
-  <import file="teamcity-common.xml"/>
-
-  <target name="package" depends="define.version">
-    <package.teamcity.plugin name="${plugin.name}"
-                             server.output="${mercurial-server.output.dir}"
-                             agent.output="${mercurial-agent.output.dir}"
-                             common.output="${mercurial-common.output.dir}"
-                             plugin.descriptor.file="${basedir}/teamcity-plugin.xml"
-                             plugin.version="${plugin.version}"/>
-  </target>
-
-  <target name="define.version" depends="define.version.if.under.teamcity">
-    <tstamp>
-      <format property="current.time" pattern="yyyyMMddHHmm"/>
-    </tstamp>
-    <property name="plugin.version" value="SNAPSHOT-${current.time}"/>
-  </target>
-
-  <target name="define.version.if.under.teamcity" if="build.number">
-    <property name="plugin.version" value="${build.number}"/>
-  </target>
-
-  <target name="dist" depends="check.teamcitydistribution,all,package"/>
-
-  <taskdef name="testng" classname="org.testng.TestNGAntTask" classpath="${basedir}/mercurial-tests/lib/testng-5.7-jdk15.jar"/>
-
-  <path id="tests_classpath">
-    <pathelement location="${agent.home.dir}/lib/runtime-util.jar"/>
-    <pathelement location="${agent.home.dir}/lib/buildServerRuntimeUtil.jar"/>
-    <path refid="mercurial-tests.runtime.module.classpath"/>
-  </path>
-
-  <target name="run-tests" depends="clean, init, compile.module.mercurial-tests">
-    <property name="suspend" value="n"/>
-
-    <testng haltonfailure="no" failureProperty="failure_found" listener="org.testng.reporters.TestHTMLReporter"
-            outputdir="${basedir}/test-output" classpathref="tests_classpath" dumpcommand="true" workingDir="${basedir}">
-
-      <jvmarg value="-ea"/>
-      <!--<jvmarg value="-Xrunjdwp:transport=dt_socket,server=y,suspend=${suspend},address=5555"/>-->
-
-      <sysproperty key="java.awt.headless" value="true"/>
-
-      <xmlfileset dir="${basedir}/mercurial-tests/src">
-        <include name="testng.xml"/>
-      </xmlfileset>
-    </testng>
-  </target>
-</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/build/ant.build.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,47 @@
+<!--
+  ~ Copyright 2000-2018 JetBrains s.r.o.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<project name="build hooks" default="replace-tokens-in-teamcity-plugin-xml" basedir=".">
+
+  <target name="replace-tokens-in-teamcity-plugin-xml">
+    <property name="home" value="${artifact.output.path}"/>
+
+    <tstamp>
+      <format property="timestamp" pattern="yyyyMMddhhmmss"/>
+    </tstamp>
+
+    <property name="snapshot.build.number" value="SNAPSHOT-${timestamp}"/>
+
+    <condition property="plugin.version" value="${snapshot.build.number}" else="${build.number}">
+      <or>
+        <not>
+          <isset property="build.number"/>
+        </not>
+        <matches pattern="snapshot-.*" string="${build.number}" casesensitive="false"/>
+      </or>
+    </condition>
+
+    <echo message="replacing tokens in teamcity-plugin.xml file under ${home}"/>
+    <echo message="Setting version to: ${plugin.version}"/>
+
+    <!-- update all references in teamcity-plugin.xml files -->
+    <replace dir="${home}" summary="true">
+      <include name="**/teamcity-plugin.xml"/>
+      <replacefilter token="@version@" value="${plugin.version}"/>
+    </replace>
+  </target>
+
+</project>
Binary file lib/commons-codec-1.4.jar has changed
Binary file lib/commons-compress-1.5.jar has changed
Binary file lib/quartz-1.6.0.jar has changed
Binary file lib/trove4j.jar has changed
--- a/mercurial-agent/mercurial-agent.iml	Fri Sep 24 19:45:30 2010 +0400
+++ b/mercurial-agent/mercurial-agent.iml	Thu Aug 06 12:15:36 2020 +0300
@@ -5,16 +5,15 @@
       <configuration />
     </facet>
   </component>
-  <component name="NewModuleRootManager" inherit-compiler-output="false">
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="false">
     <output url="file://$MODULE_DIR$/classes" />
     <exclude-output />
     <content url="file://$MODULE_DIR$">
       <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
     </content>
-    <orderEntry type="inheritedJdk" />
+    <orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
     <orderEntry type="sourceFolder" forTests="false" />
     <orderEntry type="library" name="TeamCityAPI-agent" level="project" />
     <orderEntry type="module" module-name="mercurial-common" />
   </component>
-</module>
-
+</module>
\ No newline at end of file
--- a/mercurial-agent/src/META-INF/build-agent-plugin-mercurial.xml	Fri Sep 24 19:45:30 2010 +0400
+++ b/mercurial-agent/src/META-INF/build-agent-plugin-mercurial.xml	Thu Aug 06 12:15:36 2020 +0300
@@ -1,6 +1,36 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
-
-<beans default-autowire="constructor">
-  <bean id="mercurialAgent" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialAgentSideVcsSupport" />
-</beans>
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2000-2018 JetBrains s.r.o.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
+       default-autowire="constructor">
+  <bean id="mercurialAgent" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialAgentSideVcsSupport" />
+  <bean id="hgPathProvider" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentHgPathProvider" />
+  <bean id="hgDetector" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.HgDetector" />
+  <bean id="pluginConfig" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentPluginConfigImpl"/>
+  <bean id="mirrorManager" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MirrorManagerImpl" />
+  <bean id="mirrorCleaner" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentMirrorCleaner" />
+  <bean id="commandSettingsFactory" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentCommandSettingsFactory" />
+  <bean id="agentRepoFactory" class="jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentRepoFactory"/>
+
+  <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsForRootImpl"/>
+  <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.ExtensionsWeaver"/>
+  <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandlineViaFileWrapperWeaver"/>
+
+  <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.MercurialExtensionManager"/>
+  <bean class="jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.impl.SparseCheckoutProvider"/>
+</beans>
--- a/mercurial-agent/src/build-agent-plugin.xml	Fri Sep 24 19:45:30 2010 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-<!--
-PicoContainer configuration for old fashioned TeamCity agent plugins.
-In TeamCity 4.0 Spring configuration must be created instead.
--->
-<container>
-  <component class="jetbrains.buildServer.buildTriggers.vcs.mercurial.MercurialAgentSideVcsSupport" />
-</container>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentCommandSettingsFactory.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettings;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsFactory;
+import org.jetbrains.annotations.NotNull;
+
+public class AgentCommandSettingsFactory implements CommandSettingsFactory {
+
+  @NotNull
+  public CommandSettings create() {
+    return new CommandSettings().setLogLevel("info");
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentHgPathProvider.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.agent.BuildAgentConfiguration;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.parameters.ProcessingResult;
+import jetbrains.buildServer.parameters.ValueResolver;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author dmitry.neverov
+ */
+public class AgentHgPathProvider implements HgPathProvider {
+
+  private final ValueResolver myResolver;
+
+
+  public AgentHgPathProvider(@NotNull final BuildAgentConfiguration agentConfig) {
+    myResolver = agentConfig.getParametersResolver();
+  }
+
+
+  public String getHgPath(@NotNull final HgVcsRoot root) {
+    String pathFromRoot = root.getHgPath();
+    return resolve(pathFromRoot);
+  }
+
+
+  private String resolve(@NotNull final String value) {
+    ProcessingResult result = myResolver.resolve(value);
+    return result.getResult();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentMirrorCleaner.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import com.intellij.openapi.diagnostic.Logger;
+import jetbrains.buildServer.agent.DirectoryCleanersProvider;
+import jetbrains.buildServer.agent.DirectoryCleanersProviderContext;
+import jetbrains.buildServer.agent.DirectoryCleanersRegistry;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.AuthSettings;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.vcs.VcsRoot;
+import jetbrains.buildServer.vcs.VcsRootEntry;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author dmitry.neverov
+ */
+public class AgentMirrorCleaner implements DirectoryCleanersProvider {
+
+  private final static Logger ourLog = Logger.getInstance(AgentMirrorCleaner.class.getName());
+  private final MirrorManager myMirrorManager;
+
+  public AgentMirrorCleaner(@NotNull final MirrorManager mirrorManager) {
+    myMirrorManager = mirrorManager;
+  }
+
+  @NotNull
+  public String getCleanerName() {
+    return "Mercurial mirrors cleaner";
+  }
+
+  public void registerDirectoryCleaners(@NotNull DirectoryCleanersProviderContext context,
+                                        @NotNull DirectoryCleanersRegistry registry) {
+    Set<String> repositoriesUsedInBuild = getRunningBuildRepositories(context);
+    for (Map.Entry<String, File> entry : myMirrorManager.getMappings().entrySet()) {
+      String repository = entry.getKey();
+      File mirror = entry.getValue();
+      if (!repositoriesUsedInBuild.contains(repository)) {
+        ourLog.debug("Register cleaner for mirror " + mirror.getAbsolutePath());
+        registry.addCleaner(mirror, new Date(myMirrorManager.getLastUsedTime(mirror)));
+      }
+    }
+  }
+
+  private Set<String> getRunningBuildRepositories(@NotNull DirectoryCleanersProviderContext context) {
+    Set<String> repositories = new HashSet<>();
+    for (VcsRootEntry entry : context.getRunningBuild().getVcsRootEntries()) {
+      VcsRoot root = entry.getVcsRoot();
+      if (!isHgRoot(root))
+        continue;
+      HgVcsRoot hgRoot = new HgVcsRoot(root);
+      AuthSettings auth = hgRoot.getAuthSettings();
+      ourLog.debug("Repository " + auth.getRepositoryUrlWithHiddenPassword(hgRoot.getRepository()) +
+              " is used in the build, its mirror won't be cleaned");
+      repositories.add(hgRoot.getRepository());
+    }
+    return repositories;
+  }
+
+  private boolean isHgRoot(@NotNull VcsRoot root) {
+    return Constants.VCS_NAME.equals(root.getVcsName());
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentPluginConfig.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.agent.AgentRunningBuild;
+import jetbrains.buildServer.vcs.VcsRoot;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+/**
+ * @author dmitry.neverov
+ */
+public interface AgentPluginConfig extends PluginConfig {
+
+  boolean isUseLocalMirrors(@NotNull AgentRunningBuild build, @NotNull VcsRoot root);
+
+  boolean shareLocalMirrors(@NotNull AgentRunningBuild build, @NotNull VcsRoot root);
+
+  int getPullTimeout(@NotNull AgentRunningBuild build);
+
+  boolean runWithTraceback(@NotNull AgentRunningBuild build);
+
+  boolean runWithProfile(@NotNull  AgentRunningBuild build);
+
+  @NotNull
+  File getTempDir();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentPluginConfigImpl.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.agent.AgentRunningBuild;
+import jetbrains.buildServer.agent.BuildAgentConfiguration;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.util.StringUtil;
+import jetbrains.buildServer.vcs.VcsRoot;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+
+/**
+ * @author dmitry.neverov
+ */
+public class AgentPluginConfigImpl implements AgentPluginConfig {
+
+  private static final String PULL_TIMEOUT_SECONDS = "teamcity.hg.pull.timeout.seconds";
+  private static final int DEFAULT_PULL_TIMEOUT_SECONDS = 3600;
+  public static final String VCS_ROOT_MIRRORS_STRATEGY = "teamcity.hg.mirrorStrategy";
+  public static final String VCS_ROOT_MIRRORS_STRATEGY_SHARED_MIRRORS = "sharedMirrors";
+  public static final String VCS_ROOT_MIRRORS_STRATEGY_MIRRORS_ONLY = "mirrors";
+
+  private final BuildAgentConfiguration myAgentConfig;
+
+  public AgentPluginConfigImpl(@NotNull BuildAgentConfiguration agentConfig) {
+    myAgentConfig = agentConfig;
+  }
+
+  public boolean isUseLocalMirrors(@NotNull AgentRunningBuild build, @NotNull VcsRoot root) {
+    String buildValue = build.getSharedConfigParameters().get("teamcity.hg.use.local.mirrors");
+    if (!StringUtil.isEmpty(buildValue))
+      return Boolean.parseBoolean(buildValue);
+    HgVcsRoot hgRoot = new HgVcsRoot(root);
+    Boolean rootValue = hgRoot.getUseAgentMirrors();
+    String mirrorStrategy = getMirrorStrategy(build);
+    if (rootValue != null && rootValue && (VCS_ROOT_MIRRORS_STRATEGY_SHARED_MIRRORS.equals(mirrorStrategy) || VCS_ROOT_MIRRORS_STRATEGY_MIRRORS_ONLY.equals(mirrorStrategy)))
+      return true;
+    return false;
+  }
+
+  @SuppressWarnings("ConstantConditions")
+  public boolean shareLocalMirrors(@NotNull AgentRunningBuild build, @NotNull VcsRoot root) {
+    String buildValue = build.getSharedConfigParameters().get("teamcity.hg.shareLocalMirrors");
+    if (!StringUtil.isEmpty(buildValue))
+      return Boolean.parseBoolean(buildValue);
+    HgVcsRoot hgRoot = new HgVcsRoot(root);
+    Boolean rootValue = hgRoot.getUseAgentMirrors();
+    String mirrorStrategy = getMirrorStrategy(build);
+    if (rootValue != null && rootValue && VCS_ROOT_MIRRORS_STRATEGY_SHARED_MIRRORS.equals(mirrorStrategy))
+      return true;
+    return false;
+  }
+
+  @NotNull
+  private String getMirrorStrategy(@NotNull AgentRunningBuild build) {
+    String strategy = build.getSharedConfigParameters().get(VCS_ROOT_MIRRORS_STRATEGY);
+    if (!StringUtil.isEmpty(strategy))
+      return strategy;
+    return VCS_ROOT_MIRRORS_STRATEGY_SHARED_MIRRORS;
+  }
+
+  @NotNull
+  public File getCachesDir() {
+    return myAgentConfig.getCacheDirectory("mercurial");
+  }
+
+  public int getPullTimeout(@NotNull AgentRunningBuild build) {
+    Integer timeout = parseTimeout(build.getSharedConfigParameters().get(PULL_TIMEOUT_SECONDS));
+    if (timeout != null)
+      return timeout;
+    return DEFAULT_PULL_TIMEOUT_SECONDS;
+  }
+
+  public boolean runWithTraceback(@NotNull AgentRunningBuild build) {
+    return Boolean.parseBoolean(build.getSharedConfigParameters().get("teamcity.hg.run.commands.with.traceback"));
+  }
+
+  public boolean runWithProfile(@NotNull AgentRunningBuild build) {
+    return Boolean.parseBoolean(build.getSharedConfigParameters().get("teamcity.hg.runCommandsWithProfile"));
+  }
+
+  @Nullable
+  public Integer parseTimeout(@Nullable String timeoutStr) {
+    if (timeoutStr == null)
+      return null;
+    try {
+      int timeout = Integer.parseInt(timeoutStr);
+      if (timeout > 0)
+        return timeout;
+      else
+         return null;
+    } catch (NumberFormatException e) {
+      return null;
+    }
+  }
+
+  @NotNull
+  public File getTempDir() {
+    return myAgentConfig.getTempDirectory();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/AgentRepoFactory.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettings;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsFactory;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsForRoot;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+public class AgentRepoFactory implements HgRepoFactory {
+
+  private final AgentPluginConfig myPluginConfig;
+  private final CommandSettingsForRoot myCommandSettingsFactory;
+  private final HgPathProvider myHgPathProvider;
+
+  public AgentRepoFactory(@NotNull AgentPluginConfig pluginConfig,
+                          @NotNull CommandSettingsForRoot commandSettingsFactory,
+                          @NotNull HgPathProvider hgPathProvider) {
+    myPluginConfig = pluginConfig;
+    myCommandSettingsFactory = commandSettingsFactory;
+    myHgPathProvider = hgPathProvider;
+  }
+
+  @NotNull
+  public HgRepo createRepo(@NotNull HgVcsRoot root, @NotNull File workingDir) throws VcsException {
+    return new HgRepo(myCommandSettingsFactory.forRoot(root), workingDir, myHgPathProvider.getHgPath(root), root.getAuthSettings());
+  }
+
+  @NotNull
+  public HgRepo createRepo(@NotNull HgVcsRoot root, @NotNull File workingDir, @NotNull MercurialProgress progress) throws VcsException {
+    CommandSettingsFactory settingsFactory = myCommandSettingsFactory.forRoot(root);
+    return new HgRepo(new FactoryWithProgess(settingsFactory, progress), workingDir, myHgPathProvider.getHgPath(root), root.getAuthSettings());
+  }
+
+
+  @NotNull
+  public HgRepo createTempDirRepo(@NotNull HgVcsRoot root) {
+    return new HgRepo(myCommandSettingsFactory.forRoot(root), myPluginConfig.getTempDir(),
+            myHgPathProvider.getHgPath(root), root.getAuthSettings());
+  }
+
+
+  private static class FactoryWithProgess implements CommandSettingsFactory {
+    private final CommandSettingsFactory myDelegate;
+    private final MercurialProgress myProgress;
+    public FactoryWithProgess(@NotNull CommandSettingsFactory delegate, @NotNull MercurialProgress progress) {
+      myDelegate = delegate;
+      myProgress = progress;
+    }
+
+    @NotNull
+    public CommandSettings create() {
+      CommandSettings commandSettings = myDelegate.create();
+      commandSettings.setProgress(myProgress);
+      return commandSettings;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgDetector.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import com.intellij.openapi.diagnostic.Logger;
+import jetbrains.buildServer.agent.AgentLifeCycleAdapter;
+import jetbrains.buildServer.agent.AgentLifeCycleListener;
+import jetbrains.buildServer.agent.BuildAgent;
+import jetbrains.buildServer.agent.BuildAgentConfiguration;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandSettingsFactory;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.VersionCommand;
+import jetbrains.buildServer.util.EventDispatcher;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author dmitry.neverov
+ */
+public class HgDetector extends AgentLifeCycleAdapter {
+
+  final static String AGENT_HG_PATH_PROPERTY = "teamcity.hg.agent.path";
+  private final static String AGENT_HG_VERSION = "teamcity.hg.version";
+  private final static Logger LOG = Logger.getInstance(HgDetector.class.getName());
+  private final static HgVersion LEAST_SUPPORTED_VERSION = new HgVersion(1, 5, 2);
+  private final CommandSettingsFactory myCommandSettingsFactory;
+  private final List<String> myHgPaths = Arrays.asList("hg");
+
+
+  public HgDetector(@NotNull EventDispatcher<AgentLifeCycleListener> dispatcher,
+                    @NotNull CommandSettingsFactory commandSettingsFactory) {
+    myCommandSettingsFactory = commandSettingsFactory;
+    dispatcher.addListener(this);
+  }
+
+
+  @Override
+  public void beforeAgentConfigurationLoaded(@NotNull final BuildAgent agent) {
+    BuildAgentConfiguration config = agent.getConfiguration();
+    String agentHgPath = config.getConfigurationParameters().get(AGENT_HG_PATH_PROPERTY);
+    File tmpDir = config.getTempDirectory();
+    if (agentHgPath == null) {
+      HgExec detectedHg = detectHg(tmpDir);
+      if (detectedHg != null) {
+        LOG.info("Detect installed mercurial at path " + detectedHg.getPath() + ", provide it as a property " + AGENT_HG_PATH_PROPERTY);
+        config.addConfigurationParameter(AGENT_HG_PATH_PROPERTY, "hg");
+        config.addConfigurationParameter(AGENT_HG_VERSION, detectedHg.getVersion().toString());
+      } else {
+        LOG.info("Cannot detect installed mercurial");
+      }
+    } else {
+      HgExec hg = detectHgAtPath(tmpDir, agentHgPath, true);
+      if (hg == null) {
+        LOG.warn("Cannot run mercurial at path " + agentHgPath);
+      } else {
+        if (isCompatible(hg.getVersion())) {
+          config.addConfigurationParameter(AGENT_HG_VERSION, hg.getVersion().toString());
+        } else {
+          LOG.warn("Mercurial at path " + agentHgPath + " is not compatible with TeamCity");
+        }
+      }
+    }
+  }
+
+
+  @Nullable
+  private HgExec detectHg(@NotNull File tmpDir) {
+    for (String path : myHgPaths) {
+      HgExec exec = detectHgAtPath(tmpDir, path, false);
+      if (exec == null)
+        continue;
+      if (isCompatible(exec.getVersion())) {
+        return exec;
+      } else {
+        warn("Mercurial version at path " + path + " is " + exec.getVersion() + ", required version is " + LEAST_SUPPORTED_VERSION + "+", false);
+      }
+    }
+    return null;
+  }
+
+
+  @Nullable
+  private HgExec detectHgAtPath(@NotNull File tmpDir, @NotNull String hgPath, boolean logWarnings) {
+    try {
+      HgVersion version = getVersion(hgPath, tmpDir);
+      return new HgExec(hgPath, version);
+    } catch (VcsException e) {
+      warn("Error while trying to get hg version, hg path: " + hgPath, e, logWarnings);
+      return null;
+    }
+  }
+
+
+  @NotNull
+  private HgVersion getVersion(@NotNull String hgPath, @NotNull File workDir) throws VcsException {
+    return new VersionCommand(myCommandSettingsFactory.create(), hgPath, workDir).call();
+  }
+
+
+  private void warn(@NotNull final String msg, final boolean logWarnings) {
+    if (logWarnings)
+      LOG.warn(msg);
+    else if (LOG.isDebugEnabled())
+      LOG.debug(msg);
+  }
+
+  private void warn(@NotNull final String msg, @NotNull final Throwable t, final boolean logWarnings) {
+    if (logWarnings)
+      LOG.warn(msg, t);
+    else if (LOG.isDebugEnabled())
+      LOG.debug(msg, t);
+  }
+
+  private boolean isCompatible(@NotNull final HgVersion version) {
+    return version.isEqualsOrGreaterThan(LEAST_SUPPORTED_VERSION);
+  }
+
+
+  private static class HgExec {
+    private final String myPath;
+    private final HgVersion myVersion;
+
+    public HgExec(@NotNull String path, @NotNull HgVersion version) {
+      myPath = path;
+      myVersion = version;
+    }
+
+    @NotNull
+    public String getPath() {
+      return myPath;
+    }
+
+    @NotNull
+    public HgVersion getVersion() {
+      return myVersion;
+    }
+  }
+}
--- a/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java	Fri Sep 24 19:45:30 2010 +0400
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialAgentSideVcsSupport.java	Thu Aug 06 12:15:36 2020 +0300
@@ -1,143 +1,141 @@
-/*
- * Copyright 2000-2010 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial;
-
-import jetbrains.buildServer.agent.BuildProgressLogger;
-import jetbrains.buildServer.agent.vcs.AgentVcsSupport;
-import jetbrains.buildServer.agent.vcs.IncludeRuleUpdater;
-import jetbrains.buildServer.agent.vcs.UpdateByIncludeRules;
-import jetbrains.buildServer.agent.vcs.UpdatePolicy;
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*;
-import jetbrains.buildServer.util.FileUtil;
-import jetbrains.buildServer.vcs.CheckoutRules;
-import jetbrains.buildServer.vcs.IncludeRule;
-import jetbrains.buildServer.vcs.VcsException;
-import jetbrains.buildServer.vcs.VcsRoot;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.io.IOException;
-
-public class MercurialAgentSideVcsSupport extends AgentVcsSupport implements UpdateByIncludeRules {
-  private void updateWorkingDir(final Settings settings, final String version, final BuildProgressLogger logger) throws VcsException {
-    logger.message("Updating working directory from the local repository copy");
-    UpdateCommand uc = new UpdateCommand(settings);
-    ChangeSet cs = new ChangeSet(version);
-    uc.setToId(cs.getId());
-    uc.execute();
-    logger.message("Working directory updated successfully");
-  }
-
-  private File cloneRepository(final Settings settings) throws VcsException {
-    File tempDir;
-    try {
-      File parent = FileUtil.createTempDirectory("hg", "clone");
-      parent.deleteOnExit();
-      tempDir = new File(parent, "hg");
-    } catch (IOException e) {
-      throw new VcsException("Failed to create temp directory: " + e.getLocalizedMessage());
-    }
-
-    CloneCommand cc = new CloneCommand(settings);
-    cc.setDestDir(tempDir.getAbsolutePath());
-
-    cc.setUpdateWorkingDir(false);
-    cc.execute();
-    return tempDir;
-  }
-
-  /**
-   * Moves files from one directory to another with all subdirectories.
-   * Removes old directory if it became empty.
-   */
-  private static boolean moveDir(File oldDir, File newDir) {
-    // both old and new directories exist
-    boolean moveSuccessful = true;
-    final File[] files = oldDir.listFiles();
-    if (files != null) {
-      for (File file: files) {
-        if (file.isFile()) {
-          File destFile = new File(newDir, file.getName());
-          destFile.getParentFile().mkdirs();
-          if (!file.renameTo(destFile)) {
-            moveSuccessful = false;
-          }
-        } else if (!moveDir(file, new File(newDir, file.getName()))) {
-          moveSuccessful = false;
-        }
-      }
-    }
-
-    if (moveSuccessful) {
-      FileUtil.deleteIfEmpty(oldDir);
-    }
-
-    return moveSuccessful;
-  }
-
-  @NotNull
-  @Override
-  public UpdatePolicy getUpdatePolicy() {
-    return this;
-  }
-
-  @NotNull
-  @Override
-  public String getName() {
-    return Constants.VCS_NAME;
-  }
-
-  public IncludeRuleUpdater getUpdater(@NotNull final VcsRoot vcsRoot, @NotNull final CheckoutRules checkoutRules, @NotNull final String toVersion, @NotNull final File checkoutDirectory, @NotNull final BuildProgressLogger logger) throws VcsException {
-    return new IncludeRuleUpdater() {
-      public void process(@NotNull final IncludeRule includeRule, @NotNull final File workingDir) throws VcsException {
-        if (includeRule.getTo() != null && includeRule.getTo().length() > 0) {
-          if (!".".equals(includeRule.getFrom()) && includeRule.getFrom().length() != 0) {
-            throw new VcsException("Invalid include rule: " + includeRule.toString() + ", Mercurial plugin supports mapping of the form: +:.=>dir only.");
-          }
-        }
-
-        Settings settings = new Settings(workingDir, vcsRoot);
-        settings.setWorkingDir(workingDir);
-        if (settings.hasCopyOfRepository()) {
-          // execute pull command
-          logger.message("Repository in working directory found, start pulling changes");
-          PullCommand pc = new PullCommand(settings);
-          pc.execute();
-          logger.message("Changes successfully pulled");
-        } else {
-          // execute clone command
-          logger.message("No repository in working directory found, start cloning repository to temporary folder");
-          File parentDir = cloneRepository(settings);
-          logger.message("Repository successfully cloned to: " + parentDir.getAbsolutePath());
-          logger.message("Moving repository to working directory: " + workingDir.getAbsolutePath());
-          if (!moveDir(parentDir, workingDir)) {
-            File hgDir = new File(workingDir, ".hg");
-            if (hgDir.isDirectory()) {
-              FileUtil.delete(hgDir);
-            }
-            throw new VcsException("Failed to move directory content: " + parentDir.getAbsolutePath() + " to: " + workingDir.getAbsolutePath());
-          }
-
-          logger.message("Repository successfully moved to working directory: " + workingDir.getAbsolutePath());
-        }
-        updateWorkingDir(settings, toVersion, logger);
-      }
-
-      public void dispose() throws VcsException {
-      }
-    };
-  }
-}
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.agent.AgentRunningBuild;
+import jetbrains.buildServer.agent.vcs.*;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.CheckoutInfo;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.MercurialExtension;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.MercurialExtensionManager;
+import jetbrains.buildServer.vcs.*;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.*;
+
+public class MercurialAgentSideVcsSupport extends AgentVcsSupport implements UpdateByIncludeRules2 {
+
+  private final AgentPluginConfig myConfig;
+  private final MirrorManager myMirrorManager;
+  private final AgentRepoFactory myRepoFactory;
+  private final MercurialExtensionManager myExtentionManager;
+
+  public MercurialAgentSideVcsSupport(@NotNull AgentPluginConfig pluginConfig,
+                                      @NotNull MirrorManager mirrorManager,
+                                      @NotNull AgentRepoFactory repoFactory,
+                                      @NotNull MercurialExtensionManager extentionManager) {
+    myConfig = pluginConfig;
+    myMirrorManager = mirrorManager;
+    myRepoFactory = repoFactory;
+    myExtentionManager = extentionManager;
+  }
+
+  public IncludeRuleUpdater getUpdater(@NotNull final VcsRoot vcsRoot, @NotNull final CheckoutRules checkoutRules, @NotNull final String toVersion, @NotNull final File checkoutDirectory, @NotNull final AgentRunningBuild build, boolean cleanCheckoutRequested) throws VcsException {
+    MercurialIncludeRuleUpdater updater;
+    if (myConfig.shareLocalMirrors(build, vcsRoot)) {
+      updater = new SharingMercurialUpdater(myConfig, myMirrorManager, myRepoFactory, vcsRoot, toVersion, build);
+    } else {
+      updater = new MercurialIncludeRuleUpdater(myConfig, myMirrorManager, myRepoFactory, vcsRoot, toVersion, build);
+    }
+    registerExtensions(vcsRoot, checkoutRules, updater);
+    return updater;
+  }
+
+  @NotNull
+  @Override
+  public AgentCheckoutAbility canCheckout(@NotNull VcsRoot vcsRoot, @NotNull CheckoutRules checkoutRules, @NotNull AgentRunningBuild build) {
+    CheckoutInfo info = new CheckoutInfo(myRepoFactory, new HgVcsRoot(vcsRoot), checkoutRules);
+    try {
+      info.getTempDirRepo().version().call();
+    } catch (VcsException e) {
+      return AgentCheckoutAbility.noVcsClientOnAgent(e.getMessage());
+    }
+
+    Set<String> targetDirs = new HashSet<>();
+    try {
+      for (IncludeRule rule : checkoutRules.getRootIncludeRules()) {
+        MercurialIncludeRuleUpdater.checkRuleIsValid(rule);
+        targetDirs.add(rule.getTo());
+      }
+    } catch (VcsException e) {
+      return AgentCheckoutAbility.notSupportedCheckoutRules(e.getMessage());
+    }
+
+    List<VcsRootEntry> mercurialEntries = getMercurialEntries(build);
+    if (mercurialEntries.size() > 1) {
+      for (VcsRootEntry entry : mercurialEntries) {
+        VcsRoot otherRoot = entry.getVcsRoot();
+        if (vcsRoot.equals(otherRoot))
+          continue;
+        for (IncludeRule rule : getOtherRootRules(entry.getCheckoutRules().getRootIncludeRules())) {
+          if (targetDirs.contains(rule.getTo()))
+            return AgentCheckoutAbility.canNotCheckout("Cannot checkout VCS root '" + vcsRoot.getName() + "' into the same directory as VCS root '" + otherRoot.getName() + "'");
+        }
+      }
+    }
+
+    return AgentCheckoutAbility.canCheckout();
+  }
+
+
+  @NotNull
+  private List<VcsRootEntry> getMercurialEntries(@NotNull AgentRunningBuild build) {
+    List<VcsRootEntry> result = new ArrayList<>();
+    for (VcsRootEntry entry : build.getVcsRootEntries()) {
+      if (Constants.VCS_NAME.equals(entry.getVcsRoot().getVcsName()))
+        result.add(entry);
+    }
+    return result;
+  }
+
+
+  @NotNull
+  private List<IncludeRule> getOtherRootRules(@NotNull List<IncludeRule> rules) {
+    List<IncludeRule> result = new ArrayList<>();
+    for (IncludeRule rule : rules) {
+      try {
+        MercurialIncludeRuleUpdater.checkRuleIsValid(rule);
+      } catch (VcsException e) {
+        //return empty root rules to skip checks; appropriate cannot checkout reason
+        //will be returned during otherRoot's canCheckout() call
+        return Collections.emptyList();
+      }
+      result.add(rule);
+    }
+    return result;
+  }
+
+
+  private void registerExtensions(@NotNull VcsRoot root, @NotNull CheckoutRules checkoutRules, @NotNull MercurialIncludeRuleUpdater updater) {
+    CheckoutInfo info = new CheckoutInfo(myRepoFactory, new HgVcsRoot(root), checkoutRules);
+    for (MercurialExtension ext :myExtentionManager.getExtensionsForCheckout(info)) {
+      updater.registerExtension(ext);
+    }
+  }
+
+  @NotNull
+  @Override
+  public String getName() {
+    return Constants.VCS_NAME;
+  }
+
+  @NotNull
+  @Override
+  public UpdatePolicy getUpdatePolicy() {
+    return this;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialBuildLogProgress.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.agent.BuildProgressLogger;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class MercurialBuildLogProgress implements MercurialProgress {
+
+  private final BuildProgressLogger myLogger;
+  private final AtomicInteger myBlockMessageCount = new AtomicInteger(0);
+  private String myPrevMessage;
+  private int myPrevPercents;
+
+  public MercurialBuildLogProgress(@NotNull BuildProgressLogger logger) {
+     myLogger = logger;
+  }
+
+  public void progressStarted(@NotNull String progressMessage) {
+    myBlockMessageCount.set(0);
+    myLogger.activityStarted(progressMessage, "CUSTOM_HG_PROGRESS");
+  }
+
+  public void reportProgress(@NotNull String progressMessage) {
+    myBlockMessageCount.incrementAndGet();
+    myLogger.message(progressMessage);
+  }
+
+  public void progressFinished(@NotNull String progressMessage) {
+    if (myBlockMessageCount.get() == 0)
+      myLogger.message("");//add an empty message so that the empty block is shown in UI
+    myLogger.activityFinished(progressMessage, "CUSTOM_HG_PROGRESS");
+  }
+
+  public void reportProgress(float percentage, @NotNull String stage) {
+    if (percentage < 0) {
+      resetPrevProgress();
+      reportProgress(stage);
+    } else {
+      int percents = (int) Math.floor(percentage * 100);
+      if (!isDuplicate(stage, percents)) {
+        myLogger.message(stage + " " + percents + "%");
+        updatePrevProgress(stage, percents);
+      }
+    }
+  }
+
+  private void resetPrevProgress() {
+    myPrevMessage = null;
+    myPrevPercents = -1;
+  }
+
+  private boolean isDuplicate(@NotNull String message, int percents) {
+    return message.equals(myPrevMessage) && percents == myPrevPercents;
+  }
+
+  private void updatePrevProgress(@NotNull String message, int percents) {
+    myPrevMessage = message;
+    myPrevPercents = percents;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialIncludeRuleUpdater.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.agent.AgentRunningBuild;
+import jetbrains.buildServer.agent.BuildProgressLogger;
+import jetbrains.buildServer.agent.vcs.IncludeRuleUpdater;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.AuthSettings;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.PullCommand;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.AbandonedTransactionFound;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.UnrelatedRepositoryException;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.WrongSubrepoUrlException;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.BeforeWorkingDirUpdateExtension;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.MercurialExtension;
+import jetbrains.buildServer.log.Loggers;
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.vcs.IncludeRule;
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.VcsRoot;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static com.intellij.openapi.util.io.FileUtil.delete;
+
+/**
+ * @author dmitry.neverov
+ */
+public class MercurialIncludeRuleUpdater implements IncludeRuleUpdater {
+
+  protected final MirrorManager myMirrorManager;
+  protected final AgentRepoFactory myRepoFactory;
+  protected final HgVcsRoot myRoot;
+  private final AuthSettings myAuthSettings;
+  private final String myToVersion;
+  private final BuildProgressLogger myLogger;
+  private final boolean myUseLocalMirrors;
+  private int myPullTimeout;
+  private final boolean myUseTraceback;
+  private final boolean myProfile;
+  private final List<MercurialExtension> myExtensions = new ArrayList<>();
+  protected final MercurialProgress myProgress;
+
+  public MercurialIncludeRuleUpdater(@NotNull AgentPluginConfig pluginConfig,
+                                     @NotNull MirrorManager mirrorManager,
+                                     @NotNull AgentRepoFactory repoFactory,
+                                     @NotNull VcsRoot root,
+                                     @NotNull String toVersion,
+                                     @NotNull AgentRunningBuild build) {
+    myMirrorManager = mirrorManager;
+    myRepoFactory = repoFactory;
+    myRoot = new HgVcsRoot(root);
+    myAuthSettings = myRoot.getAuthSettings();
+    myToVersion = toVersion;
+    myLogger = build.getBuildLogger();
+    myUseLocalMirrors = pluginConfig.isUseLocalMirrors(build, root);
+    myPullTimeout = pluginConfig.getPullTimeout(build);
+    myUseTraceback = pluginConfig.runWithTraceback(build);
+    myProfile = pluginConfig.runWithProfile(build);
+    myProgress = new MercurialBuildLogProgress(build.getBuildLogger().getFlowLogger("-1"));
+  }
+
+
+  public void process(@NotNull IncludeRule rule, @NotNull File workingDir) throws VcsException {
+    try {
+      checkRuleIsValid(rule);
+      if (myUseLocalMirrors)
+        updateLocalMirror(myRoot.getRepository(), myToVersion);
+      updateRepository(workingDir);
+      updateWorkingDir(workingDir, myToVersion, myRoot.getRepository());
+    } catch (Exception e) {
+      throwVcsException(e);
+    }
+  }
+
+
+  public void dispose() throws VcsException {
+  }
+
+
+  public void registerExtension(@NotNull MercurialExtension extention) {
+    myExtensions.add(extention);
+  }
+
+
+  @NotNull
+  protected <T extends MercurialExtension> List<T> getExtensions(@NotNull Class<T> extensionClass) {
+    List<T> extentions = new ArrayList<>();
+    for (MercurialExtension e : myExtensions) {
+      if (extensionClass.isInstance(e))
+        extentions.add(extensionClass.cast(e));
+    }
+    return extentions;
+  }
+
+
+  protected void updateLocalMirror(@NotNull String repositoryUrl, @NotNull String revision) throws VcsException, IOException {
+    File mirrorDir = myMirrorManager.getMirrorDir(repositoryUrl);
+    HgRepo mirrorRepo = myRepoFactory.createRepo(myRoot, mirrorDir, myProgress);
+    if (!mirrorRepo.isValidRepository()) {
+      delete(mirrorDir);
+      mirrorRepo.init().call();
+    }
+    mirrorRepo.setDefaultPath(myRoot.getRepository());
+    mirrorRepo.setTeamCityConfig(myRoot.getCustomHgConfig());
+    myLogger.message("Update local mirror of " + myAuthSettings.getRepositoryUrlWithHiddenPassword(repositoryUrl) + " at " + mirrorDir);
+    if (mirrorRepo.containsRevision(revision)) {
+      myLogger.message("Local mirror is already up-to-date");
+    } else {
+      PullCommand pull = mirrorRepo.pull().fromRepository(repositoryUrl)
+              .withTraceback(myUseTraceback)
+              .withProfile(myProfile)
+              .withTimeout(myPullTimeout);
+      try {
+        pull.call();
+      } catch (AbandonedTransactionFound e) {
+        myLogger.message("Abandoned transaction found, trying to recover");
+        mirrorRepo.recover().call();
+        pull.call();
+      }
+    }
+  }
+
+
+  protected void updateRepository(@NotNull File workingDir) throws VcsException, IOException {
+    String repositoryUrl = getDefaultPullUrl(myRoot, myUseLocalMirrors);
+    HgRepo repo = myRepoFactory.createRepo(myRoot, workingDir, myProgress);
+    myLogger.message("Update repository " + workingDir.getAbsolutePath());
+    disableSharing(workingDir);
+    if (!repo.isValidRepository())
+      repo.init().call();
+    repo.setDefaultPath(myRoot.getRepository());
+    repo.setTeamCityConfig(myRoot.getCustomHgConfig());
+    if (repo.containsRevision(myToVersion)) {
+      myLogger.message("Repository already contains revision " + myToVersion);
+    } else {
+      try {
+        repo.pull().fromRepository(repositoryUrl)
+                .withTraceback(myUseTraceback)
+                .withProfile(myProfile)
+                .withTimeout(myPullTimeout)
+                .call();
+      } catch (UnrelatedRepositoryException e) {
+        throw new UnrelatedRepositoryException(myAuthSettings.getRepositoryUrlWithHiddenPassword(repositoryUrl), workingDir);
+      }
+    }
+  }
+
+
+  private void disableSharing(@NotNull File workingDir) {
+    File dotHg = new File(workingDir, ".hg");
+    File sharedpath = new File(dotHg, "sharedpath");
+    if (sharedpath.exists())
+      FileUtil.delete(sharedpath);
+  }
+
+
+  private void updateWorkingDir(@NotNull File workingDir, @NotNull String toVersion, @NotNull String repositoryUrl) throws VcsException, IOException {
+    HgRepo repo = myRepoFactory.createRepo(myRoot, workingDir, myProgress);
+    List<File> repos = new ArrayList<>();
+    updateSubrepositories(repo, toVersion, repositoryUrl, repos);
+    doUpdateWorkingDir(repo, toVersion);
+    purge(repos);
+  }
+
+  private void purge(@NotNull List<File> dirs) throws VcsException {
+    HgVcsRoot.PurgePolicy purgePolicy = myRoot.getPurgePolicy();
+    if (purgePolicy == HgVcsRoot.PurgePolicy.DONT_RUN)
+      return;
+    for (File dir : dirs) {
+      HgRepo repo = myRepoFactory.createRepo(myRoot, dir, myProgress);
+      repo.purge().withPolicy(purgePolicy).call();
+    }
+  }
+
+  private void updateSubrepositories(@NotNull HgRepo repo,
+                                     @NotNull String toVersion,
+                                     @NotNull String parentRepositoryUrl,
+                                     @NotNull List<File> repoAccumulator) throws VcsException, IOException {
+    repoAccumulator.add(repo.getWorkingDir());
+    if (!repo.hasSubreposAtRevision(toVersion))
+      return;
+    myLogger.message("Process subrepos of " + parentRepositoryUrl);
+    String workingDirRevision = repo.getWorkingDirRevision();
+    Map<String, SubRepo> workingDirSubrepos = repo.getSubrepositories(workingDirRevision);
+    Map<String, SubRepo> subrepos = repo.getSubrepositories(toVersion);
+    for (Map.Entry<String, SubRepo> entry : subrepos.entrySet()) {
+      String path = entry.getKey();
+      SubRepo subrepoConfig = entry.getValue();
+      myLogger.message("Process subrepoConfig at path " + path + " (url: " + subrepoConfig.url() + ")");
+      SubRepo workingDirSubrepo = workingDirSubrepos.get(path);
+      if (workingDirSubrepo != null && subrepoConfig.hasDifferentUrlThan(workingDirSubrepo)) {
+        myLogger.message("The url of subrepoConfig was changed between revisions " + workingDirRevision + " and " + toVersion + " , delete the subrepoConfig");
+        delete(subrepoConfigDir(repo, subrepoConfig));
+      }
+      HgRepo subrepository = myRepoFactory.createRepo(myRoot, subrepoConfigDir(repo, subrepoConfig), myProgress);
+      String subrepoUrl;
+      try {
+        subrepoUrl = subrepoConfig.resolveUrl(parentRepositoryUrl);
+        if (myUseLocalMirrors && subrepoConfig.vcsType() == SubRepo.VcsType.hg && !isRelativeUrl(subrepoUrl))
+          syncSubrepo(subrepository, subrepoUrl, subrepoConfig.revision());
+      } catch (WrongSubrepoUrlException e) {
+        myLogger.warning("Failed to resolve subrepo url '" + subrepoConfig.url() + "': " + e.getMessage());
+        Loggers.VCS.warn("Failed to resolve subrepo url '" + subrepoConfig.url() + "'", e);
+        subrepoUrl = subrepoConfig.url();
+      }
+      updateSubrepositories(subrepository, subrepoConfig.revision(), subrepoUrl, repoAccumulator);
+    }
+  }
+
+  private boolean isRelativeUrl(@NotNull String url) {
+    return url.startsWith(".");
+  }
+
+  protected void syncSubrepo(@NotNull HgRepo subrepository, @NotNull String subrepoUrl, @NotNull String subrepoRevision) throws VcsException, IOException {
+    disableSharing(subrepository.getWorkingDir());
+    if (!subrepository.isValidRepository() || !subrepository.containsRevision(subrepoRevision)) {
+      updateLocalMirror(subrepoUrl, subrepoRevision);
+      File mirrorDir = myMirrorManager.getMirrorDir(subrepoUrl);
+      if (!subrepository.isValidRepository())
+        subrepository.init().call();
+      subrepository.setDefaultPath(subrepoUrl);
+      subrepository.setTeamCityConfig(myRoot.getCustomHgConfig());
+      myLogger.message("Pull from local mirror");
+      subrepository.pull().fromRepository(mirrorDir)
+              .withTraceback(myUseTraceback)
+              .withProfile(myProfile)
+              .withTimeout(myPullTimeout)
+              .call();
+      myLogger.message("done");
+    }
+  }
+
+
+  private void doUpdateWorkingDir(@NotNull HgRepo repo, @NotNull String revision) throws VcsException {
+    for (BeforeWorkingDirUpdateExtension e : getExtensions(BeforeWorkingDirUpdateExtension.class)) {
+      e.call(repo, revision);
+    }
+    repo.update().withTraceback(myUseTraceback).withProfile(myProfile).toRevision(revision).call();
+  }
+
+
+  protected String getDefaultPullUrl(HgVcsRoot root, boolean useLocalMirror) throws IOException {
+    if (useLocalMirror) {
+      File mirrorDir = myMirrorManager.getMirrorDir(root.getRepository());
+      return mirrorDir.getCanonicalPath();
+    } else {
+      return root.getRepository();
+    }
+  }
+
+
+  public static void checkRuleIsValid(IncludeRule includeRule) throws VcsException {
+    if (!".".equals(includeRule.getFrom()) && includeRule.getFrom().length() != 0)
+      throw new VcsException("Invalid include rule: " + includeRule.toString() + ", Mercurial plugin supports mapping of the form: +:.=>dir only.");
+  }
+
+
+  private void throwVcsException(Exception e) throws VcsException {
+    if (e instanceof VcsException)
+      throw (VcsException) e;
+    else
+      throw new VcsException(e);
+  }
+
+  private File subrepoConfigDir(@NotNull HgRepo parentRepo, @NotNull SubRepo subrepo) {
+    return new File(parentRepo.getWorkingDir(), subrepo.path());
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SharingMercurialUpdater.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.agent.AgentRunningBuild;
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.vcs.VcsException;
+import jetbrains.buildServer.vcs.VcsRoot;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Updater which uses local mirrors and hg share extension (available in hg 1.3+)
+ * http://mercurial.selenic.com/wiki/ShareExtension
+ */
+public class SharingMercurialUpdater extends MercurialIncludeRuleUpdater {
+  public SharingMercurialUpdater(@NotNull AgentPluginConfig pluginConfig,
+                                 @NotNull MirrorManager mirrorManager,
+                                 @NotNull AgentRepoFactory repoFactory,
+                                 @NotNull VcsRoot root,
+                                 @NotNull String toVersion,
+                                 @NotNull AgentRunningBuild build) {
+    super(pluginConfig, mirrorManager, repoFactory, root, toVersion, build);
+  }
+
+  @Override
+  protected void updateRepository(@NotNull File workingDir) throws VcsException, IOException {
+    HgRepo repo = myRepoFactory.createRepo(myRoot, workingDir, myProgress);
+    if (repo.isEmpty() || !repo.isValidRepository())
+      repo.init().call();
+    repo.setDefaultPath(myRoot.getRepository());
+    repo.setTeamCityConfig(myRoot.getCustomHgConfig());
+
+    File mirrorHg = getMirrorHg(myRoot.getRepository());
+    String sharedPath = readSharedPath(workingDir);
+    if (mirrorHg.getCanonicalPath().equals(sharedPath)) {
+      writeRequires(mirrorHg, workingDir);
+    } else {
+      writeRequires(mirrorHg, workingDir);
+      writeSharedPath(mirrorHg, workingDir);
+    }
+  }
+
+  @Override
+  protected void syncSubrepo(@NotNull HgRepo subrepository, @NotNull String subrepoUrl, @NotNull String subrepoRevision) throws VcsException, IOException {
+    if (subrepository.isEmpty() || !subrepository.isValidRepository())
+      subrepository.init().call();
+    subrepository.setDefaultPath(subrepoUrl);
+    subrepository.setTeamCityConfig(myRoot.getCustomHgConfig());
+
+    if (!subrepository.containsRevision(subrepoRevision)) {
+      updateLocalMirror(subrepoUrl, subrepoRevision);
+      File mirrorHg = getMirrorHg(subrepoUrl);
+      String sharedPath = readSharedPath(subrepository.getWorkingDir());
+      if (mirrorHg.getCanonicalPath().equals(sharedPath)) {
+        writeRequires(mirrorHg, subrepository.getWorkingDir());
+      } else {
+        writeRequires(mirrorHg, subrepository.getWorkingDir());
+        writeSharedPath(mirrorHg, subrepository.getWorkingDir());
+      }
+    }
+  }
+
+  @Nullable
+  private String readSharedPath(@NotNull File workingDir) throws IOException {
+    File sharedPath = getSharedPath(workingDir);
+    if (!sharedPath.exists())
+      return null;
+    return FileUtil.readText(sharedPath);
+  }
+
+  private void writeSharedPath(@NotNull File mirrorHg, @NotNull File workingDir) throws IOException {
+    FileUtil.writeToFile(getSharedPath(workingDir), mirrorHg.getCanonicalPath().getBytes(StandardCharsets.UTF_8));
+  }
+
+  private void writeRequires(@NotNull File mirrorHg, @NotNull File workingDir) throws IOException {
+    //Copy .hg/requires from mirror to working dir because it could be created by
+    //an older version or mercurial. When shared repositories are used this can
+    //lead to errors, because mercurial command is trying to work with .hg in
+    //mirrors as if it was of a newer version.
+    File mirrorRequires = new File(mirrorHg, "requires");
+    File workingDirRequires = new File(new File(workingDir, ".hg"), "requires");
+    FileUtil.copy(mirrorRequires, workingDirRequires);
+    FileUtil.writeToFile(workingDirRequires, "shared\n".getBytes(), true);
+  }
+
+  @NotNull
+  private File getSharedPath(File workingDir) {
+    return new File(new File(workingDir, ".hg"), "sharedpath");
+  }
+
+  @NotNull
+  private File getMirrorHg(@NotNull String repositoryUrl) {
+    File mirrorDir = myMirrorManager.getMirrorDir(repositoryUrl);
+    return new File(mirrorDir, ".hg");
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ext/BeforeWorkingDirUpdateExtension.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.ext;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgRepo;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+public interface BeforeWorkingDirUpdateExtension extends MercurialExtension {
+  void call(@NotNull HgRepo repo, @NotNull String revision) throws VcsException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ext/CheckoutInfo.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.ext;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.AgentRepoFactory;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgRepo;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.vcs.CheckoutRules;
+import org.jetbrains.annotations.NotNull;
+
+public class CheckoutInfo {
+  private final AgentRepoFactory myRepoFactory;
+  private final HgVcsRoot myRoot;
+  private final CheckoutRules myRules;
+
+  public CheckoutInfo(@NotNull AgentRepoFactory repoFactory,
+                      @NotNull HgVcsRoot root,
+                      @NotNull CheckoutRules rules) {
+    myRepoFactory = repoFactory;
+    myRoot = root;
+    myRules = rules;
+  }
+
+  @NotNull
+  public CheckoutRules getCheckoutRules() {
+    return myRules;
+  }
+
+  @NotNull
+  public HgVcsRoot getRoot() {
+    return myRoot;
+  }
+
+  @NotNull
+  public HgRepo getTempDirRepo() {
+    return myRepoFactory.createTempDirRepo(getRoot());
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ext/MercurialExtension.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.ext;
+
+public interface MercurialExtension {
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ext/MercurialExtensionManager.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.ext;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MercurialExtensionManager {
+
+  private final List<MercurialExtensionProvider> myProviders = new ArrayList<>();
+
+  public void registerExtentionFactory(@NotNull MercurialExtensionProvider provider) {
+    myProviders.add(provider);
+  }
+
+  @NotNull
+  public List<MercurialExtension> getExtensionsForCheckout(@NotNull CheckoutInfo info) {
+    List<MercurialExtension> extensions = new ArrayList<>();
+    for (MercurialExtensionProvider provider : myProviders) {
+      MercurialExtension ext = provider.getExtentionForCheckout(info);
+      if (ext != null)
+        extensions.add(ext);
+    }
+    return extensions;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ext/MercurialExtensionProvider.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.ext;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public interface MercurialExtensionProvider {
+
+  @Nullable
+  MercurialExtension getExtentionForCheckout(@NotNull CheckoutInfo info);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-agent/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/ext/impl/SparseCheckoutProvider.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.impl;
+
+import com.intellij.openapi.util.text.StringUtil;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgRepo;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgVersion;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.CommandResult;
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.ext.*;
+import jetbrains.buildServer.log.Loggers;
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.vcs.CheckoutRules;
+import jetbrains.buildServer.vcs.FileRule;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+public class SparseCheckoutProvider implements MercurialExtensionProvider {
+
+  private static final HgVersion MIN_SPARSE_CHECKOUT_VERSION = new HgVersion(3, 0, 0);
+
+  public SparseCheckoutProvider(@NotNull MercurialExtensionManager extentionManager) {
+    extentionManager.registerExtentionFactory(this);
+  }
+
+  @Nullable
+  public MercurialExtension getExtentionForCheckout(@NotNull CheckoutInfo info) {
+    if (sparseIsAvailable(info)) {
+      return new SparseCheckout(info.getCheckoutRules());
+    } else {
+      return new UndoSparseCheckout();
+    }
+  }
+
+
+  private boolean sparseIsAvailable(@NotNull CheckoutInfo info) {
+    if (info.getCheckoutRules().getExcludeRules().isEmpty())
+      return false;
+    try {
+      HgVersion version = info.getTempDirRepo().version().call();
+      if (!version.isEqualsOrGreaterThan(MIN_SPARSE_CHECKOUT_VERSION)) {
+        Loggers.VCS.info("The sparse checkout is not supported in the mercurial version " + version.toString());
+        return false;
+      }
+      CommandResult result = info.getTempDirRepo().runCommand("help", "extensions");
+      boolean enabledExtensions = false;
+      for (String line : StringUtil.splitByLines(result.getRawStdout())) {
+        if (line.trim().equals("enabled extensions:")) {
+          enabledExtensions = true;
+          continue;
+        }
+        if (line.trim().equals("disabled extensions:")) {
+          return false;
+        }
+        if (enabledExtensions) {
+          List<String> words = StringUtil.getWordsIn(line.trim());
+          if (words.isEmpty())
+            continue;
+          if (words.get(0).equals("sparse")) {
+            Loggers.VCS.info("Sparse extension is enabled");
+            return true;
+          }
+        }
+      }
+      return true;
+    } catch (VcsException e) {
+      //log
+      return false;
+    }
+  }
+
+
+  private static class SparseCheckout implements BeforeWorkingDirUpdateExtension {
+    private final CheckoutRules myRules;
+    private SparseCheckout(@NotNull CheckoutRules rules) {
+      myRules = rules;
+    }
+
+    public void call(@NotNull HgRepo repo, @NotNull String revision) throws VcsException {
+      String currentConfig = readSparseContent(repo.getWorkingDir());
+      String newConfig = getSparseContentFromRules();
+      if (currentConfig.equals(newConfig))
+        return;
+      writeSparseConfig(repo.getWorkingDir(), newConfig);
+      repo.runCommand("sparse", "--refresh");
+    }
+
+    private void writeSparseConfig(@NotNull File workingDir, @NotNull String sparseContent) throws VcsException {
+      try {
+        FileUtil.writeToFile(getSparseConfig(workingDir), sparseContent.getBytes(StandardCharsets.UTF_8), false);
+      } catch (IOException e) {
+        Loggers.VCS.warn("Error while writing .hg/sparse, will not do a sparse checkout", e);
+      }
+    }
+
+    @NotNull
+    private String getSparseContentFromRules() {
+      StringBuilder sparse = new StringBuilder();
+      sparse.append("[exclude]\n");
+      for (FileRule<?> rule : myRules.getExcludeRules()) {
+        sparse.append(rule.getFrom()).append("\n");
+      }
+      return sparse.toString();
+    }
+
+    @NotNull
+    private String readSparseContent(@NotNull File workingDir) {
+      File sparseConfig = getSparseConfig(workingDir);
+      if (!sparseConfig.exists())
+        return "";
+      try {
+        return FileUtil.readText(sparseConfig);
+      } catch (IOException e) {
+        Loggers.VCS.warn("Error while reading .hg/sparse, assume it was empty", e);
+        return "";
+      }
+    }
+  }
+
+
+  private static class UndoSparseCheckout implements BeforeWorkingDirUpdateExtension {
+    public void call(@NotNull HgRepo repo, @NotNull String revision) throws VcsException {
+      File workingDir = repo.getWorkingDir();
+      File sparseConfig = getSparseConfig(workingDir);
+      if (sparseConfig.exists()) {
+        Loggers.VCS.info("Remove sparse extension config and reset working directory state");
+        FileUtil.delete(sparseConfig);
+        FileUtil.delete(new File(new File(workingDir, ".hg"), "dirstate"));
+      }
+    }
+  }
+
+
+  private static File getSparseConfig(@NotNull File workingDir) {
+    return new File(new File(workingDir, ".hg"), "sparse");
+  }
+}
--- a/mercurial-common/mercurial-common.iml	Fri Sep 24 19:45:30 2010 +0400
+++ b/mercurial-common/mercurial-common.iml	Thu Aug 06 12:15:36 2020 +0300
@@ -1,15 +1,16 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <module relativePaths="true" type="JAVA_MODULE" version="4">
-  <component name="NewModuleRootManager" inherit-compiler-output="false">
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="false">
     <output url="file://$MODULE_DIR$/classes" />
     <exclude-output />
     <content url="file://$MODULE_DIR$">
       <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
     </content>
-    <orderEntry type="inheritedJdk" />
+    <orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
     <orderEntry type="sourceFolder" forTests="false" />
     <orderEntry type="library" exported="" name="TeamCityAPI-common" level="project" />
     <orderEntry type="library" exported="" name="IDEA-openapi" level="project" />
+    <orderEntry type="library" name="jdom" level="project" />
+    <orderEntry type="library" name="commons-codec-1.4" level="project" />
   </component>
-</module>
-
+</module>
\ No newline at end of file
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Constants.java	Fri Sep 24 19:45:30 2010 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/Constants.java	Thu Aug 06 12:15:36 2020 +0300
@@ -1,28 +1,49 @@
-/*
- * Copyright 2000-2010 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial;
-
-import jetbrains.buildServer.vcs.VcsRoot;
-
-public interface Constants {
-  String VCS_NAME = "mercurial";
-  String REPOSITORY_PROP = "repositoryPath";
-  String BRANCH_NAME_PROP = "branchName";
-  String HG_COMMAND_PATH_PROP = "hgCommandPath";
-  String SERVER_CLONE_PATH_PROP = "serverClonePath";
-  String USERNAME = "username";
-  String PASSWORD = VcsRoot.SECURE_PROPERTY_PREFIX + "password";
-}
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.vcs.VcsRoot;
+
+public interface Constants {
+  String VCS_NAME = "mercurial";
+  String REPOSITORY_PROP = "repositoryPath";
+  String BRANCH_NAME_PROP = "branchName";
+  String HG_COMMAND_PATH_PROP = "hgCommandPath";
+  String HG_PATH_ENV = "TEAMCITY_HG_PATH";
+  String SERVER_CLONE_PATH_PROP = "serverClonePath";
+  String USERNAME = "username";
+  String PASSWORD = VcsRoot.SECURE_PROPERTY_PREFIX + "password";
+  String USER_FOR_TAG = "tagUsername";
+  String DETECT_SUBREPO_CHANGES = "detectSubrepoChanges";
+  String USE_TAGS_AS_BRANCHES = "useTagsAsBranches";
+  String INCLUDE_SUBREPOS_IN_PATCH = "includeSubreposInPatch";
+  String USE_ARCHIVE_FOR_PATCH = "useArchiveForPatch";
+  String HG_EXTENSIONS = "hg.extensions";
+  String HG_COMMANDLINE_VIA_FILE = "hg.pass.commandline.via.file";
+  String PURGE_POLICY = "purgePolicy";
+  String GLOBAL_DETECT_SUBREPO_CHANGES = "teamcity.hg.detectSubrepoChanges";
+  String IGNORE_MISSING_DEFAULT_BRANCH = "IGNORE_MISSING_DEFAULT_BRANCH";
+  String CUSTOM_HG_CONFIG_PROP = "customHgConfig";
+  String TEAMCITY_HG_CONFIG_FILE_NAME = "teamcity";
+  String USE_AGENT_MIRRORS = "useSharedMirrors";
+
+  String SHOW_CUSTOM_CLONE_PATH = "teamcity.hg.showCustomClonePath";
+  String CUSTOM_CLONE_PATH_ENABLED = "teamcity.hg.customClonePathEnabled";
+  String CUSTOM_CLONE_PATH_WHITELIST = "teamcity.hg.customClonePathWhitelist";
+  String CUSTOM_CACHES_DIR = "teamcity.hg.customCachesDir";
+  String CUSTOM_SERVER_HG_PATH_WHITELIST = "teamcity.hg.customServerHgPathWhitelist";
+  String CUSTOM_HG_CONFIG_ENABLED = "teamcity.hg.customConfigEnabled";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgFileUtil.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.SystemInfo;
+import jetbrains.buildServer.util.FileUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author dmitry.neverov
+ */
+public final class HgFileUtil {
+
+  private final static String TEMP_DIR_PREFIX = "hg";
+  private final static Object ourTmpDirLock = new Object();
+
+  private HgFileUtil() {
+  }
+
+  /**
+   * Create a temp dir with short name
+   * @return created dir
+   * @throws IOException in case of I/O error
+   */
+  @NotNull
+  public static File createTempDir() throws IOException {
+    File parentDir = new File(FileUtil.getTempDirectory());
+    return createTempDir(parentDir);
+  }
+
+  @NotNull
+  public static File createTempDir(@NotNull final File parentDir) throws IOException {
+    //noinspection ResultOfMethodCallIgnored
+    parentDir.mkdirs();
+
+    int suffix = 0;
+    File dir;
+    while (true) {
+      suffix++;
+      String tmpDirName = TEMP_DIR_PREFIX + suffix;
+      dir = new File(parentDir, tmpDirName);
+      if (dir.exists())
+        continue;
+
+      synchronized (ourTmpDirLock) {
+        try {
+          if (!dir.createNewFile())
+            continue;
+        } catch (IOException e) {
+          //workaround for http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6325169
+          if (SystemInfo.isWindows && "Access is denied".equals(e.getMessage()))
+            continue;
+          throw e;
+        }
+        if (!dir.delete())
+          continue;
+        if (!dir.mkdir())
+          continue;
+      }
+      return dir;
+    }
+  }
+
+
+  public static void deleteDir(@Nullable final File dir, @NotNull final Logger logger) {
+    if (dir == null)
+      return;
+    FileUtil.symlinkAwareDelete(dir);
+    if (dir.exists())
+      logger.warn("Cannot delete directory " + dir.getAbsolutePath());
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgPathProvider.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author dmitry.neverov
+ */
+public interface HgPathProvider {
+
+  String getHgPath(@NotNull HgVcsRoot root);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgRepo.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,396 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.*;
+import jetbrains.buildServer.log.Loggers;
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.emptyMap;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.HgFileUtil.deleteDir;
+import static jetbrains.buildServer.util.FileUtil.isEmptyDir;
+
+/**
+* @author dmitry.neverov
+*/
+public class HgRepo {
+
+  protected final CommandSettingsFactory myCommandSettingsFactory;
+  protected final File myWorkingDir;
+  protected final String myHgPath;
+  protected final AuthSettings myAuthSettings;
+  protected final Map<String, Map<String, SubRepo>> mySubreposCache = new HashMap<>();
+
+  public HgRepo(@NotNull CommandSettingsFactory commandSettingsFactory,
+                @NotNull File workingDir,
+                @NotNull String hgPath,
+                @NotNull AuthSettings authSettings) {
+    myCommandSettingsFactory = commandSettingsFactory;
+    myWorkingDir = workingDir;
+    myHgPath = hgPath;
+    myAuthSettings = authSettings;
+  }
+
+  public PullCommand pull() {
+    return new PullCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
+  }
+
+  public PushCommand push() {
+    return new PushCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
+  }
+
+  public CloneCommand doClone() {
+    return new CloneCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
+  }
+
+  public IdentifyCommand id() {
+    return new IdentifyCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
+  }
+
+  public Init init() {
+    return new Init(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
+  }
+
+  public LogCommand log() {
+    return new LogCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
+  }
+
+  @NotNull
+  public CommitsAndMountPointsCommand logSubstates() throws VcsException {
+    return new CommitsAndMountPointsCommand(this, myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
+  }
+
+  public UpdateCommand update() {
+    return new UpdateCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
+  }
+
+  public BranchCommand branch() {
+    return new BranchCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
+  }
+
+  public BranchesCommand branches() {
+    return new BranchesCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
+  }
+
+  public BookmarksCommand bookmarks() {
+    return new BookmarksCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
+  }
+
+  public UpdateBookmarkCommand updateBookmark() {
+    return new UpdateBookmarkCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
+  }
+
+  public TagsCommand tags() {
+    return new TagsCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
+  }
+
+  public Map<String, String> getBranchRevisions(boolean includeBookmarks, boolean includeTags) throws VcsException {
+    Map<String, String> revisions = new HashMap<>();
+    if (includeTags)
+      revisions.putAll(tags().call());
+    if (includeBookmarks && version().call().isEqualsOrGreaterThan(BookmarksCommand.REQUIRED_HG_VERSION))
+      revisions.putAll(bookmarks().call());
+    revisions.putAll(branches().call());
+    return revisions;
+  }
+
+  public StatusCommand status() {
+    return new StatusCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
+  }
+
+  public TagCommand tag() {
+    return new TagCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
+  }
+
+  public CatCommand cat() {
+    return new CatCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
+  }
+
+  public ArchiveCommand archive() {
+    return new ArchiveCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, myAuthSettings);
+  }
+
+  public VersionCommand version() {
+    return new VersionCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
+  }
+
+  public ParentsCommand parents() {
+    return new ParentsCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
+  }
+
+  public MergeCommand merge() {
+    return new MergeCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
+  }
+
+  public ResolveCommand resolve() {
+    return new ResolveCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
+  }
+
+  public CommitCommand commit() {
+    return new CommitCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
+  }
+
+  public AddRemoveCommand addRemove() {
+    return new AddRemoveCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
+  }
+
+  public RecoverCommand recover() {
+    return new RecoverCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
+  }
+
+  public String path() {
+    return myWorkingDir.getAbsolutePath();
+  }
+
+  public File getWorkingDir() {
+    return myWorkingDir;
+  }
+
+  public boolean isEmpty() {
+    return isEmptyDir(myWorkingDir);
+  }
+
+  public boolean isBookmark(@NotNull String branch) throws VcsException {
+    if (branches().call().keySet().contains(branch))
+      return false;
+    return bookmarks().call().keySet().contains(branch);
+  }
+
+  public void resetBookmarks() {
+    File dotHg = new File(getWorkingDir(), ".hg");
+    FileUtil.delete(new File(dotHg, "bookmarks"));
+    FileUtil.delete(new File(dotHg, "bookmarks.current"));
+  }
+
+  public PurgeCommand purge() {
+    return new PurgeCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir);
+  }
+
+  @NotNull
+  public CommandResult runCommand(@NotNull String command, @NotNull String... args) throws VcsException {
+    return new FreeStyleCommand(myCommandSettingsFactory.create(), myHgPath, myWorkingDir, command, args).call();
+  }
+
+  public String getHgPath() {
+    return myHgPath;
+  }
+
+  @NotNull
+  public List<String> listFiles(@NotNull String revision) throws VcsException {
+    List<FileStatus> fileStatuses = status()
+            .fromRevision(revision)
+            .toRevision(revision)
+            .hideStatus()
+            .showAllFiles()
+            .call();
+    List<String> files = new ArrayList<>(fileStatuses.size());
+    for (FileStatus fileStatus : fileStatuses)
+      files.add(fileStatus.getPath());
+    return files;
+  }
+
+  @NotNull
+  public String getWorkingDirRevision() throws VcsException {
+    List<String> workingDirParents = parents().call();
+    if (workingDirParents.isEmpty())
+      return LogCommand.ZERO_PARENT_SHORT_ID;//'hg id' shows zeroid when a working dir has no parents
+    //if a working dir is in an uncommitted merge state, choose the first parent
+    return workingDirParents.get(0);
+  }
+
+  public boolean containsRevision(@NotNull String revision) {
+    return containsRevision(new ChangeSet(revision));
+  }
+
+  public boolean containsRevision(@NotNull ChangeSet cset) {
+    try {
+      id().revision(cset).inLocalRepository().call();
+      return true;
+    } catch (VcsException e) {
+      return false;
+    }
+  }
+
+  public boolean isValidRepository() {
+    // need better way to check that repository copy is ok
+    return myWorkingDir.isDirectory() && new File(myWorkingDir, ".hg").isDirectory();
+  }
+
+  public void setDefaultPath(@NotNull String defaultPath) throws VcsException {
+    if (defaultPath.contains("\n") || defaultPath.contains("\r"))
+      throw new VcsException("Newline in repository url '" + defaultPath + "'");
+    try {
+      File hgrc = new File(new File(myWorkingDir, ".hg"), "hgrc");
+      String content = "%include " + Constants.TEAMCITY_HG_CONFIG_FILE_NAME + "\n\n[paths]\ndefault = " + defaultPath;
+      FileUtil.writeFileAndReportErrors(hgrc, content);
+    } catch (IOException e) {
+      throw new VcsException(e);
+    }
+  }
+
+  public void setTeamCityConfig(@NotNull String configContent) throws VcsException {
+    try {
+      File teamcityConfig = new File(new File(myWorkingDir, ".hg"), Constants.TEAMCITY_HG_CONFIG_FILE_NAME);
+      FileUtil.writeFileAndReportErrors(teamcityConfig, configContent);
+    } catch (IOException e) {
+      throw new VcsException(e);
+    }
+  }
+
+  public boolean hasSubreposAtRevision(@NotNull String revision) {
+    return !getSubrepositories(new ChangeSet(revision)).isEmpty();
+  }
+
+  public boolean hasSubreposAtRevision(@NotNull ChangeSet cset) {
+    return !getSubrepositories(cset).isEmpty();
+  }
+
+  public Map<String, SubRepo> getSubrepositories(@NotNull String revision) {
+    return getSubrepositories(new ChangeSet(revision));
+  }
+
+  public List<HgSubrepoConfigChange> getSubrepoConfigChanges(@NotNull String revision) throws VcsException {
+    if (containsSubrepoConfigChange(revision)) {
+      List<String> parents = parents().ofRevision(revision).call();
+      return getSubrepoConfigChanges(revision, parents);
+    }
+    return emptyList();
+  }
+
+  public List<HgSubrepoConfigChange> getSubrepoConfigChanges(@NotNull ChangeSet cset) {
+    if (containsSubrepoConfigChange(cset)) {
+      List<String> parents = new ArrayList<>();
+      for (ChangeSetRevision p : cset.getParents()) {
+        parents.add(p.getId());
+      }
+      return getSubrepoConfigChanges(cset.getId(), parents);
+    }
+    return emptyList();
+  }
+
+  public List<HgSubrepoConfigChange> getSubrepoConfigChanges(@NotNull String fromRevision, @NotNull String toRevision) {
+    return getSubrepoConfigChanges(toRevision, asList(fromRevision));
+  }
+
+  public List<HgSubrepoConfigChange> getSubrepoConfigChanges(@NotNull String revision, @NotNull List<String> parentRevisions) {
+    Map<String, SubRepo> curSubrepos = getSubrepositories(revision);
+    List<Map<String, SubRepo>> prevSubrepos = new ArrayList<>();
+    for (String parentRevision : parentRevisions) {
+      prevSubrepos.add(getSubrepositories(parentRevision));
+    }
+    return getSubrepoConfigChanges(revision, prevSubrepos, curSubrepos);
+
+  }
+
+  private List<HgSubrepoConfigChange> getSubrepoConfigChanges(@NotNull String mainRepoRevision,
+                                                              @NotNull List<Map<String, SubRepo>> prevSubrepos,
+                                                              @NotNull Map<String, SubRepo> curSubrepos) {
+    List<HgSubrepoConfigChange> configChanges = new ArrayList<>();
+    for (Map.Entry<String, SubRepo> e : curSubrepos.entrySet()) {
+      String path = e.getKey();
+      SubRepo curSubrepo = e.getValue();
+      List<SubRepo> prevs = new ArrayList<>();
+      for (Map<String, SubRepo> prev : prevSubrepos) {
+        SubRepo prevSubrepo = prev.remove(path);
+        if (prevSubrepo == null) //no subrepo at this path in previous revision
+          continue;
+        if (prevSubrepo.equals(curSubrepo)) //subrepo configuration doesn't change since previous revision
+          continue;
+        prevs.add(prevSubrepo);
+      }
+      configChanges.add(new HgSubrepoConfigChange(mainRepoRevision, e.getKey(), prevs, curSubrepo));
+    }
+    for (Map<String, SubRepo> prev : prevSubrepos) {
+      for (Map.Entry<String, SubRepo> e : prev.entrySet()) {
+        configChanges.add(new HgSubrepoConfigChange(mainRepoRevision, e.getKey(), e.getValue(), null));
+      }
+    }
+    return configChanges;
+  }
+
+  private boolean containsSubrepoConfigChange(@NotNull ChangeSet cset) {
+    for (FileStatus f : cset.getModifiedFiles()) {
+      if (containsSubrepoConfigChange(f))
+        return true;
+    }
+    return false;
+  }
+
+  private boolean containsSubrepoConfigChange(@NotNull String revision) throws VcsException {
+    List<FileStatus> changedFiles = status()
+            .fromRevision(revision)
+            .toRevision(revision)
+            .showAllFiles()
+            .call();
+    for (FileStatus f : changedFiles) {
+      if (containsSubrepoConfigChange(f))
+        return true;
+    }
+    return false;
+  }
+
+  private boolean containsSubrepoConfigChange(@NotNull FileStatus f) {
+    return f.getPath().equals(".hgsubstate");
+  }
+
+  //path->subrepo
+  @NotNull
+  public Map<String, SubRepo> getSubrepositories(@NotNull final ChangeSet cset) {
+    final String revId = cset.getId();
+
+    Map<String, SubRepo> subrepos = mySubreposCache.get(revId);
+    if (subrepos != null) {
+      return new HashMap<>(subrepos);
+    }
+
+    File catDir = null;
+    final CatCommand cc = cat().setRevId(revId).files(asList(".hgsub", ".hgsubstate")).checkForFailure(false);
+    try {
+      catDir = cc.call();
+      subrepos = HgSubs.readSubrepositories(new File(catDir, ".hgsub"), new File(catDir, ".hgsubstate"));
+      mySubreposCache.put(revId, subrepos);
+      return new HashMap<>(subrepos);
+    } catch (VcsException e) {
+      return emptyMap();
+    } finally {
+      deleteDir(catDir, Loggers.VCS);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return myWorkingDir.getAbsolutePath();
+  }
+
+  @NotNull
+  public static String shortId(@NotNull final String s) {
+    if (s.length() > 12)
+      return s.substring(0, 12);
+    return s;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgRepoFactory.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+public interface HgRepoFactory {
+
+  @NotNull
+  HgRepo createRepo(@NotNull HgVcsRoot root, @NotNull File workingDir) throws VcsException;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgSubrepoConfigChange.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static java.util.Arrays.asList;
+
+/**
+ * A change in subrepo configuration
+ */
+public class HgSubrepoConfigChange {
+
+  private final String myMainRepoRevision;
+  private final String myPath;
+  private final SubRepo myCurrent;
+  private final List<SubRepo> myPrevious;
+
+  public HgSubrepoConfigChange(@NotNull String mainRepoRevision,
+                               @NotNull String path,
+                               @Nullable SubRepo previous,
+                               @Nullable SubRepo current) {
+    this(mainRepoRevision, path, previous != null ? asList(previous) : new ArrayList<>(), current);
+  }
+
+  public HgSubrepoConfigChange(@NotNull String mainRepoRevision,
+                               @NotNull String path,
+                               @NotNull List<SubRepo> previous,
+                               @Nullable SubRepo current) {
+    myMainRepoRevision = mainRepoRevision;
+    myPath = path;
+    myPrevious = previous;
+    myCurrent = current;
+  }
+
+  @NotNull
+  public String getMainRepoRevision() {
+    return myMainRepoRevision;
+  }
+
+  @NotNull
+  public String getPath() {
+    return myPath;
+  }
+
+  @NotNull
+  public List<SubRepo> getPrevious() {
+    return myPrevious;
+  }
+
+  public SubRepo getCurrent() {
+    return myCurrent;
+  }
+
+  public boolean subrepoRemoved() {
+    return myCurrent == null && !myPrevious.isEmpty();
+  }
+
+  public boolean subrepoAdded() {
+    return myCurrent != null && myPrevious.isEmpty();
+  }
+
+  public boolean subrepoUrlChanged() {
+    if (myCurrent == null)
+      return false;
+    if (myPrevious.isEmpty())
+      return false;
+    for (SubRepo sr : myPrevious) {
+      if (!myCurrent.url().equals(sr.url()))
+        return true;
+    }
+    return false;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgSubs.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.util.StringUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+import static java.util.Collections.emptyMap;
+
+/**
+ * Created 13.01.14 19:51
+ *
+ * @author Eugene Petrenko (eugene.petrenko@jetbrains.com)
+ */
+public class HgSubs {
+
+  @NotNull
+  public static Map<String, SubRepo> readSubrepositories(@NotNull final File hgsub,
+                                                         @NotNull final File hgsubstate) {
+    if (!hgsub.exists() || !hgsubstate.exists()) {
+      return emptyMap();
+    }
+
+    final Map<String, String> path2repo;
+    final Map<String, String> path2revision;
+    try {
+      path2repo = readHgsub(hgsub);
+      path2revision = readHgsubstate(hgsubstate);
+    } catch (IOException e) {
+      return emptyMap();
+    }
+
+    return readSubrepositories(path2repo, path2revision);
+  }
+
+  @NotNull
+  public static Map<String, SubRepo> readSubrepositories(@Nullable final String hgsubText,
+                                                         @Nullable final String hgsubstateText) {
+
+    if (hgsubstateText == null || hgsubText == null) return emptyMap();
+
+    return readSubrepositories(
+            readHgsub(Arrays.asList(StringUtil.splitByLines(hgsubText))),
+            readHgsubstate(Arrays.asList(StringUtil.splitByLines(hgsubstateText)))
+    );
+  }
+
+  @NotNull
+  private static Map<String, SubRepo> readSubrepositories(@NotNull final Map<String, String> path2repo,
+                                                          @NotNull final Map<String, String> path2revision) {
+    final Map<String, SubRepo> result = new HashMap<>();
+    for (Map.Entry<String, String> entry : path2repo.entrySet()) {
+      final String path = entry.getKey();
+      final String url = entry.getValue();
+      final String revision = path2revision.get(path);
+      if (revision != null)
+        result.put(path, new SubRepo(path, url, revision));
+    }
+    return result;
+  }
+
+  @NotNull
+  /*returns map: relative path -> repository url */
+  private static Map<String, String> readHgsub(@NotNull final File hgsub) throws IOException {
+    return readHgsub(FileUtil.readFile(hgsub));
+  }
+
+  @NotNull
+  /*returns map: relative path -> repository url */
+  private static Map<String, String> readHgsub(@NotNull final Collection<String> lines) {
+    Map<String, String> result = new HashMap<>();
+    for (String line : lines) {
+      String[] parts = line.split(" = ");
+      if (parts.length == 2)
+        result.put(parts[0], parts[1]);
+    }
+    return result;
+  }
+
+
+  @NotNull
+  /*returns map: relative path -> revision */
+  private static Map<String, String> readHgsubstate(@NotNull final File hgsubstate) throws IOException {
+    return readHgsubstate(FileUtil.readFile(hgsubstate));
+  }
+
+  @NotNull
+  /*returns map: relative path -> revision */
+  private static Map<String, String> readHgsubstate(@NotNull final Collection<String> lines) {
+    final Map<String, String> result = new HashMap<>();
+    for (String line : lines) {
+      String[] parts = line.split(" ");
+      if (parts.length == 2)
+        result.put(parts[1], parts[0]);
+    }
+    return result;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/HgVersion.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.ParseHgVersionException;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author dmitry.neverov
+ */
+public class HgVersion implements Comparable<HgVersion> {
+
+  private static final String PREFIX = "Mercurial Distributed SCM (version ";
+
+  private final int myMajor;
+  private final int myMinor;
+  private final int myThird;
+
+
+  public HgVersion(int major, int minor, int third) {
+    myMajor = major;
+    myMinor = minor;
+    myThird = third;
+  }
+
+
+  public static HgVersion parse(@NotNull final String version) throws ParseHgVersionException {
+    Parser p = new Parser(version);
+    if (!p.skipString(PREFIX))
+      throw new ParseHgVersionException(version);
+    int major = p.readInt();
+    p.skipString(".");
+    int minor = p.readInt();
+    int third = p.skipString(".") ? p.readInt() : 0;
+    return new HgVersion(major, minor, third);
+  }
+
+
+  public boolean isEqualsOrGreaterThan(HgVersion other) {
+    return compareTo(other) >= 0;
+  }
+
+
+  public boolean isLessThan(@NotNull HgVersion other) {
+    return compareTo(other) < 0;
+  }
+
+
+  @Override
+  public String toString() {
+    return myMajor + "." + myMinor + "." + myThird;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    return (o instanceof HgVersion) && this.compareTo((HgVersion) o) == 0;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = myMajor;
+    result = 31 * result + myMinor;
+    result = 31 * result + myThird;
+    return result;
+  }
+
+  public int compareTo(HgVersion other) {
+    int d = myMajor - other.myMajor;
+    if (d != 0)
+      return d;
+
+    d = myMinor - other.myMinor;
+    if (d != 0)
+      return d;
+
+    return myThird - other.myThird;
+  }
+
+
+  private static final class Parser {
+
+    private final String myString;
+    private int myIndex = 0;
+
+    Parser(@NotNull String string) {
+      myString = string;
+    }
+
+
+    boolean skipString(String str) {
+      if (myIndex == myString.length() || myIndex + str.length() > myString.length())
+        return false;
+      String substr = myString.substring(myIndex, myIndex + str.length());
+      if (substr.equals(str)) {
+        myIndex = myIndex + str.length();
+        return true;
+      } else {
+        return false;
+      }
+    }
+
+
+    int readInt() {
+      int result = 0;
+      while (myIndex < myString.length() && Character.isDigit(myString.codePointAt(myIndex))) {
+        result = result * 10 + Character.digit(myString.codePointAt(myIndex), 10);
+        myIndex++;
+      }
+      return result;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialClasspathTemplate.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+
+import static com.intellij.openapi.util.io.FileUtil.createTempFile;
+import static com.intellij.openapi.util.io.FileUtil.delete;
+
+/**
+* Created 25.02.14 11:29
+*
+* @author Eugene Petrenko (eugene.petrenko@jetbrains.com)
+*/
+public class MercurialClasspathTemplate implements MercurialTemplate {
+  private final String myResourcePath;
+  private final String myTmpFileSuffix;
+
+  public MercurialClasspathTemplate(@NotNull final String resourcePath,
+                                    @NotNull final String tmpFileSuffix) {
+    myResourcePath = resourcePath;
+    myTmpFileSuffix = tmpFileSuffix;
+  }
+
+  @NotNull
+  public <T> T withTemplate(@NotNull final WithTemplate<T> action) throws VcsException {
+    //TODO: we may pack plugin and use jetbrains.buildServer.web.openapi.PluginDescriptor.getPluginRoot()
+    //TODO: to get path to existing file
+    final File template = copyTemplate();
+    try {
+      return action.action(template);
+    } finally {
+      delete(template);
+    }
+  }
+
+  private File copyTemplate() throws VcsException {
+    try {
+      final File template = createTempFile("teamcity", myTmpFileSuffix);
+      FileUtil.copyResource(getClass(), myResourcePath, template);
+      return template;
+    } catch (IOException e) {
+      throw new VcsException("Cannot create mercurial log template", e);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialProgress.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import org.jetbrains.annotations.NotNull;
+
+public interface MercurialProgress {
+
+  void progressStarted(@NotNull String progressMessage);
+
+  void progressFinished(@NotNull String progressMessage);
+
+  void reportProgress(@NotNull String progressMessage);
+
+  void reportProgress(float percentage, @NotNull String stage);
+
+  MercurialProgress NO_OP = new MercurialProgress() {
+    public void progressStarted(@NotNull String progressMessage) {
+    }
+    public void progressFinished(@NotNull String progressMessage) {
+    }
+    public void reportProgress(@NotNull String progressMessage) {
+    }
+    public void reportProgress(float percentage, @NotNull String stage) {
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialTemplate.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+/**
+ * Created 25.02.14 11:51
+ *
+ * @author Eugene Petrenko (eugene.petrenko@jetbrains.com)
+ */
+public interface MercurialTemplate {
+  @NotNull
+  <T> T withTemplate(@NotNull WithTemplate<T> action) throws VcsException;
+
+  interface WithTemplate<T> {
+    @NotNull
+    T action(@NotNull final File template) throws VcsException;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MercurialVcsOperationProgress.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.vcs.VcsOperationProgress;
+import org.jetbrains.annotations.NotNull;
+
+public class MercurialVcsOperationProgress implements MercurialProgress {
+
+  private final VcsOperationProgress myProgress;
+  private String myPrevMessage;
+  private int myPrevPercents;
+
+  public MercurialVcsOperationProgress(@NotNull VcsOperationProgress progress) {
+    myProgress = progress;
+  }
+
+  public void progressStarted(@NotNull String progressMessage) {
+    reportProgress(progressMessage);
+  }
+
+  public void progressFinished(@NotNull String progressMessage) {
+    reportProgress(progressMessage);
+  }
+
+  public void reportProgress(@NotNull String progressMessage) {
+    myProgress.reportProgress(progressMessage);
+  }
+
+  public void reportProgress(float percentage, @NotNull String stage) {
+    if (percentage < 0) {
+      resetPrevProgress();
+      myProgress.reportProgress(stage);
+    } else {
+      int percents = (int) Math.floor(percentage * 100);
+      if (!isDuplicate(stage, percents)) {
+        myProgress.reportProgress(stage + " " + percents + "%");
+        updatePrevProgress(stage, percents);
+      }
+    }
+  }
+
+  private void resetPrevProgress() {
+    myPrevMessage = null;
+    myPrevPercents = -1;
+  }
+
+  private boolean isDuplicate(@NotNull String message, int percents) {
+    return message.equals(myPrevMessage) && percents == myPrevPercents;
+  }
+
+  private void updatePrevProgress(@NotNull String message, int percents) {
+    myPrevMessage = message;
+    myPrevPercents = percents;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManager.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author dmitry.neverov
+ */
+public interface MirrorManager {
+
+  /**
+   * Get directory of local mirror repository for specified url, if directory is not exists it is created
+   * @param url url of interest
+   * @return see above
+   */
+  @NotNull
+  File getMirrorDir(@NotNull final String url);
+
+  /**
+   * Get all local mirror repository dirs
+   * @return see above
+   */
+  @NotNull
+  List<File> getMirrors();
+
+  long getLastUsedTime(@NotNull final File mirrorDir);
+
+  /**
+   * Forget specified dir. After call to this method with non-empty dir,
+   * all urls which were mapped to this dir will be mapped to another.
+   * If dir is empty, subsequent call getMirrorDir(dir) will return the
+   * same dir.
+   *
+   * @param dir dir of interest
+   */
+  void forgetDir(@NotNull final File dir);
+
+  @NotNull
+  Map<String, File> getMappings();
+
+  void lockDir(@NotNull File dir);
+
+  void unlockDir(@NotNull File dir);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/MirrorManagerImpl.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,386 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import com.intellij.openapi.diagnostic.Logger;
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.util.Hash;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import static jetbrains.buildServer.util.FileUtil.isEmptyDir;
+
+/**
+ * Manages local mirrors of remote repositories.
+ * Each unique url get unique local mirror. Each mirror is used for one url only.
+ * @author dmitry.neverov
+ */
+public final class MirrorManagerImpl implements MirrorManager {
+
+  private static Logger LOG = Logger.getInstance(MirrorManagerImpl.class.getName());
+  private static final String MIRROR_DIR_PREFIX = "hg_";
+  private static final String MAPPING_FILE_NAME = "map";
+
+  private final ReadWriteLock myLock = new ReentrantReadWriteLock();
+  private final File myRootDir;
+  /*Only one thread read or write to this file, it is protected by myLock.writeLock()*/
+  private final File myMappingFile;
+  /*Protected by myLock*/
+  private final Map<String, File> myMirrors = new HashMap<>();
+  private HashCalculator myHash = new StandartHash();
+
+  private final ConcurrentMap<String, Lock> myDirLocks = new ConcurrentHashMap<>();
+
+  public MirrorManagerImpl(@NotNull PluginConfig config) {
+    myRootDir = config.getCachesDir();
+    myMappingFile = new File(myRootDir, MAPPING_FILE_NAME);
+    readMappingFromFile();
+  }
+
+
+  /**
+   * Get directory of local mirror repository for specified url, if directory is not exists it is created
+   * @param url url of interest
+   * @return see above
+   */
+  @NotNull
+  public File getMirrorDir(@NotNull final String url) {
+    File result = getMirrorDirWithLock(url);
+    if (result == null) {
+      result = createDirFor(url);
+    }
+    updateLastUsedTime(result);
+    return result;
+  }
+
+
+  /**
+   * Get all local mirror repository dirs
+   * @return see above
+   */
+  @NotNull
+  public List<File> getMirrors() {
+    myLock.readLock().lock();
+    try {
+      return new ArrayList<>(myMirrors.values());
+    } finally {
+      myLock.readLock().unlock();
+    }
+  }
+
+
+  @NotNull
+  public Map<String, File> getMappings() {
+    myLock.readLock().lock();
+    try {
+      return new HashMap<>(myMirrors);
+    } finally {
+      myLock.readLock().unlock();
+    }
+  }
+
+  public void lockDir(@NotNull final File dir) {
+    lockFor(dir).lock();
+  }
+
+  public void unlockDir(@NotNull final File dir) {
+    lockFor(dir).unlock();
+  }
+
+  private Lock lockFor(final File dir) {
+    String path = dir.getAbsolutePath();
+    Lock lock = myDirLocks.get(path);
+    if (lock == null) {
+      lock = new ReentrantLock();
+      Lock curLock = myDirLocks.putIfAbsent(path, lock);
+      if (curLock != null)
+        lock = curLock;
+    }
+    return lock;
+  }
+
+  /**
+   * Forget specified dir. After call to this method with non-empty dir,
+   * all urls which were mapped to this dir will be mapped to another.
+   * If dir is empty, subsequent call getMirrorDir(dir) will return the
+   * same dir.
+   *
+   * @param dir dir of interest
+   */
+  public void forgetDir(@NotNull final File dir) {
+    myLock.writeLock().lock();
+    try {
+      removeMappingsToDir(dir);
+      saveMappingToFile();
+    } finally {
+      myLock.writeLock().unlock();
+    }
+  }
+
+  private void removeMappingsToDir(@NotNull final File dir) {
+    Set<String> keysToRemove = getUrlsMappedToDir(dir);
+    for (String key : keysToRemove) {
+      myMirrors.remove(key);
+    }
+  }
+
+  private Set<String> getUrlsMappedToDir(@NotNull final File dir) {
+    Set<String> urlsMappedToDir = new HashSet<>();
+    for (Map.Entry<String, File> entry : myMirrors.entrySet()) {
+      File f = entry.getValue();
+      if (f.equals(dir))
+        urlsMappedToDir.add(entry.getKey());
+    }
+    return urlsMappedToDir;
+  }
+
+
+  //for tests only
+  void setHashCalculator(HashCalculator hash) {
+    myHash = hash;
+  }
+
+
+  private File createDirFor(String url) {
+    File result;
+    myLock.writeLock().lock();
+    try {
+      File mirrorDir = getUniqueDir(url);
+      result = saveMappingIfAbsent(url, mirrorDir);
+    } finally {
+      myLock.writeLock().unlock();
+    }
+    if (!result.exists()) {
+      result.mkdirs();
+    }
+    return result;
+  }
+
+
+  private File getMirrorDirWithLock(String url) {
+    myLock.readLock().lock();
+    try {
+      return myMirrors.get(url);
+    } finally {
+      myLock.readLock().unlock();
+    }
+  }
+
+
+  //should be called with myLock.writeLock() held
+  private File saveMappingIfAbsent(String url, File mirrorDir) {
+    File existing = myMirrors.get(url);
+    if (existing != null) {
+      return existing;
+    } else {
+      myMirrors.put(url, mirrorDir);
+      saveMappingToFile();
+      return mirrorDir;
+    }
+  }
+
+
+  private File getUniqueDir(String url) {
+    myLock.readLock().lock();
+    try {
+      String dirName = MIRROR_DIR_PREFIX + hash(normalize(url));
+      File result = PathUtil.getCanonicalFile(new File(myRootDir, dirName));
+      while (isUsedForOtherUrl(result, url) || !isEmptyDir(result)) {
+        dirName = MIRROR_DIR_PREFIX + hash(result.getName());
+        result = PathUtil.getCanonicalFile(new File(myRootDir, dirName));
+      }
+      return result;
+    } finally {
+      myLock.readLock().unlock();
+    }
+  }
+
+
+  private boolean isUsedForOtherUrl(File repositoryDir, String url) {
+    myLock.readLock().lock();
+    try {
+      for (Map.Entry<String, File> mirror : myMirrors.entrySet()) {
+        String mirrorUrl = mirror.getKey();
+        File mirrorDir = mirror.getValue();
+        if (mirrorDir.equals(repositoryDir) && !mirrorUrl.equals(url)) {
+          return true;
+        }
+      }
+      return false;
+    } finally {
+      myLock.readLock().unlock();
+    }
+  }
+
+
+  private String hash(String value) {
+    return String.valueOf(myHash.calc(value));
+  }
+
+
+  private static String normalize(final String path) {
+    String normalized = PathUtil.normalizeSeparator(path);
+    if (path.endsWith("/")) {
+      return normalized.substring(0, normalized.length()-1);
+    }
+    return normalized;
+  }
+
+
+  private void readMappingFromFile() {
+    myLock.writeLock().lock();
+    try {
+      LOG.debug("Parse mapping file " + myMappingFile.getAbsolutePath());
+      for (String line : readLines()) {
+        int separatorIndex = line.lastIndexOf(" = ");
+        if (separatorIndex == -1) {
+          if (!line.equals(""))
+            LOG.warn("Cannot parse mapping '" + line + "', skip it.");
+        } else {
+          String url = line.substring(0, separatorIndex);
+          String dirName = line.substring(separatorIndex + 3);
+          File repositoryDir = PathUtil.getCanonicalFile(new File(myRootDir, dirName));
+          if (isUsedForOtherUrl(repositoryDir, url)) {
+            LOG.error("Skip mapping " + line + ": " + dirName + " is used for url other than " + url);
+          } else {
+            myMirrors.put(url, PathUtil.getCanonicalFile(new File(myRootDir, dirName)));
+          }
+        }
+      }
+    } finally {
+      myLock.writeLock().unlock();
+    }
+  }
+
+  /*Should be called with myLock.writeLock() held*/
+  private List<String> readLines() {
+    if (myMappingFile.exists()) {
+      try {
+        return FileUtil.readFile(myMappingFile);
+      } catch (IOException e) {
+        LOG.error("Error while reading a mapping file at " + myMappingFile.getAbsolutePath() + " starting with empty mapping", e);
+        return new ArrayList<>();
+      }
+    } else {
+      LOG.debug("No mapping file found at " + myMappingFile.getAbsolutePath() + " starting with empty mapping");
+      File parentDir = myMappingFile.getParentFile();
+      if (!parentDir.exists() && !parentDir.mkdirs()) {
+        LOG.error("Cannot create local mirrors dir at " + parentDir.getAbsolutePath());
+      } else {
+        try {
+          if (!myMappingFile.createNewFile())
+            LOG.warn("Someone else creates a mapping file " + myMappingFile.getAbsolutePath() + ", will use it");
+        } catch (IOException e) {
+          LOG.error("Cannot create a mapping file at " + myMappingFile.getAbsolutePath(), e);
+        }
+      }
+      return new ArrayList<>();
+    }
+  }
+
+
+  private void saveMappingToFile() {
+    myLock.writeLock().lock();
+    try {
+      StringBuilder sb = new StringBuilder();
+      for (Map.Entry<String, File> mirror : myMirrors.entrySet()) {
+        String url = mirror.getKey();
+        String dir = mirror.getValue().getName();
+        sb.append(url).append(" = ").append(dir).append("\n");
+      }
+      FileUtil.writeFile(myMappingFile, sb.toString());
+    } finally {
+      myLock.writeLock().unlock();
+    }
+  }
+
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    myLock.readLock().lock();
+    try {
+      Iterator<Map.Entry<String, File>> iter = myMirrors.entrySet().iterator();
+      while (iter.hasNext()) {
+        Map.Entry<String, File> entry = iter.next();
+        sb.append("[").append(entry.getKey()).append("]").append("->").append(entry.getValue().getAbsolutePath());
+        if (iter.hasNext())
+          sb.append("\n");
+      }
+    } finally {
+      myLock.readLock().unlock();
+    }
+    return sb.toString();
+  }
+
+  public long getLastUsedTime(@NotNull final File mirrorDir) {
+    File dotHg = new File(mirrorDir, ".hg");
+    File timestamp = new File(dotHg, "timestamp");
+    if (timestamp.exists()) {
+      try {
+        List<String> lines = FileUtil.readFile(timestamp);
+        if (lines.isEmpty())
+          return mirrorDir.lastModified();
+        else
+          return Long.parseLong(lines.get(0));
+      } catch (IOException e) {
+        return mirrorDir.lastModified();
+      }
+    } else {
+      return mirrorDir.lastModified();
+    }
+  }
+
+  private void updateLastUsedTime(@NotNull final File dir) {
+    File dotHg = new File(dir, ".hg");
+    //create timestamp only if .hg exist, otherwise subsequent clone in this directory will
+    //fail since directory is not empty
+    if (!dotHg.exists())
+      return;
+
+    lockDir(dir);
+    try {
+      File timestamp = new File(dotHg, "timestamp");
+      if (!timestamp.exists())
+        timestamp.createNewFile();
+      FileUtil.writeFileAndReportErrors(timestamp, String.valueOf(System.currentTimeMillis()));
+    } catch (IOException e) {
+      LOG.error("Error while updating timestamp in " + dir.getAbsolutePath(), e);
+    } finally {
+      unlockDir(dir);
+    }
+  }
+
+  final static class StandartHash implements HashCalculator {
+    public long calc(String value) {
+      return Hash.calc(value);
+    }
+  }
+
+  public interface HashCalculator {
+    long calc(String value);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/OS.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.serverSide.TeamCityProperties;
+
+/**
+ * Created 27.01.14 17:21
+ *
+ * @author Eugene Petrenko (eugene.petrenko@jetbrains.com)
+ */
+public class OS {
+  private static final int COMMAND_LINE_LIMIT = _COMMAND_LINE_LIMIT();
+
+  private static int _COMMAND_LINE_LIMIT() {
+    final String OS_NAME = System.getProperty("os.name").toLowerCase();
+    ///http://partmaps.org/era/unix/arg-max.html
+    if (!OS_NAME.contains("windows")) return 128 * 1024 - 1; //128kb
+
+    //http://support.microsoft.com/kb/830473/en-us
+    if (OS_NAME.matches("windows\\s+(2000|xp|nt)")) {
+      return 2047;
+    }
+
+    return 8191;
+  }
+
+  public static int getMaxCommandLineSize() {
+    return TeamCityProperties.getInteger("teamcity.mercurial.maxCommandLineSize", COMMAND_LINE_LIMIT);
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/PathUtil.java	Fri Sep 24 19:45:30 2010 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/PathUtil.java	Thu Aug 06 12:15:36 2020 +0300
@@ -1,37 +1,37 @@
-/*
- * Copyright 2000-2010 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.io.IOException;
-
-public class PathUtil {
-  @NotNull
-  public static String normalizeSeparator(@NotNull String repPath) {
-    return repPath.replace('\\', '/');
-  }
-
-  @NotNull
-  public static File getCanonicalFile(@NotNull File file) {
-    try {
-      return file.getCanonicalFile();
-    } catch (IOException e) {
-      return file.getAbsoluteFile();
-    }
-  }
-}
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+
+public class PathUtil {
+  @NotNull
+  public static String normalizeSeparator(@NotNull String repPath) {
+    return repPath.replace('\\', '/');
+  }
+
+  @NotNull
+  public static File getCanonicalFile(@NotNull File file) {
+    try {
+      return file.getCanonicalFile();
+    } catch (IOException e) {
+      return file.getAbsoluteFile();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/PluginConfig.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+/**
+ * @author dmitry.neverov
+ */
+public interface PluginConfig {
+
+  @NotNull
+  File getCachesDir();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/SubRepo.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.command.exception.WrongSubrepoUrlException;
+import jetbrains.buildServer.util.FileUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+* @author dmitry.neverov
+*/
+public class SubRepo {
+  private final String myPath;
+  private final String myUrl;
+  private final String myRevision;
+  private final VcsType myVcsType;
+
+  public SubRepo(@NotNull String path, @NotNull String url, @NotNull String revision) {
+    myPath = path;
+    myRevision = revision;
+
+    if (url.startsWith("[svn]")) {
+      myVcsType = VcsType.svn;
+      myUrl = url.substring(5).trim();
+    } else if (url.startsWith("[git]")) {
+      myVcsType = VcsType.git;
+      myUrl = url.substring(5).trim();
+    } else {
+      myVcsType = VcsType.hg;
+      myUrl = url.trim();
+    }
+  }
+
+  @NotNull
+  public String path() {
+    return myPath;
+  }
+
+  @NotNull
+  public String url() {
+    return myUrl;
+  }
+
+  @NotNull
+  public String revision() {
+    return HgRepo.shortId(myRevision);
+  }
+
+  public String fullRevision() {
+    return myRevision;
+  }
+
+  @NotNull
+  public VcsType vcsType() {
+    return myVcsType;
+  }
+
+  public boolean hasDifferentUrlThan(@NotNull SubRepo other) {
+    return !myUrl.equals(other.url());
+  }
+
+  @NotNull
+  public String resolveUrl(@NotNull final String parentRepoUrl) throws WrongSubrepoUrlException {
+    try {
+      URI parentURI = parentRepoUrl.endsWith("/") ? new URI(parentRepoUrl) : new URI(parentRepoUrl + "/");
+      URI subrepoAbsUrl = parentURI.resolve(url());
+      if (isSsh(subrepoAbsUrl) && isPathFromRoot(parentURI))
+        return getUrlWithPathFromRoot(subrepoAbsUrl);
+      return subrepoAbsUrl.toString();
+    } catch (Exception e) {
+      File parentRepoDir = new File(parentRepoUrl);
+      if (parentRepoDir.isDirectory()) {//handle windows paths
+        try {
+          return FileUtil.resolvePath(parentRepoDir, FileUtil.normalizeSeparator(url())).getCanonicalPath();
+        } catch (IOException e1) {
+          throw new WrongSubrepoUrlException(parentRepoUrl, url(), e);
+        }
+      }
+      throw new WrongSubrepoUrlException(parentRepoUrl, url(), e);
+    }
+  }
+
+  private boolean isSsh(@NotNull URI uri) {
+    return "ssh".equals(uri.getScheme());
+  }
+
+  private boolean isPathFromRoot(@NotNull URI uri) {
+    return uri.getPath() != null && uri.getPath().startsWith("//");
+  }
+
+  @NotNull
+  private String getUrlWithPathFromRoot(@NotNull URI uri) throws URISyntaxException {
+    return new URI(uri.getScheme(),
+            uri.getUserInfo(),
+            uri.getHost(),
+            uri.getPort(),
+            "/" + uri.getPath(),
+            uri.getQuery(),
+            uri.getFragment()).toString();
+  }
+
+  @Override
+  public String toString() {
+    return myPath + " = " + myUrl + "#" + myRevision;
+  }
+
+  public enum VcsType {
+    hg(Constants.VCS_NAME), git("jetbrains.git"), svn("svn")
+    ;
+
+    private final String myVcsPluginName;
+
+    VcsType(@NotNull String vcsPluginName) {
+      myVcsPluginName = vcsPluginName;
+    }
+
+    @NotNull
+    public String getVcsPluginName() {
+      return myVcsPluginName;
+    }
+
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o)
+      return true;
+    if (!(o instanceof SubRepo))
+      return false;
+
+    SubRepo subRepo = (SubRepo) o;
+
+    if (!myPath.equals(subRepo.myPath))
+      return false;
+    if (!myRevision.equals(subRepo.myRevision))
+      return false;
+    if (!myUrl.equals(subRepo.myUrl))
+      return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = myPath.hashCode();
+    result = 31 * result + myUrl.hashCode();
+    result = 31 * result + myRevision.hashCode();
+    return result;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/AddRemoveCommand.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+public class AddRemoveCommand extends BaseCommand {
+
+  public AddRemoveCommand(@NotNull CommandSettings commandSettings,
+                          @NotNull String hgPath,
+                          @NotNull File workingDir) {
+    super(commandSettings, hgPath, workingDir);
+  }
+
+  public void call() throws VcsException {
+    MercurialCommandLine cmd = createCommandLine();
+    cmd.addParameter("addremove");
+    runCommand(cmd);
+  }
+
+  @NotNull
+  @Override
+  protected String getDescription() {
+    return "hg addremove";
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ArchiveCommand.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.util.FileUtil;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ArchiveCommand extends AuthCommand {
+  private File myDestination;
+  private String myToId;
+  private String myType = "files";
+  private List<String> myIncludeRules = new ArrayList<>();
+
+  public ArchiveCommand(@NotNull CommandSettings commandSettings,
+                        @NotNull String hgPath,
+                        @NotNull File workingDir,
+                        @NotNull AuthSettings authSettings) {
+    super(commandSettings, hgPath, workingDir, authSettings);
+  }
+
+  public ArchiveCommand destination(@NotNull File destination) {
+    myDestination = destination;
+    return this;
+  }
+
+  public ArchiveCommand revision(@NotNull ChangeSet cset) {
+    myToId = cset.getId();
+    return this;
+  }
+
+  public ArchiveCommand type(@NotNull String type) {
+    myType = type;
+    return this;
+  }
+
+  public boolean addIncludePathRule(@NotNull String rule) {
+    final MercurialCommandLine cmd = createCmd();
+
+    String pathRule = "path:" + rule;
+    final int cmdSize = cmd.getCommandLineLength();
+    if (cmdSize + pathRule.length() + 3 + (myIncludeRules.isEmpty() ? 0 : "-I ".length()) > myCommandSettings.getMaxCommandLineSize()) {
+      return false;
+    }
+
+    myIncludeRules.add(pathRule);
+    return true;
+  }
+
+  public void call() throws VcsException {
+    if (myDestination == null)
+      throw new IllegalStateException("Destination dir must be specified");
+    MercurialCommandLine cli = createCmd();
+
+    runCommand(cli, myCommandSettings.setFailWhenStderrNotEmpty(true));
+    deleteHgArchival();
+  }
+
+  @NotNull
+  private MercurialCommandLine createCmd() {
+    final MercurialCommandLine cli = createCommandLine();
+
+    addHttpAuthParams(cli);
+
+    cli.addParameter("archive");
+    cli.addParameter("-t");
+    cli.addParameter(myType);
+    cli.addParameter("-r");
+
+    if (myToId != null) {
+      cli.addParameter(myToId);
+    } else {
+      cli.addParameter("tip");
+    }
+
+    for (String include : myIncludeRules) {
+      cli.addParameter("-I");
+      cli.addParameter(include);
+    }
+    cli.addParameter(myDestination.getAbsolutePath());
+
+    return cli;
+  }
+
+  /**
+   * hg archive generates .hg_archival.txt, delete it since original repository do not have such file
+   */
+  private void deleteHgArchival() {
+    if (myDestination == null)
+      throw new IllegalStateException("Destination dir must be specified");
+    File hg_arhival = new File(myDestination, ".hg_archival.txt");
+    if (hg_arhival.exists())
+      FileUtil.delete(hg_arhival);
+  }
+
+  @NotNull
+  @Override
+  protected String getDescription() {
+    return "hg archive -r " + (myToId != null ? myToId : "tip");
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/AuthCommand.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+/**
+ * Command that may require authentication
+ */
+public class AuthCommand extends VcsRootCommand {
+
+  public AuthCommand(@NotNull CommandSettings commandSettings,
+                     @NotNull String hgPath,
+                     @NotNull File workingDir,
+                     @NotNull AuthSettings authSettings) {
+    super(commandSettings, hgPath, workingDir, authSettings);
+  }
+
+  @NotNull
+  protected MercurialCommandLine createCL() {
+    MercurialCommandLine cmd = super.createCL();
+    cmd.addParameters("--config", "ui.interactive=False");
+    return cmd;
+  }
+
+  protected void addHttpAuthParams(@NotNull final MercurialCommandLine cmd) {
+    if (myAuthSettings.getUsername() == null || myAuthSettings.getPassword() == null)
+      return;
+    cmd.addParameters("--config", "auth.tc.prefix=*");
+    cmd.addParameters("--config", "auth.tc.username=" + myAuthSettings.getUsername());
+    cmd.addParameters("--config", "auth.tc.password=" + myAuthSettings.getPassword());
+    cmd.addParameters("--config", "auth.tc.schemes=http https");
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/AuthSettings.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.log.Loggers;
+import jetbrains.buildServer.serverSide.TeamCityProperties;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.net.*;
+import java.util.HashSet;
+import java.util.Set;
+
+import static com.intellij.openapi.util.text.StringUtil.isEmpty;
+import static java.util.Arrays.asList;
+
+/**
+ * @author dmitry.neverov
+ */
+public class AuthSettings {
+
+  private final static Set<String> AUTH_PROTOS = new HashSet<>(asList("http", "https", "ssh"));
+  private final String myUsername;
+  private final String myPassword;
+
+  public AuthSettings() {
+    this(null, null);
+  }
+
+  public AuthSettings(@Nullable String username, @Nullable String password) {
+    myUsername = username;
+    myPassword = password;
+  }
+
+  public String getUsername() {
+    return myUsername;
+  }
+
+  public String getPassword() {
+    return myPassword;
+  }
+
+  public String getRepositoryUrlWithCredentials(@NotNull String repositoryUrl) {
+    String preparedUrl = escapeUnsafeChars(repositoryUrl);
+    if (isRequireCredentials(preparedUrl)) {
+      if (containsCredentials(preparedUrl))
+        return preparedUrl;
+      try {
+        return createURLWithCredentials(preparedUrl);
+      } catch (MalformedURLException e) {
+        Loggers.VCS.warn("Error while parsing url " + preparedUrl, e);
+      }
+      return preparedUrl;
+    } else {
+      return preparedUrl;
+    }
+  }
+
+  @NotNull
+  private String escapeUnsafeChars(@NotNull String repositoryUrl) {
+    if (!TeamCityProperties.getBooleanOrTrue("teamcity.mercurial.identifyCommand.escapeUnsafeChars")) return repositoryUrl;
+
+    StringBuilder res = new StringBuilder();
+
+    for (char c: repositoryUrl.toCharArray()) {
+      switch (c) {
+        case '"':
+          res.append("%22");
+          break;
+        case '\'':
+          res.append("%27");
+          break;
+        case '`':
+          res.append("%60");
+          break;
+        case ' ':
+          res.append("%20");
+          break;
+        case ';':
+          res.append("%3B");
+          break;
+        default:
+          res.append(c);
+      }
+    }
+
+    return res.toString();
+  }
+
+  public String getRepositoryUrlWithHiddenPassword(@NotNull String repositoryUrl) {
+    if (isEmpty(myPassword))
+      return repositoryUrl;
+    return repositoryUrl.replace(myPassword, "******");
+  }
+
+  private boolean isRequireCredentials(@NotNull String repositoryUrl) {
+    for (String scheme : AUTH_PROTOS) {
+      if (repositoryUrl.startsWith(scheme + ":"))
+        return true;
+    }
+    return false;
+  }
+
+  private boolean containsCredentials(final String repositoryUrl) {
+    try {
+      URL url = new URL(null, repositoryUrl, new FakeStreamHandler());
+      String userInfo = url.getUserInfo();
+      return userInfo != null && userInfo.contains(":");
+    } catch (MalformedURLException e) {
+      return false;
+    }
+  }
+
+  private String createURLWithCredentials(String originalUrl) throws MalformedURLException {
+    String userInfo = createUserInfo();
+    if (!isEmpty(userInfo)) {
+      URL url = new URL(null, originalUrl, new FakeStreamHandler());
+      return url.getProtocol() + "://"
+              + userInfo + "@"
+              + url.getHost()
+              + (url.getPort() != -1 ? ":" + url.getPort() : "")
+              + url.getFile()
+              + (url.getRef() != null ? url.getRef() : "");
+    } else {
+      return originalUrl;
+    }
+  }
+
+  private String createUserInfo() {
+    String userInfo = "";
+    if (!isEmpty(myUsername)) {
+      userInfo += myUsername;
+      if (!isEmpty(myPassword)) {
+        userInfo += ":" + myPassword;
+      }
+    }
+    return getEscapedUserInfo(userInfo);
+  }
+
+  private static String getEscapedUserInfo(String userInfo) {
+    try {
+      URI uri = new URI("http", userInfo, "somewhere.com", 80, "", "", "");
+      String escapedURI = uri.toASCIIString();
+      int from = "http://".length();
+      int to = escapedURI.indexOf("somewhere.com") - 1;
+      String escapedUserInfo = escapedURI.substring(from, to);
+      escapedUserInfo = escapedUserInfo.replaceAll("&", "%26");
+      return escapedUserInfo;
+   } catch (URISyntaxException e) {
+      assert false;
+    }
+    return userInfo;
+  }
+
+  @NotNull
+  public static String escapePassword(@NotNull String password) {
+    String escaped = getEscapedUserInfo("user:" + password);
+    return escaped.substring(5);
+  }
+
+  private static class FakeStreamHandler extends URLStreamHandler {
+    @Override
+    protected URLConnection openConnection(URL u) throws IOException {
+      throw new UnsupportedOperationException();
+    }
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommand.java	Fri Sep 24 19:45:30 2010 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BaseCommand.java	Thu Aug 06 12:15:36 2020 +0300
@@ -1,82 +1,107 @@
-/*
- * Copyright 2000-2010 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import com.intellij.execution.configurations.GeneralCommandLine;
-import jetbrains.buildServer.ExecResult;
-import jetbrains.buildServer.util.StringUtil;
-import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Collections;
-import java.util.Set;
-
-/**
- * @author pavel
- */
-public class BaseCommand {
-  private Settings mySettings;
-  private String myWorkDirectory;
-
-  public BaseCommand(@NotNull final Settings settings) {
-    mySettings = settings;
-    myWorkDirectory = settings.getLocalRepositoryDir().getAbsolutePath();
-  }
-
-  public Settings getSettings() {
-    return mySettings;
-  }
-
-  /**
-   * Sets new working directory, by default working directory is taken from the Settings#getLocalRepositoryDir
-   * @param workDirectory work dir
-   */
-  public void setWorkDirectory(final String workDirectory) {
-    myWorkDirectory = workDirectory;
-  }
-
-  protected GeneralCommandLine createCommandLine() {
-    GeneralCommandLine cli = new GeneralCommandLine();
-    cli.setExePath(getSettings().getHgCommandPath());
-    cli.setWorkDirectory(myWorkDirectory);
-    cli.setPassParentEnvs(true);
-    return cli;
-  }
-
-  protected ExecResult runCommand(@NotNull GeneralCommandLine cli) throws VcsException {
-    return CommandUtil.runCommand(cli, getPrivateData());
-  }
-
-  protected ExecResult runCommand(@NotNull GeneralCommandLine cli, int executionTimeout) throws VcsException {
-    return CommandUtil.runCommand(cli, executionTimeout, getPrivateData());
-  }
-
-  protected void failIfNotEmptyStdErr(@NotNull GeneralCommandLine cli, @NotNull ExecResult res) throws VcsException {
-    if (!StringUtil.isEmpty(res.getStderr())) {
-      CommandUtil.commandFailed(cli.getCommandLineString(), res);
-    }
-  }
-
-  protected void failIfNonZeroExitCode(@NotNull GeneralCommandLine cli, @NotNull ExecResult res) throws VcsException {
-    if (res.getExitCode() != 0) {
-      CommandUtil.commandFailed(cli.getCommandLineString(), res);
-    }
-  }
-
-  public Set<String> getPrivateData() {
-    return Collections.singleton(mySettings.getPassword());
-  }
-}
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.Set;
+
+import static java.util.Collections.emptySet;
+
+/**
+ * @author pavel
+ */
+public class BaseCommand {
+
+  protected final CommandSettings myCommandSettings;
+  private final String myHgPath;
+  private final File myWorkDirectory;
+
+  public BaseCommand(@NotNull final CommandSettings commandSettings,
+                     @NotNull final String hgPath,
+                     @NotNull final File workingDir) {
+    myCommandSettings = commandSettings;
+    myHgPath = hgPath;
+    myWorkDirectory = workingDir;
+  }
+
+  public File getWorkDirectory() {
+    return myWorkDirectory;
+  }
+
+  @NotNull
+  protected MercurialCommandLine createCommandLine() {
+    MercurialCommandLine cli = createCL();
+    cli.setWorkDirectory(myWorkDirectory.getAbsolutePath());
+    return cli;
+  }
+
+  @NotNull
+  protected MercurialCommandLine createCL() {
+    final MercurialCommandLine cl = new MercurialCommandLine(getPrivateData());
+    cl.setExePath(myHgPath);
+    cl.setEnvParams(myCommandSettings.getHgEnv());
+    cl.setDescription(getDescription());
+
+    //include global arguments if any
+    cl.addParameters(myCommandSettings.getGlobalArguments());
+
+    return cl;
+  }
+
+  @NotNull
+  protected final CommandResult runCommand(@NotNull MercurialCommandLine cli) throws VcsException {
+    return runCommand(cli, myCommandSettings);
+  }
+
+  @NotNull
+  protected final CommandResult runCommand(@NotNull final MercurialCommandLine cli,
+                                           @NotNull final CommandSettings commandSettings) throws VcsException {
+
+    if (!myCommandSettings.getUseCommandlineViaFileWrapper()) {
+      cli.logCommandStarted(commandSettings.getProgress());
+      try {
+        return CommandUtil.runCommand(cli, commandSettings.setPrivateData(getPrivateData()));
+      } finally {
+        cli.logCommandFinished(commandSettings.getProgress());
+      }
+    }
+
+    return CommandUtil.runWrappedCommand(cli, commandSettings);
+  }
+
+  protected void setupExtensionsFromResource(@NotNull final MercurialCommandLine cli,
+                                             @NotNull final File tempDir,
+                                             @NotNull final String commandPy) throws VcsException{
+    CommandUtil.setupExtensionsFromResource(cli, tempDir, commandPy);
+  }
+
+  @NotNull
+  protected Set<String> getPrivateData() {
+    return emptySet();
+  }
+
+  protected String getHgPath() {
+    return myHgPath;
+  }
+
+  @NotNull
+  protected String getDescription() {
+    return "";
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BookmarksCommand.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgVersion;
+import org.jetbrains.annotations.NotNull;
+import java.io.File;
+
+
+public class BookmarksCommand extends BranchesCommand {
+
+  //hg 2.4 automatically pulls bookmarks
+  public static final HgVersion REQUIRED_HG_VERSION = new HgVersion(2, 4, 0);
+
+  public BookmarksCommand(@NotNull CommandSettings commandSettings,
+                          @NotNull String hgPath,
+                          @NotNull File workingDir,
+                          @NotNull AuthSettings authSettings) {
+    super(commandSettings, hgPath, workingDir, authSettings);
+  }
+
+  @NotNull
+  @Override
+  protected String getBranchesCommand() {
+    return "bookmarks";
+  }
+
+  @NotNull
+  @Override
+  protected String getDescription() {
+    return "hg bookmarks";
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BranchCommand.java	Thu Aug 06 12:15:36 2020 +0300
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+public class BranchCommand extends BaseCommand {
+
+  private String myBranch;
+
+  public BranchCommand(@NotNull CommandSettings commandSettings,
+                       @NotNull String hgPath,
+                       @NotNull File workingDir) {
+    super(commandSettings, hgPath, workingDir);
+  }
+
+  @NotNull
+  public BranchCommand name(String branch) {
+    myBranch = branch;
+    return this;
+  }
+
+  public void call() throws VcsException {
+    if (myBranch == null)
+      return;
+    MercurialCommandLine cmd = createCommandLine();
+    cmd.addParameter("branch");
+    cmd.addParameter(myBranch);
+    runCommand(cmd);
+  }
+
+  @NotNull
+  @Override
+  protected String getDescription() {
+    if (myBranch == null)
+      return "";
+    return "hg branch " + myBranch;
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BranchesCommand.java	Fri Sep 24 19:45:30 2010 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/BranchesCommand.java	Thu Aug 06 12:15:36 2020 +0300
@@ -1,59 +1,73 @@
-/*
- * Copyright 2000-2010 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import com.intellij.execution.configurations.GeneralCommandLine;
-import jetbrains.buildServer.ExecResult;
-import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.regex.Pattern;
-import java.util.regex.Matcher;
-
-/**
- * @author Pavel.Sher
- *         Date: 26.10.2008
- */
-public class BranchesCommand extends BaseCommand {
-  public BranchesCommand(@NotNull final Settings settings) {
-    super(settings);
-  }
-
-  /**
-   * Returns map of branch name to latest changeset in that branch
-   * @return see above
-   * @throws jetbrains.buildServer.vcs.VcsException if error occurs
-   */
-  public Map<String, ChangeSet> execute() throws VcsException {
-    GeneralCommandLine cli = createCommandLine();
-    cli.addParameter("branches");
-    ExecResult res = runCommand(cli);
-    String stdout = res.getStdout();
-    Map<String, ChangeSet> result = new HashMap<String, ChangeSet>();
-    Pattern branchPattern = Pattern.compile("(.*)[\\s]+([0-9]+:[A-Za-z0-9]+).*");
-    for (String line: stdout.split("[\r\n]+")) {
-      Matcher matcher = branchPattern.matcher(line);
-      if (matcher.matches()) {
-        String branchName = matcher.group(1).trim();
-        String changeId = matcher.group(2).trim();
-        result.put(branchName, new ChangeSet(changeId));
-      }
-    }
-    return result;
-  }
-}
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author Pavel.Sher
+ *         Date: 26.10.2008
+ */
+public class BranchesCommand extends VcsRootCommand {
+
+  public BranchesCommand(@NotNull CommandSettings commandSettings,
+                         @NotNull String hgPath,
+                         @NotNull File workingDir,
+                         @NotNull AuthSettings authSettings) {
+    super(commandSettings, hgPath, workingDir, authSettings);
+  }
+
+  /**
+   * Returns map of branch name to latest changeset in that branch
+   * @return see above
+   * @throws jetbrains.buildServer.vcs.VcsException if error occurs
+   */
+  public Map<String, String> call() throws VcsException {
+    MercurialCommandLine cli = createCommandLine();
+    cli.addParameter(getBranchesCommand());
+    CommandResult res = runCommand(cli);
+    String stdout = res.getRawStdout();
+    Map<String, String> result = new HashMap<>();
+    Pattern branchPattern = Pattern.compile("(.*)[\\s]+([0-9]+:[A-Za-z0-9]+).*");
+    for (String line: stdout.split("[\r\n]+")) {
+      Matcher matcher = branchPattern.matcher(line);
+      if (matcher.matches()) {
+        String branchName = matcher.group(1).trim();
+        String changeId = matcher.group(2).trim();
+        result.put(branchName, new ChangeSet(changeId).getId());
+      }
+    }
+    return result;
+  }
+
+  @NotNull
+  protected String getBranchesCommand() {
+    return "branches";
+  }
+
+  @NotNull
+  @Override
+  protected String getDescription() {
+    return "hg branches";
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommand.java	Fri Sep 24 19:45:30 2010 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/CatCommand.java	Thu Aug 06 12:15:36 2020 +0300
@@ -1,83 +1,152 @@
-/*
- * Copyright 2000-2010 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import com.intellij.execution.configurations.GeneralCommandLine;
-import jetbrains.buildServer.util.FileUtil;
-import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Queue;
-
-public class CatCommand extends BaseCommand {
-  private String myRevId;
-  private final static int MAX_CMD_LEN = 900;
-
-  public CatCommand(@NotNull final Settings settings) {
-    super(settings);
-  }
-
-  public void setRevId(final String revId) {
-    myRevId = revId;
-  }
-
-  public File execute(List<String> relPaths) throws VcsException {
-    File tempDir;
-    try {
-      tempDir = FileUtil.createTempDirectory("mercurial", "catresult");
-    } catch (IOException e) {
-      throw new VcsException("Unable to create temporary directory");
-    }
-    for (String path: relPaths) {
-      final File parentFile = new File(tempDir, path).getParentFile();
-      if (!parentFile.isDirectory() && !parentFile.mkdirs()) {
-        throw new VcsException("Failed to create directory: " + parentFile.getAbsolutePath());
-      }
-    }
-
-    Queue<String> paths = new LinkedList<String>(relPaths);
-    while (!paths.isEmpty()) {
-      GeneralCommandLine cli = createCommandLine(tempDir);
-      int cmdSize = cli.getCommandLineString().length();
-
-      do {
-        String path = paths.poll();
-        cli.addParameter(path);
-        cmdSize += path.length();
-      } while (cmdSize < MAX_CMD_LEN && !paths.isEmpty());
-
-      runCommand(cli);
-    }
-
-    return tempDir;
-  }
-
-  private GeneralCommandLine createCommandLine(final File tempDir) {
-    GeneralCommandLine cli = createCommandLine();
-    cli.addParameter("cat");
-    cli.addParameter("-o");
-    cli.addParameter(tempDir.getAbsolutePath() + File.separator + "%p");
-    if (myRevId != null) {
-      cli.addParameter("-r");
-      cli.addParameter(myRevId);
-    }
-    return cli;
-  }
-}
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgFileUtil;
+import jetbrains.buildServer.log.Loggers;
+import jetbrains.buildServer.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+import static java.util.Collections.singletonList;
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.HgFileUtil.deleteDir;
+
+public class CatCommand extends AuthCommand {
+  private String myRevId;
+
+  private List<String> myRelativePaths = new ArrayList<>();
+  private boolean myCheckForFailure = true;
+
+  public CatCommand(@NotNull CommandSettings commandSettings,
+                    @NotNull String hgPath,
+                    @NotNull File workingDir,
+                    @NotNull AuthSettings authSettings) {
+    super(commandSettings, hgPath, workingDir, authSettings);
+  }
+
+  @NotNull
+  public CatCommand files(@NotNull String relativePath) {
+    myRelativePaths = singletonList(relativePath);
+    return this;
+  }
+
+  @NotNull
+  public CatCommand files(@NotNull List<String> relativePaths) {
+    myRelativePaths = relativePaths;
+    return this;
+  }
+
+  @NotNull
+  public CatCommand setRevId(final String revId) {
+    myRevId = revId;
+    return this;
+  }
+
+  @NotNull
+  public CatCommand atRevision(@NotNull ChangeSet cset) {
+    myRevId = cset.getId();
+    return this;
+  }
+
+  @NotNull
+  public CatCommand checkForFailure(boolean doCheckForFailure) {
+    myCheckForFailure = doCheckForFailure;
+    return this;
+  }
+
+  @NotNull
+  public File call() throws VcsException {
+    File tempDir = null;
+    try {
+      tempDir = createTmpDir();
+      createDirectories(myRelativePaths, tempDir);
+      catFiles(myRelativePaths, myCheckForFailure, tempDir);
+      return tempDir;
+    } catch (VcsException e) {
+      deleteDir(tempDir, Loggers.VCS);
+      throw e;
+    }
+  }
+
+  @NotNull
+  private File createTmpDir() throws VcsException {
+    try {
+      return HgFileUtil.createTempDir();
+    } catch (IOException e) {
+      throw new VcsException("Unable to create temporary directory", e);
+    }
+  }
+
+  private void createDirectories(@NotNull final List<String> relPaths, @NotNull final File tempDir) throws VcsException {
+    for (String path: relPaths) {
+      File parentFile = new File(tempDir, path).getParentFile();
+      if (!parentFile.isDirectory() && !parentFile.mkdirs())
+        throw new VcsException("Failed to create directory: " + parentFile.getAbsolutePath());
+    }
+  }
+
+  private void catFiles(@NotNull final List<String> relPaths,
+                        final boolean checkFailure,
+                        @NotNull final File tempDir) throws VcsException {
+    final Queue<String> paths = new LinkedList<>(relPaths);
+    while (!paths.isEmpty()) {
+      MercurialCommandLine cli = createCommandLine(tempDir);
+      int cmdSize = cli.getCommandLineLength() + 42;
+
+      List<String> pathsForDescription = new ArrayList<>(2);
+      do {
+        String path = paths.poll();
+        if (pathsForDescription.size() < 2)
+          pathsForDescription.add(path);
+        cli.addParameter(path);
+        cmdSize += path.length() + 3; //quotes + space
+      } while (cmdSize < myCommandSettings.getMaxCommandLineSize() && !paths.isEmpty());
+
+      cli.setDescription(getDescription(pathsForDescription));
+      runCommand(cli, myCommandSettings.setCheckForFailure(checkFailure));
+    }
+  }
+
+  @NotNull
+  private String getDescription(List<String> pathsForDescription) {
+    StringBuilder description = new StringBuilder();
+    description.append("hg cat -r ").append(myRevId).append(" ");
+    for (String p : pathsForDescription) {
+      description.append(p).append(" ");
+    }
+    return description.toString();
+  }
+
+  @NotNull
+  private MercurialCommandLine createCommandLine(@NotNull final File tempDir) {
+    final MercurialCommandLine cli = createCommandLine();
+    addHttpAuthParams(cli);
+    cli.addParameter("cat");
+    cli.addParameter("-o");
+    cli.addParameter(tempDir.getAbsolutePath() + File.separator + "%p");
+    if (myRevId != null) {
+      cli.addParameter("-r");
+      cli.addParameter(myRevId);
+    }
+    return cli;
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangeSet.java	Fri Sep 24 19:45:30 2010 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangeSet.java	Thu Aug 06 12:15:36 2020 +0300
@@ -1,112 +1,127 @@
-/*
- * Copyright 2000-2010 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-/**
- * Represents Mercurial change set
- */
-public class ChangeSet extends ChangeSetRevision {
-  @NotNull private String myUser;
-  @NotNull private Date myTimestamp;
-  private String myDescription;
-  private boolean myContainsFiles;
-  private List<ChangeSetRevision> myParents;
-
-  public ChangeSet(final int revNumber, @NotNull final String id) {
-    super(revNumber, id);
-  }
-
-  /**
-   * Constructor for version in the form revnum:changeset_id or just changeset_id (in this case rev number is set to -1)
-   * @param fullVersion full changeset version as reported by hg log command
-   */
-  public ChangeSet(@NotNull final String fullVersion) {
-    super(fullVersion);
-  }
-
-  public void setUser(@NotNull final String user) {
-    myUser = user;
-  }
-
-  public void setTimestamp(@NotNull final Date timestamp) {
-    myTimestamp = timestamp;
-  }
-
-  public void setDescription(final String description) {
-    myDescription = description;
-  }
-
-  public void setContainsFiles(final boolean containsFiles) {
-    myContainsFiles = containsFiles;
-  }
-
-  public void addParent(@NotNull ChangeSetRevision rev) {
-    if (myParents == null) {
-      myParents = new ArrayList<ChangeSetRevision>();
-    }
-    myParents.add(rev);
-  }
-
-  /**
-   * Returns user who made changeset
-   * @return user who made changeset
-   */
-  @NotNull
-  public String getUser() {
-    return myUser;
-  }
-
-  /**
-   * Returns changeset timestamp
-   * @return changeset timestamp
-   */
-  @NotNull
-  public Date getTimestamp() {
-    return myTimestamp;
-  }
-
-  /**
-   * Returns changeset summary specified by user
-   * @return changeset summary
-   */
-  public String getDescription() {
-    return myDescription;
-  }
-
-  /**
-   * Returns parrents of this change set, or null if there were no parents.
-   * @return see above
-   */
-  @Nullable
-  public List<ChangeSetRevision> getParents() {
-    return myParents;
-  }
-
-  /**
-   * Returns true if this change has changed files
-   * @return see above
-   */
-  public boolean containsFiles() {
-    return myContainsFiles;
-  }
-}
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import static jetbrains.buildServer.buildTriggers.vcs.mercurial.command.HgVcsRoot.DEFAULT_BRANCH_NAME;
+
+/**
+ * Represents Mercurial change set
+ */
+public class ChangeSet extends ChangeSetRevision {
+  @NotNull private String myUser;
+  @NotNull private Date myTimestamp;
+  private String myDescription;
+  private String myBranch = DEFAULT_BRANCH_NAME;
+  private List<ChangeSetRevision> myParents = new ArrayList<>();
+  private List<FileStatus> myModifiedFiles = new ArrayList<>();
+
+  public ChangeSet(final int revNumber, @NotNull final String id) {
+    super(revNumber, id);
+  }
+
+  /**
+   * Constructor for version in the form revnum:changeset_id or just changeset_id (in this case rev number is set to -1)
+   * @param fullVersion full changeset version as reported by hg log command
+   */
+  public ChangeSet(@NotNull final String fullVersion) {
+    super(fullVersion);
+  }
+
+  public void setUser(@NotNull final String user) {
+    myUser = user;
+  }
+
+  public void setTimestamp(@NotNull final Date timestamp) {
+    myTimestamp = timestamp;
+  }
+
+  public void setDescription(final String description) {
+    myDescription = description;
+  }
+
+  public void addParent(@NotNull ChangeSetRevision rev) {
+    myParents.add(rev);
+  }
+
+  /**
+   * Returns user who made changeset
+   * @return user who made changeset
+   */
+  @NotNull
+  public String getUser() {
+    return myUser;
+  }
+
+  /**
+   * Returns changeset timestamp
+   * @return changeset timestamp
+   */
+  @NotNull
+  public Date getTimestamp() {
+    return myTimestamp;
+  }
+
+  /**
+   * Returns changeset summary specified by user
+   * @return changeset summary
+   */
+  public String getDescription() {
+    return myDescription;
+  }
+
+  /**
+   * Returns parrents of this change set
+   * @return see above
+   */
+  @NotNull
+  public List<ChangeSetRevision> getParents() {
+    return myParents;
+  }
+
+  /**
+   * Check if changeset is initial changeset (has no parents)
+   * @return true if changeset is initial changeset
+   */
+  public boolean isInitial() {
+    return getParents().isEmpty();
+  }
+
+  public void setModifiedFiles(@NotNull final List<FileStatus> files) {
+    myModifiedFiles = files;
+  }
+
+  @NotNull
+  public List<FileStatus> getModifiedFiles() {
+    return myModifiedFiles;
+  }
+
+  @NotNull
+  public String getBranch() {
+    return myBranch;
+  }
+
+  public void setBranch(@Nullable String branch) {
+    if (branch == null || branch.trim().isEmpty() || DEFAULT_BRANCH_NAME.equals(branch)) branch = DEFAULT_BRANCH_NAME;
+    myBranch = branch;
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangeSetRevision.java	Fri Sep 24 19:45:30 2010 +0400
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangeSetRevision.java	Thu Aug 06 12:15:36 2020 +0300
@@ -1,99 +1,99 @@
-/*
- * Copyright 2000-2010 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import org.jetbrains.annotations.NotNull;
-
-/**
- * @author Pavel.Sher
- */
-public class ChangeSetRevision {
-  private final int myRevNumber;
-  @NotNull
-  private final String myId;
-
-  public ChangeSetRevision(final int revNumber, @NotNull final String id) {
-    myRevNumber = revNumber;
-    myId = id;
-  }
-
-  /**
-   * Constructor for version in the form revnum:changeset_id or just changeset_id (in this case rev number is set to -1)
-   * @param fullVersion full changeset version as reported by hg log command
-   */
-  public ChangeSetRevision(@NotNull final String fullVersion) {
-    try {
-      int colon = fullVersion.indexOf(":");
-      if (colon != -1) {
-        myRevNumber = Integer.parseInt(fullVersion.substring(0, colon));
-        myId = fullVersion.substring(colon+1);
-      } else {
-        myRevNumber = -1;
-        myId = fullVersion;
-      }
-    } catch (Throwable e) {
-      throw new IllegalArgumentException(e);
-    }
-  }
-
-  /**
-   * Returns changeset revision id
-   * @return changeset revision id
-   */
-  @NotNull
-  public String getId() {
-    return myId;
-  }
-
-  /**
-   * Returns changeset revision number
-   * @return changeset revision number
-   */
-  public int getRevNumber() {
-    return myRevNumber;
-  }
-
-  /**
-   * Returns full changeset version as reported by hg log command: revnum:revid
-   * @return full changeset version as reported by hg log
-   */
-  @NotNull
-  public String getFullVersion() {
-    return myRevNumber + ":" + myId;
-  }
-
-  @Override
-  public boolean equals(final Object o) {
-    if (this == o) return true;
-    if (o == null || getClass() != o.getClass()) return false;
-
-    final ChangeSetRevision that = (ChangeSetRevision) o;
-
-    return myRevNumber == that.myRevNumber && myId.equals(that.myId);
-  }
-
-  @Override
-  public int hashCode() {
-    int result = myRevNumber;
-    result = 31 * result + myId.hashCode();
-    return result;
-  }
-
-  @Override
-  public String toString() {
-    return "change:" + myId;
-  }
-}
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author Pavel.Sher
+ */
+public class ChangeSetRevision {
+  private final int myRevNumber;
+  @NotNull
+  private final String myId;
+
+  public ChangeSetRevision(final int revNumber, @NotNull final String id) {
+    myRevNumber = revNumber;
+    myId = id;
+  }
+
+  /**
+   * Constructor for version in the form revnum:changeset_id or just changeset_id (in this case rev number is set to -1)
+   * @param fullVersion full changeset version as reported by hg log command
+   */
+  public ChangeSetRevision(@NotNull final String fullVersion) {
+    try {
+      int colon = fullVersion.indexOf(":");
+      if (colon != -1) {
+        myRevNumber = Integer.parseInt(fullVersion.substring(0, colon));
+        myId = fullVersion.substring(colon+1);
+      } else {
+        myRevNumber = -1;
+        myId = fullVersion;
+      }
+    } catch (Throwable e) {
+      throw new IllegalArgumentException(e);
+    }
+  }
+
+  /**
+   * Returns changeset revision id
+   * @return changeset revision id
+   */
+  @NotNull
+  public String getId() {
+    return myId;
+  }
+
+  /**
+   * Returns changeset revision number
+   * @return changeset revision number
+   */
+  public int getRevNumber() {
+    return myRevNumber;
+  }
+
+  /**
+   * Returns full changeset version as reported by hg log command: revnum:revid
+   * @return full changeset version as reported by hg log
+   */
+  @NotNull
+  public String getFullVersion() {
+    return myRevNumber + ":" + myId;
+  }
+
+  @Override
+  public boolean equals(final Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    final ChangeSetRevision that = (ChangeSetRevision) o;
+
+    return myRevNumber == that.myRevNumber && myId.equals(that.myId);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = myRevNumber;
+    result = 31 * result + myId.hashCode();
+    return result;
+  }
+
+  @Override
+  public String toString() {
+    return "change:" + myId;
+  }
+}
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/ChangedFilesCommand.java	Fri Sep 24 19:45:30 2010 +0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-/*
- * Copyright 2000-2010 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
-
-import com.intellij.execution.configurations.GeneralCommandLine;
-import jetbrains.buildServer.ExecResult;
-import jetbrains.buildServer.util.FileUtil;
-import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
-/**
- * @author Pavel.Sher
- */
-public class ChangedFilesCommand extends BaseCommand {
-  private String myRevId;
-
-  public ChangedFilesCommand(@NotNull final Settings settings) {
-    super(settings);
-  }
-
-  public void setRevId(@NotNull final String revId) {
-    myRevId = revId;
-  }
-
-  public List<ModifiedFile> execute() throws VcsException {
-    File styleFile;
-    try {
-      styleFile = getStyleFile();
-    } catch (IOException e) {
-      throw new VcsException("Unable to create style file: " + e.toString(), e);
-    }
-    try {
-      GeneralCommandLine cli = createCommandLine();
-      cli.addParameter("log");
-      cli.addParameter("-r");