by
Changes to 69 files · Browse files at e631d4af058e Showing diff from parent d7cd3090d424 37c3f4e1e3e1 Diff from another changeset...
|
@@ -23,4 +23,4 @@ Post Major Release:
* Increment minimum Mercurial version in tortoisehg/util/hgversion.py
* Sweep through code and remove hacks for older Mercurial releases
-* Update http://bitbucket.org/tortoisehg/stable/wiki/ReleaseNotes#matching-versions
+* Update http://bitbucket.org/tortoisehg/thg/wiki/ReleaseNotes#matching-versions
|
@@ -6,13 +6,13 @@
To clone a repository you have to run the clone dialog.
From the explorer context menu select :menuselection:`TortoiseHg... --> Clone a repository`
-or type :command:`thg clone`.
+or type :command:`thg clone`.
.. figure:: figures/clone.png
:alt: Clone dialog
Clone Dialog
-
+
:guilabel:`Source`
It is the path (or URL) of the repository that will be cloned. Use
the :guilabel:`Browse...` to choose a local folder.
@@ -39,11 +39,17 @@ To use uncompressed transfer (fast over LAN).
:guilabel:`Include patch queue`
To also clone an MQ patch repository along with the main repository.
+ It is possible to provide a patch queue name that differs from the
+ default one.
:guilabel:`Use proxy server`
To use the proxy server configured in :menuselection:`TortoiseHg... --> Global Settings --> Proxy`.
This is enabled only if a proxy is configured.
+:guilabel:`Do not verify host certificate`
+ Skip checking server certificate for https:// url (ignoring web.cacerts config).
:guilabel:`Remote command`
Specify a Mercurial command to run on the remote side.
+:guilabel:`Hg command`
+ This field displays the command that will be executed by the dialog.
From command line
-----------------
|
@@ -137,8 +137,9 @@
*TortoiseHg Integration*
-Imported Subversion changesets will display the original Subversion
-checkin number in the Repository Explorer browser.
+Imported Subversion changesets will display the original Subversion checkin
+number in the Changeset Info widget in the Revision Details task tab of the
+Workbench.
hg-git (git)
============
|
|
|
@@ -0,0 +1,150 @@ + <?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ height="256"
+ id="svg2"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="thg-git-subrepo.svg"
+ sodipodi:version="0.32"
+ version="1.0"
+ width="256">
+ <metadata
+ id="metadata3">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:title></dc:title>
+ <dc:description />
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>unsorted</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ <dc:publisher>
+ <cc:Agent
+ rdf:about="http://www.openclipart.org/">
+ <dc:title>Open Clip Art Library, Source: Wiki Commons, Source: Wikimedia Commons</dc:title>
+ </cc:Agent>
+ </dc:publisher>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title />
+ </cc:Agent>
+ </dc:creator>
+ <dc:rights>
+ <cc:Agent>
+ <dc:title />
+ </cc:Agent>
+ </dc:rights>
+ <dc:date />
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <cc:license
+ rdf:resource="GPL" />
+ <dc:language>en</dc:language>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs22">
+ <inkscape:perspective
+ id="perspective26"
+ inkscape:persp3d-origin="35.5 : 8.6666667 : 1"
+ inkscape:vp_x="0 : 13 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="71 : 13 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ </defs>
+ <sodipodi:namedview
+ bordercolor="#666666"
+ borderopacity="1.0"
+ gridtolerance="10.0"
+ guidetolerance="10.0"
+ id="base"
+ inkscape:current-layer="svg2"
+ inkscape:cx="89.736991"
+ inkscape:cy="131.8906"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:window-height="738"
+ inkscape:window-width="1280"
+ inkscape:window-x="-8"
+ inkscape:window-y="-8"
+ inkscape:zoom="1.9683098"
+ objecttolerance="10.0"
+ pagecolor="#ffffff"
+ showgrid="false"
+ inkscape:window-maximized="1" />
+ <rect
+ y="0.79069471"
+ x="0.69856781"
+ width="247.80269"
+ style="fill:#ffffff"
+ ry="24.249325"
+ id="background"
+ height="242.49326" />
+ <g
+ id="g3786"
+ transform="translate(1.8429833,2.6771937)">
+ <rect
+ height="19.39946"
+ id="minus"
+ style="fill:#c00000"
+ width="57.91848"
+ x="21.129864"
+ y="72.660362" />
+ <use
+ height="26"
+ id="use8"
+ transform="translate(77.224633,-7.7244905e-7)"
+ width="71"
+ x="0"
+ xlink:href="#minus"
+ y="0" />
+ <use
+ height="26"
+ id="use10"
+ transform="translate(154.44928,-7.7244905e-7)"
+ width="71"
+ x="0"
+ xlink:href="#minus"
+ y="0" />
+ <path
+ d="m 40.436017,121.15902 0,19.39946 -19.30616,0 0,19.39946 19.30616,0 0,19.39946 19.30617,0 0,-19.39946 19.30616,0 0,-19.39946 -19.30616,0 0,-19.39946 -19.30617,0 z"
+ id="plus"
+ style="fill:#008000"
+ inkscape:connector-curvature="0" />
+ <use
+ height="26"
+ id="use13"
+ transform="translate(77.224633,-7.7244905e-7)"
+ width="71"
+ x="0"
+ xlink:href="#plus"
+ y="0" />
+ <use
+ height="26"
+ id="use15"
+ transform="translate(154.44928,-7.7244905e-7)"
+ width="71"
+ x="0"
+ xlink:href="#plus"
+ y="0" />
+ </g>
+ <path
+ style="fill:#60605d"
+ d="m 29.516103,7.4516989 c -13.434128,0 -24.2500023,10.8158741 -24.2500023,24.2500001 l 0,193.968751 c 0,13.43413 10.8158743,24.25 24.2500023,24.25 l 199.281247,0 c 13.43413,0 24.25,-10.81587 24.25,-24.25 l 0,-193.968751 c 0,-13.434126 -10.81587,-24.2500001 -24.25,-24.2500001 l -199.281247,0 z m 1.6875,1.21875 193.968747,0 c 13.93155,0 25.15625,11.2247041 25.15625,25.1562501 l 0,181.750001 c 0,13.93155 -11.2247,25.15625 -25.15625,25.15625 l -193.968747,0 c -13.931548,0 -25.1562523,-11.2247 -25.1562523,-25.15625 l 0,-181.750001 c 0,-13.931546 11.2247043,-25.1562501 25.1562523,-25.1562501 z"
+ id="shadow"
+ inkscape:connector-curvature="0" />
+</svg>
|
|
|
@@ -0,0 +1,152 @@ + <?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ height="256"
+ id="svg2"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="thg-subrepo.svg"
+ sodipodi:version="0.32"
+ version="1.0"
+ width="256">
+ <metadata
+ id="metadata3">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:title></dc:title>
+ <dc:description />
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>unsorted</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ <dc:publisher>
+ <cc:Agent
+ rdf:about="http://www.openclipart.org/">
+ <dc:title>Open Clip Art Library, Source: Wiki Commons, Source: Wikimedia Commons</dc:title>
+ </cc:Agent>
+ </dc:publisher>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title />
+ </cc:Agent>
+ </dc:creator>
+ <dc:rights>
+ <cc:Agent>
+ <dc:title />
+ </cc:Agent>
+ </dc:rights>
+ <dc:date />
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <cc:license
+ rdf:resource="GPL" />
+ <dc:language>en</dc:language>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs22">
+ <inkscape:perspective
+ id="perspective26"
+ inkscape:persp3d-origin="35.5 : 8.6666667 : 1"
+ inkscape:vp_x="0 : 13 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="71 : 13 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6551"
+ id="linearGradient6557"
+ x1="55.080357"
+ y1="110.15236"
+ x2="73.14286"
+ y2="110.15236"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.00344154,1.2102096,-1.6263159,0.18275943,243.22716,8.6686994)" />
+ <linearGradient
+ id="linearGradient6551">
+ <stop
+ style="stop-color:#f2f53a;stop-opacity:1;"
+ offset="0"
+ id="stop6553" />
+ <stop
+ style="stop-color:#fdff8e;stop-opacity:0;"
+ offset="1"
+ id="stop6555" />
+ </linearGradient>
+ <linearGradient
+ y2="110.15236"
+ x2="73.14286"
+ y1="110.15236"
+ x1="55.080357"
+ gradientTransform="matrix(0.00344154,1.2102096,-1.6263159,0.18275943,309.0051,57.366183)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3022"
+ xlink:href="#linearGradient6551"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6551"
+ id="linearGradient3848"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.00344154,1.2102096,-1.6263159,0.18275943,309.0051,57.366183)"
+ x1="55.080357"
+ y1="110.15236"
+ x2="73.14286"
+ y2="110.15236" />
+ </defs>
+ <sodipodi:namedview
+ bordercolor="#666666"
+ borderopacity="1.0"
+ gridtolerance="10.0"
+ guidetolerance="10.0"
+ id="base"
+ inkscape:current-layer="svg2"
+ inkscape:cx="89.736991"
+ inkscape:cy="122.43398"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:window-height="738"
+ inkscape:window-width="1280"
+ inkscape:window-x="-8"
+ inkscape:window-y="-8"
+ inkscape:zoom="1.9683098"
+ objecttolerance="10.0"
+ pagecolor="#ffffff"
+ showgrid="false"
+ inkscape:window-maximized="1" />
+ <path
+ style="color:#000000;fill:#fff08b;fill-opacity:1;fill-rule:nonzero;stroke:#c4a000;stroke-width:6.1813693;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 125.93864,98.872518 c -0.009,-0.685743 -0.68406,-1.277866 -1.42388,-1.248212 l -15.76003,0.630734 -56.285668,2.25282 -45.5914241,1.82489 c -1.0459723,0.0424 -1.9589962,0.93548 -1.9465597,1.90518 l 1.7798561,138.8014 c 0.012437,0.96977 0.947448,1.78948 1.9934245,1.74752 L 110.58138,240.70913 c 1.046,-0.0402 1.95917,-0.93553 1.94667,-1.90515 l -0.98024,-76.44525 13.78995,-0.55375 c 0.74031,-0.0302 1.39924,-0.67454 1.39041,-1.3607 l -0.78971,-61.573643 z"
+ id="rect2995"
+ inkscape:connector-curvature="0" />
+ <path
+ style="color:#000000;fill:#fff08b;fill-opacity:1;fill-rule:nonzero;stroke:#c4a000;stroke-width:6.90631437;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+ d="m 126.53106,174.8151 c -0.002,-0.73164 -0.68305,-1.31092 -1.58265,-1.35089 l -18.6286,-0.81954 -0.18942,-62.78471 c -0.002,-0.674 -0.75201,-1.31432 -1.58251,-1.35078 L 7.3000652,104.2305 c -0.8291401,-0.0366 -1.5767724,0.53831 -1.5747635,1.21186 l 0.4190185,138.89489 c 0.00201,0.67395 0.7526549,1.31425 1.5825125,1.35078 l 70.7257353,3.11177 c 0.209509,0.54277 0.538841,1.0484 1.265881,1.08068 l 45.466451,2.00049 c 0.89981,0.0402 1.57713,-0.48056 1.5749,-1.21195 l -0.22887,-75.85389 z"
+ id="rect3043"
+ inkscape:connector-curvature="0" />
+ <text
+ xml:space="preserve"
+ style="font-size:142.12782288px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000080;fill-opacity:1;stroke:none;font-family:Arial;-inkscape-font-specification:Arial Bold"
+ x="12.382761"
+ y="221.77342"
+ id="text3821"
+ sodipodi:linespacing="125%"
+ transform="scale(0.9640397,1.0373017)"><tspan
+ sodipodi:role="line"
+ id="tspan3823"
+ x="12.382761"
+ y="221.77342">S</tspan></text>
+</svg>
|
|
|
@@ -0,0 +1,135 @@ + <?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ height="256"
+ id="svg2"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:version="0.48.0 r9654"
+ sodipodi:docname="thg-svn-subrepo.svg"
+ sodipodi:version="0.32"
+ version="1.0"
+ width="256">
+ <metadata
+ id="metadata3">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:title></dc:title>
+ <dc:description />
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>unsorted</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ <dc:publisher>
+ <cc:Agent
+ rdf:about="http://www.openclipart.org/">
+ <dc:title>Open Clip Art Library, Source: Wiki Commons, Source: Wikimedia Commons</dc:title>
+ </cc:Agent>
+ </dc:publisher>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title />
+ </cc:Agent>
+ </dc:creator>
+ <dc:rights>
+ <cc:Agent>
+ <dc:title />
+ </cc:Agent>
+ </dc:rights>
+ <dc:date />
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <cc:license
+ rdf:resource="GPL" />
+ <dc:language>en</dc:language>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs22">
+ <inkscape:perspective
+ id="perspective26"
+ inkscape:persp3d-origin="35.5 : 8.6666667 : 1"
+ inkscape:vp_x="0 : 13 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="71 : 13 : 1"
+ sodipodi:type="inkscape:persp3d" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6551"
+ id="linearGradient6557"
+ x1="55.080357"
+ y1="110.15236"
+ x2="73.14286"
+ y2="110.15236"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.00344154,1.2102096,-1.6263159,0.18275943,243.22716,8.6686994)" />
+ <linearGradient
+ id="linearGradient6551">
+ <stop
+ style="stop-color:#f2f53a;stop-opacity:1;"
+ offset="0"
+ id="stop6553" />
+ <stop
+ style="stop-color:#fdff8e;stop-opacity:0;"
+ offset="1"
+ id="stop6555" />
+ </linearGradient>
+ <linearGradient
+ y2="110.15236"
+ x2="73.14286"
+ y1="110.15236"
+ x1="55.080357"
+ gradientTransform="matrix(0.00344154,1.2102096,-1.6263159,0.18275943,309.0051,57.366183)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3022"
+ xlink:href="#linearGradient6551"
+ inkscape:collect="always" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6551"
+ id="linearGradient3848"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.00344154,1.2102096,-1.6263159,0.18275943,309.0051,57.366183)"
+ x1="55.080357"
+ y1="110.15236"
+ x2="73.14286"
+ y2="110.15236" />
+ </defs>
+ <sodipodi:namedview
+ bordercolor="#666666"
+ borderopacity="1.0"
+ gridtolerance="10.0"
+ guidetolerance="10.0"
+ id="base"
+ inkscape:current-layer="svg2"
+ inkscape:cx="51.56764"
+ inkscape:cy="94.638993"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:window-height="738"
+ inkscape:window-width="1280"
+ inkscape:window-x="-8"
+ inkscape:window-y="-8"
+ inkscape:zoom="1.9683098"
+ objecttolerance="10.0"
+ pagecolor="#ffffff"
+ showgrid="false"
+ inkscape:window-maximized="1" />
+ <path
+ inkscape:connector-curvature="0"
+ d="m 0.32992046,0.20734072 0,92.68331928 C 34.848621,73.004547 80.970856,53.019612 139.01298,32.852882 176.20444,19.950325 212.14896,9.1011362 246.86753,0.20734072 l -246.53760954,0 z M 255.72733,19.969777 C 222.06985,26.89033 191.88559,35.034216 165.32117,44.235044 134.90992,54.803095 110.42768,65.487058 91.911358,76.380272 73.371352,87.078403 64.758735,95.730648 65.964787,102.39664 c 1.087803,5.88563 8.852263,8.55783 23.324794,8.00504 7.449121,-0.19508 19.510179,-1.32856 36.252869,-3.37713 16.74277,-2.04857 38.35919,-5.01103 64.82123,-8.880584 26.22939,-3.746717 47.95432,-6.280751 65.36365,-7.754879 l 0,-70.41931 z m -74.7659,117.573983 c -7.44906,0.13009 -17.70389,0.91822 -30.82849,2.25141 -13.17188,1.36574 -29.467,3.33242 -49.00015,6.12886 -30.316639,4.48734 -55.549738,7.98026 -75.579547,10.25645 -9.608021,1.12175 -17.9552499,1.81915 -25.22332254,2.3765 l 0,75.17229 C 40.111127,225.68406 76.097953,216.09183 108.18447,204.96118 c 31.52268,-10.92571 55.75044,-21.58714 72.77696,-31.89507 17.05017,-10.30789 25.01072,-19.05271 23.68642,-26.14145 -1.11149,-6.11321 -9.09564,-9.21832 -23.68642,-9.3809 z m 74.7659,26.39161 C 220.77006,182.88145 177.38199,201.46286 125.54245,219.4703 77.278383,236.21287 35.587684,248.19222 0.32992046,255.61804 l 0,0.62541 255.39740954,0 0,-92.30808 z M 10.997854,179.69528 c 0.130246,-0.0264 0.229985,-0.002 0.361618,0 0.660236,0.0308 1.364189,0.31696 1.808131,1.00063 0.678024,0.96569 0.854069,2.51386 0.452029,3.75236 -0.43723,1.42533 -1.597602,2.28493 -2.712189,2.00126 -1.2303394,-0.24712 -2.1842943,-1.92033 -2.0793372,-3.62728 0.035631,-1.60701 1.0215998,-2.92796 2.1697482,-3.12697 z m 9.040613,0 c 0.57655,0.032 1.112825,0.19372 1.536897,0.75047 0.801103,0.9107 1.116882,2.54845 0.723249,3.87744 -0.36853,1.36558 -1.378333,2.38006 -2.440955,2.25141 -0.912693,-0.0517 -1.810533,-0.83856 -2.169749,-2.00124 -0.496734,-1.51128 -0.09208,-3.55529 0.99447,-4.37776 0.405095,-0.34314 0.879931,-0.50696 1.356088,-0.50032 z m -8.678995,22.13892 c 1.077061,-0.0478 2.039357,1.00128 2.350558,2.3765 0.329631,1.52957 -0.194781,3.45414 -1.265677,4.1276 -1.075686,0.74675 -2.4885027,0.27342 -3.1642179,-1.12571 -0.687779,-1.36614 -0.5927238,-3.42703 0.361618,-4.50283 0.4677099,-0.57764 1.0894719,-0.89387 1.7177189,-0.87556 z m 8.678995,0 c 0.856618,-0.0648 1.61794,0.68525 2.079337,1.62604 0.664733,1.43462 0.386626,3.50364 -0.632838,4.50283 -1.022614,1.07599 -2.621151,0.89559 -3.435439,-0.50031 -0.841778,-1.38689 -0.786224,-3.74845 0.27122,-4.87808 0.458862,-0.52449 1.118697,-0.77677 1.71772,-0.75048 z"
+ id="polygon3517"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;letter-spacing:normal;word-spacing:normal;text-anchor:start;fill:#809cc8;fill-opacity:1;fill-rule:nonzero;stroke:none" />
+</svg>
|
@@ -45,11 +45,8 @@ demandimport.ignore.append('icons_rc')
demandimport.ignore.append('translations_rc')
demandimport.enable()
-from mercurial import ui as uimod, util
-from tortoisehg.util.hgversion import hgversion, checkhgversion
-import cStringIO
-import traceback
+# Verify we can reach TortoiseHg sources first
try:
import tortoisehg.hgqt.run
except ImportError, e:
@@ -59,50 +56,18 @@ sys.stderr.write("(check your install and PYTHONPATH)\n")
sys.exit(-1)
-ui = uimod.ui()
-capt = ui.configbool('tortoisehg', 'stderrcapt', True)
-
-errors = ('Traceback', 'TypeError', 'NameError', 'AttributeError',
- 'NotImplementedError')
-
-err = checkhgversion(hgversion)
-if err:
+# Verify we have an acceptable version of Mercurial
+from tortoisehg.util.hgversion import hgversion, checkhgversion
+errmsg = checkhgversion(hgversion)
+if errmsg:
from tortoisehg.hgqt.bugreport import run
from tortoisehg.hgqt.run import qtrun
opts = {}
opts['cmd'] = ' '.join(sys.argv[1:])
- opts['error'] = '\n' + err + '\n'
+ opts['error'] = '\n' + errmsg + '\n'
opts['nofork'] = True
qtrun(run, ui, **opts)
sys.exit(1)
-if not capt or 'THGDEBUG' in os.environ or '--profile' in sys.argv:
- sys.exit(tortoisehg.hgqt.run.dispatch(sys.argv[1:]))
-else:
- mystderr = cStringIO.StringIO()
- origstderr = sys.stderr
- sys.stderr = mystderr
- ret = 0
- try:
- ret = tortoisehg.hgqt.run.dispatch(sys.argv[1:])
- sys.stderr = origstderr
- mystderr.seek(0)
- for l in mystderr.readlines():
- if l.startswith(errors):
- from tortoisehg.hgqt.bugreport import run
- from tortoisehg.hgqt.run import qtrun
- error = 'Recoverable runtime error (stderr):\n'
- error += mystderr.getvalue()
- opts = {}
- opts['cmd'] = ' '.join(sys.argv[1:])
- opts['error'] = error
- opts['nofork'] = True
- qtrun(run, ui, **opts)
- break
- sys.exit(ret)
- except:
- if sys.exc_info()[0] not in [SystemExit, KeyboardInterrupt]:
- sys.stderr = origstderr
- traceback.print_exc()
- else:
- raise SystemExit(ret)
+ret = tortoisehg.hgqt.run.dispatch(sys.argv[1:])
+sys.exit(ret)
|
|
|
@@ -1,456 +0,0 @@ - # annotate.py - File annotation widget
-#
-# Copyright 2010 Steve Borho <steve@borho.org>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2, incorporated herein by reference.
-
-import os
-
-from mercurial import ui, error, util
-
-from tortoisehg.hgqt import visdiff, qtlib, qscilib, wctxactions, thgrepo, lexers
-from tortoisehg.util import paths, hglib, colormap, thread2
-from tortoisehg.hgqt.i18n import _
-from tortoisehg.hgqt.grep import SearchWidget
-
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-from PyQt4.Qsci import QsciScintilla, QsciStyle
-
-# Technical Debt
-# Pass search parameters to grep
-# forward/backward history buttons
-# menu options for viewing appropriate changesets
-
-class AnnotateView(qscilib.Scintilla):
- revisionHint = pyqtSignal(QString)
-
- searchRequested = pyqtSignal(QString)
- """Emitted (pattern) when user request to search content"""
-
- editSelected = pyqtSignal(unicode, object, int)
- """Emitted (path, rev, line) when user requests to open editor"""
-
- grepRequested = pyqtSignal(QString, dict)
- """Emitted (pattern, **opts) when user request to search changelog"""
-
- sourceChanged = pyqtSignal(unicode, object)
- """Emitted (path, rev) when the content source changed"""
-
- def __init__(self, repo, parent=None, **opts):
- super(AnnotateView, self).__init__(parent)
- self.setReadOnly(True)
- self.setMarginLineNumbers(1, True)
- self.setMarginType(2, QsciScintilla.TextMarginRightJustified)
- self.setMouseTracking(True)
- self.setFont(qtlib.getfont('fontdiff').font())
- self.setContextMenuPolicy(Qt.CustomContextMenu)
- self.customContextMenuRequested.connect(self.menuRequest)
-
- self.repo = repo
- self.repo.configChanged.connect(self.configChanged)
- self.configChanged()
- self._rev = None
- self.annfile = None
- self._annotation_enabled = bool(opts.get('annotationEnabled', False))
-
- self._links = [] # by line
- self._revmarkers = {} # by rev
- self._lastrev = None
-
- self._thread = _AnnotateThread(self)
- self._thread.finished.connect(self.fillModel)
-
- def configChanged(self):
- self.setIndentationWidth(self.repo.tabwidth)
- self.setTabWidth(self.repo.tabwidth)
-
- def keyPressEvent(self, event):
- if event.key() == Qt.Key_Escape:
- self._thread.abort()
- return
- return super(AnnotateView, self).keyPressEvent(event)
-
- def mouseMoveEvent(self, event):
- self._emitRevisionHintAtLine(self.lineAt(event.pos()))
- super(AnnotateView, self).mouseMoveEvent(event)
-
- def _emitRevisionHintAtLine(self, line):
- if line < 0:
- return
- try:
- fctx = self._links[line][0]
- if fctx.rev() != self._lastrev:
- s = hglib.get_revision_desc(fctx, self.annfile)
- self.revisionHint.emit(s)
- self._lastrev = fctx.rev()
- except IndexError:
- pass
-
- @pyqtSlot(QPoint)
- def menuRequest(self, point):
- menu = self.createStandardContextMenu()
- line = self.lineAt(point)
- point = self.mapToGlobal(point)
-
- if not self.isAnnotationEnabled():
- return menu.exec_(point)
- if line < 0 or line >= len(self._links):
- return menu.exec_(point)
-
- fctx, line = self._links[line]
- data = [hglib.tounicode(fctx.path()), fctx.rev(), line]
-
- if self.hasSelectedText():
- selection = self.selectedText()
- def sreq(**opts):
- return lambda: self.grepRequested.emit(selection, opts)
- def sann():
- self.searchRequested.emit(selection)
- menu.addSeparator()
- for name, func in [(_('Search in original revision'),
- sreq(rev=fctx.rev())),
- (_('Search in working revision'),
- sreq(rev='.')),
- (_('Search in current annotation'), sann),
- (_('Search in history'), sreq(all=True))]:
- def add(name, func):
- action = menu.addAction(name)
- action.triggered.connect(func)
- add(name, func)
-
- def annorig():
- self.setSource(*data)
- def editorig():
- self.editSelected.emit(*data)
- menu.addSeparator()
- for name, func in [(_('Annotate originating revision'), annorig),
- (_('View originating revision'), editorig)]:
- def add(name, func):
- action = menu.addAction(name)
- action.triggered.connect(func)
- add(name, func)
- for pfctx in fctx.parents():
- pdata = [hglib.tounicode(pfctx.path()), pfctx.changectx().rev(),
- line]
- def annparent(data):
- self.setSource(*data)
- def editparent(data):
- self.editSelected.emit(*data)
- for name, func in [(_('Annotate parent revision %d') % pdata[1],
- annparent),
- (_('View parent revision %d') % pdata[1],
- editparent)]:
- def add(name, func):
- action = menu.addAction(name)
- action.data = pdata
- action.run = lambda: func(action.data)
- action.triggered.connect(action.run)
- add(name, func)
- menu.exec_(point)
-
- @property
- def rev(self):
- """Returns the current revision number"""
- return self._rev
-
- @pyqtSlot(unicode, object, int)
- def setSource(self, wfile, rev, line=None):
- """Change the content to the specified file at rev [unicode]
-
- line is counted from 1.
- """
- if isinstance(wfile, (unicode, QString)):
- # setSource can be used as a slot, in which case wfile is a QString
- lwfile = hglib.fromunicode(wfile)
- else:
- # or it can be called directly with a local encoded wfile string
- lwfile = wfile
- wfile = hglib.tounicode(wfile)
-
- if self.annfile == lwfile and self.rev == rev:
- if line:
- self.setCursorPosition(int(line) - 1, 0)
- return
-
- try:
- ctx = self.repo[rev]
- fctx = ctx[lwfile]
- except error.LookupError:
- qtlib.ErrorMsgBox(_('Unable to annotate'),
- _('%s is not found in revision %d') % (wfile, ctx.rev()))
- return
-
- try:
- if rev is None:
- size = fctx.size()
- else:
- size = fctx._filelog.rawsize(fctx.filerev())
- except (EnvironmentError, error.LookupError), e:
- self.setText(_('File or diffs not displayed: ') + \
- hglib.tounicode(str(e)))
- self.error = p + hglib.tounicode(str(e))
- return
-
- if size > ctx._repo.maxdiff:
- self.setText(_('File or diffs not displayed: ') + \
- _('File is larger than the specified max size.\n'))
- else:
- self._rev = ctx.rev()
- self.clear()
- self.annfile = lwfile
- if util.binary(fctx.data()):
- self.setText(_('File is binary.\n'))
- else:
- self.setText(hglib.tounicode(fctx.data()))
- if line:
- self.setCursorPosition(int(line) - 1, 0)
- self._updatelexer(fctx)
- self._updatemarginwidth()
- self.sourceChanged.emit(wfile, self._rev)
- self._updateannotation()
-
- def _updateannotation(self):
- if not self.isAnnotationEnabled() or not self.annfile:
- return
- ctx = self.repo[self._rev]
- fctx = ctx[self.annfile]
- if util.binary(fctx.data()):
- return
- self._thread.abort()
- self._thread.start(fctx)
-
- @pyqtSlot()
- def fillModel(self):
- self._thread.wait()
- if self._thread.data is None:
- return
-
- self._links = list(self._thread.data)
-
- self._updaterevmargin()
- self._updatemarkers()
- self._updatemarginwidth()
-
- def clear(self):
- super(AnnotateView, self).clear()
- self.clearMarginText()
- self.markerDeleteAll()
- self.annfile = None
-
- @pyqtSlot(bool)
- def setAnnotationEnabled(self, enabled):
- """Enable / disable annotation"""
- enabled = bool(enabled)
- if enabled == self.isAnnotationEnabled():
- return
- self._annotation_enabled = enabled
- self._updateannotation()
- self._updatemarginwidth()
- self.setMouseTracking(enabled)
- if not self.isAnnotationEnabled():
- self.annfile = None
- self.markerDeleteAll()
-
- def isAnnotationEnabled(self):
- """True if annotation enabled and available"""
- if self.rev is None:
- return False # annotate working copy is not supported
- return self._annotation_enabled
-
- def _updatelexer(self, fctx):
- """Update the lexer according to the given file"""
- lex = lexers.get_lexer(fctx.path(), hglib.tounicode(fctx.data()), self)
- self.setLexer(lex)
- if lex is None:
- self.setFont(qtlib.getfont('fontlog').font())
-
- def _updaterevmargin(self):
- """Update the content of margin area showing revisions"""
- s = self._margin_style
- # Workaround to set style of the current sci widget.
- # QsciStyle sends style data only to the first sci widget.
- # See qscintilla2/Qt4/qscistyle.cpp
- self.SendScintilla(QsciScintilla.SCI_STYLESETBACK,
- s.style(), s.paper())
- self.SendScintilla(QsciScintilla.SCI_STYLESETFONT,
- s.style(), s.font().family().toAscii().data())
- self.SendScintilla(QsciScintilla.SCI_STYLESETSIZE,
- s.style(), s.font().pointSize())
- for i, (fctx, _origline) in enumerate(self._links):
- self.setMarginText(i, str(fctx.rev()), s)
-
- def _updatemarkers(self):
- """Update markers which colorizes each line"""
- self._redefinemarkers()
- for i, (fctx, _origline) in enumerate(self._links):
- m = self._revmarkers.get(fctx.rev())
- if m is not None:
- self.markerAdd(i, m)
-
- def _redefinemarkers(self):
- """Redefine line markers according to the current revs"""
- curdate = self.repo[self._rev].date()[0]
-
- # make sure to colorize at least 1 year
- mindate = curdate - 365 * 24 * 60 * 60
-
- self._revmarkers.clear()
- filectxs = iter(fctx for fctx, _origline in self._links)
- palette = colormap.makeannotatepalette(filectxs, curdate,
- maxcolors=32, maxhues=8,
- maxsaturations=16,
- mindate=mindate)
- for i, (color, fctxs) in enumerate(palette.iteritems()):
- self.markerDefine(QsciScintilla.Background, i)
- self.setMarkerBackgroundColor(QColor(color), i)
- for fctx in fctxs:
- self._revmarkers[fctx.rev()] = i
-
- @util.propertycache
- def _margin_style(self):
- """Style for margin area"""
- s = QsciStyle()
- s.setPaper(QApplication.palette().color(QPalette.Window))
- s.setFont(self.font())
- return s
-
- @pyqtSlot()
- def _updatemarginwidth(self):
- self.setMarginsFont(self.font())
- def lentext(s):
- return 'M' * (len(str(s)) + 2) # 2 for margin
- self.setMarginWidth(1, lentext(self.lines()))
- if self.isAnnotationEnabled() and self._links:
- maxrev = max(fctx.rev() for fctx, _origline in self._links)
- self.setMarginWidth(2, lentext(maxrev))
- else:
- self.setMarginWidth(2, 0)
-
-class _AnnotateThread(QThread):
- 'Background thread for annotating a file at a revision'
- def __init__(self, parent=None):
- super(_AnnotateThread, self).__init__(parent)
- self._threadid = None
-
- @pyqtSlot(object)
- def start(self, fctx):
- self._fctx = fctx
- super(_AnnotateThread, self).start()
- self.data = None
-
- @pyqtSlot()
- def abort(self):
- if self._threadid is None:
- return
- try:
- thread2._async_raise(self._threadid, KeyboardInterrupt)
- self.wait()
- except ValueError:
- pass
-
- def run(self):
- assert self.currentThread() != qApp.thread()
- self._threadid = self.currentThreadId()
- try:
- try:
- data = []
- for (fctx, line), _text in self._fctx.annotate(True, True):
- data.append((fctx, line))
- self.data = data
- except KeyboardInterrupt:
- pass
- finally:
- self._threadid = None
- del self._fctx
-
-class AnnotateDialog(QMainWindow):
- def __init__(self, *pats, **opts):
- super(AnnotateDialog,self).__init__(opts.get('parent'), Qt.Window)
-
- root = opts.get('root') or paths.find_root()
- repo = thgrepo.repository(ui.ui(), path=root)
- # TODO: handle repo not found
-
- av = AnnotateView(repo, self, annotationEnabled=True)
- self.setCentralWidget(av)
- self.av = av
-
- status = QStatusBar()
- self.setStatusBar(status)
- av.revisionHint.connect(status.showMessage)
- av.editSelected.connect(self.editSelected)
- av.grepRequested.connect(self._openSearchWidget)
-
- self._searchbar = qscilib.SearchToolBar()
- self.addToolBar(self._searchbar)
- self._searchbar.setPattern(hglib.tounicode(opts.get('pattern', '')))
- self._searchbar.searchRequested.connect(self.av.find)
- self._searchbar.conditionChanged.connect(self.av.highlightText)
- av.searchRequested.connect(self._searchbar.search)
- QShortcut(QKeySequence.Find, self,
- lambda: self._searchbar.setFocus(Qt.OtherFocusReason))
-
- self.av.sourceChanged.connect(
- lambda *args: self.setWindowTitle(_('Annotate %s@%d') % args))
-
- self.searchwidget = opts.get('searchwidget')
-
- self.opts = opts
- line = opts.get('line')
- if line and isinstance(line, str):
- line = int(line)
-
- self.repo = repo
-
- self.restoreSettings()
-
- # run heavy operation after the dialog visible
- path = hglib.tounicode(pats[0])
- rev = opts.get('rev') or '.'
- QTimer.singleShot(0, lambda: av.setSource(path, rev, line))
-
- def closeEvent(self, event):
- self.storeSettings()
- super(AnnotateDialog, self).closeEvent(event)
-
- def editSelected(self, wfile, rev, line):
- pattern = hglib.fromunicode(self._searchbar._le.text()) or None
- wfile = hglib.fromunicode(wfile)
- repo = self.repo
- try:
- ctx = repo[rev]
- fctx = ctx[wfile]
- except Exception, e:
- self.statusBar().showMessage(hglib.tounicode(str(e)))
-
- base, _ = visdiff.snapshot(repo, [wfile], repo[rev])
- files = [os.path.join(base, wfile)]
- wctxactions.edit(self, repo.ui, repo, files, line, pattern)
-
- @pyqtSlot(unicode, dict)
- def _openSearchWidget(self, pattern, opts):
- opts = dict((str(k), str(v)) for k, v in opts.iteritems())
- if self.searchwidget is None:
- self.searchwidget = SearchWidget([pattern], repo=self.repo,
- **opts)
- self.searchwidget.show()
- else:
- self.searchwidget.setSearch(pattern, **opts)
- self.searchwidget.show()
- self.searchwidget.raise_()
-
- def storeSettings(self):
- s = QSettings()
- s.setValue('annotate/geom', self.saveGeometry())
- self.av.saveSettings(s, 'annotate/av')
-
- def restoreSettings(self):
- s = QSettings()
- self.restoreGeometry(s.value('annotate/geom').toByteArray())
- self.av.loadSettings(s, 'annotate/av')
-
-def run(ui, *pats, **opts):
- pats = hglib.canonpaths(pats)
- return AnnotateDialog(*pats, **opts)
|
|
|
@@ -5,281 +5,587 @@ # This software may be used and distributed according to the terms of the
# GNU General Public License version 2, incorporated herein by reference.
-from PyQt4.QtCore import *
-from PyQt4.QtGui import *
-
-from mercurial import merge as mergemod
+from mercurial import hg, merge as mergemod
from tortoisehg.util import hglib
from tortoisehg.hgqt.i18n import _
from tortoisehg.hgqt import qtlib, csinfo, i18n, cmdui, status, resolve
from tortoisehg.hgqt import commit, qscilib, thgrepo
-keep = i18n.keepgettext()
+from PyQt4.QtCore import *
+from PyQt4.QtGui import *
-class BackoutDialog(QDialog):
+class BackoutDialog(QWizard):
- def __init__(self, repo, rev='tip', parent=None, opts={}):
+ def __init__(self, rev, repo, parent):
super(BackoutDialog, self).__init__(parent)
f = self.windowFlags()
self.setWindowFlags(f & ~Qt.WindowContextHelpButtonHint)
+
+ self.backoutrev = rev
+ self.parentbackout = False
+
+ self.setWindowTitle(_('Backout - %s') % repo.displayname)
self.setWindowIcon(qtlib.geticon('hg-revert'))
+ self.setOption(QWizard.NoBackButtonOnStartPage, True)
+ self.setOption(QWizard.NoBackButtonOnLastPage, True)
+ self.setOption(QWizard.IndependentPages, True)
+
+ self.addPage(SummaryPage(repo, self))
+ self.addPage(BackoutPage(repo, self))
+ self.addPage(CommitPage(repo, self))
+ self.addPage(ResultPage(repo, self))
+ self.currentIdChanged.connect(self.pageChanged)
+
+ self.resize(QSize(700, 489).expandedTo(self.minimumSizeHint()))
+
+ repo.repositoryChanged.connect(self.repositoryChanged)
+ repo.configChanged.connect(self.configChanged)
+
+ def repositoryChanged(self):
+ self.currentPage().repositoryChanged()
+
+ def configChanged(self):
+ self.currentPage().configChanged()
+
+ def pageChanged(self, id):
+ if id != -1:
+ self.currentPage().currentPage()
+
+ def reject(self):
+ if self.currentPage().canExit():
+ super(BackoutDialog, self).reject()
+
+
+class BasePage(QWizardPage):
+ def __init__(self, repo, parent):
+ super(BasePage, self).__init__(parent)
self.repo = repo
- # main layout box
- box = QVBoxLayout()
- box.setSpacing(8)
- box.setContentsMargins(*(6,)*4)
+ def validatePage(self):
+ 'user pressed NEXT button, can we proceed?'
+ return True
- ## target revision
- target_sep = qtlib.LabeledSeparator(_('Target changeset'))
- box.addWidget(target_sep)
+ def isComplete(self):
+ 'should NEXT button be sensitive?'
+ return True
- style = csinfo.panelstyle(selectable=True)
- self.targetinfo = csinfo.create(self.repo, rev, style, withupdate=True)
- box.addWidget(self.targetinfo)
+ def repositoryChanged(self):
+ 'repository has detected a change to changelog or parents'
+ pass
- ## backout message
- msg_sep = qtlib.LabeledSeparator(_('Backout commit message'))
- box.addWidget(msg_sep)
+ def configChanged(self):
+ 'repository has detected a change to config files'
+ pass
- revhex = self.targetinfo.get_data('revid')
- self.msgset = keep._('Backed out changeset: ')
- self.msgset['id'] += revhex
- self.msgset['str'] += revhex
+ def currentPage(self):
+ pass
- self.msgTextEdit = commit.MessageEntry(self)
- self.msgTextEdit.installEventFilter(qscilib.KeyPressInterceptor(self))
- self.msgTextEdit.refresh(repo)
- self.msgTextEdit.loadSettings(QSettings(), 'backout/message')
- self.msgTextEdit.setText(self.msgset['str'])
- box.addWidget(self.msgTextEdit, 2)
+ def canExit(self):
+ return True
- ## options
- opt_sep = qtlib.LabeledSeparator(_('Options'))
- box.addWidget(opt_sep)
- obox = QVBoxLayout()
- obox.setSpacing(3)
- box.addLayout(obox)
+class SummaryPage(BasePage):
+
+ def __init__(self, repo, parent):
+ super(SummaryPage, self).__init__(repo, parent)
+ self.clean = False
+ self.th = None
+
+ def initializePage(self):
+ if self.layout():
+ return
+ self.setTitle(_('Prepare to backout'))
+ self.setSubTitle(_('Verify backout revision and ensure your working '
+ 'directory is clean.'))
+ self.setLayout(QVBoxLayout())
+
+ repo = self.repo
+ try:
+ bctx = repo[self.wizard().backoutrev]
+ pctx = repo['.']
+ except error.RepoLookupError:
+ qtlib.InfoMsgBox(_('Unable to backout'),
+ _('Backout revision not found'))
+ QTimer.singleShot(0, self.wizard().close)
+
+ if pctx == bctx:
+ lbl = _('Backing out a parent revision is a single step operation')
+ self.layout().addWidget(QLabel(u'<b>%s</b>' % lbl))
+ self.wizard().parentbackout = True
+
+ op1, op2 = repo.dirstate.parents()
+ a = repo.changelog.ancestor(op1, bctx.node())
+ if a != bctx.node():
+ qtlib.InfoMsgBox(_('Unable to backout'),
+ _('Cannot backout change on a different branch'))
+ QTimer.singleShot(0, self.wizard().close)
+
+ ## backout revision
+ style = csinfo.panelstyle(contents=csinfo.PANEL_DEFAULT)
+ create = csinfo.factory(repo, None, style, withupdate=True)
+ sep = qtlib.LabeledSeparator(_('Backout revision'))
+ self.layout().addWidget(sep)
+ backoutCsInfo = create(bctx.rev())
+ self.layout().addWidget(backoutCsInfo)
+
+ ## current revision
+ contents = ('ishead',) + csinfo.PANEL_DEFAULT
+ style = csinfo.panelstyle(contents=contents)
+ def markup_func(widget, item, value):
+ if item == 'ishead' and value is False:
+ text = _('Not a head, backout will create a new head!')
+ return qtlib.markup(text, fg='red', weight='bold')
+ raise csinfo.UnknownItem(item)
+ custom = csinfo.custom(markup=markup_func)
+ create = csinfo.factory(repo, custom, style, withupdate=True)
+
+ sep = qtlib.LabeledSeparator(_('Current local revision'))
+ self.layout().addWidget(sep)
+ localCsInfo = create(pctx.rev())
+ self.layout().addWidget(localCsInfo)
+ self.localCsInfo = localCsInfo
+
+ ## working directory status
+ sep = qtlib.LabeledSeparator(_('Working directory status'))
+ self.layout().addWidget(sep)
+
+ self.groups = qtlib.WidgetGroups()
+
+ wdbox = QHBoxLayout()
+ self.layout().addLayout(wdbox)
+ self.wd_status = qtlib.StatusLabel()
+ self.wd_status.set_status(_('Checking...'))
+ wdbox.addWidget(self.wd_status)
+ wd_prog = QProgressBar()
+ wd_prog.setMaximum(0)
+ wd_prog.setTextVisible(False)
+ self.groups.add(wd_prog, 'prog')
+ wdbox.addWidget(wd_prog, 1)
+
+ text = _('Before backout, you must <a href="commit"><b>commit</b></a>, '
+ '<a href="shelve"><b>shelve</b></a> to patch, '
+ 'or <a href="discard"><b>discard</b></a> changes.')
+ wd_text = QLabel(text)
+ wd_text.setWordWrap(True)
+ wd_text.linkActivated.connect(self.onLinkActivated)
+ self.wd_text = wd_text
+ self.groups.add(wd_text, 'dirty')
+ self.layout().addWidget(wd_text)
+
+ ## auto-resolve
+ autoresolve_chk = QCheckBox(_('Automatically resolve merge conflicts '
+ 'where possible'))
+ autoresolve_chk.setChecked(
+ repo.ui.configbool('tortoisehg', 'autoresolve', False))
+ self.registerField('autoresolve', autoresolve_chk)
+ self.layout().addWidget(autoresolve_chk)
+ self.autoresolve_chk = autoresolve_chk
+ self.groups.set_visible(False, 'dirty')
+
+ def isComplete(self):
+ 'should Next button be sensitive?'
+ return self.clean
+
+ def repositoryChanged(self):
+ 'repository has detected a change to changelog or parents'
+ pctx = self.repo['.']
+ self.localCsInfo.update(pctx)
+ self.wizard().localrev = str(pctx.rev())
+
+ def canExit(self):
+ 'can backout tool be closed?'
+ if self.th is not None and self.th.isRunning():
+ self.th.cancel()
+ self.th.wait()
+ return True
+
+ def currentPage(self):
+ self.refresh()
+
+ def refresh(self):
+ if self.th is None:
+ self.th = CheckThread(self.repo, self)
+ self.th.finished.connect(self.threadFinished)
+ if self.th.isRunning():
+ return
+ self.groups.set_visible(True, 'prog')
+ self.th.start()
+
+ def threadFinished(self):
+ self.groups.set_visible(False, 'prog')
+ if self.th.canceled:
+ return
+ dirty, parents = self.th.results
+ self.clean = not dirty
+ if dirty:
+ self.groups.set_visible(True, 'dirty')
+ self.wd_status.set_status(_('<b>Uncommitted local changes '
+ 'are detected</b>'), 'thg-warning')
+ else:
+ self.groups.set_visible(False, 'dirty')
+ self.wd_status.set_status(_('Clean'), True)
+ self.completeChanged.emit()
+
+ @pyqtSlot(QString)
+ def onLinkActivated(self, cmd):
+ cmd = hglib.fromunicode(cmd)
+ repo = self.repo
+ if cmd == 'commit':
+ dlg = commit.CommitDialog(repo, [], {}, self.wizard())
+ dlg.finished.connect(dlg.deleteLater)
+ dlg.exec_()
+ self.refresh()
+ elif cmd == 'shelve':
+ from tortoisehg.hgqt import shelve
+ dlg = shelve.ShelveDialog(repo, self.wizard())
+ dlg.finished.connect(dlg.deleteLater)
+ dlg.exec_()
+ self.refresh()
+ elif cmd.startswith('discard'):
+ if cmd != 'discard:noconfirm':
+ labels = [(QMessageBox.Yes, _('&Discard')),
+ (QMessageBox.No, _('Cancel'))]
+ if not qtlib.QuestionMsgBox(_('Confirm Discard'),
+ _('Discard outstanding changes to working directory?'),
+ labels=labels, parent=self):
+ return
+ def finished(ret):
+ repo.decrementBusyCount()
+ self.refresh()
+ cmdline = ['update', '--clean', '--repository', repo.root,
+ '--rev', '.']
+ self.runner = cmdui.Runner(True, self)
+ self.runner.commandFinished.connect(finished)
+ repo.incrementBusyCount()
+ self.runner.run(cmdline)
+ elif cmd == 'view':
+ dlg = status.StatusDialog(repo, [], {}, self)
+ dlg.exec_()
+ self.refresh()
+ else:
+ raise 'unknown command: %s' % cmd
+
+
+class BackoutPage(BasePage):
+ def __init__(self, repo, parent):
+ super(BackoutPage, self).__init__(repo, parent)
+ self.backoutcomplete = False
+
+ self.setTitle(_('Backing out, then merging...'))
+ self.setSubTitle(_('All conflicting files will be marked unresolved.'))
+ self.setLayout(QVBoxLayout())
+
+ self.cmd = cmdui.Widget(True, False, self)
+ self.cmd.commandFinished.connect(self.onCommandFinished)
+ self.cmd.setShowOutput(True)
+ self.layout().addWidget(self.cmd)
+
+ self.reslabel = QLabel()
+ self.reslabel.linkActivated.connect(self.onLinkActivated)
+ self.reslabel.setWordWrap(True)
+ self.layout().addWidget(self.reslabel)
+
+ self.autonext = QCheckBox(_('Automatically advance to next page '
+ 'when backout and merge are complete.'))
+ checked = QSettings().value('backout/autoadvance', False).toBool()
+ self.autonext.setChecked(checked)
+ self.autonext.toggled.connect(self.tryAutoAdvance)
+ self.layout().addWidget(self.autonext)
+
+ def currentPage(self):
+ if self.wizard().parentbackout:
+ self.wizard().next()
+ return
+ cmdline = ['--repository', self.repo.root, 'backout']
+ tool = self.field('autoresolve').toBool() and 'merge' or 'fail'
+ cmdline += ['--tool=internal:' + tool]
+ cmdline += ['--rev', str(self.wizard().backoutrev)]
+ self.repo.incrementBusyCount()
+ self.cmd.core.clearOutput()
+ self.cmd.run(cmdline)
+
+ def isComplete(self):
+ 'should Next button be sensitive?'
+ if not self.backoutcomplete:
+ return False
+ count = 0
+ for root, path, status in thgrepo.recursiveMergeStatus(self.repo):
+ if status == 'u':
+ count += 1
+ if count:
+ # if autoresolve is enabled, we know these were real conflicts
+ self.reslabel.setText(_('%d files have <b>merge conflicts</b> '
+ 'that must be <a href="resolve">'
+ '<b>resolved</b></a>') % count)
+ return False
+ else:
+ self.reslabel.setText(_('No merge conflicts, ready to commit'))
+ return True
+
+ def tryAutoAdvance(self, checked):
+ if checked and self.isComplete():
+ self.wizard().next()
+
+ def cleanupPage(self):
+ QSettings().setValue('backout/autoadvance', self.autonext.isChecked())
+
+ def onCommandFinished(self, ret):
+ self.repo.decrementBusyCount()
+ if ret in (0, 1):
+ self.backoutcomplete = True
+ if self.autonext.isChecked():
+ self.tryAutoAdvance(True)
+ self.completeChanged.emit()
+
+ @pyqtSlot(QString)
+ def onLinkActivated(self, cmd):
+ if cmd == 'resolve':
+ dlg = resolve.ResolveDialog(self.repo, self)
+ dlg.finished.connect(dlg.deleteLater)
+ dlg.exec_()
+ if self.autonext.isChecked():
+ self.tryAutoAdvance(True)
+ self.completeChanged.emit()
+
+
+class CommitPage(BasePage):
+
+ def __init__(self, repo, parent):
+ super(CommitPage, self).__init__(repo, parent)
+ self.commitComplete = False
+
+ self.setTitle(_('Commit backout and merge results'))
+ self.setSubTitle(' ')
+ self.setLayout(QVBoxLayout())
+ self.setCommitPage(True)
+
+ # csinfo
+ def label_func(widget, item, ctx):
+ if item == 'rev':
+ return _('Revision:')
+ elif item == 'parents':
+ return _('Parents')
+ raise csinfo.UnknownItem()
+ def data_func(widget, item, ctx):
+ if item == 'rev':
+ return _('Working Directory'), str(ctx)
+ elif item == 'parents':
+ parents = []
+ cbranch = ctx.branch()
+ for pctx in ctx.parents():
+ branch = None
+ if hasattr(pctx, 'branch') and pctx.branch() != cbranch:
+ branch = pctx.branch()
+ parents.append((str(pctx.rev()), str(pctx), branch, pctx))
+ return parents
+ raise csinfo.UnknownItem()
+ def markup_func(widget, item, value):
+ if item == 'rev':
+ text, rev = value
+ if self.wizard() and self.wizard().parentbackout:
+ return '%s (%s)' % (text, rev)
+ else:
+ return '<a href="view">%s</a> (%s)' % (text, rev)
+ elif item == 'parents':
+ def branch_markup(branch):
+ opts = dict(fg='black', bg='#aaffaa')
+ return qtlib.markup(' %s ' % branch, **opts)
+ csets = []
+ for rnum, rid, branch, pctx in value:
+ line = '%s (%s)' % (rnum, rid)
+ if branch:
+ line = '%s %s' % (line, branch_markup(branch))
+ msg = widget.info.get_data('summary', widget,
+ pctx, widget.custom)
+ if msg:
+ line = '%s %s' % (line, msg)
+ csets.append(line)
+ return csets
+ raise csinfo.UnknownItem()
+ custom = csinfo.custom(label=label_func, data=data_func,
+ markup=markup_func)
+ contents = ('rev', 'user', 'dateage', 'branch', 'parents')
+ style = csinfo.panelstyle(contents=contents, margin=6)
+
+ # merged files
+ rev_sep = qtlib.LabeledSeparator(_('Working Directory (merged)'))
+ self.layout().addWidget(rev_sep)
+ bkCsInfo = csinfo.create(repo, None, style, custom=custom,
+ withupdate=True)
+ bkCsInfo.linkActivated.connect(self.onLinkActivated)
+ self.layout().addWidget(bkCsInfo)
+
+ # commit message area
+ msg_sep = qtlib.LabeledSeparator(_('Commit message'))
+ self.layout().addWidget(msg_sep)
+ msgEntry = commit.MessageEntry(self)
+ msgEntry.installEventFilter(qscilib.KeyPressInterceptor(self))
+ msgEntry.refresh(repo)
+ msgEntry.loadSettings(QSettings(), 'backout/message')
+
+ msgEntry.textChanged.connect(self.completeChanged)
+ self.layout().addWidget(msgEntry)
+ self.msgEntry = msgEntry
+
+ self.cmd = cmdui.Widget(True, False, self)
+ self.cmd.commandFinished.connect(self.onCommandFinished)
+ self.cmd.setShowOutput(False)
+ self.layout().addWidget(self.cmd)
+
+ def tryperform():
+ if self.isComplete():
+ self.wizard().next()
+ actionEnter = QAction('alt-enter', self)
+ actionEnter.setShortcuts([Qt.CTRL+Qt.Key_Return, Qt.CTRL+Qt.Key_Enter])
+ actionEnter.triggered.connect(tryperform)
+ self.addAction(actionEnter)
+
+ self.skiplast = QCheckBox(_('Skip final confirmation page, '
+ 'close after commit.'))
+ checked = QSettings().value('backout/skiplast', False).toBool()
+ self.skiplast.setChecked(checked)
+ self.layout().addWidget(self.skiplast)
+
+ def eng_toggled(checked):
+ if self.isComplete():
+ oldmsg = self.msgEntry.text()
+ msgset = i18n.keepgettext()._('Backed out changeset: ')
+ msg = checked and msgset['id'] or msgset['str']
+ if oldmsg and oldmsg != msg:
+ if not qtlib.QuestionMsgBox(_('Confirm Discard Message'),
+ _('Discard current backout message?'), parent=self):
+ self.engChk.blockSignals(True)
+ self.engChk.setChecked(not checked)
+ self.engChk.blockSignals(False)
+ return
+ self.msgEntry.setText(msg
+ + str(self.repo[self.wizard().backoutrev]))
+ self.msgEntry.moveCursorToEnd()
self.engChk = QCheckBox(_('Use English backout message'))
- self.engChk.toggled.connect(self.eng_toggled)
+ self.engChk.toggled.connect(eng_toggled)
engmsg = self.repo.ui.configbool('tortoisehg', 'engmsg', False)
self.engChk.setChecked(engmsg)
+ self.layout().addWidget(self.engChk)
- obox.addWidget(self.engChk)
- self.mergeChk = QCheckBox(_('Commit backout before merging with '
- 'current working parent'))
- self.mergeChk.toggled.connect(self.merge_toggled)
- self.mergeChk.setChecked(bool(opts.get('merge')))
- self.msgTextEdit.setEnabled(False)
- obox.addWidget(self.mergeChk)
+ def refresh(self):
+ pass
- self.autoresolve_chk = QCheckBox(_('Automatically resolve merge conflicts '
- 'where possible'))
- self.autoresolve_chk.setChecked(
- repo.ui.configbool('tortoisehg', 'autoresolve', False))
- obox.addWidget(self.autoresolve_chk)
+ def cleanupPage(self):
+ s = QSettings()
+ s.setValue('backout/skiplast', self.skiplast.isChecked())
+ self.msgEntry.saveSettings(s, 'backout/message')
- if repo[revhex] == repo.parents()[0]:
- # backing out the working parent is a one-step process
- self.msgTextEdit.setEnabled(True)
- self.mergeChk.setVisible(False)
- self.autoresolve_chk.setVisible(False)
- self.backoutParent = True
- else:
- self.backoutParent = False
+ def currentPage(self):
+ engmsg = self.repo.ui.configbool('tortoisehg', 'engmsg', False)
+ msgset = i18n.keepgettext()._('Backed out changeset: ')
+ msg = engmsg and msgset['id'] or msgset['str']
+ self.msgEntry.setText(msg + str(self.repo[self.wizard().backoutrev]))
+ self.msgEntry.moveCursorToEnd()
- self.reslabel = QLabel()
- self.reslabel.linkActivated.connect(self.link_activated)
- box.addWidget(self.reslabel)
+ @pyqtSlot(QString)
+ def onLinkActivated(self, cmd):
+ if cmd == 'view':
+ dlg = status.StatusDialog(self.repo, [], {}, self)
+ dlg.exec_()
+ self.refresh()
- ## command widget
- self.cmd = cmdui.Widget(True, False, self)
- self.cmd.commandStarted.connect(self.command_started)
- self.cmd.commandFinished.connect(self.command_finished)
- self.cmd.commandCanceling.connect(self.command_canceling)
- box.addWidget(self.cmd, 1)
+ def isComplete(self):
+ return len(self.msgEntry.text()) > 0
- ## bottom buttons
- buttons = QDialogButtonBox()
- self.cancelBtn = buttons.addButton(QDialogButtonBox.Cancel)
- self.cancelBtn.clicked.connect(self.cancel_clicked)
- self.closeBtn = buttons.addButton(QDialogButtonBox.Close)
- self.closeBtn.clicked.connect(self.reject)
- self.backoutBtn = buttons.addButton(_('&Backout'),
- QDialogButtonBox.ActionRole)
- self.backoutBtn.clicked.connect(self.backout)
- self.detailBtn = buttons.addButton(_('Detail'),
- QDialogButtonBox.ResetRole)
- self.detailBtn.setAutoDefault(False)
- self.detailBtn.setCheckable(True)
- self.detailBtn.toggled.connect(self.detail_toggled)
- box.addWidget(buttons)
+ def validatePage(self):
+ if self.commitComplete:
+ # commit succeeded, repositoryChanged() called wizard().next()
+ if self.skiplast.isChecked():
+ self.wizard().close()
+ return True
+ if self.cmd.core.running():
+ return False
- # dialog setting
- self.setLayout(box)
- self.setMinimumWidth(480)
- self.setMaximumHeight(800)
- self.resize(0, 340)
- self.setWindowTitle(_("Backout '%s' - %s") % (revhex,
- self.repo.displayname))
-
- # prepare to show
- self.cmd.setHidden(True)
- self.cancelBtn.setHidden(True)
- self.detailBtn.setHidden(True)
- self.msgTextEdit.setFocus()
- self.msgTextEdit.moveCursorToEnd()
-
- ### Private Methods ###
-
- def merge_toggled(self, checked):
- self.msgTextEdit.setEnabled(checked)
-
- def eng_toggled(self, checked):
- msg = self.msgTextEdit.text()
- origmsg = (checked and self.msgset['str'] or self.msgset['id'])
- if msg != origmsg:
- if not qtlib.QuestionMsgBox(_('Confirm Discard Message'),
- _('Discard current backout message?'), parent=self):
- self.engChk.blockSignals(True)
- self.engChk.setChecked(not checked)
- self.engChk.blockSignals(False)
- return
- newmsg = (checked and self.msgset['id'] or self.msgset['str'])
- self.msgTextEdit.setText(newmsg)
-
- def backout(self):
user = qtlib.getCurrentUsername(self, self.repo)
if not user:
- return
- # prepare command line
- revhex = self.targetinfo.get_data('revid')
- cmdline = ['backout', '--rev', revhex, '--repository', self.repo.root,
- '--user', user]
- cmdline += ['--tool=internal:' +
- (self.autoresolve_chk.isChecked() and 'merge' or 'fail')]
- if self.backoutParent:
- msg = self.msgTextEdit.text()
- cmdline += ['--message='+hglib.fromunicode(msg)]
- commandlines = [cmdline]
- pushafter = self.repo.ui.config('tortoisehg', 'cipushafter')
- if pushafter:
- cmd = ['push', '--repository', self.repo.root, pushafter]
- commandlines.append(cmd)
- elif self.mergeChk.isChecked():
- cmdline += ['--merge']
- msg = self.msgTextEdit.text()
- cmdline += ['--message', hglib.fromunicode(msg)]
- commandlines = [cmdline]
+ return False
+
+ if self.wizard().parentbackout:
+ self.setTitle(_('Backing out and committing...'))
+ self.setSubTitle(_('Please wait while making backout.'))
+ message = hglib.fromunicode(self.msgEntry.text())
+ cmdline = ['backout', '--verbose', '--message', message, '--rev',
+ str(self.wizard().backoutrev), '--user', user,
+ '--repository', self.repo.root]
else:
- commandlines = [cmdline]
-
- # start backing out
- self.cmdline = cmdline
- self.repo.incrementBusyCount()
- self.cmd.run(*commandlines)
-
- def commit(self):
- cmdline = ['commit', '--repository', self.repo.root]
- msg = self.msgTextEdit.text()
- cmdline += ['--message='+hglib.fromunicode(msg)]
- self.cmdline = cmdline
+ self.setTitle(_('Committing...'))
+ self.setSubTitle(_('Please wait while committing merged files.'))
+ message = hglib.fromunicode(self.msgEntry.text())
+ cmdline = ['commit', '--verbose', '--message', message,
+ '--repository', self.repo.root, '--user', user]
commandlines = [cmdline]
pushafter = self.repo.ui.config('tortoisehg', 'cipushafter')
if pushafter:
cmd = ['push', '--repository', self.repo.root, pushafter]
commandlines.append(cmd)
+
self.repo.incrementBusyCount()
+ self.cmd.setShowOutput(True)
self.cmd.run(*commandlines)
+ return False
- ### Signal Handlers ###
+ def onCommandFinished(self, ret):
+ self.repo.decrementBusyCount()
+ if ret == 0:
+ self.commitComplete = True
+ self.wizard().next()
- def cancel_clicked(self):
- self.cmd.cancel()
- def detail_toggled(self, checked):
- self.cmd.setShowOutput(checked)
+class ResultPage(BasePage):
+ def __init__(self, repo, parent):
+ super(ResultPage, self).__init__(repo, parent)
+ self.setTitle(_('Finished'))
+ self.setFinalPage(True)
- def command_started(self):
- self.cmd.setShown(True)
- self.mergeChk.setVisible(False)
- self.closeBtn.setHidden(True)
- self.cancelBtn.setShown(True)
- self.detailBtn.setShown(True)
- self.backoutBtn.setEnabled(False)
+ self.setLayout(QVBoxLayout())
+ sep = qtlib.LabeledSeparator(_('Backout changeset'))
+ self.layout().addWidget(sep)
+ bkCsInfo = csinfo.create(self.repo, 'tip', withupdate=True)
+ self.layout().addWidget(bkCsInfo)
+ self.bkCsInfo = bkCsInfo
+ self.layout().addStretch(1)
- def command_canceling(self):
- self.cancelBtn.setDisabled(True)
+ def currentPage(self):
+ self.bkCsInfo.update(self.repo['tip'])
+ self.wizard().setOption(QWizard.NoCancelButton, True)
- def command_finished(self, ret):
- self.repo.decrementBusyCount()
- self.cancelBtn.setHidden(True)
- # If the action wasn't successful, display the output and we're done
- if ret not in (0, 1):
- self.detailBtn.setChecked(True)
- self.closeBtn.setShown(True)
- self.closeBtn.setAutoDefault(True)
- self.closeBtn.setFocus()
- else:
- finished = True
- #If we backed out our parent, there is no second commit step
- if self.cmdline[0] == 'backout' and not self.backoutParent:
- finished = False
- self.msgTextEdit.setEnabled(True)
- self.backoutBtn.setEnabled(True)
- self.backoutBtn.setText(_('Commit', 'action button'))
- self.backoutBtn.clicked.disconnect(self.backout)
- self.backoutBtn.clicked.connect(self.commit)
- self.checkResolve()
+class CheckThread(QThread):
+ def __init__(self, repo, parent):
+ QThread.__init__(self, parent)
+ self.repo = hg.repository(repo.ui, repo.root)
+ self.results = (False, 1)
+ self.canceled = False
- if finished:
- if not self.cmd.outputShown():
- self.accept()
- else:
- self.closeBtn.clicked.disconnect(self.reject)
- self.closeBtn.clicked.connect(self.accept)
- self.closeBtn.setHidden(False)
+ def run(self):
+ self.repo.dirstate.invalidate()
+ unresolved = False
+ for root, path, status in thgrepo.recursiveMergeStatus(self.repo):
+ if self.canceled:
+ return
+ if status == 'u':
+ unresolved = True
+ break
+ wctx = self.repo[None]
+ dirty = bool(wctx.dirty()) or unresolved
+ self.results = (dirty, len(wctx.parents()))
- def checkResolve(self):
- for root, path, status in thgrepo.recursiveMergeStatus(self.repo):
- if status == 'u':
- txt = _('Backout generated merge <b>conflicts</b> that must '
- 'be <a href="resolve"><b>resolved</b></a>')
- self.backoutBtn.setEnabled(False)
- break
- else:
- self.backoutBtn.setEnabled(True)
- txt = _('You may commit the backed out changes after '
- '<a href="status"><b>verifying</b></a> them')
- self.reslabel.setText(txt)
+ def cancel(self):
+ self.canceled = True
- @pyqtSlot(QString)
- def link_activated(self, cmd):
- if cmd == 'resolve':
- dlg = resolve.ResolveDialog(self.repo, self)
- dlg.finished.connect(dlg.deleteLater)
- dlg.exec_()
- self.checkResolve()
- elif cmd == 'status':
- dlg = status.StatusDialog([], {}, self.repo.root, self)
- dlg.finished.connect(dlg.deleteLater)
- dlg.exec_()
- self.checkResolve()
-
- def accept(self):
- self.msgTextEdit.saveSettings(QSettings(), 'backout/message')
- super(BackoutDialog, self).accept()
def run(ui, *pats, **opts):
from tortoisehg.util import paths
repo = thgrepo.repository(ui, path=paths.find_root())
- kargs = {'opts': opts}
if opts.get('rev'):
- kargs['rev'] = opts.get('rev')
+ rev = opts.get('rev')
elif len(pats) == 1:
- kargs['rev'] = pats[0]
- return BackoutDialog(repo, **kargs)
+ rev = pats[0]
+ return BackoutDialog(rev, repo, None)
|
@@ -8,7 +8,7 @@ import os
import sys
-from mercurial import extensions, ui
+from mercurial import extensions
from tortoisehg.util import hglib, version
from tortoisehg.hgqt.i18n import _
@@ -147,6 +147,7 @@ self._textlabel = QLabel(text, wordWrap=True,
textInteractionFlags=labelflags)
self._textlabel.linkActivated.connect(self._openlink)
+ self._textlabel.setWordWrap(False)
self.layout().addWidget(self._textlabel)
bb = QDialogButtonBox(QDialogButtonBox.Close, centerButtons=True)
@@ -159,11 +160,20 @@ if ref == '#bugreport':
return BugReport(self._opts, self).exec_()
if ref.startswith('#edit:'):
- from tortoisehg.hgqt import wctxactions
fname, lineno = ref[6:].rsplit(':', 1)
- # A chicken-egg problem here, we need a ui to get your
- # editor in order to repair your ui config file.
- wctxactions.edit(self, ui.ui(), None, [fname], lineno, None)
+ try:
+ # A chicken-egg problem here, we need a ui to get your
+ # editor in order to repair your ui config file.
+ from mercurial import ui as uimod
+ from tortoisehg.hgqt import qtlib
+ class FakeRepo(object):
+ def __init__(self):
+ self.root = os.getcwd()
+ self.ui = uimod.ui()
+ fake = FakeRepo()
+ qtlib.editfiles(fake, [fname], lineno, parent=self)
+ except Exception, e:
+ QDesktopServices.openUrl(QUrl.fromLocalFile(fname))
def run(ui, *pats, **opts):
return BugReport(opts)
|
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
@@ -125,13 +125,13 @@
def commit(self):
tip, base = self.revs
- func = revset.match('%s::%s' % (base, tip))
+ func = revset.match(self.repo.ui, '%s::%s' % (base, tip))
revcount = len(self.repo)
revs = [c for c in func(self.repo, range(revcount)) if c != base]
descs = [self.repo[c].description() for c in revs]
self.repo.opener('cur-message.txt', 'w').write('\n* * *\n'.join(descs))
- dlg = commit.CommitDialog([], dict(root=self.repo.root), self)
+ dlg = commit.CommitDialog(self.repo, [], {}, self)
dlg.finished.connect(dlg.deleteLater)
dlg.exec_()
self.showMessage.emit(_('Compress is complete, old history untouched'))
@@ -141,7 +141,7 @@
def linkActivated(self, cmd):
if cmd == 'commit':
- dlg = commit.CommitDialog([], dict(root=self.repo.root), self)
+ dlg = commit.CommitDialog(self.repo, [], {}, self)
dlg.finished.connect(dlg.deleteLater)
dlg.exec_()
self.checkStatus()
|
@@ -223,7 +223,7 @@ raise UnknownItem(item)
if 'label' in custom and not kargs.get('usepreset', False):
try:
- return custom['label'](widget, item)
+ return custom['label'](widget, item, ctx)
except UnknownItem:
pass
try:
|
@@ -81,9 +81,9 @@ filename = os.path.basename(widget.target)
return filename, revid
raise csinfo.UnknownItem(item)
- def labelfunc(widget, item):
+ def labelfunc(widget, item, ctx):
if item in ('item', 'item_l'):
- if not isinstance(widget.ctx, patchctx):
+ if not isinstance(ctx, patchctx):
return _('Revision:')
return _('Patch:')
raise csinfo.UnknownItem(item)
|
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
@@ -27,23 +27,16 @@ """
Model used for listing (modified) files of a given Hg revision
"""
-
- contextChanged = pyqtSignal(object)
showMessage = pyqtSignal(QString)
- def __init__(self, repo, parent):
- """
- data is a HgHLRepo instance
- """
+ def __init__(self, parent):
QAbstractTableModel.__init__(self, parent)
- self.repo = repo
self._boldfont = parent.font()
self._boldfont.setBold(True)
self._ctx = None
self._files = []
self._filesdict = {}
self._fulllist = False
- self._secondParent = False
@pyqtSlot(bool)
def toggleFullFileList(self, value):
@@ -51,11 +44,6 @@ self.loadFiles()
self.layoutChanged.emit()
- @pyqtSlot(bool)
- def toggleSecondParent(self, value):
- self._secondParent = value
- self.layoutChanged.emit()
-
def __len__(self):
return len(self._files)
@@ -69,7 +57,6 @@ return self._files[row]['path']
def setContext(self, ctx):
- self.contextChanged.emit(ctx)
reload = False
if not self._ctx:
reload = True
@@ -88,18 +75,6 @@ row = index.row()
return self._files[row]['path']
- def revFromIndex(self, index):
- 'return revision for index. index is guarunteed to be valid'
- if len(self._ctx.parents()) < 2:
- return None
- row = index.row()
- data = self._files[row]
- if (data['wasmerged'] and self._secondParent) or \
- (data['parent'] == 1 and self._fulllist):
- return self._ctx.p2().rev()
- else:
- return self._ctx.p1().rev()
-
def dataFromIndex(self, index):
if not index.isValid() or index.row()>=len(self) or not self._ctx:
return None
|
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
@@ -14,12 +14,18 @@ # this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-from tortoisehg.hgqt.repomodel import HgRepoListModel, COLUMNNAMES
+from tortoisehg.hgqt.repomodel import HgRepoListModel, COLUMNHEADERS
from tortoisehg.hgqt.graph import Graph, filelog_grapher
from tortoisehg.hgqt.i18n import _
from PyQt4.QtCore import *
+FILE_HEADERS = (('Filename', _('Filename', 'column header')),)
+UNUSED_HEADERS = ('Graph', 'Changes')
+
+FILE_COLUMNHEADERS = tuple(c for c in COLUMNHEADERS
+ if c[0] not in UNUSED_HEADERS) + FILE_HEADERS
+
class FileRevModel(HgRepoListModel):
"""
Model used to manage the list of revisions of a file, in file
@@ -27,18 +33,18 @@ """
filled = pyqtSignal()
- _allcolumns = ('Rev', 'Branch', 'Description', 'Author', 'Age',
- 'LocalTime', 'UTCTime', 'Tags', 'Filename')
+ _allcolumns = tuple(h[0] for h in FILE_COLUMNHEADERS)
+ _allcolnames = dict(FILE_COLUMNHEADERS)
+
_columns = ('Rev', 'Branch', 'Description', 'Author', 'Age', 'Filename')
_stretchs = {'Description': 1, }
_getcolumns = "getFilelogColumns"
- def __init__(self, repo, filename=None, parent=None):
+ def __init__(self, repo, cfgname, filename=None, parent=None):
"""
data is a HgHLRepo instance
"""
- HgRepoListModel.__init__(self, repo, '', [], False, parent)
- COLUMNNAMES['Filename'] = _('Filename', 'column header')
+ HgRepoListModel.__init__(self, repo, cfgname, '', [], False, parent)
self.setFilename(filename)
def setRepo(self, repo, branch='', fromhead=None, follow=False):
|
|
|
@@ -1,33 +1,19 @@ - # Copyright (c) 2009-2010 LOGILAB S.A. (Paris, FRANCE).
-# http://www.logilab.fr/ -- mailto:contact@logilab.fr
+# fileview.py - File diff, content, and annotation display widget
#
-# This program is free software; you can redistribute it and/or modify it under
-# the terms of the GNU General Public License as published by the Free Software
-# Foundation; either version 2 of the License, or (at your option) any later
-# version.
+# Copyright 2010 Steve Borho <steve@borho.org>
#
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along with
-# this program; if not, write to the Free Software Foundation, Inc.,
-# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-"""
-Qt4 high level widgets for hg repo changelogs and filelogs
-"""
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2, incorporated herein by reference.
import os
import difflib
-import re
-from mercurial import hg, error, match, patch, util
-from mercurial import ui as uimod, mdiff
+from mercurial import error, util
-from tortoisehg.util import hglib, patchctx
+from tortoisehg.util import hglib, patchctx, colormap, thread2
from tortoisehg.hgqt.i18n import _
-from tortoisehg.hgqt import annotate, qscilib, qtlib, blockmatcher, lexers
-from tortoisehg.hgqt import visdiff, wctxactions
+from tortoisehg.hgqt import qscilib, qtlib, blockmatcher, lexers
+from tortoisehg.hgqt import visdiff, filedata
from PyQt4.QtCore import *
from PyQt4.QtGui import *
@@ -35,8 +21,12 @@
qsci = Qsci.QsciScintilla
+DiffMode = 1
+FileMode = 2
+AnnMode = 3
+
class HgFileView(QFrame):
- """file diff and content viewer"""
+ "file diff, content, and annotation viewer"
linkActivated = pyqtSignal(QString)
fileDisplayed = pyqtSignal(QString, QString)
@@ -44,9 +34,6 @@ revisionSelected = pyqtSignal(int)
shelveToolExited = pyqtSignal()
- searchRequested = pyqtSignal(unicode)
- """Emitted (pattern) when user request to search content"""
-
grepRequested = pyqtSignal(unicode, dict)
"""Emitted (pattern, opts) when user request to search changelog"""
@@ -94,15 +81,14 @@ l.addLayout(hbox)
self.blk = blockmatcher.BlockList(self)
- self.sci = annotate.AnnotateView(repo, self)
+ self.sci = AnnotateView(repo, self)
hbox.addWidget(self.blk)
hbox.addWidget(self.sci, 1)
- for name in ('searchRequested', 'editSelected', 'grepRequested'):
- getattr(self.sci, name).connect(getattr(self, name))
- self.sci.revisionHint.connect(self.showMessage)
- self.sci.sourceChanged.connect(self.sourceChanged)
+ self.sci.showMessage.connect(self.showMessage)
self.sci.setAnnotationEnabled(False)
+ self.sci.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.sci.customContextMenuRequested.connect(self.menuRequest)
self.blk.linkScrollBar(self.sci.verticalScrollBar())
self.blk.setVisible(False)
@@ -135,23 +121,25 @@ self._filename = None
self._status = None
self._mode = None
+ self._parent = 0
self._lostMode = None
self._lastSearch = u'', False
- self._lastScrollPosition = 0
self.actionDiffMode = QAction(qtlib.geticon('view-diff'),
_('View change as unified diff output'),
self)
self.actionDiffMode.setCheckable(True)
+ self.actionDiffMode._mode = DiffMode
self.actionFileMode = QAction(qtlib.geticon('view-file'),
_('View change in context of file'),
self)
self.actionFileMode.setCheckable(True)
+ self.actionFileMode._mode = FileMode
self.actionAnnMode = QAction(qtlib.geticon('view-annotate'),
- _('View change in context, annotate with '
- 'revision number'),
+ _('annotate with revision numbers'),
self)
self.actionAnnMode.setCheckable(True)
+ self.actionAnnMode._mode = AnnMode
self.modeToggleGroup = QActionGroup(self)
self.modeToggleGroup.addAction(self.actionDiffMode)
@@ -168,8 +156,21 @@ _('Previous diff (alt+up)'), self)
self.actionPrevDiff.setShortcut('Alt+Up')
self.actionPrevDiff.triggered.connect(self.prevDiff)
+ self.setMode(self.actionDiffMode)
- self.forceMode('diff')
+ self.actionFirstParent = QAction('1', self)
+ self.actionFirstParent.setCheckable(True)
+ self.actionFirstParent.setChecked(True)
+ self.actionFirstParent.setShortcut('CTRL+1')
+ self.actionFirstParent.setToolTip(_('Show changes from first parent'))
+ self.actionSecondParent = QAction('2', self)
+ self.actionSecondParent.setCheckable(True)
+ self.actionSecondParent.setShortcut('CTRL+2')
+ self.actionSecondParent.setToolTip(_('Show changes from second parent'))
+ self.parentToggleGroup = QActionGroup(self)
+ self.parentToggleGroup.addAction(self.actionFirstParent)
+ self.parentToggleGroup.addAction(self.actionSecondParent)
+ self.parentToggleGroup.triggered.connect(self.setParent)
self.actionFind = self.searchbar.toggleViewAction()
self.actionFind.setIcon(qtlib.geticon('edit-find'))
@@ -182,6 +183,9 @@ self.actionShelf.triggered.connect(self.launchShelve)
tb = self.diffToolbar
+ tb.addAction(self.actionFirstParent)
+ tb.addAction(self.actionSecondParent)
+ tb.addSeparator()
tb.addAction(self.actionDiffMode)
tb.addAction(self.actionFileMode)
tb.addAction(self.actionAnnMode)
@@ -220,53 +224,73 @@ @pyqtSlot(QAction)
def setMode(self, action):
'One of the mode toolbar buttons has been toggled'
-
- mode = {self.actionDiffMode.text():'diff',
- self.actionFileMode.text():'file',
- self.actionAnnMode.text() :'ann'}[action.text()]
- self.actionNextDiff.setEnabled(mode == 'file')
- self.actionPrevDiff.setEnabled(False)
- self.blk.setVisible(mode == 'file')
- self.sci.setAnnotationEnabled(mode == 'ann')
+ mode = action._mode
if mode != self._mode:
self._mode = mode
- if not self._lostMode:
- self.displayFile()
+ self.actionNextDiff.setEnabled(False)
+ self.actionPrevDiff.setEnabled(False)
+ self.blk.setVisible(mode != DiffMode)
+ self.sci.setAnnotationEnabled(mode == AnnMode)
+ self.displayFile(self._filename, self._status)
- def forceMode(self, mode):
- 'Force into file or diff mode, based on content constaints'
- assert mode in ('diff', 'file')
+ @pyqtSlot(QAction)
+ def setParent(self, action):
+ if action.text() == '1':
+ parent = 0
+ else:
+ parent = 1
+ if self._parent != parent:
+ self._parent = parent
+ self.displayFile(self._filename, self._status)
+
+ def restrictModes(self, candiff, canfile, canann):
+ 'Disable modes based on content constraints'
+ self.actionDiffMode.setEnabled(candiff)
+ self.actionFileMode.setEnabled(canfile)
+ self.actionAnnMode.setEnabled(canann)
+
+ # Switch mode if necessary
+ mode = self._mode
+ if not candiff and mode == DiffMode and canfile:
+ mode = FileMode
+ if not canfile and mode != DiffMode:
+ mode = DiffMode
if self._lostMode is None:
self._lostMode = self._mode
- self._mode = mode
- if mode == 'diff':
+ if self._mode != mode:
+ self.actionNextDiff.setEnabled(False)
+ self.actionPrevDiff.setEnabled(False)
+ self.blk.setVisible(mode != DiffMode)
+ self.sci.setAnnotationEnabled(mode == AnnMode)
+ self._mode = mode
+
+ if self._mode == DiffMode:
self.actionDiffMode.setChecked(True)
+ elif self._mode == FileMode:
+ self.actionFileMode.setChecked(True)
else:
- self.actionFileMode.setChecked(True)
- self.actionDiffMode.setEnabled(False)
- self.actionFileMode.setEnabled(False)
- self.actionAnnMode.setEnabled(False)
- self.actionNextDiff.setEnabled(False)
- self.actionPrevDiff.setEnabled(False)
- self.blk.setVisible(mode == 'file')
- self.sci.setAnnotationEnabled(False)
+ self.actionAnnMode.setChecked(True)
- def setContext(self, ctx):
+ def setContext(self, ctx, ctx2=None):
self._ctx = ctx
- self._p_rev = None
+ self._ctx2 = ctx2
self.sci.setTabWidth(ctx._repo.tabwidth)
self.actionAnnMode.setVisible(ctx.rev() != None)
self.actionShelf.setVisible(ctx.rev() == None)
+ self.actionFirstParent.setVisible(len(ctx.parents()) == 2)
+ self.actionSecondParent.setVisible(len(ctx.parents()) == 2)
+ self.actionFirstParent.setEnabled(len(ctx.parents()) == 2)
+ self.actionSecondParent.setEnabled(len(ctx.parents()) == 2)
- def displayDiff(self, rev):
- if rev != self._p_rev:
- self.displayFile(rev=rev)
+ def showLine(self, line):
+ if line < self.sci.lines():
+ self.sci.setCursorPosition(line, 0)
@pyqtSlot()
def clearDisplay(self):
self._filename = None
- self._lastScrollPosition = 0
- self.forceMode('diff')
+ self.restrictModes(False, False, False)
+ self.sci.setMarginWidth(1, 0)
self.clearMarkup()
def clearMarkup(self):
@@ -276,34 +300,33 @@ # from disappearing during refresh, and tool layouts bouncing
self.filenamelabel.setText(' ')
self.extralabel.hide()
+ self.actionNextDiff.setEnabled(False)
+ self.actionPrevDiff.setEnabled(False)
- def displayFile(self, filename=None, rev=None, status=None):
- # Get the last visible line to restore it after reloading the editor
- self._lastScrollPosition = self.sci.firstVisibleLine()
-
- if filename is None:
- filename, status = self._filename, self._status
- else:
- if self._filename != filename:
- # Reset the scroll positions when the file is changed
- self._lastScrollPosition = 0
- self._filename, self._status = filename, status
+ def displayFile(self, filename=None, status=None):
if isinstance(filename, (unicode, QString)):
filename = hglib.fromunicode(filename)
- if rev is not None:
- self._p_rev = rev
+ status = hglib.fromunicode(status)
+ if self._filename == filename:
+ # Get the last visible line to restore it after reloading the editor
+ lastScrollPosition = self.sci.firstVisibleLine()
+ else:
+ # Reset the scroll positions when the file is changed
+ lastScrollPosition = 0
+ self._filename, self._status = filename, status
self.clearMarkup()
if filename is None:
- self.forceMode('file')
+ self.restrictModes(False, False, False)
return
- if self._p_rev is not None:
- ctx2 = self.repo[self._p_rev]
+ if self._ctx2:
+ ctx2 = self._ctx2
+ elif self._parent == 0 or len(self._ctx.parents()) == 1:
+ ctx2 = self._ctx.p1()
else:
- ctx2 = None
-
- fd = FileData(self._ctx, ctx2, filename, status)
+ ctx2 = self._ctx.p2()
+ fd = filedata.FileData(self._ctx, ctx2, filename, status)
if fd.elabel:
self.extralabel.setText(fd.elabel)
@@ -314,83 +337,92 @@
if not fd.isValid():
self.sci.setText(fd.error)
- self.forceMode('file')
+ self.sci.setLexer(None)
+ self.sci.setFont(qtlib.getfont('fontlog').font())
+ self.sci.setMarginWidth(1, 0)
+ self.blk.setVisible(False)
+ self.restrictModes(False, False, False)
return
- if fd.diff and not fd.contents:
- self.forceMode('diff')
- elif fd.contents and not fd.diff:
- self.forceMode('file')
- elif not fd.contents and not fd.diff:
- self.forceMode('file')
+ candiff = bool(fd.diff)
+ canfile = bool(fd.contents or fd.ucontents)
+ canann = bool(fd.contents) and type(self._ctx.rev()) is int
+
+ if not candiff or not canfile:
+ self.restrictModes(candiff, canfile, canann)
else:
self.actionDiffMode.setEnabled(True)
self.actionFileMode.setEnabled(True)
self.actionAnnMode.setEnabled(True)
if self._lostMode:
- if self._lostMode == 'diff':
+ self._mode = self._lostMode
+ if self._lostMode == DiffMode:
self.actionDiffMode.trigger()
- elif self._lostMode == 'file':
+ elif self._lostMode == FileMode:
self.actionFileMode.trigger()
- elif self._lostMode == 'ann':
+ elif self._lostMode == AnnMode:
self.actionAnnMode.trigger()
self._lostMode = None
+ self.blk.setVisible(self._mode != DiffMode)
+ self.sci.setAnnotationEnabled(self._mode == AnnMode)
- if self._mode == 'diff':
+ if self._mode == DiffMode:
self.sci.setMarginWidth(1, 0)
lexer = lexers.get_diff_lexer(self)
self.sci.setLexer(lexer)
if lexer is None:
- self.setFont(qtlib.getfont('fontlog').font())
+ self.sci.setFont(qtlib.getfont('fontlog').font())
# trim first three lines, for example:
# diff -r f6bfc41af6d7 -r c1b18806486d tortoisehg/hgqt/thgrepo.py
# --- a/tortoisehg/hgqt/thgrepo.py
# +++ b/tortoisehg/hgqt/thgrepo.py
- out = fd.diff.split('\n', 3)
- if len(out) == 4:
- self.sci.setText(hglib.tounicode(out[3]))
- else:
- # there was an error or rename without diffs
- self.sci.setText(hglib.tounicode(fd.diff))
- elif fd.contents is None:
+ if fd.diff:
+ out = fd.diff.split('\n', 3)
+ if len(out) == 4:
+ self.sci.setText(hglib.tounicode(out[3]))
+ else:
+ # there was an error or rename without diffs
+ self.sci.setText(hglib.tounicode(fd.diff))
+ elif fd.ucontents:
+ # subrepo summary and perhaps other data
+ self.sci.setText(fd.ucontents)
+ self.sci.setLexer(None)
+ self.sci.setFont(qtlib.getfont('fontlog').font())
+ self.sci.setMarginWidth(1, 0)
+ self.blk.setVisible(False)
return
- elif self._mode == 'ann':
- self.sci.setSource(filename, self._ctx.rev())
-
- # Recover the last scroll position
- # Make sure that _lastScrollPosition never exceeds the amount of
- # lines on the editor
- self._lastScrollPosition = min(self._lastScrollPosition, \
- self.sci.lines() - 1)
- self.sci.verticalScrollBar().setValue(self._lastScrollPosition)
- else:
+ elif fd.contents:
lexer = lexers.get_lexer(filename, fd.contents, self)
self.sci.setLexer(lexer)
if lexer is None:
- self.setFont(qtlib.getfont('fontlog').font())
- self.sci.setText(fd.contents)
+ self.sci.setFont(qtlib.getfont('fontlog').font())
+ self.sci.setText(hglib.tounicode(fd.contents))
+ self.blk.setVisible(True)
self.sci._updatemarginwidth()
+ if self._mode == AnnMode:
+ self.sci._updateannotation(self._ctx, filename)
+ else:
+ return
- # Recover the last scroll position
- # Make sure that _lastScrollPosition never exceeds the amount of
- # lines on the editor
- self._lastScrollPosition = min(self._lastScrollPosition, \
- self.sci.lines() - 1)
- self.sci.verticalScrollBar().setValue(self._lastScrollPosition)
+ # Recover the last scroll position
+ # Make sure that lastScrollPosition never exceeds the amount of
+ # lines on the editor
+ lastScrollPosition = min(lastScrollPosition, self.sci.lines() - 1)
+ self.sci.verticalScrollBar().setValue(lastScrollPosition)
self.highlightText(*self._lastSearch)
- uf = hglib.tounicode(self._filename)
- self.fileDisplayed.emit(uf, fd.contents or QString())
+ uf = hglib.tounicode(filename)
+ uc = hglib.tounicode(fd.contents) or ''
+ self.fileDisplayed.emit(uf, uc)
- if self._mode == 'file' and fd.contents and fd.olddata:
- # Update blk margin
+ if self._mode != DiffMode:
+ self.blk.setVisible(True)
+ self.blk.syncPageStep()
+
+ if self._mode != DiffMode and fd.contents and fd.olddata:
if self.timer.isActive():
self.timer.stop()
-
self._fd = fd
- self.actionNextDiff.setEnabled(False)
- self.actionPrevDiff.setEnabled(False)
- self.blk.syncPageStep()
self.timer.start()
#
@@ -412,20 +444,6 @@ x, y = self.sci.getCursorPosition()
self.sci.setCursorPosition(x, y-1)
- @pyqtSlot(unicode, object)
- @pyqtSlot(unicode, object, int)
- def sourceChanged(self, path, rev, line=None):
- self.revisionSelected.emit(rev)
-
- @pyqtSlot(unicode, object, int)
- def editSelected(self, path, rev, line):
- """Open editor to show the specified file"""
- path = hglib.fromunicode(path)
- base = visdiff.snapshot(self.repo, [path], self.repo[rev])[0]
- files = [os.path.join(base, path)]
- pattern = hglib.fromunicode(self._lastSearch[0])
- wctxactions.edit(self, self.repo.ui, self.repo, files, line, pattern)
-
@pyqtSlot(unicode, bool, bool, bool)
def find(self, exp, icase=True, wrap=False, forward=True):
self.sci.find(exp, icase, wrap, forward)
@@ -480,7 +498,7 @@ self.blk.setUpdatesEnabled(True)
def nextDiff(self):
- if self._mode == 'diff' or not self._diffs:
+ if self._mode == DiffMode or not self._diffs:
self.actionNextDiff.setEnabled(False)
self.actionPrevDiff.setEnabled(False)
return
@@ -497,7 +515,7 @@ self.actionPrevDiff.setEnabled(True)
def prevDiff(self):
- if self._mode == 'diff' or not self._diffs:
+ if self._mode == DiffMode or not self._diffs:
self.actionNextDiff.setEnabled(False)
self.actionPrevDiff.setEnabled(False)
return
@@ -516,286 +534,282 @@ def nDiffs(self):
return len(self._diffs)
+ def editSelected(self, path, rev, line):
+ """Open editor to show the specified file"""
+ path = hglib.fromunicode(path)
+ base = visdiff.snapshot(self.repo, [path], self.repo[rev])[0]
+ files = [os.path.join(base, path)]
+ pattern = hglib.fromunicode(self._lastSearch[0])
+ qtlib.editfiles(self.repo, files, line, pattern, self)
-class FileData(object):
- def __init__(self, ctx, ctx2, wfile, status=None):
- self.contents = None
- self.error = None
- self.olddata = None
- self.diff = None
- self.flabel = u''
- self.elabel = u''
+ @pyqtSlot(QPoint)
+ def menuRequest(self, point):
+ menu = self.sci.createStandardContextMenu()
+ line = self.sci.lineAt(point)
+ point = self.sci.mapToGlobal(point)
+
+ selection = self.sci.selectedText()
+ def sreq(**opts):
+ return lambda: self.grepRequested.emit(selection, opts)
+ def sann():
+ self.searchbar.search(selection)
+ self.searchbar.show()
+
+ if self._mode != AnnMode:
+ if selection:
+ menu.addSeparator()
+ for name, func in [(_('Search in current file'), sann),
+ (_('Search in history'), sreq(all=True))]:
+ def add(name, func):
+ action = menu.addAction(name)
+ action.triggered.connect(func)
+ add(name, func)
+ return menu.exec_(point)
+
+ if line < 0 or line >= len(self.sci._links):
+ return menu.exec_(point)
+
+ fctx, line = self.sci._links[line]
+ if selection:
+ def sreq(**opts):
+ return lambda: self.grepRequested.emit(selection, opts)
+ def sann():
+ self.searchbar.search(selection)
+ self.searchbar.show()
+ menu.addSeparator()
+ for name, func in [(_('Search in original revision'),
+ sreq(rev=fctx.rev())),
+ (_('Search in working revision'),
+ sreq(rev='.')),
+ (_('Search in current annotation'), sann),
+ (_('Search in history'), sreq(all=True))]:
+ def add(name, func):
+ action = menu.addAction(name)
+ action.triggered.connect(func)
+ add(name, func)
+
+ def setSource(path, rev, line):
+ self.revisionSelected.emit(rev)
+ self.setContext(self.repo[rev])
+ self.displayFile(path, None)
+ self.showLine(line)
+
+ data = [hglib.tounicode(fctx.path()), fctx.rev(), line]
+
+ def annorig():
+ setSource(*data)
+ def editorig():
+ self.editSelected(*data)
+ menu.addSeparator()
+ for name, func in [(_('Annotate originating revision'), annorig),
+ (_('View originating revision'), editorig)]:
+ def add(name, func):
+ action = menu.addAction(name)
+ action.triggered.connect(func)
+ add(name, func)
+ for pfctx in fctx.parents():
+ pdata = [hglib.tounicode(pfctx.path()), pfctx.changectx().rev(),
+ line]
+ def annparent(data):
+ setSource(*data)
+ def editparent(data):
+ self.editSelected(*data)
+ for name, func in [(_('Annotate parent revision %d') % pdata[1],
+ annparent),
+ (_('View parent revision %d') % pdata[1],
+ editparent)]:
+ def add(name, func):
+ action = menu.addAction(name)
+ action.data = pdata
+ action.run = lambda: func(action.data)
+ action.triggered.connect(action.run)
+ add(name, func)
+ menu.exec_(point)
+
+
+class AnnotateView(qscilib.Scintilla):
+ 'QScintilla widget capable of displaying annotations'
+
+ showMessage = pyqtSignal(QString)
+
+ def __init__(self, repo, parent=None):
+ super(AnnotateView, self).__init__(parent)
+ self.setReadOnly(True)
+ self.setMarginLineNumbers(1, True)
+ self.setMarginType(2, qsci.TextMarginRightJustified)
+ self.setMouseTracking(False)
+
+ self._annotation_enabled = False
+ self._links = [] # by line
+ self._revmarkers = {} # by rev
+ self._lastrev = None
+
+ self._thread = AnnotateThread(self)
+ self._thread.finished.connect(self.fillModel)
+
+ self.repo = repo
+ self.repo.configChanged.connect(self.configChanged)
+ self.configChanged()
+
+ def configChanged(self):
+ self.setIndentationWidth(self.repo.tabwidth)
+ self.setTabWidth(self.repo.tabwidth)
+
+ def keyPressEvent(self, event):
+ if event.key() == Qt.Key_Escape:
+ self._thread.abort()
+ return
+ return super(AnnotateView, self).keyPressEvent(event)
+
+ def mouseMoveEvent(self, event):
+ self._emitRevisionHintAtLine(self.lineAt(event.pos()))
+ super(AnnotateView, self).mouseMoveEvent(event)
+
+ def _emitRevisionHintAtLine(self, line):
+ if line < 0:
+ return
try:
- self.readStatus(ctx, ctx2, wfile, status)
- except (EnvironmentError, error.LookupError), e:
- self.error = hglib.tounicode(str(e))
+ fctx = self._links[line][0]
+ if fctx.rev() != self._lastrev:
+ s = hglib.get_revision_desc(fctx, self.annfile)
+ self.showMessage.emit(s)
+ self._lastrev = fctx.rev()
+ except IndexError:
+ pass
- def checkMaxDiff(self, ctx, wfile):
- p = _('File or diffs not displayed: ')
- try:
- fctx = ctx.filectx(wfile)
- if ctx.rev() is None:
- size = fctx.size()
- else:
- # fctx.size() can read all data into memory in rename cases so
- # we read the size directly from the filelog, this is deeper
- # under the API than I prefer to go, but seems necessary
- size = fctx._filelog.rawsize(fctx.filerev())
- except (EnvironmentError, error.LookupError), e:
- self.error = p + hglib.tounicode(str(e))
- return None
- if size > ctx._repo.maxdiff:
- self.error = p + _('File is larger than the specified max size.\n')
- return None
- try:
- data = fctx.data()
- if '\0' in data:
- self.error = p + _('File is binary.\n')
- return None
- except EnvironmentError, e:
- self.error = p + hglib.tounicode(str(e))
- return None
- return fctx, data
+ def _updateannotation(self, ctx, filename):
+ assert ctx.rev() is not None
+ wsub, filename, ctx = hglib.getDeepestSubrepoContainingFile(filename, ctx)
+ assert filename in ctx
+ self.ctx = ctx
+ self.annfile = filename
+ self._thread.abort()
+ self._thread.start(ctx[filename])
- def isValid(self):
- return self.error is None
-
- def readStatus(self, ctx, ctx2, wfile, status):
- def getstatus(repo, n1, n2, wfile):
- m = match.exact(repo.root, repo.getcwd(), [wfile])
- modified, added, removed = repo.status(n1, n2, match=m)[:3]
- if wfile in modified:
- return 'M'
- if wfile in added:
- return 'A'
- if wfile in removed:
- return 'R'
- return None
-
- repo = ctx._repo
- self.flabel += u'<b>%s</b>' % hglib.tounicode(wfile)
-
- if isinstance(ctx, patchctx.patchctx):
- self.diff = ctx.thgmqpatchdata(wfile)
- flags = ctx.flags(wfile)
- if flags in ('x', '-'):
- lbl = _("exec mode has been <font color='red'>%s</font>")
- change = (flags == 'x') and _('set') or _('unset')
- self.elabel = lbl % change
- elif flags == 'l':
- self.flabel += _(' <i>(is a symlink)</i>')
+ @pyqtSlot()
+ def fillModel(self):
+ self._thread.wait()
+ if self._thread.data is None:
return
- absfile = repo.wjoin(wfile)
- if (wfile in ctx and 'l' in ctx.flags(wfile)) or \
- os.path.islink(absfile):
- if wfile in ctx:
- data = ctx[wfile].data()
- else:
- data = os.readlink(absfile)
- self.contents = hglib.tounicode(data)
- self.flabel += _(' <i>(is a symlink)</i>')
+ self._links = list(self._thread.data)
+
+ self._updaterevmargin()
+ self._updatemarkers()
+ self._updatemarginwidth()
+
+ def clear(self):
+ super(AnnotateView, self).clear()
+ self.clearMarginText()
+ self.markerDeleteAll()
+
+ def setAnnotationEnabled(self, enabled):
+ """Enable / disable annotation"""
+ self._annotation_enabled = enabled
+ self._updatemarginwidth()
+ self.setMouseTracking(enabled)
+ if not enabled:
+ self.markerDeleteAll()
+
+ def isAnnotationEnabled(self):
+ """True if annotation enabled and available"""
+ return self._annotation_enabled
+
+ def _updaterevmargin(self):
+ """Update the content of margin area showing revisions"""
+ s = self._margin_style
+ # Workaround to set style of the current sci widget.
+ # QsciStyle sends style data only to the first sci widget.
+ # See qscintilla2/Qt4/qscistyle.cpp
+ self.SendScintilla(qsci.SCI_STYLESETBACK,
+ s.style(), s.paper())
+ self.SendScintilla(qsci.SCI_STYLESETFONT,
+ s.style(), s.font().family().toAscii().data())
+ self.SendScintilla(qsci.SCI_STYLESETSIZE,
+ s.style(), s.font().pointSize())
+ for i, (fctx, _origline) in enumerate(self._links):
+ self.setMarginText(i, str(fctx.rev()), s)
+
+ def _updatemarkers(self):
+ """Update markers which colorizes each line"""
+ self._redefinemarkers()
+ for i, (fctx, _origline) in enumerate(self._links):
+ m = self._revmarkers.get(fctx.rev())
+ if m is not None:
+ self.markerAdd(i, m)
+
+ def _redefinemarkers(self):
+ """Redefine line markers according to the current revs"""
+ curdate = self.ctx.date()[0]
+
+ # make sure to colorize at least 1 year
+ mindate = curdate - 365 * 24 * 60 * 60
+
+ self._revmarkers.clear()
+ filectxs = iter(fctx for fctx, _origline in self._links)
+ palette = colormap.makeannotatepalette(filectxs, curdate,
+ maxcolors=32, maxhues=8,
+ maxsaturations=16,
+ mindate=mindate)
+ for i, (color, fctxs) in enumerate(palette.iteritems()):
+ self.markerDefine(qsci.Background, i)
+ self.setMarkerBackgroundColor(QColor(color), i)
+ for fctx in fctxs:
+ self._revmarkers[fctx.rev()] = i
+
+ @util.propertycache
+ def _margin_style(self):
+ """Style for margin area"""
+ s = Qsci.QsciStyle()
+ s.setPaper(QApplication.palette().color(QPalette.Window))
+ s.setFont(self.font())
+ return s
+
+ @pyqtSlot()
+ def _updatemarginwidth(self):
+ self.setMarginsFont(self.font())
+ def lentext(s):
+ return 'M' * (len(str(s)) + 2) # 2 for margin
+ self.setMarginWidth(1, lentext(self.lines()))
+ if self.isAnnotationEnabled() and self._links:
+ maxrev = max(fctx.rev() for fctx, _origline in self._links)
+ self.setMarginWidth(2, lentext(maxrev))
+ else:
+ self.setMarginWidth(2, 0)
+
+class AnnotateThread(QThread):
+ 'Background thread for annotating a file at a revision'
+ def __init__(self, parent=None):
+ super(AnnotateThread, self).__init__(parent)
+ self._threadid = None
+
+ @pyqtSlot(object)
+ def start(self, fctx):
+ self._fctx = fctx
+ super(AnnotateThread, self).start()
+ self.data = None
+
+ @pyqtSlot()
+ def abort(self):
+ if self._threadid is None:
return
+ try:
+ thread2._async_raise(self._threadid, KeyboardInterrupt)
+ self.wait()
+ except ValueError:
+ pass
- if status is None:
- status = getstatus(repo, ctx.p1().node(), ctx.node(), wfile)
- if ctx2 is None:
- ctx2 = ctx.p1()
-
- if status == 'S':
+ def run(self):
+ assert self.currentThread() != qApp.thread()
+ self._threadid = self.currentThreadId()
+ try:
try:
- from mercurial import subrepo, commands
-
- def genSubrepoRevChangedDescription(subrelpath, sfrom, sto):
- """Generate a subrepository revision change description"""
- out = []
- opts = {'date':None, 'user':None, 'rev':[sfrom]}
- subabspath = os.path.join(repo.root, subrelpath)
- missingsub = not os.path.isdir(subabspath)
- def isinitialrevision(rev):
- return all([el == '0' for el in rev])
- if isinitialrevision(sfrom):
- sfrom = ''
- if isinitialrevision(sto):
- sto = ''
- if not sfrom and not sto:
- sstatedesc = 'new'
- out.append(_('Subrepo created and set to initial revision.') + u'\n\n')
- return out, sstatedesc
- elif not sfrom:
- sstatedesc = 'new'
- out.append(_('Subrepo initialized to revision:') + u'\n\n')
- elif not sto:
- sstatedesc = 'removed'
- out.append(_('Subrepo removed from repository.') + u'\n\n')
- return out, sstatedesc
- else:
- sstatedesc = 'changed'
-
- out.append(_('Revision has changed from:') + u'\n\n')
- if missingsub:
- out.append(hglib.tounicode(_('changeset: %s') % sfrom + '\n'))
- else:
- _ui.pushbuffer()
- commands.log(_ui, srepo, **opts)
- out.append(hglib.tounicode(_ui.popbuffer()))
-
- out.append(_('To:') + u'\n')
- if missingsub:
- stolog = _('changeset: %s') % sto + '\n\n'
- stolog += _('Subrepository not found in working directory.') + '\n'
- stolog += _('Further subrepository revision information cannot be retrieved.') + '\n'
- else:
- opts['rev'] = [sto]
- _ui.pushbuffer()
- commands.log(_ui, srepo, **opts)
- stolog = _ui.popbuffer()
-
- if not stolog:
- stolog = _('Initial revision')
- out.append(hglib.tounicode(stolog))
-
- return out, sstatedesc
-
- srev = ctx.substate.get(wfile, subrepo.nullstate)[1]
- srepo = None
- try:
- subabspath = os.path.join(repo.root, wfile)
- if not os.path.isdir(subabspath):
- sactual = ''
- else:
- sub = ctx.sub(wfile)
- if isinstance(sub, subrepo.hgsubrepo):
- srepo = sub._repo
- sactual = srepo['.'].hex()
- else:
- self.error = _('Not a Mercurial subrepo, not previewable')
- return
- except (util.Abort), e:
- sactual = ''
-
- out = []
- _ui = uimod.ui()
-
- if srepo is None or ctx.rev() is not None:
- data = []
- else:
- _ui.pushbuffer()
- commands.status(_ui, srepo)
- data = _ui.popbuffer()
- if data:
- out.append(_('File Status:') + u'\n')
- out.append(hglib.tounicode(data))
- out.append(u'\n')
-
- sstatedesc = 'changed'
- if ctx.rev() is not None:
- sparent = ctx.p1().substate.get(wfile, subrepo.nullstate)[1]
- subrepochange, sstatedesc = genSubrepoRevChangedDescription(wfile, sparent, srev)
- out += subrepochange
- else:
- sstatedesc = 'dirty'
- if srev != sactual:
- subrepochange, sstatedesc = \
- genSubrepoRevChangedDescription(wfile, srev, sactual)
- out += subrepochange
- if data:
- sstatedesc += ' and dirty'
- self.contents = u''.join(out)
- if not sactual:
- sstatedesc = 'removed'
- lbl = {
- 'changed': _('(is a changed sub-repository)'),
- 'dirty': _('(is a dirty sub-repository)'),
- 'new': _('(is a new sub-repository)'),
- 'removed': _('(is a removed sub-repository)'),
- 'changed and dirty': _('(is a changed and dirty sub-repository)'),
- 'new and dirty': _('(is a new and dirty sub-repository)')
- }[sstatedesc]
- self.flabel += ' <i>' + lbl + '</i>'
- if sactual:
- lbl = _(' <a href="subrepo:%s">open...</a>')
- self.flabel += lbl % hglib.tounicode(srepo.root)
- except (EnvironmentError, error.RepoError, util.Abort), e:
- self.error = _('Error previewing subrepo: %s') % \
- hglib.tounicode(str(e))
- return
-
- # TODO: elif check if a subdirectory (for manifest tool)
-
- mde = _('File or diffs not displayed: ') + \
- _('File is larger than the specified max size.\n')
-
- if status in ('R', '!'):
- if wfile in ctx.p1():
- fctx = ctx.p1()[wfile]
- if fctx._filelog.rawsize(fctx.filerev()) > ctx._repo.maxdiff:
- self.error = mde
- else:
- olddata = fctx.data()
- if '\0' in olddata:
- self.error = 'binary file'
- else:
- self.contents = hglib.tounicode(olddata)
- self.flabel += _(' <i>(was deleted)</i>')
- else:
- self.flabel += _(' <i>(was added, now missing)</i>')
- return
-
- if status in ('I', '?', 'C'):
- if os.path.getsize(repo.wjoin(wfile)) > ctx._repo.maxdiff:
- self.error = mde
- else:
- data = open(repo.wjoin(wfile), 'r').read()
- if '\0' in data:
- self.error = 'binary file'
- else:
- self.contents = hglib.tounicode(data)
- if status in ('I', '?'):
- self.flabel += _(' <i>(is unversioned)</i>')
- return
-
- if status in ('M', 'A'):
- res = self.checkMaxDiff(ctx, wfile)
- if res is None:
- return
- fctx, newdata = res
- self.contents = hglib.tounicode(newdata)
- change = None
- for pfctx in fctx.parents():
- if 'x' in fctx.flags() and 'x' not in pfctx.flags():
- change = _('set')
- elif 'x' not in fctx.flags() and 'x' in pfctx.flags():
- change = _('unset')
- if change:
- lbl = _("exec mode has been <font color='red'>%s</font>")
- self.elabel = lbl % change
-
- if status == 'A':
- renamed = fctx.renamed()
- if not renamed:
- self.flabel += _(' <i>(was added)</i>')
- return
-
- oldname, node = renamed
- fr = hglib.tounicode(oldname)
- self.flabel += _(' <i>(renamed from %s)</i>') % fr
- olddata = repo.filectx(oldname, fileid=node).data()
- elif status == 'M':
- if wfile not in ctx2:
- # merge situation where file was added in other branch
- self.flabel += _(' <i>(was added)</i>')
- return
- oldname = wfile
- olddata = ctx2[wfile].data()
- else:
- return
-
- self.olddata = hglib.tounicode(olddata)
- newdate = util.datestr(ctx.date())
- olddate = util.datestr(ctx2.date())
- revs = [str(ctx), str(ctx2)]
- diffopts = patch.diffopts(repo.ui, {})
- diffopts.git = False
- self.diff = mdiff.unidiff(olddata, olddate, newdata, newdate,
- oldname, wfile, revs, diffopts)
+ data = []
+ for (fctx, line), _text in self._fctx.annotate(True, True):
+ data.append((fctx, line))
+ self.data = data
+ except KeyboardInterrupt:
+ pass
+ finally:
+ self._threadid = None
+ del self._fctx
|
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
@@ -70,7 +70,7 @@ opts[val.name] = s in filetypes
opts['checkall'] = True # pre-check all matching files
- stwidget = status.StatusWidget(pats, opts, repo.root, self)
+ stwidget = status.StatusWidget(repo, pats, opts, self)
layout.addWidget(stwidget, 1)
if self.command == 'revert':
|
@@ -221,7 +221,7 @@ dlg.exec_()
self.checkResolve()
elif cmd == 'commit':
- dlg = commit.CommitDialog([], dict(root=self.repo.root), self)
+ dlg = commit.CommitDialog(self.repo, [], {}, self)
dlg.finished.connect(dlg.deleteLater)
dlg.exec_()
self.destcsinfo.update(self.repo['.'])
|
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
@@ -15,8 +15,10 @@
from PyQt4.QtCore import *
-def label_func(widget, item):
+def label_func(widget, item, ctx):
if item == 'cset':
+ if type(ctx.rev()) is str:
+ return _('Patch:')
return _('Changeset:')
elif item == 'parents':
return _('Parent:')
|
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
This file's diff was not loaded because this changeset is very large. Load changes Loading... |
Loading...