##// END OF EJS Templates
merge backout
Thomas Arendsen Hein -
r6142:50a277e6 merge default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

This diff has been collapsed as it changes many lines, (624 lines changed) Show them Hide them
@@ -0,0 +1,624 b''
1 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2 <!-- Created with Inkscape (http://www.inkscape.org/) -->
3 <svg
4 xmlns:dc="http://purl.org/dc/elements/1.1/"
5 xmlns:cc="http://web.resource.org/cc/"
6 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
7 xmlns:svg="http://www.w3.org/2000/svg"
8 xmlns="http://www.w3.org/2000/svg"
9 xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
10 xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
11 version="1.0"
12 width="100"
13 height="120"
14 viewBox="0 0 124.766 152.099"
15 id="Layer_1"
16 xml:space="preserve"
17 sodipodi:version="0.32"
18 inkscape:version="0.45.1"
19 sodipodi:docname="logo-droplets.svg"
20 sodipodi:docbase="/home/oxymoron/waste/selenic/public_html/hg-logo"
21 inkscape:output_extension="org.inkscape.output.svg.inkscape"><metadata
22 id="metadata6845"><rdf:RDF><cc:Work
23 rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
24 rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title>Mercurial &quot;droplets&quot; logo</dc:title><dc:creator><cc:Agent><dc:title>Cali Mastny and Matt Mackall</dc:title></cc:Agent></dc:creator><cc:license
25 rdf:resource="http://creativecommons.org/licenses/GPL/2.0/" /><dc:date>Feb 12 2008</dc:date></cc:Work><cc:License
26 rdf:about="http://creativecommons.org/licenses/GPL/2.0/"><cc:permits
27 rdf:resource="http://web.resource.org/cc/Reproduction" /><cc:permits
28 rdf:resource="http://web.resource.org/cc/Distribution" /><cc:requires
29 rdf:resource="http://web.resource.org/cc/Notice" /><cc:permits
30 rdf:resource="http://web.resource.org/cc/DerivativeWorks" /><cc:requires
31 rdf:resource="http://web.resource.org/cc/ShareAlike" /><cc:requires
32 rdf:resource="http://web.resource.org/cc/SourceCode" /></cc:License></rdf:RDF></metadata><sodipodi:namedview
33 inkscape:window-height="576"
34 inkscape:window-width="746"
35 inkscape:pageshadow="2"
36 inkscape:pageopacity="0.0"
37 guidetolerance="10.0"
38 gridtolerance="10.0"
39 objecttolerance="10.0"
40 borderopacity="1.0"
41 bordercolor="#666666"
42 pagecolor="#ffffff"
43 id="base"
44 inkscape:zoom="2.3216673"
45 inkscape:cx="4.1210694"
46 inkscape:cy="65.759396"
47 inkscape:window-x="377"
48 inkscape:window-y="398"
49 inkscape:current-layer="Layer_1"
50 width="100px"
51 height="120px"
52 units="px" /><defs
53 id="defs261" />
54 <pattern
55 overflow="visible"
56 viewBox="2.125 -70.896 69 69"
57 id="Polka_Dot_Pattern"
58 patternUnits="userSpaceOnUse"
59 height="69"
60 width="69"
61 y="736.415"
62 x="-316">
63 <g
64 id="g4">
65 <polygon
66 id="polygon6"
67 points="71.125,-1.896 2.125,-1.896 2.125,-70.896 71.125,-70.896 "
68 fill="none" />
69 <polygon
70 id="polygon8"
71 points="71.125,-1.896 2.125,-1.896 2.125,-70.896 71.125,-70.896 "
72 fill="#F7BC60" />
73 <g
74 id="g10">
75 <path
76 id="path12"
77 d="M61.772-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
78 fill="#FFFFFF" />
79 <path
80 id="path14"
81 d="M54.105-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
82 fill="#FFFFFF" />
83 <path
84 id="path16"
85 d="M46.439-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
86 fill="#FFFFFF" />
87 <path
88 id="path18"
89 d="M38.772-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
90 fill="#FFFFFF" />
91 <path
92 id="path20"
93 d="M31.105-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
94 fill="#FFFFFF" />
95 <path
96 id="path22"
97 d="M23.439-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
98 fill="#FFFFFF" />
99 <path
100 id="path24"
101 d="M15.772-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
102 fill="#FFFFFF" />
103 <path
104 id="path26"
105 d="M8.105-71.653c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
106 fill="#FFFFFF" />
107 <path
108 id="path28"
109 d="M0.439-71.653c0.018,0.072,0.008,0.127-0.026,0.19C0.361-71.362,0.3-71.4,0.248-71.335 c-0.051,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.07,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.038-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.051-0.12-0.064-0.187c-0.021-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.215,0.124-0.215,0.224c0.002,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
110 fill="#FFFFFF" />
111 </g>
112 <g
113 id="g30">
114 <path
115 id="path32"
116 d="M69.439-71.653c0.018,0.072,0.008,0.127-0.026,0.19c-0.052,0.101-0.113,0.062-0.165,0.128 c-0.051,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.07,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.038-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.051-0.12-0.064-0.187c-0.021-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.215,0.124-0.215,0.224c0.002,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
117 fill="#FFFFFF" />
118 </g>
119 <path
120 id="path34"
121 d="M0.495-71.653c0.018,0.072,0.008,0.127-0.026,0.19c-0.052,0.101-0.113,0.062-0.165,0.128 c-0.051,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.07,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.038-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.051-0.12-0.064-0.187c-0.021-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.215,0.124-0.215,0.224C0.5-71.68,0.503-71.744,0.51-71.626 c-0.021,0.011-0.021-0.005-0.03-0.025"
122 fill="#FFFFFF" />
123 <g
124 id="g36">
125 <g
126 id="g38">
127 <path
128 id="path40"
129 d="M69.439-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
130 fill="#FFFFFF" />
131 <path
132 id="path42"
133 d="M61.778-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
134 fill="#FFFFFF" />
135 <path
136 id="path44"
137 d="M54.118-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
138 fill="#FFFFFF" />
139 <path
140 id="path46"
141 d="M46.458-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
142 fill="#FFFFFF" />
143 <path
144 id="path48"
145 d="M38.797-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
146 fill="#FFFFFF" />
147 <path
148 id="path50"
149 d="M31.137-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
150 fill="#FFFFFF" />
151 <path
152 id="path52"
153 d="M23.477-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
154 fill="#FFFFFF" />
155 <path
156 id="path54"
157 d="M15.816-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
158 fill="#FFFFFF" />
159 <path
160 id="path56"
161 d="M8.156-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
162 fill="#FFFFFF" />
163 <path
164 id="path58"
165 d="M0.495-64.001c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143C2-61.45,2.217-61.397,2.391-61.46c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
166 fill="#FFFFFF" />
167 </g>
168 <g
169 id="g60">
170 <path
171 id="path62"
172 d="M69.439-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
173 fill="#FFFFFF" />
174 <path
175 id="path64"
176 d="M61.778-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
177 fill="#FFFFFF" />
178 <path
179 id="path66"
180 d="M54.118-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
181 fill="#FFFFFF" />
182 <path
183 id="path68"
184 d="M46.458-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
185 fill="#FFFFFF" />
186 <path
187 id="path70"
188 d="M38.797-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
189 fill="#FFFFFF" />
190 <path
191 id="path72"
192 d="M31.137-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
193 fill="#FFFFFF" />
194 <path
195 id="path74"
196 d="M23.477-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
197 fill="#FFFFFF" />
198 <path
199 id="path76"
200 d="M15.816-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
201 fill="#FFFFFF" />
202 <path
203 id="path78"
204 d="M8.156-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
205 fill="#FFFFFF" />
206 <path
207 id="path80"
208 d="M0.495-56.348c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224C0.5-56.374,0.503-56.438,0.51-56.32 c-0.021,0.011-0.021-0.005-0.03-0.025"
209 fill="#FFFFFF" />
210 </g>
211 <g
212 id="g82">
213 <path
214 id="path84"
215 d="M69.439-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
216 fill="#FFFFFF" />
217 <path
218 id="path86"
219 d="M61.778-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
220 fill="#FFFFFF" />
221 <path
222 id="path88"
223 d="M54.118-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
224 fill="#FFFFFF" />
225 <path
226 id="path90"
227 d="M46.458-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
228 fill="#FFFFFF" />
229 <path
230 id="path92"
231 d="M38.797-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
232 fill="#FFFFFF" />
233 <path
234 id="path94"
235 d="M31.137-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
236 fill="#FFFFFF" />
237 <path
238 id="path96"
239 d="M23.477-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
240 fill="#FFFFFF" />
241 <path
242 id="path98"
243 d="M15.816-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
244 fill="#FFFFFF" />
245 <path
246 id="path100"
247 d="M8.156-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
248 fill="#FFFFFF" />
249 <path
250 id="path102"
251 d="M0.495-48.695c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
252 fill="#FFFFFF" />
253 </g>
254 <g
255 id="g104">
256 <path
257 id="path106"
258 d="M69.439-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
259 fill="#FFFFFF" />
260 <path
261 id="path108"
262 d="M61.778-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
263 fill="#FFFFFF" />
264 <path
265 id="path110"
266 d="M54.118-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
267 fill="#FFFFFF" />
268 <path
269 id="path112"
270 d="M46.458-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
271 fill="#FFFFFF" />
272 <path
273 id="path114"
274 d="M38.797-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
275 fill="#FFFFFF" />
276 <path
277 id="path116"
278 d="M31.137-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
279 fill="#FFFFFF" />
280 <path
281 id="path118"
282 d="M23.477-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
283 fill="#FFFFFF" />
284 <path
285 id="path120"
286 d="M15.816-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
287 fill="#FFFFFF" />
288 <path
289 id="path122"
290 d="M8.156-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 C8.15-41.004,8.149-41.02,8.14-41.04"
291 fill="#FFFFFF" />
292 <path
293 id="path124"
294 d="M0.495-41.042c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
295 fill="#FFFFFF" />
296 </g>
297 <g
298 id="g126">
299 <path
300 id="path128"
301 d="M69.439-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
302 fill="#FFFFFF" />
303 <path
304 id="path130"
305 d="M61.778-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
306 fill="#FFFFFF" />
307 <path
308 id="path132"
309 d="M54.118-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
310 fill="#FFFFFF" />
311 <path
312 id="path134"
313 d="M46.458-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
314 fill="#FFFFFF" />
315 <path
316 id="path136"
317 d="M38.797-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
318 fill="#FFFFFF" />
319 <path
320 id="path138"
321 d="M31.137-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
322 fill="#FFFFFF" />
323 <path
324 id="path140"
325 d="M23.477-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
326 fill="#FFFFFF" />
327 <path
328 id="path142"
329 d="M15.816-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
330 fill="#FFFFFF" />
331 <path
332 id="path144"
333 d="M8.156-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
334 fill="#FFFFFF" />
335 <path
336 id="path146"
337 d="M0.495-33.39c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224C0.5-33.416,0.503-33.48,0.51-33.362 c-0.021,0.011-0.021-0.005-0.03-0.025"
338 fill="#FFFFFF" />
339 </g>
340 <g
341 id="g148">
342 <path
343 id="path150"
344 d="M69.439-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
345 fill="#FFFFFF" />
346 <path
347 id="path152"
348 d="M61.778-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
349 fill="#FFFFFF" />
350 <path
351 id="path154"
352 d="M54.118-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
353 fill="#FFFFFF" />
354 <path
355 id="path156"
356 d="M46.458-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
357 fill="#FFFFFF" />
358 <path
359 id="path158"
360 d="M38.797-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
361 fill="#FFFFFF" />
362 <path
363 id="path160"
364 d="M31.137-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
365 fill="#FFFFFF" />
366 <path
367 id="path162"
368 d="M23.477-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
369 fill="#FFFFFF" />
370 <path
371 id="path164"
372 d="M15.816-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
373 fill="#FFFFFF" />
374 <path
375 id="path166"
376 d="M8.156-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
377 fill="#FFFFFF" />
378 <path
379 id="path168"
380 d="M0.495-25.736c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
381 fill="#FFFFFF" />
382 </g>
383 <g
384 id="g170">
385 <path
386 id="path172"
387 d="M69.439-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
388 fill="#FFFFFF" />
389 <path
390 id="path174"
391 d="M61.778-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
392 fill="#FFFFFF" />
393 <path
394 id="path176"
395 d="M54.118-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
396 fill="#FFFFFF" />
397 <path
398 id="path178"
399 d="M46.458-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
400 fill="#FFFFFF" />
401 <path
402 id="path180"
403 d="M38.797-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
404 fill="#FFFFFF" />
405 <path
406 id="path182"
407 d="M31.137-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
408 fill="#FFFFFF" />
409 <path
410 id="path184"
411 d="M23.477-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
412 fill="#FFFFFF" />
413 <path
414 id="path186"
415 d="M15.816-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
416 fill="#FFFFFF" />
417 <path
418 id="path188"
419 d="M8.156-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
420 fill="#FFFFFF" />
421 <path
422 id="path190"
423 d="M0.495-18.084c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224C0.5-18.11,0.503-18.175,0.51-18.057 c-0.021,0.011-0.021-0.005-0.03-0.025"
424 fill="#FFFFFF" />
425 </g>
426 <g
427 id="g192">
428 <path
429 id="path194"
430 d="M69.439-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362C69-9.692,69.159-9.523,69.154-9.4c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
431 fill="#FFFFFF" />
432 <path
433 id="path196"
434 d="M61.778-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
435 fill="#FFFFFF" />
436 <path
437 id="path198"
438 d="M54.118-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
439 fill="#FFFFFF" />
440 <path
441 id="path200"
442 d="M46.458-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
443 fill="#FFFFFF" />
444 <path
445 id="path202"
446 d="M38.797-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
447 fill="#FFFFFF" />
448 <path
449 id="path204"
450 d="M31.137-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
451 fill="#FFFFFF" />
452 <path
453 id="path206"
454 d="M23.477-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
455 fill="#FFFFFF" />
456 <path
457 id="path208"
458 d="M15.816-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.009,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 c0.177,0.042,0.384-0.104,0.543-0.143c0.18-0.043,0.397,0.01,0.571-0.053C17.933-7.969,17.839-8.227,18-8.34 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
459 fill="#FFFFFF" />
460 <path
461 id="path210"
462 d="M8.156-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 C7.915-10.05,7.866-9.836,7.886-9.75C7.717-9.692,7.876-9.523,7.871-9.4C7.868-9.351,7.83-9.295,7.826-9.239 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C9.114-7.652,9.321-7.799,9.48-7.837c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
463 fill="#FFFFFF" />
464 <path
465 id="path212"
466 d="M0.495-10.431c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 C0.254-10.05,0.205-9.836,0.225-9.75C0.056-9.692,0.215-9.523,0.21-9.4c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37C0.33-8.671,0.501-8.456,0.668-8.325c0.19,0.148,0.365,0.572,0.608,0.631 C1.454-7.652,1.66-7.799,1.819-7.837C2-7.88,2.217-7.827,2.391-7.89c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46C3.477-8.933,3.471-8.995,3.5-9.071 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
467 fill="#FFFFFF" />
468 </g>
469 </g>
470 <g
471 id="g214">
472 <path
473 id="path216"
474 d="M69.439-2.778c0.018,0.072,0.008,0.127-0.026,0.19C69.361-2.487,69.3-2.525,69.248-2.46 c-0.051,0.062-0.099,0.276-0.079,0.362C69-2.04,69.159-1.871,69.154-1.748c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C70.397,0,70.604-0.146,70.763-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.07,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.038-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.051-0.12-0.064-0.187c-0.021-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.215,0.124-0.215,0.224c0.002,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
475 fill="#FFFFFF" />
476 <path
477 id="path218"
478 d="M61.778-2.778c0.018,0.072,0.007,0.127-0.026,0.19C61.7-2.487,61.64-2.525,61.587-2.46 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C62.737,0,62.943-0.146,63.103-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224C61.915-3.117,61.78-3.02,61.781-2.92c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
479 fill="#FFFFFF" />
480 <path
481 id="path220"
482 d="M54.118-2.778c0.018,0.072,0.007,0.127-0.026,0.19C54.04-2.487,53.98-2.525,53.927-2.46 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C55.077,0,55.283-0.146,55.442-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224C54.255-3.117,54.12-3.02,54.121-2.92c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
483 fill="#FFFFFF" />
484 <path
485 id="path222"
486 d="M46.458-2.778c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C47.416,0,47.623-0.146,47.782-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224C46.594-3.117,46.459-3.02,46.46-2.92c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
487 fill="#FFFFFF" />
488 <path
489 id="path224"
490 d="M38.797-2.778c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C39.756,0,39.962-0.146,40.122-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224C38.934-3.117,38.799-3.02,38.8-2.92c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
491 fill="#FFFFFF" />
492 <path
493 id="path226"
494 d="M31.137-2.778c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C32.095,0,32.302-0.146,32.461-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224C31.273-3.117,31.139-3.02,31.14-2.92c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
495 fill="#FFFFFF" />
496 <path
497 id="path228"
498 d="M23.477-2.778c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C24.435,0,24.642-0.146,24.801-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 c-0.021,0.011-0.021-0.005-0.03-0.025"
499 fill="#FFFFFF" />
500 <path
501 id="path230"
502 d="M15.816-2.778c0.018,0.072,0.007,0.127-0.026,0.19c-0.053,0.101-0.112,0.062-0.165,0.128 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C16.774,0,16.981-0.146,17.14-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789c-0.18,0.034-0.287,0.126-0.442,0.207 c-0.17,0.088-0.139,0.166-0.318,0.224c-0.081,0.026-0.216,0.124-0.215,0.224c0.001,0.115,0.005,0.051,0.012,0.169 C15.81-2.74,15.809-2.756,15.8-2.776"
503 fill="#FFFFFF" />
504 <path
505 id="path232"
506 d="M8.156-2.778c0.018,0.072,0.007,0.127-0.026,0.19C8.077-2.487,8.018-2.525,7.965-2.46 c-0.05,0.062-0.099,0.276-0.079,0.362c-0.169,0.058-0.01,0.227-0.015,0.35C7.868-1.698,7.83-1.643,7.826-1.587 c-0.01,0.119,0.017,0.266,0.068,0.37c0.097,0.198,0.268,0.413,0.435,0.544c0.19,0.148,0.365,0.572,0.608,0.631 C9.114,0,9.321-0.146,9.48-0.185c0.18-0.043,0.397,0.01,0.571-0.053c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.069,0.339-0.263,0.376-0.46c0.016-0.082,0.01-0.145,0.039-0.221 c0.039-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.052-0.12-0.064-0.187c-0.022-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789C8.954-3.54,8.847-3.448,8.692-3.367 c-0.17,0.088-0.139,0.166-0.318,0.224C8.292-3.117,8.158-3.02,8.159-2.92C8.16-2.805,8.164-2.869,8.17-2.751 C8.15-2.74,8.149-2.756,8.14-2.776"
507 fill="#FFFFFF" />
508 <path
509 id="path234"
510 d="M0.495-2.778c0.018,0.072,0.008,0.127-0.026,0.19C0.417-2.487,0.356-2.525,0.304-2.46 C0.253-2.397,0.205-2.184,0.225-2.098C0.056-2.04,0.215-1.871,0.21-1.748c-0.002,0.05-0.041,0.105-0.045,0.161 c-0.01,0.119,0.017,0.266,0.068,0.37C0.33-1.019,0.501-0.804,0.668-0.673c0.19,0.148,0.365,0.572,0.608,0.631 C1.454,0,1.66-0.146,1.819-0.185C2-0.228,2.217-0.175,2.391-0.237c0.222-0.079,0.127-0.337,0.288-0.45 c0.104-0.074,0.287-0.01,0.406-0.051c0.2-0.07,0.339-0.263,0.376-0.46C3.477-1.28,3.471-1.343,3.5-1.419 c0.038-0.103,0.111-0.16,0.09-0.293c-0.01-0.062-0.051-0.12-0.064-0.187c-0.021-0.114,0.002-0.224,0-0.337 c-0.003-0.2,0.017-0.379-0.078-0.55c-0.38-0.688-1.236-0.929-1.975-0.789C1.293-3.54,1.187-3.448,1.031-3.367 c-0.17,0.088-0.139,0.166-0.318,0.224C0.632-3.117,0.498-3.02,0.498-2.92C0.5-2.805,0.503-2.869,0.51-2.751 C0.489-2.74,0.488-2.756,0.479-2.776"
511 fill="#FFFFFF" />
512 </g>
513 </g>
514 </pattern>
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537 <rect
538 style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.97552931;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
539 id="rect6847"
540 width="124.77364"
541 height="150.12347"
542 x="0.31690097"
543 y="0.98776293" /><path
544 d="M 9.8480335,124.60683 C 11.62496,123.82337 13.513211,123.43203 15.327243,123.43203 C 17.067063,123.43203 18.177759,123.85879 18.806894,124.74937 C 20.139374,123.8596 21.953416,123.43203 23.360116,123.43203 C 27.581053,123.43203 27.728648,125.14068 27.728648,130.16028 L 27.728648,141.26547 C 27.728648,141.76382 27.802857,141.76382 25.692811,141.76382 L 25.692811,129.94606 C 25.692811,126.31544 25.618592,125.21154 23.213365,125.21154 C 22.139794,125.21154 21.029108,125.4603 19.844204,126.24379 L 19.844204,141.5142 C 19.807099,141.65677 19.732887,141.69215 19.474821,141.72758 C 19.436863,141.72758 19.400602,141.763 19.362653,141.763 L 17.807522,141.763 L 17.807522,129.94606 C 17.807522,126.45791 17.807522,125.17607 15.29098,125.17607 C 14.2174,125.17607 13.143818,125.38944 11.884705,125.99501 L 11.884705,141.26547 C 11.884705,141.76382 11.958925,141.76382 9.8488776,141.76382 L 9.8488776,124.60683 M 37.680118,123.43203 C 34.533596,123.43203 31.053954,124.32176 31.053954,133.8263 C 31.053954,141.15915 33.607611,142.29771 37.125192,142.29771 C 39.826435,142.29771 42.159131,141.40799 42.159131,140.98039 C 42.159131,140.44659 42.084921,139.62768 41.900221,139.16468 C 40.75243,139.94814 39.123081,140.37486 37.309893,140.37486 C 34.829612,140.37486 33.164001,139.66309 33.126052,134.14592 C 34.755402,134.14592 38.902128,134.11044 41.97444,133.50498 C 42.233351,132.33022 42.343821,130.62155 42.343821,129.12711 C 42.343821,125.56743 40.900016,123.43203 37.680118,123.43203 M 37.494584,125.21154 C 39.715955,125.21154 40.307995,126.67048 40.3451,129.51849 C 40.3451,130.26565 40.307995,131.15541 40.19667,132.00972 C 38.123729,132.50815 34.60612,132.50815 33.125209,132.50815 C 33.385806,126.0304 35.606333,125.21154 37.494584,125.21154 M 45.565397,124.99816 C 47.304384,123.85879 48.897464,123.43203 50.525969,123.43203 C 52.34,123.43203 53.191776,123.93046 53.191776,124.53602 C 53.191776,124.89187 53.079617,125.49742 52.894917,125.85331 C 52.376261,125.56823 51.785075,125.31945 50.821131,125.31945 C 49.637079,125.31945 48.526385,125.63909 47.638339,126.42255 L 47.638339,141.26632 C 47.638339,141.7647 47.675453,141.7647 45.565397,141.7647 L 45.565397,124.99816 M 64.254794,124.60683 C 64.254794,124.14383 62.700508,123.43203 61.145377,123.43203 C 58.145598,123.43203 54.481256,124.4643 54.481256,133.25617 C 54.481256,141.58507 56.70347,142.3331 60.589608,142.3331 C 62.514121,142.3331 64.254794,141.30089 64.254794,140.73161 C 64.254794,140.4111 64.181418,139.91269 63.99504,139.48515 C 63.217475,140.05441 62.033423,140.58905 60.775152,140.58905 C 58.11018,140.58905 56.55504,139.84185 56.55504,133.3633 C 56.55504,126.20837 59.108698,125.21154 61.330069,125.21154 C 62.58834,125.21154 63.291694,125.56743 63.99504,126.0304 C 64.181418,125.60367 64.254794,124.99816 64.254794,124.60683 M 78.435657,141.15915 C 76.806308,141.97803 74.659991,142.29851 72.808845,142.29851 C 68.070088,142.29851 67.366733,140.30571 67.366733,135.57114 L 67.366733,124.42971 C 67.366733,123.96757 67.330471,123.96757 69.440527,123.96757 L 69.440527,135.7854 C 69.440527,139.34513 69.958338,140.55538 72.734626,140.55538 C 73.808215,140.55538 75.289126,140.34199 76.399811,139.70105 L 76.399811,124.43056 C 76.399811,123.96839 76.325602,123.96839 78.435657,123.96839 L 78.435657,141.15915 M 82.657438,124.99816 C 84.396406,123.85879 85.98865,123.43203 87.617156,123.43203 C 89.431178,123.43203 90.282962,123.93046 90.282962,124.53602 C 90.282962,124.89187 90.171639,125.49742 89.986938,125.85331 C 89.468283,125.56823 88.876272,125.31945 87.913163,125.31945 C 86.729111,125.31945 85.618415,125.63909 84.729535,126.42255 L 84.729535,141.26632 C 84.729535,141.7647 84.767484,141.7647 82.657438,141.7647 L 82.657438,124.99816 M 95.036045,123.9659 C 93.406714,123.9659 92.926008,123.9659 92.926008,124.92729 L 92.926008,141.76382 C 94.99895,141.76382 95.036045,141.76382 95.036045,141.26547 L 95.036045,123.9659 M 92.851787,117.70149 C 92.851787,118.87629 93.222023,119.30304 93.961631,119.33843 C 94.813415,119.33843 95.220746,118.73376 95.220746,117.66526 C 95.257851,116.56214 94.960991,116.06374 94.11006,116.06374 C 93.296243,116.06374 92.888893,116.66926 92.851787,117.70149 M 98.547748,124.99816 C 98.547748,124.60683 98.62196,124.39264 98.770389,124.28635 C 99.473743,123.89502 102.17666,123.43203 105.24898,123.43203 C 107.58166,123.43203 109.06174,124.53602 109.06174,127.73899 L 109.06174,130.05231 C 109.06174,136.38835 108.87704,141.12293 108.87704,141.12293 C 108.02528,141.58507 106.43387,142.29771 103.84143,142.29771 C 101.17646,142.3331 98.511478,142.0843 98.511478,136.81596 C 98.511478,131.7972 101.25067,131.01375 103.98986,131.01375 C 105.02633,131.01375 106.24834,131.12082 107.06301,131.4413 C 107.06301,131.4413 107.06301,129.12711 107.06301,128.13033 C 107.06301,125.81704 105.87895,125.31862 104.47141,125.31862 C 102.58399,125.31862 99.956127,125.67451 98.808337,126.20837 C 98.585707,125.81704 98.547748,125.21154 98.547748,124.99816 M 107.06216,132.9011 C 106.35882,132.65147 105.35945,132.54522 104.65609,132.54522 C 102.54604,132.54522 100.62069,132.97198 100.62069,136.88763 C 100.62069,140.55363 102.21293,140.58991 104.10032,140.58991 C 105.28522,140.58991 106.47014,140.26946 106.87663,139.84271 C 106.87747,139.84185 107.06216,135.57029 107.06216,132.9011 M 114.91792,141.26547 C 114.91792,141.76382 114.95503,141.76382 112.88124,141.76382 L 112.88124,116.56214 C 112.88124,115.60073 113.28857,115.60073 114.91792,115.60073 L 114.91792,141.26547"
545 style="fill:#010101;stroke-width:2.02999997;stroke-miterlimit:4;stroke-dasharray:none"
546 id="text2611" /><g
547 transform="matrix(0.9351326,0,0,0.9351326,150.39508,-1.251766)"
548 id="g4503"
549 style="opacity:1"><path
550 d="M -45.749655,92.691592 C -25.709638,59.370739 -49.98206,5.3291313 -94.363693,10.819389 C -134.46337,15.776665 -135.10949,57.983708 -99.76917,68.010455 C -69.186498,76.695132 -93.451029,96.093536 -92.742037,109.01138 C -92.030055,121.92728 -66.155038,126.61324 -45.749655,92.691592 z "
551 style="fill:#1b1a1b"
552 id="path2339" /><circle
553 cx="33.728001"
554 cy="85.363998"
555 r="15.414"
556 transform="matrix(1.0917947,-0.2858168,0.2858168,1.0917947,-180.30817,13.494135)"
557 style="fill:#1b1a1b"
558 id="circle2341"
559 sodipodi:cx="33.728001"
560 sodipodi:cy="85.363998"
561 sodipodi:rx="15.414"
562 sodipodi:ry="15.414" /><path
563 d="M -140.06215,48.935849 C -146.31997,49.541603 -150.90082,55.100456 -150.29507,61.358275 C -149.68817,67.620461 -144.12955,72.20487 -137.87064,71.59883 C -131.61373,70.985148 -127.02904,65.427621 -127.63726,59.169282 C -128.24543,52.915596 -133.80324,48.329809 -140.06215,48.935849 z "
564 style="fill:#1b1a1b"
565 id="path2343" /><path
566 d="M -44.99294,91.339709 C -24.951831,58.018571 -49.224253,3.976963 -93.605885,9.4672202 C -133.70556,14.424496 -134.35249,56.632918 -99.012168,66.659664 C -68.429497,75.344341 -92.694028,94.742745 -91.984749,107.66168 C -91.271961,120.5762 -65.398322,125.26135 -44.99294,91.339709 z "
567 style="fill:#bfbfbf"
568 id="path2561" /><path
569 d="M -86.84228,112.75985 C -88.056751,110.79004 -86.19955,108.60176 -84.290569,108.76815 C -81.251858,109.03428 -74.635637,108.73252 -69.415044,105.77341 C -56.372412,98.379694 -36.300952,62.803704 -46.395841,40.365295 C -50.915249,30.320886 -53.115898,27.444964 -57.770162,22.531645 C -58.719625,21.529587 -58.174556,21.584053 -57.531623,21.923221 C -55.014762,23.244092 -50.592026,28.36035 -46.055478,36.687677 C -38.390628,50.757116 -38.788117,67.483141 -41.638835,77.975343 C -43.624548,85.27439 -50.464117,101.78644 -60.480639,108.92577 C -70.5197,116.0815 -82.266433,120.18559 -86.84228,112.75985 z "
570 style="fill:#000000"
571 id="path2563" /><path
572 d="M -95.930347,66.591355 C -102.76341,64.562985 -111.57238,61.738267 -116.66758,55.073789 C -120.42371,50.15984 -122.3305,44.796759 -122.81745,41.755703 C -122.99069,40.670602 -123.13785,39.765332 -122.82526,39.515509 C -122.68064,39.399486 -120.02045,45.412302 -116.04367,50.451645 C -112.06769,55.492366 -106.51047,58.440379 -101.88092,59.511496 C -97.763206,60.46345 -89.233623,62.555175 -86.347769,65.013729 C -83.380949,67.540918 -83.133309,73.00119 -84.131664,73.617197 C -85.138469,74.236583 -87.180025,69.187603 -95.930347,66.591355 z "
573 style="fill:#000000"
574 id="path2565" /><path
575 d="M -81.840812,113.72311 C -81.972699,115.28707 -80.176315,115.59377 -77.75828,115.23141 C -74.658947,114.76654 -72.037923,114.41754 -68.470623,112.62971 C -63.63582,110.20674 -58.742752,106.74072 -55.159223,102.06476 C -44.467444,88.115271 -40.681354,71.610444 -41.264404,69.236185 C -41.459242,71.196944 -44.040349,81.489071 -49.943268,90.767882 C -57.52457,102.68631 -63.022197,109.03464 -75.701416,112.1124 C -79.230011,112.96964 -81.668137,111.66432 -81.840812,113.72311 z "
576 style="fill:#ffffff"
577 id="path2567" /><path
578 d="M -109.96233,59.479354 C -108.51822,60.704238 -105.55938,62.336389 -99.737455,64.245644 C -92.705873,66.551032 -89.282274,68.550326 -87.848506,69.508429 C -86.329222,70.525809 -85.366279,72.795951 -85.27115,70.779631 C -85.17194,68.761076 -86.416123,67.025373 -89.192166,66.104839 C -91.070345,65.481234 -94.229847,63.996111 -97.258539,63.398373 C -99.204694,63.014221 -102.37098,62.251845 -105.08636,61.420426 C -106.57454,60.963046 -108.09089,60.161888 -109.96233,59.479354 z "
579 style="fill:#ffffff"
580 id="path2569" /><circle
581 cx="34.681"
582 cy="84.375"
583 r="15.414"
584 transform="matrix(1.0917947,-0.2858168,0.2858168,1.0917947,-180.30817,13.494135)"
585 style="fill:#bfbfbf"
586 id="circle2577"
587 sodipodi:cx="34.681"
588 sodipodi:cy="84.375"
589 sodipodi:rx="15.414"
590 sodipodi:ry="15.414" /><path
591 d="M -128.68413,108.37945 C -115.15301,120.91784 -94.786007,103.69471 -103.75445,88.482597 C -104.76154,86.774656 -106.06907,85.474351 -105.63906,86.782721 C -102.77288,95.529828 -105.42141,102.44941 -110.3632,106.01451 C -115.20857,109.5112 -121.86847,110.09622 -127.20028,107.33186 C -128.76601,106.5203 -129.41538,107.70291 -128.68413,108.37945 z "
592 style="fill:#000000"
593 id="path2579" /><path
594 d="M -118.06686,110.95477 C -116.34413,110.59244 -106.32442,107.99742 -103.97055,99.756195 C -103.23743,97.186709 -103.1058,97.702893 -103.31295,99.095232 C -104.37035,106.20143 -111.08741,111.44338 -116.80312,111.63773 C -117.963,111.75704 -119.48484,111.25131 -118.06686,110.95477 z "
595 style="fill:#ffffff"
596 id="path2585" /><path
597 d="M -139.30435,47.583681 C -145.56216,48.189435 -150.14301,53.748288 -149.53726,60.006106 C -148.93065,66.2672 -143.37174,70.852702 -137.11392,70.246948 C -130.85592,69.632979 -126.27151,64.074361 -126.88083,57.816308 C -127.48791,51.562336 -133.04544,46.977641 -139.30435,47.583681 z "
598 style="fill:#bfbfbf"
599 id="path2589" /><path
600 d="M -144.46878,67.571208 C -144.39939,68.375508 -143.29781,69.408789 -141.56718,69.883196 C -140.08038,70.290771 -136.24758,71.332594 -131.32372,68.224839 C -126.39986,65.117084 -125.8321,56.804464 -128.07041,54.35955 C -128.76326,53.121154 -129.66426,52.21957 -128.94737,54.195974 C -127.13695,59.186468 -130.65487,63.854586 -133.68917,66.0162 C -136.72238,68.177528 -140.56932,67.154692 -142.14014,66.675779 C -143.71095,66.196867 -144.53929,66.740369 -144.46878,67.571208 z "
601 style="fill:#000000"
602 id="path2591" /><path
603 d="M -138.11472,68.687851 C -137.66344,68.281557 -135.37889,68.447629 -133.31622,67.338341 C -131.25464,66.229338 -128.80419,63.798254 -128.36692,60.343756 C -128.10933,58.315237 -128.03197,58.824631 -127.92942,59.929403 C -128.24939,65.67243 -133.53086,68.844638 -136.55132,69.263202 C -137.36636,69.376239 -138.8007,69.307247 -138.11472,68.687851 z "
604 style="fill:#ffffff"
605 id="path2597" /><path
606 d="M -47.767489,69.693822 C -39.234739,45.099506 -57.090457,7.9576459 -93.212919,12.425552 C -125.85191,16.461012 -126.37823,50.814524 -97.613495,58.976486 C -65.031338,63.908526 -84.650966,88.487524 -87.434101,100.88229 C -89.929232,111.99304 -61.102889,113.82164 -47.767489,69.693822 z "
607 style="fill:#999999"
608 id="path2561_1_" /><path
609 d="M -70.093288,88.904346 C -78.920045,87.812046 -91.622267,107.74061 -79.645446,105.40671 C -67.670523,103.07448 -91.622267,107.74061 -79.645446,105.40671 C -73.888849,104.55302 -69.119803,102.52058 -64.850547,97.64761 C -59.283982,91.295233 -50.968477,77.5735 -48.563483,68.707586 C -46.537563,61.232354 -47.555881,49.650767 -49.644305,60.532553 C -51.786232,71.700167 -61.266532,89.996647 -70.093288,88.904346 z "
610 style="fill:#f3f3f3"
611 id="path2571" /><path
612 d="M -129.3854,104.84502 C -127.34184,104.87935 -126.10573,105.16706 -124.03635,106.61908 C -119.94568,108.31891 -112.42648,107.24179 -108.9543,102.67081 C -105.48212,98.099823 -105.36811,91.801741 -106.69103,87.996073 C -109.92728,78.682039 -123.67593,78.846722 -129.81795,86.579362 C -136.46216,95.2146 -131.42897,104.81069 -129.3854,104.84502 z "
613 style="fill:#999999"
614 id="path2581" /><path
615 d="M -147.63565,61.683628 C -147.22833,62.966318 -146.18754,64.837882 -143.9897,65.149887 C -141.05481,65.566524 -140.45479,66.892551 -136.9892,66.204631 C -133.52361,65.516711 -130.89674,62.676625 -129.84557,59.535064 C -128.64212,55.188187 -130.44406,52.944024 -133.15599,50.940416 C -135.86791,48.936808 -141.83359,49.152263 -145.3938,52.39768 C -147.92393,54.702631 -148.62733,58.560726 -147.63565,61.683628 z "
616 style="fill:#999999"
617 id="path2593_2_" /><path
618 d="M -136.11009,64.55822 C -133.44721,63.861113 -129.92545,60.232613 -131.67381,57.462279 C -133.83086,54.048798 -139.84051,56.970651 -140.04374,60.77103 C -140.24777,64.572786 -138.93238,65.297057 -136.11009,64.55822 z "
619 style="fill:#f3f3f3"
620 id="path256" /><path
621 d="M -116.11512,105.50904 C -113.8431,104.91425 -106.88259,102.0818 -108.18994,91.962983 C -108.85161,86.83742 -111.64725,98.324328 -116.82409,100.04237 C -124.66721,102.64507 -123.78607,107.51719 -116.11512,105.50904 z "
622 style="fill:#f3f3f3"
623 id="path258" /></g>
624 </svg> No newline at end of file
@@ -0,0 +1,50 b''
1 # Some default global settings for common merge tools
2
3 [merge-tools]
4 kdiff3.args=--auto -L1 base --L2 local --L3 other $base $local $other -o $output
5 kdiff3.regkey=Software\KDiff3
6 kdiff3.regappend=\kdiff3.exe
7 kdiff3.fixeol=True
8 kdiff3.gui=True
9
10 gvimdiff.args=--nofork -d -g -O $local $other $base
11 gvimdiff.regkey=Software\Vim\GVim
12 gvimdiff.regname=path
13 gvimdiff.priority=-9
14
15 merge.checkconflicts=True
16 merge.priority=-10
17
18 gpyfm.gui=True
19
20 meld.gui=True
21
22 tkdiff.args=$local $other -a $base -o $output
23 tkdiff.gui=True
24 tkdiff.priority=-8
25
26 xxdiff.args=--show-merged-pane --exit-with-merge-status --title1 local --title2 base --title3 other --merged-filename $output --merge $local $base $other
27 xxdiff.gui=True
28 xxdiff.priority=-8
29
30 diffmerge.args=--nosplash --merge --title1=base --title2=local --title3=other $base $local $other
31 diffmerge.checkchanged=True
32 diffmerge.gui=True
33
34 p4merge.args=$base $local $other $output
35 p4merge.regkey=Software\Perforce\Environment
36 p4merge.regname=P4INSTROOT
37 p4merge.regappend=\p4merge.exe
38 p4merge.gui=True
39 p4merge.priority=-8
40
41 tortoisemerge.args=/base: $output /mine:$local /theirs:$other /merged:$output
42 tortoisemerge.regkey=Software\TortoiseSVN
43 tortoisemerge.gui=True
44
45 ecmerge.args=$base $local $other --mode=merge3 --title0=base --title1=local --title2=other --to=$output
46 ecmerge.regkey=Software\Elli\xc3\xa9 Computing\Merge
47 ecmerge.gui=True
48
49 filemerge.args=-left $other -right $local -ancestor $base -merge $output
50 filemerge.gui=True
@@ -0,0 +1,301 b''
1 # GNU Arch support for the convert extension
2
3 from common import NoRepo, checktool, commandline, commit, converter_source
4 from mercurial.i18n import _
5 from mercurial import util
6 import os, shutil, tempfile, stat
7
8 class gnuarch_source(converter_source, commandline):
9
10 class gnuarch_rev:
11 def __init__(self, rev):
12 self.rev = rev
13 self.summary = ''
14 self.date = None
15 self.author = ''
16 self.add_files = []
17 self.mod_files = []
18 self.del_files = []
19 self.ren_files = {}
20 self.ren_dirs = {}
21
22 def __init__(self, ui, path, rev=None):
23 super(gnuarch_source, self).__init__(ui, path, rev=rev)
24
25 if not os.path.exists(os.path.join(path, '{arch}')):
26 raise NoRepo(_("%s does not look like a GNU Arch repo" % path))
27
28 # Could use checktool, but we want to check for baz or tla.
29 self.execmd = None
30 if util.find_exe('baz'):
31 self.execmd = 'baz'
32 else:
33 if util.find_exe('tla'):
34 self.execmd = 'tla'
35 else:
36 raise util.Abort(_('cannot find a GNU Arch tool'))
37
38 commandline.__init__(self, ui, self.execmd)
39
40 self.path = os.path.realpath(path)
41 self.tmppath = None
42
43 self.treeversion = None
44 self.lastrev = None
45 self.changes = {}
46 self.parents = {}
47 self.tags = {}
48 self.modecache = {}
49
50 def before(self):
51 if self.execmd == 'tla':
52 output = self.run0('tree-version', self.path)
53 else:
54 output = self.run0('tree-version', '-d', self.path)
55 self.treeversion = output.strip()
56
57 self.ui.status(_('analyzing tree version %s...\n' % self.treeversion))
58
59 # Get name of temporary directory
60 version = self.treeversion.split('/')
61 self.tmppath = os.path.join(tempfile.gettempdir(),
62 'hg-%s' % version[1])
63
64 # Generate parents dictionary
65 child = []
66 output, status = self.runlines('revisions', self.treeversion)
67 self.checkexit(status, 'archive registered?')
68 for l in output:
69 rev = l.strip()
70 self.changes[rev] = self.gnuarch_rev(rev)
71
72 # Read author, date and summary
73 catlog = self.runlines0('cat-log', '-d', self.path, rev)
74 self._parsecatlog(catlog, rev)
75
76 self.parents[rev] = child
77 child = [rev]
78 if rev == self.rev:
79 break
80 self.parents[None] = child
81
82 def after(self):
83 self.ui.debug(_('cleaning up %s\n' % self.tmppath))
84 shutil.rmtree(self.tmppath, ignore_errors=True)
85
86 def getheads(self):
87 return self.parents[None]
88
89 def getfile(self, name, rev):
90 if rev != self.lastrev:
91 raise util.Abort(_('internal calling inconsistency'))
92
93 # Raise IOError if necessary (i.e. deleted files).
94 if not os.path.exists(os.path.join(self.tmppath, name)):
95 raise IOError
96
97 data, mode = self._getfile(name, rev)
98 self.modecache[(name, rev)] = mode
99
100 return data
101
102 def getmode(self, name, rev):
103 return self.modecache[(name, rev)]
104
105 def getchanges(self, rev):
106 self.modecache = {}
107 self._update(rev)
108 changes = []
109 copies = {}
110
111 for f in self.changes[rev].add_files:
112 changes.append((f, rev))
113
114 for f in self.changes[rev].mod_files:
115 changes.append((f, rev))
116
117 for f in self.changes[rev].del_files:
118 changes.append((f, rev))
119
120 for src in self.changes[rev].ren_files:
121 to = self.changes[rev].ren_files[src]
122 changes.append((src, rev))
123 changes.append((to, rev))
124 copies[src] = to
125
126 for src in self.changes[rev].ren_dirs:
127 to = self.changes[rev].ren_dirs[src]
128 chgs, cps = self._rendirchanges(src, to);
129 changes += [(f, rev) for f in chgs]
130 for c in cps:
131 copies[c] = cps[c]
132
133 changes.sort()
134 self.lastrev = rev
135
136 return changes, copies
137
138 def getcommit(self, rev):
139 changes = self.changes[rev]
140 return commit(author = changes.author, date = changes.date,
141 desc = changes.summary, parents = self.parents[rev])
142
143 def gettags(self):
144 return self.tags
145
146 def _execute(self, cmd, *args, **kwargs):
147 cmdline = [self.execmd, cmd]
148 cmdline += args
149 cmdline = [util.shellquote(arg) for arg in cmdline]
150 cmdline += ['>', util.nulldev, '2>', util.nulldev]
151 cmdline = util.quotecommand(' '.join(cmdline))
152 self.ui.debug(cmdline, '\n')
153 return os.system(cmdline)
154
155 def _update(self, rev):
156 if rev == 'base-0':
157 # Initialise 'base-0' revision
158 self._obtainrevision(rev)
159 else:
160 self.ui.debug(_('applying revision %s...\n' % rev))
161 revision = '%s--%s' % (self.treeversion, rev)
162 changeset, status = self.runlines('replay', '-d', self.tmppath,
163 revision)
164 if status:
165 # Something went wrong while merging (baz or tla
166 # issue?), get latest revision and try from there
167 shutil.rmtree(self.tmppath, ignore_errors=True)
168 self._obtainrevision(rev)
169 else:
170 old_rev = self.parents[rev][0]
171 self.ui.debug(_('computing changeset between %s and %s...\n' \
172 % (old_rev, rev)))
173 rev_a = '%s--%s' % (self.treeversion, old_rev)
174 rev_b = '%s--%s' % (self.treeversion, rev)
175 self._parsechangeset(changeset, rev)
176
177 def _getfile(self, name, rev):
178 mode = os.lstat(os.path.join(self.tmppath, name)).st_mode
179 if stat.S_ISLNK(mode):
180 data = os.readlink(os.path.join(self.tmppath, name))
181 mode = mode and 'l' or ''
182 else:
183 data = open(os.path.join(self.tmppath, name), 'rb').read()
184 mode = (mode & 0111) and 'x' or ''
185 return data, mode
186
187 def _exclude(self, name):
188 exclude = [ '{arch}', '.arch-ids', '.arch-inventory' ]
189 for exc in exclude:
190 if name.find(exc) != -1:
191 return True
192 return False
193
194 def _readcontents(self, path):
195 files = []
196 contents = os.listdir(path)
197 while len(contents) > 0:
198 c = contents.pop()
199 p = os.path.join(path, c)
200 # os.walk could be used, but here we avoid internal GNU
201 # Arch files and directories, thus saving a lot time.
202 if not self._exclude(p):
203 if os.path.isdir(p):
204 contents += [os.path.join(c, f) for f in os.listdir(p)]
205 else:
206 files.append(c)
207 return files
208
209 def _rendirchanges(self, src, dest):
210 changes = []
211 copies = {}
212 files = self._readcontents(os.path.join(self.tmppath, dest))
213 for f in files:
214 s = os.path.join(src, f)
215 d = os.path.join(dest, f)
216 changes.append(s)
217 changes.append(d)
218 copies[s] = d
219 return changes, copies
220
221 def _obtainrevision(self, rev):
222 self.ui.debug(_('obtaining revision %s...\n' % rev))
223 revision = '%s--%s' % (self.treeversion, rev)
224 output = self._execute('get', revision, self.tmppath)
225 self.checkexit(output)
226 self.ui.debug(_('analysing revision %s...\n' % rev))
227 files = self._readcontents(self.tmppath)
228 self.changes[rev].add_files += files
229
230 def _stripbasepath(self, path):
231 if path.startswith('./'):
232 return path[2:]
233 return path
234
235 def _parsecatlog(self, data, rev):
236 summary = []
237 for l in data:
238 l = l.strip()
239 if summary:
240 summary.append(l)
241 elif l.startswith('Summary:'):
242 summary.append(l[len('Summary: '):])
243 elif l.startswith('Standard-date:'):
244 date = l[len('Standard-date: '):]
245 strdate = util.strdate(date, '%Y-%m-%d %H:%M:%S')
246 self.changes[rev].date = util.datestr(strdate)
247 elif l.startswith('Creator:'):
248 self.changes[rev].author = l[len('Creator: '):]
249 self.changes[rev].summary = '\n'.join(summary)
250
251 def _parsechangeset(self, data, rev):
252 for l in data:
253 l = l.strip()
254 # Added file (ignore added directory)
255 if l.startswith('A') and not l.startswith('A/'):
256 file = self._stripbasepath(l[1:].strip())
257 if not self._exclude(file):
258 self.changes[rev].add_files.append(file)
259 # Deleted file (ignore deleted directory)
260 elif l.startswith('D') and not l.startswith('D/'):
261 file = self._stripbasepath(l[1:].strip())
262 if not self._exclude(file):
263 self.changes[rev].del_files.append(file)
264 # Modified binary file
265 elif l.startswith('Mb'):
266 file = self._stripbasepath(l[2:].strip())
267 if not self._exclude(file):
268 self.changes[rev].mod_files.append(file)
269 # Modified link
270 elif l.startswith('M->'):
271 file = self._stripbasepath(l[3:].strip())
272 if not self._exclude(file):
273 self.changes[rev].mod_files.append(file)
274 # Modified file
275 elif l.startswith('M'):
276 file = self._stripbasepath(l[1:].strip())
277 if not self._exclude(file):
278 self.changes[rev].mod_files.append(file)
279 # Renamed file (or link)
280 elif l.startswith('=>'):
281 files = l[2:].strip().split(' ')
282 if len(files) == 1:
283 files = l[2:].strip().split('\t')
284 src = self._stripbasepath(files[0])
285 dst = self._stripbasepath(files[1])
286 if not self._exclude(src) and not self._exclude(dst):
287 self.changes[rev].ren_files[src] = dst
288 # Conversion from file to link or from link to file (modified)
289 elif l.startswith('ch'):
290 file = self._stripbasepath(l[2:].strip())
291 if not self._exclude(file):
292 self.changes[rev].mod_files.append(file)
293 # Renamed directory
294 elif l.startswith('/>'):
295 dirs = l[2:].strip().split(' ')
296 if len(dirs) == 1:
297 dirs = l[2:].strip().split('\t')
298 src = self._stripbasepath(dirs[0])
299 dst = self._stripbasepath(dirs[1])
300 if not self._exclude(src) and not self._exclude(dst):
301 self.changes[rev].ren_dirs[src] = dst
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,50 +1,50 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # An example CGI script to export multiple hgweb repos, edit as necessary
3 # An example CGI script to export multiple hgweb repos, edit as necessary
4
4
5 # adjust python path if not a system-wide install:
5 # adjust python path if not a system-wide install:
6 #import sys
6 #import sys
7 #sys.path.insert(0, "/path/to/python/lib")
7 #sys.path.insert(0, "/path/to/python/lib")
8
8
9 # enable demandloading to reduce startup time
9 # enable demandloading to reduce startup time
10 from mercurial import demandimport; demandimport.enable()
10 from mercurial import demandimport; demandimport.enable()
11
11
12 # send python tracebacks to the browser if an error occurs:
12 # Uncomment to send python tracebacks to the browser if an error occurs:
13 import cgitb
13 #import cgitb
14 cgitb.enable()
14 #cgitb.enable()
15
15
16 # If you'd like to serve pages with UTF-8 instead of your default
16 # If you'd like to serve pages with UTF-8 instead of your default
17 # locale charset, you can do so by uncommenting the following lines.
17 # locale charset, you can do so by uncommenting the following lines.
18 # Note that this will cause your .hgrc files to be interpreted in
18 # Note that this will cause your .hgrc files to be interpreted in
19 # UTF-8 and all your repo files to be displayed using UTF-8.
19 # UTF-8 and all your repo files to be displayed using UTF-8.
20 #
20 #
21 #import os
21 #import os
22 #os.environ["HGENCODING"] = "UTF-8"
22 #os.environ["HGENCODING"] = "UTF-8"
23
23
24 from mercurial.hgweb.hgwebdir_mod import hgwebdir
24 from mercurial.hgweb.hgwebdir_mod import hgwebdir
25 from mercurial.hgweb.request import wsgiapplication
25 from mercurial.hgweb.request import wsgiapplication
26 from flup.server.fcgi import WSGIServer
26 from flup.server.fcgi import WSGIServer
27
27
28 # The config file looks like this. You can have paths to individual
28 # The config file looks like this. You can have paths to individual
29 # repos, collections of repos in a directory tree, or both.
29 # repos, collections of repos in a directory tree, or both.
30 #
30 #
31 # [paths]
31 # [paths]
32 # virtual/path = /real/path
32 # virtual/path = /real/path
33 # virtual/path = /real/path
33 # virtual/path = /real/path
34 #
34 #
35 # [collections]
35 # [collections]
36 # /prefix/to/strip/off = /root/of/tree/full/of/repos
36 # /prefix/to/strip/off = /root/of/tree/full/of/repos
37 #
37 #
38 # collections example: say directory tree /foo contains repos /foo/bar,
38 # collections example: say directory tree /foo contains repos /foo/bar,
39 # /foo/quux/baz. Give this config section:
39 # /foo/quux/baz. Give this config section:
40 # [collections]
40 # [collections]
41 # /foo = /foo
41 # /foo = /foo
42 # Then repos will list as bar and quux/baz.
42 # Then repos will list as bar and quux/baz.
43 #
43 #
44 # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
44 # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
45 # or use a dictionary with entries like 'virtual/path': '/real/path'
45 # or use a dictionary with entries like 'virtual/path': '/real/path'
46
46
47 def make_web_app():
47 def make_web_app():
48 return hgwebdir("hgweb.config")
48 return hgwebdir("hgweb.config")
49
49
50 WSGIServer(wsgiapplication(make_web_app)).run()
50 WSGIServer(wsgiapplication(make_web_app)).run()
This diff has been collapsed as it changes many lines, (546 lines changed) Show them Hide them
@@ -1,562 +1,64 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # Copyright (C) 2004, 2005 Canonical Ltd
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18
19 # mbp: "you know that thing where cvs gives you conflict markers?"
20 # s: "i hate that."
21
2
22 from mercurial import demandimport
3 from mercurial import demandimport
23 demandimport.enable()
4 demandimport.enable()
24
5
25 from mercurial import util, mdiff, fancyopts
6 import os, sys
26 from mercurial.i18n import _
7 from mercurial.i18n import _
27
8 from mercurial import simplemerge, fancyopts, util
28
29 class CantReprocessAndShowBase(Exception):
30 pass
31
32
33 def warn(message):
34 sys.stdout.flush()
35 sys.stderr.write(message)
36 sys.stderr.flush()
37
38
39 def intersect(ra, rb):
40 """Given two ranges return the range where they intersect or None.
41
42 >>> intersect((0, 10), (0, 6))
43 (0, 6)
44 >>> intersect((0, 10), (5, 15))
45 (5, 10)
46 >>> intersect((0, 10), (10, 15))
47 >>> intersect((0, 9), (10, 15))
48 >>> intersect((0, 9), (7, 15))
49 (7, 9)
50 """
51 assert ra[0] <= ra[1]
52 assert rb[0] <= rb[1]
53
54 sa = max(ra[0], rb[0])
55 sb = min(ra[1], rb[1])
56 if sa < sb:
57 return sa, sb
58 else:
59 return None
60
61
62 def compare_range(a, astart, aend, b, bstart, bend):
63 """Compare a[astart:aend] == b[bstart:bend], without slicing.
64 """
65 if (aend-astart) != (bend-bstart):
66 return False
67 for ia, ib in zip(xrange(astart, aend), xrange(bstart, bend)):
68 if a[ia] != b[ib]:
69 return False
70 else:
71 return True
72
73
74
75
76 class Merge3Text(object):
77 """3-way merge of texts.
78
79 Given strings BASE, OTHER, THIS, tries to produce a combined text
80 incorporating the changes from both BASE->OTHER and BASE->THIS."""
81 def __init__(self, basetext, atext, btext, base=None, a=None, b=None):
82 self.basetext = basetext
83 self.atext = atext
84 self.btext = btext
85 if base is None:
86 base = mdiff.splitnewlines(basetext)
87 if a is None:
88 a = mdiff.splitnewlines(atext)
89 if b is None:
90 b = mdiff.splitnewlines(btext)
91 self.base = base
92 self.a = a
93 self.b = b
94
95
96
97 def merge_lines(self,
98 name_a=None,
99 name_b=None,
100 name_base=None,
101 start_marker='<<<<<<<',
102 mid_marker='=======',
103 end_marker='>>>>>>>',
104 base_marker=None,
105 reprocess=False):
106 """Return merge in cvs-like form.
107 """
108 self.conflicts = False
109 newline = '\n'
110 if len(self.a) > 0:
111 if self.a[0].endswith('\r\n'):
112 newline = '\r\n'
113 elif self.a[0].endswith('\r'):
114 newline = '\r'
115 if base_marker and reprocess:
116 raise CantReprocessAndShowBase()
117 if name_a:
118 start_marker = start_marker + ' ' + name_a
119 if name_b:
120 end_marker = end_marker + ' ' + name_b
121 if name_base and base_marker:
122 base_marker = base_marker + ' ' + name_base
123 merge_regions = self.merge_regions()
124 if reprocess is True:
125 merge_regions = self.reprocess_merge_regions(merge_regions)
126 for t in merge_regions:
127 what = t[0]
128 if what == 'unchanged':
129 for i in range(t[1], t[2]):
130 yield self.base[i]
131 elif what == 'a' or what == 'same':
132 for i in range(t[1], t[2]):
133 yield self.a[i]
134 elif what == 'b':
135 for i in range(t[1], t[2]):
136 yield self.b[i]
137 elif what == 'conflict':
138 self.conflicts = True
139 yield start_marker + newline
140 for i in range(t[3], t[4]):
141 yield self.a[i]
142 if base_marker is not None:
143 yield base_marker + newline
144 for i in range(t[1], t[2]):
145 yield self.base[i]
146 yield mid_marker + newline
147 for i in range(t[5], t[6]):
148 yield self.b[i]
149 yield end_marker + newline
150 else:
151 raise ValueError(what)
152
153
154
155
156
157 def merge_annotated(self):
158 """Return merge with conflicts, showing origin of lines.
159
160 Most useful for debugging merge.
161 """
162 for t in self.merge_regions():
163 what = t[0]
164 if what == 'unchanged':
165 for i in range(t[1], t[2]):
166 yield 'u | ' + self.base[i]
167 elif what == 'a' or what == 'same':
168 for i in range(t[1], t[2]):
169 yield what[0] + ' | ' + self.a[i]
170 elif what == 'b':
171 for i in range(t[1], t[2]):
172 yield 'b | ' + self.b[i]
173 elif what == 'conflict':
174 yield '<<<<\n'
175 for i in range(t[3], t[4]):
176 yield 'A | ' + self.a[i]
177 yield '----\n'
178 for i in range(t[5], t[6]):
179 yield 'B | ' + self.b[i]
180 yield '>>>>\n'
181 else:
182 raise ValueError(what)
183
184
185
186
187
188 def merge_groups(self):
189 """Yield sequence of line groups. Each one is a tuple:
190
191 'unchanged', lines
192 Lines unchanged from base
193
194 'a', lines
195 Lines taken from a
196
197 'same', lines
198 Lines taken from a (and equal to b)
199
200 'b', lines
201 Lines taken from b
202
203 'conflict', base_lines, a_lines, b_lines
204 Lines from base were changed to either a or b and conflict.
205 """
206 for t in self.merge_regions():
207 what = t[0]
208 if what == 'unchanged':
209 yield what, self.base[t[1]:t[2]]
210 elif what == 'a' or what == 'same':
211 yield what, self.a[t[1]:t[2]]
212 elif what == 'b':
213 yield what, self.b[t[1]:t[2]]
214 elif what == 'conflict':
215 yield (what,
216 self.base[t[1]:t[2]],
217 self.a[t[3]:t[4]],
218 self.b[t[5]:t[6]])
219 else:
220 raise ValueError(what)
221
222
223 def merge_regions(self):
224 """Return sequences of matching and conflicting regions.
225
226 This returns tuples, where the first value says what kind we
227 have:
228
229 'unchanged', start, end
230 Take a region of base[start:end]
231
232 'same', astart, aend
233 b and a are different from base but give the same result
234
235 'a', start, end
236 Non-clashing insertion from a[start:end]
237
238 Method is as follows:
239
240 The two sequences align only on regions which match the base
241 and both descendents. These are found by doing a two-way diff
242 of each one against the base, and then finding the
243 intersections between those regions. These "sync regions"
244 are by definition unchanged in both and easily dealt with.
245
246 The regions in between can be in any of three cases:
247 conflicted, or changed on only one side.
248 """
249
250 # section a[0:ia] has been disposed of, etc
251 iz = ia = ib = 0
252
253 for zmatch, zend, amatch, aend, bmatch, bend in self.find_sync_regions():
254 #print 'match base [%d:%d]' % (zmatch, zend)
255
256 matchlen = zend - zmatch
257 assert matchlen >= 0
258 assert matchlen == (aend - amatch)
259 assert matchlen == (bend - bmatch)
260
261 len_a = amatch - ia
262 len_b = bmatch - ib
263 len_base = zmatch - iz
264 assert len_a >= 0
265 assert len_b >= 0
266 assert len_base >= 0
267
268 #print 'unmatched a=%d, b=%d' % (len_a, len_b)
269
270 if len_a or len_b:
271 # try to avoid actually slicing the lists
272 equal_a = compare_range(self.a, ia, amatch,
273 self.base, iz, zmatch)
274 equal_b = compare_range(self.b, ib, bmatch,
275 self.base, iz, zmatch)
276 same = compare_range(self.a, ia, amatch,
277 self.b, ib, bmatch)
278
279 if same:
280 yield 'same', ia, amatch
281 elif equal_a and not equal_b:
282 yield 'b', ib, bmatch
283 elif equal_b and not equal_a:
284 yield 'a', ia, amatch
285 elif not equal_a and not equal_b:
286 yield 'conflict', iz, zmatch, ia, amatch, ib, bmatch
287 else:
288 raise AssertionError("can't handle a=b=base but unmatched")
289
290 ia = amatch
291 ib = bmatch
292 iz = zmatch
293
294 # if the same part of the base was deleted on both sides
295 # that's OK, we can just skip it.
296
297
298 if matchlen > 0:
299 assert ia == amatch
300 assert ib == bmatch
301 assert iz == zmatch
302
303 yield 'unchanged', zmatch, zend
304 iz = zend
305 ia = aend
306 ib = bend
307
308
309 def reprocess_merge_regions(self, merge_regions):
310 """Where there are conflict regions, remove the agreed lines.
311
312 Lines where both A and B have made the same changes are
313 eliminated.
314 """
315 for region in merge_regions:
316 if region[0] != "conflict":
317 yield region
318 continue
319 type, iz, zmatch, ia, amatch, ib, bmatch = region
320 a_region = self.a[ia:amatch]
321 b_region = self.b[ib:bmatch]
322 matches = mdiff.get_matching_blocks(''.join(a_region),
323 ''.join(b_region))
324 next_a = ia
325 next_b = ib
326 for region_ia, region_ib, region_len in matches[:-1]:
327 region_ia += ia
328 region_ib += ib
329 reg = self.mismatch_region(next_a, region_ia, next_b,
330 region_ib)
331 if reg is not None:
332 yield reg
333 yield 'same', region_ia, region_len+region_ia
334 next_a = region_ia + region_len
335 next_b = region_ib + region_len
336 reg = self.mismatch_region(next_a, amatch, next_b, bmatch)
337 if reg is not None:
338 yield reg
339
340
341 def mismatch_region(next_a, region_ia, next_b, region_ib):
342 if next_a < region_ia or next_b < region_ib:
343 return 'conflict', None, None, next_a, region_ia, next_b, region_ib
344 mismatch_region = staticmethod(mismatch_region)
345
346
347 def find_sync_regions(self):
348 """Return a list of sync regions, where both descendents match the base.
349
350 Generates a list of (base1, base2, a1, a2, b1, b2). There is
351 always a zero-length sync region at the end of all the files.
352 """
353
354 ia = ib = 0
355 amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
356 bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
357 len_a = len(amatches)
358 len_b = len(bmatches)
359
360 sl = []
361
362 while ia < len_a and ib < len_b:
363 abase, amatch, alen = amatches[ia]
364 bbase, bmatch, blen = bmatches[ib]
365
366 # there is an unconflicted block at i; how long does it
367 # extend? until whichever one ends earlier.
368 i = intersect((abase, abase+alen), (bbase, bbase+blen))
369 if i:
370 intbase = i[0]
371 intend = i[1]
372 intlen = intend - intbase
373
374 # found a match of base[i[0], i[1]]; this may be less than
375 # the region that matches in either one
376 assert intlen <= alen
377 assert intlen <= blen
378 assert abase <= intbase
379 assert bbase <= intbase
380
381 asub = amatch + (intbase - abase)
382 bsub = bmatch + (intbase - bbase)
383 aend = asub + intlen
384 bend = bsub + intlen
385
386 assert self.base[intbase:intend] == self.a[asub:aend], \
387 (self.base[intbase:intend], self.a[asub:aend])
388
389 assert self.base[intbase:intend] == self.b[bsub:bend]
390
391 sl.append((intbase, intend,
392 asub, aend,
393 bsub, bend))
394
395 # advance whichever one ends first in the base text
396 if (abase + alen) < (bbase + blen):
397 ia += 1
398 else:
399 ib += 1
400
401 intbase = len(self.base)
402 abase = len(self.a)
403 bbase = len(self.b)
404 sl.append((intbase, intbase, abase, abase, bbase, bbase))
405
406 return sl
407
408
409
410 def find_unconflicted(self):
411 """Return a list of ranges in base that are not conflicted."""
412 am = mdiff.get_matching_blocks(self.basetext, self.atext)
413 bm = mdiff.get_matching_blocks(self.basetext, self.btext)
414
415 unc = []
416
417 while am and bm:
418 # there is an unconflicted block at i; how long does it
419 # extend? until whichever one ends earlier.
420 a1 = am[0][0]
421 a2 = a1 + am[0][2]
422 b1 = bm[0][0]
423 b2 = b1 + bm[0][2]
424 i = intersect((a1, a2), (b1, b2))
425 if i:
426 unc.append(i)
427
428 if a2 < b2:
429 del am[0]
430 else:
431 del bm[0]
432
433 return unc
434
435
436 # bzr compatible interface, for the tests
437 class Merge3(Merge3Text):
438 """3-way merge of texts.
439
440 Given BASE, OTHER, THIS, tries to produce a combined text
441 incorporating the changes from both BASE->OTHER and BASE->THIS.
442 All three will typically be sequences of lines."""
443 def __init__(self, base, a, b):
444 basetext = '\n'.join([i.strip('\n') for i in base] + [''])
445 atext = '\n'.join([i.strip('\n') for i in a] + [''])
446 btext = '\n'.join([i.strip('\n') for i in b] + [''])
447 if util.binary(basetext) or util.binary(atext) or util.binary(btext):
448 raise util.Abort(_("don't know how to merge binary files"))
449 Merge3Text.__init__(self, basetext, atext, btext, base, a, b)
450
451
452 def simplemerge(local, base, other, **opts):
453 def readfile(filename):
454 f = open(filename, "rb")
455 text = f.read()
456 f.close()
457 if util.binary(text):
458 msg = _("%s looks like a binary file.") % filename
459 if not opts.get('text'):
460 raise util.Abort(msg)
461 elif not opts.get('quiet'):
462 warn(_('warning: %s\n') % msg)
463 return text
464
465 name_a = local
466 name_b = other
467 labels = opts.get('label', [])
468 if labels:
469 name_a = labels.pop(0)
470 if labels:
471 name_b = labels.pop(0)
472 if labels:
473 raise util.Abort(_("can only specify two labels."))
474
475 localtext = readfile(local)
476 basetext = readfile(base)
477 othertext = readfile(other)
478
479 orig = local
480 local = os.path.realpath(local)
481 if not opts.get('print'):
482 opener = util.opener(os.path.dirname(local))
483 out = opener(os.path.basename(local), "w", atomictemp=True)
484 else:
485 out = sys.stdout
486
487 reprocess = not opts.get('no_minimal')
488
489 m3 = Merge3Text(basetext, localtext, othertext)
490 for line in m3.merge_lines(name_a=name_a, name_b=name_b,
491 reprocess=reprocess):
492 out.write(line)
493
494 if not opts.get('print'):
495 out.rename()
496
497 if m3.conflicts:
498 if not opts.get('quiet'):
499 warn(_("warning: conflicts during merge.\n"))
500 return 1
501
9
502 options = [('L', 'label', [], _('labels to use on conflict markers')),
10 options = [('L', 'label', [], _('labels to use on conflict markers')),
503 ('a', 'text', None, _('treat all files as text')),
11 ('a', 'text', None, _('treat all files as text')),
504 ('p', 'print', None,
12 ('p', 'print', None,
505 _('print results instead of overwriting LOCAL')),
13 _('print results instead of overwriting LOCAL')),
506 ('', 'no-minimal', None,
14 ('', 'no-minimal', None,
507 _('do not try to minimize conflict regions')),
15 _('do not try to minimize conflict regions')),
508 ('h', 'help', None, _('display help and exit')),
16 ('h', 'help', None, _('display help and exit')),
509 ('q', 'quiet', None, _('suppress output'))]
17 ('q', 'quiet', None, _('suppress output'))]
510
18
511 usage = _('''simplemerge [OPTS] LOCAL BASE OTHER
19 usage = _('''simplemerge [OPTS] LOCAL BASE OTHER
512
20
513 Simple three-way file merge utility with a minimal feature set.
21 Simple three-way file merge utility with a minimal feature set.
514
22
515 Apply to LOCAL the changes necessary to go from BASE to OTHER.
23 Apply to LOCAL the changes necessary to go from BASE to OTHER.
516
24
517 By default, LOCAL is overwritten with the results of this operation.
25 By default, LOCAL is overwritten with the results of this operation.
518 ''')
26 ''')
519
27
28 class ParseError(Exception):
29 """Exception raised on errors in parsing the command line."""
30
520 def showhelp():
31 def showhelp():
521 sys.stdout.write(usage)
32 sys.stdout.write(usage)
522 sys.stdout.write('\noptions:\n')
33 sys.stdout.write('\noptions:\n')
523
34
524 out_opts = []
35 out_opts = []
525 for shortopt, longopt, default, desc in options:
36 for shortopt, longopt, default, desc in options:
526 out_opts.append(('%2s%s' % (shortopt and '-%s' % shortopt,
37 out_opts.append(('%2s%s' % (shortopt and '-%s' % shortopt,
527 longopt and ' --%s' % longopt),
38 longopt and ' --%s' % longopt),
528 '%s' % desc))
39 '%s' % desc))
529 opts_len = max([len(opt[0]) for opt in out_opts])
40 opts_len = max([len(opt[0]) for opt in out_opts])
530 for first, second in out_opts:
41 for first, second in out_opts:
531 sys.stdout.write(' %-*s %s\n' % (opts_len, first, second))
42 sys.stdout.write(' %-*s %s\n' % (opts_len, first, second))
532
43
533 class ParseError(Exception):
44 try:
534 """Exception raised on errors in parsing the command line."""
45 opts = {}
535
536 def main(argv):
537 try:
46 try:
538 opts = {}
47 args = fancyopts.fancyopts(sys.argv[1:], options, opts)
539 try:
48 except fancyopts.getopt.GetoptError, e:
540 args = fancyopts.fancyopts(argv[1:], options, opts)
49 raise ParseError(e)
541 except fancyopts.getopt.GetoptError, e:
50 if opts['help']:
542 raise ParseError(e)
543 if opts['help']:
544 showhelp()
545 return 0
546 if len(args) != 3:
547 raise ParseError(_('wrong number of arguments'))
548 return simplemerge(*args, **opts)
549 except ParseError, e:
550 sys.stdout.write("%s: %s\n" % (sys.argv[0], e))
551 showhelp()
51 showhelp()
552 return 1
52 sys.exit(0)
553 except util.Abort, e:
53 if len(args) != 3:
554 sys.stderr.write("abort: %s\n" % e)
54 raise ParseError(_('wrong number of arguments'))
555 return 255
55 sys.exit(simplemerge.simplemerge(*args, **opts))
556 except KeyboardInterrupt:
56 except ParseError, e:
557 return 255
57 sys.stdout.write("%s: %s\n" % (sys.argv[0], e))
558
58 showhelp()
559 if __name__ == '__main__':
59 sys.exit(1)
560 import sys
60 except util.Abort, e:
561 import os
61 sys.stderr.write("abort: %s\n" % e)
562 sys.exit(main(sys.argv))
62 sys.exit(255)
63 except KeyboardInterrupt:
64 sys.exit(255)
@@ -1,590 +1,677 b''
1 HGRC(5)
1 HGRC(5)
2 =======
2 =======
3 Bryan O'Sullivan <bos@serpentine.com>
3 Bryan O'Sullivan <bos@serpentine.com>
4
4
5 NAME
5 NAME
6 ----
6 ----
7 hgrc - configuration files for Mercurial
7 hgrc - configuration files for Mercurial
8
8
9 SYNOPSIS
9 SYNOPSIS
10 --------
10 --------
11
11
12 The Mercurial system uses a set of configuration files to control
12 The Mercurial system uses a set of configuration files to control
13 aspects of its behaviour.
13 aspects of its behaviour.
14
14
15 FILES
15 FILES
16 -----
16 -----
17
17
18 Mercurial reads configuration data from several files, if they exist.
18 Mercurial reads configuration data from several files, if they exist.
19 The names of these files depend on the system on which Mercurial is
19 The names of these files depend on the system on which Mercurial is
20 installed. Windows registry keys contain PATH-like strings, every
20 installed. Windows registry keys contain PATH-like strings, every
21 part must reference a Mercurial.ini file or be a directory where *.rc
21 part must reference a Mercurial.ini file or be a directory where *.rc
22 files will be read.
22 files will be read.
23
23
24 (Unix) <install-root>/etc/mercurial/hgrc.d/*.rc::
24 (Unix) <install-root>/etc/mercurial/hgrc.d/*.rc::
25 (Unix) <install-root>/etc/mercurial/hgrc::
25 (Unix) <install-root>/etc/mercurial/hgrc::
26 Per-installation configuration files, searched for in the
26 Per-installation configuration files, searched for in the
27 directory where Mercurial is installed. For example, if installed
27 directory where Mercurial is installed. For example, if installed
28 in /shared/tools, Mercurial will look in
28 in /shared/tools, Mercurial will look in
29 /shared/tools/etc/mercurial/hgrc. Options in these files apply to
29 /shared/tools/etc/mercurial/hgrc. Options in these files apply to
30 all Mercurial commands executed by any user in any directory.
30 all Mercurial commands executed by any user in any directory.
31
31
32 (Unix) /etc/mercurial/hgrc.d/*.rc::
32 (Unix) /etc/mercurial/hgrc.d/*.rc::
33 (Unix) /etc/mercurial/hgrc::
33 (Unix) /etc/mercurial/hgrc::
34 (Windows) HKEY_LOCAL_MACHINE\SOFTWARE\Mercurial::
34 (Windows) HKEY_LOCAL_MACHINE\SOFTWARE\Mercurial::
35 or::
35 or::
36 (Windows) C:\Mercurial\Mercurial.ini::
36 (Windows) C:\Mercurial\Mercurial.ini::
37 Per-system configuration files, for the system on which Mercurial
37 Per-system configuration files, for the system on which Mercurial
38 is running. Options in these files apply to all Mercurial
38 is running. Options in these files apply to all Mercurial
39 commands executed by any user in any directory. Options in these
39 commands executed by any user in any directory. Options in these
40 files override per-installation options.
40 files override per-installation options.
41
41
42 (Unix) $HOME/.hgrc::
42 (Unix) $HOME/.hgrc::
43 (Windows) C:\Documents and Settings\USERNAME\Mercurial.ini::
43 (Windows) C:\Documents and Settings\USERNAME\Mercurial.ini::
44 (Windows) $HOME\Mercurial.ini::
44 (Windows) $HOME\Mercurial.ini::
45 Per-user configuration file, for the user running Mercurial.
45 Per-user configuration file, for the user running Mercurial.
46 Options in this file apply to all Mercurial commands executed by
46 Options in this file apply to all Mercurial commands executed by
47 any user in any directory. Options in this file override
47 any user in any directory. Options in this file override
48 per-installation and per-system options.
48 per-installation and per-system options.
49 On Windows system, one of these is chosen exclusively according
49 On Windows system, one of these is chosen exclusively according
50 to definition of HOME environment variable.
50 to definition of HOME environment variable.
51
51
52 (Unix, Windows) <repo>/.hg/hgrc::
52 (Unix, Windows) <repo>/.hg/hgrc::
53 Per-repository configuration options that only apply in a
53 Per-repository configuration options that only apply in a
54 particular repository. This file is not version-controlled, and
54 particular repository. This file is not version-controlled, and
55 will not get transferred during a "clone" operation. Options in
55 will not get transferred during a "clone" operation. Options in
56 this file override options in all other configuration files.
56 this file override options in all other configuration files.
57 On Unix, most of this file will be ignored if it doesn't belong
57 On Unix, most of this file will be ignored if it doesn't belong
58 to a trusted user or to a trusted group. See the documentation
58 to a trusted user or to a trusted group. See the documentation
59 for the trusted section below for more details.
59 for the trusted section below for more details.
60
60
61 SYNTAX
61 SYNTAX
62 ------
62 ------
63
63
64 A configuration file consists of sections, led by a "[section]" header
64 A configuration file consists of sections, led by a "[section]" header
65 and followed by "name: value" entries; "name=value" is also accepted.
65 and followed by "name: value" entries; "name=value" is also accepted.
66
66
67 [spam]
67 [spam]
68 eggs=ham
68 eggs=ham
69 green=
69 green=
70 eggs
70 eggs
71
71
72 Each line contains one entry. If the lines that follow are indented,
72 Each line contains one entry. If the lines that follow are indented,
73 they are treated as continuations of that entry.
73 they are treated as continuations of that entry.
74
74
75 Leading whitespace is removed from values. Empty lines are skipped.
75 Leading whitespace is removed from values. Empty lines are skipped.
76
76
77 The optional values can contain format strings which refer to other
77 The optional values can contain format strings which refer to other
78 values in the same section, or values in a special DEFAULT section.
78 values in the same section, or values in a special DEFAULT section.
79
79
80 Lines beginning with "#" or ";" are ignored and may be used to provide
80 Lines beginning with "#" or ";" are ignored and may be used to provide
81 comments.
81 comments.
82
82
83 SECTIONS
83 SECTIONS
84 --------
84 --------
85
85
86 This section describes the different sections that may appear in a
86 This section describes the different sections that may appear in a
87 Mercurial "hgrc" file, the purpose of each section, its possible
87 Mercurial "hgrc" file, the purpose of each section, its possible
88 keys, and their possible values.
88 keys, and their possible values.
89
89
90 decode/encode::
90 decode/encode::
91 Filters for transforming files on checkout/checkin. This would
91 Filters for transforming files on checkout/checkin. This would
92 typically be used for newline processing or other
92 typically be used for newline processing or other
93 localization/canonicalization of files.
93 localization/canonicalization of files.
94
94
95 Filters consist of a filter pattern followed by a filter command.
95 Filters consist of a filter pattern followed by a filter command.
96 Filter patterns are globs by default, rooted at the repository
96 Filter patterns are globs by default, rooted at the repository
97 root. For example, to match any file ending in ".txt" in the root
97 root. For example, to match any file ending in ".txt" in the root
98 directory only, use the pattern "*.txt". To match any file ending
98 directory only, use the pattern "*.txt". To match any file ending
99 in ".c" anywhere in the repository, use the pattern "**.c".
99 in ".c" anywhere in the repository, use the pattern "**.c".
100
100
101 The filter command can start with a specifier, either "pipe:" or
101 The filter command can start with a specifier, either "pipe:" or
102 "tempfile:". If no specifier is given, "pipe:" is used by default.
102 "tempfile:". If no specifier is given, "pipe:" is used by default.
103
103
104 A "pipe:" command must accept data on stdin and return the
104 A "pipe:" command must accept data on stdin and return the
105 transformed data on stdout.
105 transformed data on stdout.
106
106
107 Pipe example:
107 Pipe example:
108
108
109 [encode]
109 [encode]
110 # uncompress gzip files on checkin to improve delta compression
110 # uncompress gzip files on checkin to improve delta compression
111 # note: not necessarily a good idea, just an example
111 # note: not necessarily a good idea, just an example
112 *.gz = pipe: gunzip
112 *.gz = pipe: gunzip
113
113
114 [decode]
114 [decode]
115 # recompress gzip files when writing them to the working dir (we
115 # recompress gzip files when writing them to the working dir (we
116 # can safely omit "pipe:", because it's the default)
116 # can safely omit "pipe:", because it's the default)
117 *.gz = gzip
117 *.gz = gzip
118
118
119 A "tempfile:" command is a template. The string INFILE is replaced
119 A "tempfile:" command is a template. The string INFILE is replaced
120 with the name of a temporary file that contains the data to be
120 with the name of a temporary file that contains the data to be
121 filtered by the command. The string OUTFILE is replaced with the
121 filtered by the command. The string OUTFILE is replaced with the
122 name of an empty temporary file, where the filtered data must be
122 name of an empty temporary file, where the filtered data must be
123 written by the command.
123 written by the command.
124
124
125 NOTE: the tempfile mechanism is recommended for Windows systems,
125 NOTE: the tempfile mechanism is recommended for Windows systems,
126 where the standard shell I/O redirection operators often have
126 where the standard shell I/O redirection operators often have
127 strange effects and may corrupt the contents of your files.
127 strange effects and may corrupt the contents of your files.
128
128
129 The most common usage is for LF <-> CRLF translation on Windows.
129 The most common usage is for LF <-> CRLF translation on Windows.
130 For this, use the "smart" convertors which check for binary files:
130 For this, use the "smart" convertors which check for binary files:
131
131
132 [extensions]
132 [extensions]
133 hgext.win32text =
133 hgext.win32text =
134 [encode]
134 [encode]
135 ** = cleverencode:
135 ** = cleverencode:
136 [decode]
136 [decode]
137 ** = cleverdecode:
137 ** = cleverdecode:
138
138
139 or if you only want to translate certain files:
139 or if you only want to translate certain files:
140
140
141 [extensions]
141 [extensions]
142 hgext.win32text =
142 hgext.win32text =
143 [encode]
143 [encode]
144 **.txt = dumbencode:
144 **.txt = dumbencode:
145 [decode]
145 [decode]
146 **.txt = dumbdecode:
146 **.txt = dumbdecode:
147
147
148 defaults::
148 defaults::
149 Use the [defaults] section to define command defaults, i.e. the
149 Use the [defaults] section to define command defaults, i.e. the
150 default options/arguments to pass to the specified commands.
150 default options/arguments to pass to the specified commands.
151
151
152 The following example makes 'hg log' run in verbose mode, and
152 The following example makes 'hg log' run in verbose mode, and
153 'hg status' show only the modified files, by default.
153 'hg status' show only the modified files, by default.
154
154
155 [defaults]
155 [defaults]
156 log = -v
156 log = -v
157 status = -m
157 status = -m
158
158
159 The actual commands, instead of their aliases, must be used when
159 The actual commands, instead of their aliases, must be used when
160 defining command defaults. The command defaults will also be
160 defining command defaults. The command defaults will also be
161 applied to the aliases of the commands defined.
161 applied to the aliases of the commands defined.
162
162
163 diff::
163 diff::
164 Settings used when displaying diffs. They are all boolean and
164 Settings used when displaying diffs. They are all boolean and
165 defaults to False.
165 defaults to False.
166 git;;
166 git;;
167 Use git extended diff format.
167 Use git extended diff format.
168 nodates;;
168 nodates;;
169 Don't include dates in diff headers.
169 Don't include dates in diff headers.
170 showfunc;;
170 showfunc;;
171 Show which function each change is in.
171 Show which function each change is in.
172 ignorews;;
172 ignorews;;
173 Ignore white space when comparing lines.
173 Ignore white space when comparing lines.
174 ignorewsamount;;
174 ignorewsamount;;
175 Ignore changes in the amount of white space.
175 Ignore changes in the amount of white space.
176 ignoreblanklines;;
176 ignoreblanklines;;
177 Ignore changes whose lines are all blank.
177 Ignore changes whose lines are all blank.
178
178
179 email::
179 email::
180 Settings for extensions that send email messages.
180 Settings for extensions that send email messages.
181 from;;
181 from;;
182 Optional. Email address to use in "From" header and SMTP envelope
182 Optional. Email address to use in "From" header and SMTP envelope
183 of outgoing messages.
183 of outgoing messages.
184 to;;
184 to;;
185 Optional. Comma-separated list of recipients' email addresses.
185 Optional. Comma-separated list of recipients' email addresses.
186 cc;;
186 cc;;
187 Optional. Comma-separated list of carbon copy recipients'
187 Optional. Comma-separated list of carbon copy recipients'
188 email addresses.
188 email addresses.
189 bcc;;
189 bcc;;
190 Optional. Comma-separated list of blind carbon copy
190 Optional. Comma-separated list of blind carbon copy
191 recipients' email addresses. Cannot be set interactively.
191 recipients' email addresses. Cannot be set interactively.
192 method;;
192 method;;
193 Optional. Method to use to send email messages. If value is
193 Optional. Method to use to send email messages. If value is
194 "smtp" (default), use SMTP (see section "[smtp]" for
194 "smtp" (default), use SMTP (see section "[smtp]" for
195 configuration). Otherwise, use as name of program to run that
195 configuration). Otherwise, use as name of program to run that
196 acts like sendmail (takes "-f" option for sender, list of
196 acts like sendmail (takes "-f" option for sender, list of
197 recipients on command line, message on stdin). Normally, setting
197 recipients on command line, message on stdin). Normally, setting
198 this to "sendmail" or "/usr/sbin/sendmail" is enough to use
198 this to "sendmail" or "/usr/sbin/sendmail" is enough to use
199 sendmail to send messages.
199 sendmail to send messages.
200
200
201 Email example:
201 Email example:
202
202
203 [email]
203 [email]
204 from = Joseph User <joe.user@example.com>
204 from = Joseph User <joe.user@example.com>
205 method = /usr/sbin/sendmail
205 method = /usr/sbin/sendmail
206
206
207 extensions::
207 extensions::
208 Mercurial has an extension mechanism for adding new features. To
208 Mercurial has an extension mechanism for adding new features. To
209 enable an extension, create an entry for it in this section.
209 enable an extension, create an entry for it in this section.
210
210
211 If you know that the extension is already in Python's search path,
211 If you know that the extension is already in Python's search path,
212 you can give the name of the module, followed by "=", with nothing
212 you can give the name of the module, followed by "=", with nothing
213 after the "=".
213 after the "=".
214
214
215 Otherwise, give a name that you choose, followed by "=", followed by
215 Otherwise, give a name that you choose, followed by "=", followed by
216 the path to the ".py" file (including the file name extension) that
216 the path to the ".py" file (including the file name extension) that
217 defines the extension.
217 defines the extension.
218
218
219 Example for ~/.hgrc:
219 Example for ~/.hgrc:
220
220
221 [extensions]
221 [extensions]
222 # (the mq extension will get loaded from mercurial's path)
222 # (the mq extension will get loaded from mercurial's path)
223 hgext.mq =
223 hgext.mq =
224 # (this extension will get loaded from the file specified)
224 # (this extension will get loaded from the file specified)
225 myfeature = ~/.hgext/myfeature.py
225 myfeature = ~/.hgext/myfeature.py
226
226
227 format::
227 format::
228
228
229 usestore;;
229 usestore;;
230 Enable or disable the "store" repository format which improves
230 Enable or disable the "store" repository format which improves
231 compatibility with systems that fold case or otherwise mangle
231 compatibility with systems that fold case or otherwise mangle
232 filenames. Enabled by default. Disabling this option will allow
232 filenames. Enabled by default. Disabling this option will allow
233 you to store longer filenames in some situations at the expense of
233 you to store longer filenames in some situations at the expense of
234 compatibility.
234 compatibility.
235
235
236 merge-patterns::
237 This section specifies merge tools to associate with particular file
238 patterns. Tools matched here will take precedence over the default
239 merge tool. Patterns are globs by default, rooted at the repository root.
240
241 Example:
242
243 [merge-patterns]
244 **.c = kdiff3
245 **.jpg = myimgmerge
246
247 merge-tools::
248 This section configures external merge tools to use for file-level
249 merges.
250
251 Example ~/.hgrc:
252
253 [merge-tools]
254 # Override stock tool location
255 kdiff3.executable = ~/bin/kdiff3
256 # Specify command line
257 kdiff3.args = $base $local $other -o $output
258 # Give higher priority
259 kdiff3.priority = 1
260
261 # Define new tool
262 myHtmlTool.args = -m $local $other $base $output
263 myHtmlTool.regkey = Software\FooSoftware\HtmlMerge
264 myHtmlTool.priority = 1
265
266 Supported arguments:
267 priority;;
268 The priority in which to evaluate this tool.
269 Default: 0.
270 executable;;
271 Either just the name of the executable or its pathname.
272 Default: the tool name.
273 args;;
274 The arguments to pass to the tool executable. You can refer to the files
275 being merged as well as the output file through these variables: $base,
276 $local, $other, $output.
277 Default: $local $base $other
278 premerge;;
279 Attempt to run internal non-interactive 3-way merge tool before
280 launching external tool.
281 Default: True
282 binary;;
283 This tool can merge binary files. Defaults to False, unless tool
284 was selected by file pattern match.
285 symlink;;
286 This tool can merge symlinks. Defaults to False, even if tool was
287 selected by file pattern match.
288 checkconflicts;;
289 Check whether there are conflicts even though the tool reported
290 success.
291 Default: False
292 checkchanged;;
293 Check whether outputs were written even though the tool reported
294 success.
295 Default: False
296 fixeol;;
297 Attempt to fix up EOL changes caused by the merge tool.
298 Default: False
299 gui:;
300 This tool requires a graphical interface to run. Default: False
301 regkey;;
302 Windows registry key which describes install location of this tool.
303 Mercurial will search for this key first under HKEY_CURRENT_USER and
304 then under HKEY_LOCAL_MACHINE. Default: None
305 regname;;
306 Name of value to read from specified registry key. Defaults to the
307 unnamed (default) value.
308 regappend;;
309 String to append to the value read from the registry, typically the
310 executable name of the tool. Default: None
311
236 hooks::
312 hooks::
237 Commands or Python functions that get automatically executed by
313 Commands or Python functions that get automatically executed by
238 various actions such as starting or finishing a commit. Multiple
314 various actions such as starting or finishing a commit. Multiple
239 hooks can be run for the same action by appending a suffix to the
315 hooks can be run for the same action by appending a suffix to the
240 action. Overriding a site-wide hook can be done by changing its
316 action. Overriding a site-wide hook can be done by changing its
241 value or setting it to an empty string.
317 value or setting it to an empty string.
242
318
243 Example .hg/hgrc:
319 Example .hg/hgrc:
244
320
245 [hooks]
321 [hooks]
246 # do not use the site-wide hook
322 # do not use the site-wide hook
247 incoming =
323 incoming =
248 incoming.email = /my/email/hook
324 incoming.email = /my/email/hook
249 incoming.autobuild = /my/build/hook
325 incoming.autobuild = /my/build/hook
250
326
251 Most hooks are run with environment variables set that give added
327 Most hooks are run with environment variables set that give added
252 useful information. For each hook below, the environment variables
328 useful information. For each hook below, the environment variables
253 it is passed are listed with names of the form "$HG_foo".
329 it is passed are listed with names of the form "$HG_foo".
254
330
255 changegroup;;
331 changegroup;;
256 Run after a changegroup has been added via push, pull or
332 Run after a changegroup has been added via push, pull or
257 unbundle. ID of the first new changeset is in $HG_NODE. URL from
333 unbundle. ID of the first new changeset is in $HG_NODE. URL from
258 which changes came is in $HG_URL.
334 which changes came is in $HG_URL.
259 commit;;
335 commit;;
260 Run after a changeset has been created in the local repository.
336 Run after a changeset has been created in the local repository.
261 ID of the newly created changeset is in $HG_NODE. Parent
337 ID of the newly created changeset is in $HG_NODE. Parent
262 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
338 changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
263 incoming;;
339 incoming;;
264 Run after a changeset has been pulled, pushed, or unbundled into
340 Run after a changeset has been pulled, pushed, or unbundled into
265 the local repository. The ID of the newly arrived changeset is in
341 the local repository. The ID of the newly arrived changeset is in
266 $HG_NODE. URL that was source of changes came is in $HG_URL.
342 $HG_NODE. URL that was source of changes came is in $HG_URL.
267 outgoing;;
343 outgoing;;
268 Run after sending changes from local repository to another. ID of
344 Run after sending changes from local repository to another. ID of
269 first changeset sent is in $HG_NODE. Source of operation is in
345 first changeset sent is in $HG_NODE. Source of operation is in
270 $HG_SOURCE; see "preoutgoing" hook for description.
346 $HG_SOURCE; see "preoutgoing" hook for description.
271 post-<command>;;
347 post-<command>;;
272 Run after successful invocations of the associated command. The
348 Run after successful invocations of the associated command. The
273 contents of the command line are passed as $HG_ARGS and the result
349 contents of the command line are passed as $HG_ARGS and the result
274 code in $HG_RESULT. Hook failure is ignored.
350 code in $HG_RESULT. Hook failure is ignored.
275 pre-<command>;;
351 pre-<command>;;
276 Run before executing the associated command. The contents of the
352 Run before executing the associated command. The contents of the
277 command line are passed as $HG_ARGS. If the hook returns failure,
353 command line are passed as $HG_ARGS. If the hook returns failure,
278 the command doesn't execute and Mercurial returns the failure code.
354 the command doesn't execute and Mercurial returns the failure code.
279 prechangegroup;;
355 prechangegroup;;
280 Run before a changegroup is added via push, pull or unbundle.
356 Run before a changegroup is added via push, pull or unbundle.
281 Exit status 0 allows the changegroup to proceed. Non-zero status
357 Exit status 0 allows the changegroup to proceed. Non-zero status
282 will cause the push, pull or unbundle to fail. URL from which
358 will cause the push, pull or unbundle to fail. URL from which
283 changes will come is in $HG_URL.
359 changes will come is in $HG_URL.
284 precommit;;
360 precommit;;
285 Run before starting a local commit. Exit status 0 allows the
361 Run before starting a local commit. Exit status 0 allows the
286 commit to proceed. Non-zero status will cause the commit to fail.
362 commit to proceed. Non-zero status will cause the commit to fail.
287 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
363 Parent changeset IDs are in $HG_PARENT1 and $HG_PARENT2.
288 preoutgoing;;
364 preoutgoing;;
289 Run before collecting changes to send from the local repository to
365 Run before collecting changes to send from the local repository to
290 another. Non-zero status will cause failure. This lets you
366 another. Non-zero status will cause failure. This lets you
291 prevent pull over http or ssh. Also prevents against local pull,
367 prevent pull over http or ssh. Also prevents against local pull,
292 push (outbound) or bundle commands, but not effective, since you
368 push (outbound) or bundle commands, but not effective, since you
293 can just copy files instead then. Source of operation is in
369 can just copy files instead then. Source of operation is in
294 $HG_SOURCE. If "serve", operation is happening on behalf of
370 $HG_SOURCE. If "serve", operation is happening on behalf of
295 remote ssh or http repository. If "push", "pull" or "bundle",
371 remote ssh or http repository. If "push", "pull" or "bundle",
296 operation is happening on behalf of repository on same system.
372 operation is happening on behalf of repository on same system.
297 pretag;;
373 pretag;;
298 Run before creating a tag. Exit status 0 allows the tag to be
374 Run before creating a tag. Exit status 0 allows the tag to be
299 created. Non-zero status will cause the tag to fail. ID of
375 created. Non-zero status will cause the tag to fail. ID of
300 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
376 changeset to tag is in $HG_NODE. Name of tag is in $HG_TAG. Tag
301 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
377 is local if $HG_LOCAL=1, in repo if $HG_LOCAL=0.
302 pretxnchangegroup;;
378 pretxnchangegroup;;
303 Run after a changegroup has been added via push, pull or unbundle,
379 Run after a changegroup has been added via push, pull or unbundle,
304 but before the transaction has been committed. Changegroup is
380 but before the transaction has been committed. Changegroup is
305 visible to hook program. This lets you validate incoming changes
381 visible to hook program. This lets you validate incoming changes
306 before accepting them. Passed the ID of the first new changeset
382 before accepting them. Passed the ID of the first new changeset
307 in $HG_NODE. Exit status 0 allows the transaction to commit.
383 in $HG_NODE. Exit status 0 allows the transaction to commit.
308 Non-zero status will cause the transaction to be rolled back and
384 Non-zero status will cause the transaction to be rolled back and
309 the push, pull or unbundle will fail. URL that was source of
385 the push, pull or unbundle will fail. URL that was source of
310 changes is in $HG_URL.
386 changes is in $HG_URL.
311 pretxncommit;;
387 pretxncommit;;
312 Run after a changeset has been created but the transaction not yet
388 Run after a changeset has been created but the transaction not yet
313 committed. Changeset is visible to hook program. This lets you
389 committed. Changeset is visible to hook program. This lets you
314 validate commit message and changes. Exit status 0 allows the
390 validate commit message and changes. Exit status 0 allows the
315 commit to proceed. Non-zero status will cause the transaction to
391 commit to proceed. Non-zero status will cause the transaction to
316 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
392 be rolled back. ID of changeset is in $HG_NODE. Parent changeset
317 IDs are in $HG_PARENT1 and $HG_PARENT2.
393 IDs are in $HG_PARENT1 and $HG_PARENT2.
318 preupdate;;
394 preupdate;;
319 Run before updating the working directory. Exit status 0 allows
395 Run before updating the working directory. Exit status 0 allows
320 the update to proceed. Non-zero status will prevent the update.
396 the update to proceed. Non-zero status will prevent the update.
321 Changeset ID of first new parent is in $HG_PARENT1. If merge, ID
397 Changeset ID of first new parent is in $HG_PARENT1. If merge, ID
322 of second new parent is in $HG_PARENT2.
398 of second new parent is in $HG_PARENT2.
323 tag;;
399 tag;;
324 Run after a tag is created. ID of tagged changeset is in
400 Run after a tag is created. ID of tagged changeset is in
325 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
401 $HG_NODE. Name of tag is in $HG_TAG. Tag is local if
326 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
402 $HG_LOCAL=1, in repo if $HG_LOCAL=0.
327 update;;
403 update;;
328 Run after updating the working directory. Changeset ID of first
404 Run after updating the working directory. Changeset ID of first
329 new parent is in $HG_PARENT1. If merge, ID of second new parent
405 new parent is in $HG_PARENT1. If merge, ID of second new parent
330 is in $HG_PARENT2. If update succeeded, $HG_ERROR=0. If update
406 is in $HG_PARENT2. If update succeeded, $HG_ERROR=0. If update
331 failed (e.g. because conflicts not resolved), $HG_ERROR=1.
407 failed (e.g. because conflicts not resolved), $HG_ERROR=1.
332
408
333 Note: it is generally better to use standard hooks rather than the
409 Note: it is generally better to use standard hooks rather than the
334 generic pre- and post- command hooks as they are guaranteed to be
410 generic pre- and post- command hooks as they are guaranteed to be
335 called in the appropriate contexts for influencing transactions.
411 called in the appropriate contexts for influencing transactions.
336 Also, hooks like "commit" will be called in all contexts that
412 Also, hooks like "commit" will be called in all contexts that
337 generate a commit (eg. tag) and not just the commit command.
413 generate a commit (eg. tag) and not just the commit command.
338
414
339 Note2: Environment variables with empty values may not be passed to
415 Note2: Environment variables with empty values may not be passed to
340 hooks on platforms like Windows. For instance, $HG_PARENT2 will
416 hooks on platforms like Windows. For instance, $HG_PARENT2 will
341 not be available under Windows for non-merge changesets while being
417 not be available under Windows for non-merge changesets while being
342 set to an empty value under Unix-like systems.
418 set to an empty value under Unix-like systems.
343
419
344 The syntax for Python hooks is as follows:
420 The syntax for Python hooks is as follows:
345
421
346 hookname = python:modulename.submodule.callable
422 hookname = python:modulename.submodule.callable
347
423
348 Python hooks are run within the Mercurial process. Each hook is
424 Python hooks are run within the Mercurial process. Each hook is
349 called with at least three keyword arguments: a ui object (keyword
425 called with at least three keyword arguments: a ui object (keyword
350 "ui"), a repository object (keyword "repo"), and a "hooktype"
426 "ui"), a repository object (keyword "repo"), and a "hooktype"
351 keyword that tells what kind of hook is used. Arguments listed as
427 keyword that tells what kind of hook is used. Arguments listed as
352 environment variables above are passed as keyword arguments, with no
428 environment variables above are passed as keyword arguments, with no
353 "HG_" prefix, and names in lower case.
429 "HG_" prefix, and names in lower case.
354
430
355 If a Python hook returns a "true" value or raises an exception, this
431 If a Python hook returns a "true" value or raises an exception, this
356 is treated as failure of the hook.
432 is treated as failure of the hook.
357
433
358 http_proxy::
434 http_proxy::
359 Used to access web-based Mercurial repositories through a HTTP
435 Used to access web-based Mercurial repositories through a HTTP
360 proxy.
436 proxy.
361 host;;
437 host;;
362 Host name and (optional) port of the proxy server, for example
438 Host name and (optional) port of the proxy server, for example
363 "myproxy:8000".
439 "myproxy:8000".
364 no;;
440 no;;
365 Optional. Comma-separated list of host names that should bypass
441 Optional. Comma-separated list of host names that should bypass
366 the proxy.
442 the proxy.
367 passwd;;
443 passwd;;
368 Optional. Password to authenticate with at the proxy server.
444 Optional. Password to authenticate with at the proxy server.
369 user;;
445 user;;
370 Optional. User name to authenticate with at the proxy server.
446 Optional. User name to authenticate with at the proxy server.
371
447
372 smtp::
448 smtp::
373 Configuration for extensions that need to send email messages.
449 Configuration for extensions that need to send email messages.
374 host;;
450 host;;
375 Host name of mail server, e.g. "mail.example.com".
451 Host name of mail server, e.g. "mail.example.com".
376 port;;
452 port;;
377 Optional. Port to connect to on mail server. Default: 25.
453 Optional. Port to connect to on mail server. Default: 25.
378 tls;;
454 tls;;
379 Optional. Whether to connect to mail server using TLS. True or
455 Optional. Whether to connect to mail server using TLS. True or
380 False. Default: False.
456 False. Default: False.
381 username;;
457 username;;
382 Optional. User name to authenticate to SMTP server with.
458 Optional. User name to authenticate to SMTP server with.
383 If username is specified, password must also be specified.
459 If username is specified, password must also be specified.
384 Default: none.
460 Default: none.
385 password;;
461 password;;
386 Optional. Password to authenticate to SMTP server with.
462 Optional. Password to authenticate to SMTP server with.
387 If username is specified, password must also be specified.
463 If username is specified, password must also be specified.
388 Default: none.
464 Default: none.
389 local_hostname;;
465 local_hostname;;
390 Optional. It's the hostname that the sender can use to identify itself
466 Optional. It's the hostname that the sender can use to identify itself
391 to the MTA.
467 to the MTA.
392
468
393 paths::
469 paths::
394 Assigns symbolic names to repositories. The left side is the
470 Assigns symbolic names to repositories. The left side is the
395 symbolic name, and the right gives the directory or URL that is the
471 symbolic name, and the right gives the directory or URL that is the
396 location of the repository. Default paths can be declared by
472 location of the repository. Default paths can be declared by
397 setting the following entries.
473 setting the following entries.
398 default;;
474 default;;
399 Directory or URL to use when pulling if no source is specified.
475 Directory or URL to use when pulling if no source is specified.
400 Default is set to repository from which the current repository
476 Default is set to repository from which the current repository
401 was cloned.
477 was cloned.
402 default-push;;
478 default-push;;
403 Optional. Directory or URL to use when pushing if no destination
479 Optional. Directory or URL to use when pushing if no destination
404 is specified.
480 is specified.
405
481
406 server::
482 server::
407 Controls generic server settings.
483 Controls generic server settings.
408 uncompressed;;
484 uncompressed;;
409 Whether to allow clients to clone a repo using the uncompressed
485 Whether to allow clients to clone a repo using the uncompressed
410 streaming protocol. This transfers about 40% more data than a
486 streaming protocol. This transfers about 40% more data than a
411 regular clone, but uses less memory and CPU on both server and
487 regular clone, but uses less memory and CPU on both server and
412 client. Over a LAN (100Mbps or better) or a very fast WAN, an
488 client. Over a LAN (100Mbps or better) or a very fast WAN, an
413 uncompressed streaming clone is a lot faster (~10x) than a regular
489 uncompressed streaming clone is a lot faster (~10x) than a regular
414 clone. Over most WAN connections (anything slower than about
490 clone. Over most WAN connections (anything slower than about
415 6Mbps), uncompressed streaming is slower, because of the extra
491 6Mbps), uncompressed streaming is slower, because of the extra
416 data transfer overhead. Default is False.
492 data transfer overhead. Default is False.
417
493
418 trusted::
494 trusted::
419 For security reasons, Mercurial will not use the settings in
495 For security reasons, Mercurial will not use the settings in
420 the .hg/hgrc file from a repository if it doesn't belong to a
496 the .hg/hgrc file from a repository if it doesn't belong to a
421 trusted user or to a trusted group. The main exception is the
497 trusted user or to a trusted group. The main exception is the
422 web interface, which automatically uses some safe settings, since
498 web interface, which automatically uses some safe settings, since
423 it's common to serve repositories from different users.
499 it's common to serve repositories from different users.
424
500
425 This section specifies what users and groups are trusted. The
501 This section specifies what users and groups are trusted. The
426 current user is always trusted. To trust everybody, list a user
502 current user is always trusted. To trust everybody, list a user
427 or a group with name "*".
503 or a group with name "*".
428
504
429 users;;
505 users;;
430 Comma-separated list of trusted users.
506 Comma-separated list of trusted users.
431 groups;;
507 groups;;
432 Comma-separated list of trusted groups.
508 Comma-separated list of trusted groups.
433
509
434 ui::
510 ui::
435 User interface controls.
511 User interface controls.
436 debug;;
512 debug;;
437 Print debugging information. True or False. Default is False.
513 Print debugging information. True or False. Default is False.
438 editor;;
514 editor;;
439 The editor to use during a commit. Default is $EDITOR or "vi".
515 The editor to use during a commit. Default is $EDITOR or "vi".
440 fallbackencoding;;
516 fallbackencoding;;
441 Encoding to try if it's not possible to decode the changelog using
517 Encoding to try if it's not possible to decode the changelog using
442 UTF-8. Default is ISO-8859-1.
518 UTF-8. Default is ISO-8859-1.
443 ignore;;
519 ignore;;
444 A file to read per-user ignore patterns from. This file should be in
520 A file to read per-user ignore patterns from. This file should be in
445 the same format as a repository-wide .hgignore file. This option
521 the same format as a repository-wide .hgignore file. This option
446 supports hook syntax, so if you want to specify multiple ignore
522 supports hook syntax, so if you want to specify multiple ignore
447 files, you can do so by setting something like
523 files, you can do so by setting something like
448 "ignore.other = ~/.hgignore2". For details of the ignore file
524 "ignore.other = ~/.hgignore2". For details of the ignore file
449 format, see the hgignore(5) man page.
525 format, see the hgignore(5) man page.
450 interactive;;
526 interactive;;
451 Allow to prompt the user. True or False. Default is True.
527 Allow to prompt the user. True or False. Default is True.
452 logtemplate;;
528 logtemplate;;
453 Template string for commands that print changesets.
529 Template string for commands that print changesets.
454 merge;;
530 merge;;
455 The conflict resolution program to use during a manual merge.
531 The conflict resolution program to use during a manual merge.
456 Default is "hgmerge".
532 There are some internal tools available:
533
534 internal:local;;
535 keep the local version
536 internal:other;;
537 use the other version
538 internal:merge;;
539 use the internal non-interactive merge tool
540 internal:fail;;
541 fail to merge
542
543 See the merge-tools section for more information on configuring tools.
457 patch;;
544 patch;;
458 command to use to apply patches. Look for 'gpatch' or 'patch' in PATH if
545 command to use to apply patches. Look for 'gpatch' or 'patch' in PATH if
459 unset.
546 unset.
460 quiet;;
547 quiet;;
461 Reduce the amount of output printed. True or False. Default is False.
548 Reduce the amount of output printed. True or False. Default is False.
462 remotecmd;;
549 remotecmd;;
463 remote command to use for clone/push/pull operations. Default is 'hg'.
550 remote command to use for clone/push/pull operations. Default is 'hg'.
464 report_untrusted;;
551 report_untrusted;;
465 Warn if a .hg/hgrc file is ignored due to not being owned by a
552 Warn if a .hg/hgrc file is ignored due to not being owned by a
466 trusted user or group. True or False. Default is True.
553 trusted user or group. True or False. Default is True.
467 slash;;
554 slash;;
468 Display paths using a slash ("/") as the path separator. This only
555 Display paths using a slash ("/") as the path separator. This only
469 makes a difference on systems where the default path separator is not
556 makes a difference on systems where the default path separator is not
470 the slash character (e.g. Windows uses the backslash character ("\")).
557 the slash character (e.g. Windows uses the backslash character ("\")).
471 Default is False.
558 Default is False.
472 ssh;;
559 ssh;;
473 command to use for SSH connections. Default is 'ssh'.
560 command to use for SSH connections. Default is 'ssh'.
474 strict;;
561 strict;;
475 Require exact command names, instead of allowing unambiguous
562 Require exact command names, instead of allowing unambiguous
476 abbreviations. True or False. Default is False.
563 abbreviations. True or False. Default is False.
477 style;;
564 style;;
478 Name of style to use for command output.
565 Name of style to use for command output.
479 timeout;;
566 timeout;;
480 The timeout used when a lock is held (in seconds), a negative value
567 The timeout used when a lock is held (in seconds), a negative value
481 means no timeout. Default is 600.
568 means no timeout. Default is 600.
482 username;;
569 username;;
483 The committer of a changeset created when running "commit".
570 The committer of a changeset created when running "commit".
484 Typically a person's name and email address, e.g. "Fred Widget
571 Typically a person's name and email address, e.g. "Fred Widget
485 <fred@example.com>". Default is $EMAIL or username@hostname.
572 <fred@example.com>". Default is $EMAIL or username@hostname.
486 If the username in hgrc is empty, it has to be specified manually or
573 If the username in hgrc is empty, it has to be specified manually or
487 in a different hgrc file (e.g. $HOME/.hgrc, if the admin set "username ="
574 in a different hgrc file (e.g. $HOME/.hgrc, if the admin set "username ="
488 in the system hgrc).
575 in the system hgrc).
489 verbose;;
576 verbose;;
490 Increase the amount of output printed. True or False. Default is False.
577 Increase the amount of output printed. True or False. Default is False.
491
578
492
579
493 web::
580 web::
494 Web interface configuration.
581 Web interface configuration.
495 accesslog;;
582 accesslog;;
496 Where to output the access log. Default is stdout.
583 Where to output the access log. Default is stdout.
497 address;;
584 address;;
498 Interface address to bind to. Default is all.
585 Interface address to bind to. Default is all.
499 allow_archive;;
586 allow_archive;;
500 List of archive format (bz2, gz, zip) allowed for downloading.
587 List of archive format (bz2, gz, zip) allowed for downloading.
501 Default is empty.
588 Default is empty.
502 allowbz2;;
589 allowbz2;;
503 (DEPRECATED) Whether to allow .tar.bz2 downloading of repo revisions.
590 (DEPRECATED) Whether to allow .tar.bz2 downloading of repo revisions.
504 Default is false.
591 Default is false.
505 allowgz;;
592 allowgz;;
506 (DEPRECATED) Whether to allow .tar.gz downloading of repo revisions.
593 (DEPRECATED) Whether to allow .tar.gz downloading of repo revisions.
507 Default is false.
594 Default is false.
508 allowpull;;
595 allowpull;;
509 Whether to allow pulling from the repository. Default is true.
596 Whether to allow pulling from the repository. Default is true.
510 allow_push;;
597 allow_push;;
511 Whether to allow pushing to the repository. If empty or not set,
598 Whether to allow pushing to the repository. If empty or not set,
512 push is not allowed. If the special value "*", any remote user
599 push is not allowed. If the special value "*", any remote user
513 can push, including unauthenticated users. Otherwise, the remote
600 can push, including unauthenticated users. Otherwise, the remote
514 user must have been authenticated, and the authenticated user name
601 user must have been authenticated, and the authenticated user name
515 must be present in this list (separated by whitespace or ",").
602 must be present in this list (separated by whitespace or ",").
516 The contents of the allow_push list are examined after the
603 The contents of the allow_push list are examined after the
517 deny_push list.
604 deny_push list.
518 allowzip;;
605 allowzip;;
519 (DEPRECATED) Whether to allow .zip downloading of repo revisions.
606 (DEPRECATED) Whether to allow .zip downloading of repo revisions.
520 Default is false. This feature creates temporary files.
607 Default is false. This feature creates temporary files.
521 baseurl;;
608 baseurl;;
522 Base URL to use when publishing URLs in other locations, so
609 Base URL to use when publishing URLs in other locations, so
523 third-party tools like email notification hooks can construct URLs.
610 third-party tools like email notification hooks can construct URLs.
524 Example: "http://hgserver/repos/"
611 Example: "http://hgserver/repos/"
525 contact;;
612 contact;;
526 Name or email address of the person in charge of the repository.
613 Name or email address of the person in charge of the repository.
527 Defaults to ui.username or $EMAIL or "unknown" if unset or empty.
614 Defaults to ui.username or $EMAIL or "unknown" if unset or empty.
528 deny_push;;
615 deny_push;;
529 Whether to deny pushing to the repository. If empty or not set,
616 Whether to deny pushing to the repository. If empty or not set,
530 push is not denied. If the special value "*", all remote users
617 push is not denied. If the special value "*", all remote users
531 are denied push. Otherwise, unauthenticated users are all denied,
618 are denied push. Otherwise, unauthenticated users are all denied,
532 and any authenticated user name present in this list (separated by
619 and any authenticated user name present in this list (separated by
533 whitespace or ",") is also denied. The contents of the deny_push
620 whitespace or ",") is also denied. The contents of the deny_push
534 list are examined before the allow_push list.
621 list are examined before the allow_push list.
535 description;;
622 description;;
536 Textual description of the repository's purpose or contents.
623 Textual description of the repository's purpose or contents.
537 Default is "unknown".
624 Default is "unknown".
538 encoding;;
625 encoding;;
539 Character encoding name.
626 Character encoding name.
540 Example: "UTF-8"
627 Example: "UTF-8"
541 errorlog;;
628 errorlog;;
542 Where to output the error log. Default is stderr.
629 Where to output the error log. Default is stderr.
543 hidden;;
630 hidden;;
544 Whether to hide the repository in the hgwebdir index. Default is false.
631 Whether to hide the repository in the hgwebdir index. Default is false.
545 ipv6;;
632 ipv6;;
546 Whether to use IPv6. Default is false.
633 Whether to use IPv6. Default is false.
547 name;;
634 name;;
548 Repository name to use in the web interface. Default is current
635 Repository name to use in the web interface. Default is current
549 working directory.
636 working directory.
550 maxchanges;;
637 maxchanges;;
551 Maximum number of changes to list on the changelog. Default is 10.
638 Maximum number of changes to list on the changelog. Default is 10.
552 maxfiles;;
639 maxfiles;;
553 Maximum number of files to list per changeset. Default is 10.
640 Maximum number of files to list per changeset. Default is 10.
554 port;;
641 port;;
555 Port to listen on. Default is 8000.
642 Port to listen on. Default is 8000.
556 prefix;;
643 prefix;;
557 Prefix path to serve from. Default is '' (server root).
644 Prefix path to serve from. Default is '' (server root).
558 push_ssl;;
645 push_ssl;;
559 Whether to require that inbound pushes be transported over SSL to
646 Whether to require that inbound pushes be transported over SSL to
560 prevent password sniffing. Default is true.
647 prevent password sniffing. Default is true.
561 staticurl;;
648 staticurl;;
562 Base URL to use for static files. If unset, static files (e.g.
649 Base URL to use for static files. If unset, static files (e.g.
563 the hgicon.png favicon) will be served by the CGI script itself.
650 the hgicon.png favicon) will be served by the CGI script itself.
564 Use this setting to serve them directly with the HTTP server.
651 Use this setting to serve them directly with the HTTP server.
565 Example: "http://hgserver/static/"
652 Example: "http://hgserver/static/"
566 stripes;;
653 stripes;;
567 How many lines a "zebra stripe" should span in multiline output.
654 How many lines a "zebra stripe" should span in multiline output.
568 Default is 1; set to 0 to disable.
655 Default is 1; set to 0 to disable.
569 style;;
656 style;;
570 Which template map style to use.
657 Which template map style to use.
571 templates;;
658 templates;;
572 Where to find the HTML templates. Default is install path.
659 Where to find the HTML templates. Default is install path.
573
660
574
661
575 AUTHOR
662 AUTHOR
576 ------
663 ------
577 Bryan O'Sullivan <bos@serpentine.com>.
664 Bryan O'Sullivan <bos@serpentine.com>.
578
665
579 Mercurial was written by Matt Mackall <mpm@selenic.com>.
666 Mercurial was written by Matt Mackall <mpm@selenic.com>.
580
667
581 SEE ALSO
668 SEE ALSO
582 --------
669 --------
583 hg(1), hgignore(5)
670 hg(1), hgignore(5)
584
671
585 COPYING
672 COPYING
586 -------
673 -------
587 This manual page is copyright 2005 Bryan O'Sullivan.
674 This manual page is copyright 2005 Bryan O'Sullivan.
588 Mercurial is copyright 2005-2007 Matt Mackall.
675 Mercurial is copyright 2005-2007 Matt Mackall.
589 Free use of this software is granted under the terms of the GNU General
676 Free use of this software is granted under the terms of the GNU General
590 Public License (GPL).
677 Public License (GPL).
@@ -1,108 +1,109 b''
1 # convert.py Foreign SCM converter
1 # convert.py Foreign SCM converter
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import convcmd
8 import convcmd
9 from mercurial import commands
9 from mercurial import commands
10
10
11 # Commands definition was moved elsewhere to ease demandload job.
11 # Commands definition was moved elsewhere to ease demandload job.
12
12
13 def convert(ui, src, dest=None, revmapfile=None, **opts):
13 def convert(ui, src, dest=None, revmapfile=None, **opts):
14 """Convert a foreign SCM repository to a Mercurial one.
14 """Convert a foreign SCM repository to a Mercurial one.
15
15
16 Accepted source formats:
16 Accepted source formats:
17 - Mercurial
17 - Mercurial
18 - CVS
18 - CVS
19 - Darcs
19 - Darcs
20 - git
20 - git
21 - Subversion
21 - Subversion
22 - GNU Arch
22
23
23 Accepted destination formats:
24 Accepted destination formats:
24 - Mercurial
25 - Mercurial
25 - Subversion (history on branches is not preserved)
26 - Subversion (history on branches is not preserved)
26
27
27 If no revision is given, all revisions will be converted. Otherwise,
28 If no revision is given, all revisions will be converted. Otherwise,
28 convert will only import up to the named revision (given in a format
29 convert will only import up to the named revision (given in a format
29 understood by the source).
30 understood by the source).
30
31
31 If no destination directory name is specified, it defaults to the
32 If no destination directory name is specified, it defaults to the
32 basename of the source with '-hg' appended. If the destination
33 basename of the source with '-hg' appended. If the destination
33 repository doesn't exist, it will be created.
34 repository doesn't exist, it will be created.
34
35
35 If <MAPFILE> isn't given, it will be put in a default location
36 If <MAPFILE> isn't given, it will be put in a default location
36 (<dest>/.hg/shamap by default). The <MAPFILE> is a simple text
37 (<dest>/.hg/shamap by default). The <MAPFILE> is a simple text
37 file that maps each source commit ID to the destination ID for
38 file that maps each source commit ID to the destination ID for
38 that revision, like so:
39 that revision, like so:
39 <source ID> <destination ID>
40 <source ID> <destination ID>
40
41
41 If the file doesn't exist, it's automatically created. It's updated
42 If the file doesn't exist, it's automatically created. It's updated
42 on each commit copied, so convert-repo can be interrupted and can
43 on each commit copied, so convert-repo can be interrupted and can
43 be run repeatedly to copy new commits.
44 be run repeatedly to copy new commits.
44
45
45 The [username mapping] file is a simple text file that maps each source
46 The [username mapping] file is a simple text file that maps each source
46 commit author to a destination commit author. It is handy for source SCMs
47 commit author to a destination commit author. It is handy for source SCMs
47 that use unix logins to identify authors (eg: CVS). One line per author
48 that use unix logins to identify authors (eg: CVS). One line per author
48 mapping and the line format is:
49 mapping and the line format is:
49 srcauthor=whatever string you want
50 srcauthor=whatever string you want
50
51
51 The filemap is a file that allows filtering and remapping of files
52 The filemap is a file that allows filtering and remapping of files
52 and directories. Comment lines start with '#'. Each line can
53 and directories. Comment lines start with '#'. Each line can
53 contain one of the following directives:
54 contain one of the following directives:
54
55
55 include path/to/file
56 include path/to/file
56
57
57 exclude path/to/file
58 exclude path/to/file
58
59
59 rename from/file to/file
60 rename from/file to/file
60
61
61 The 'include' directive causes a file, or all files under a
62 The 'include' directive causes a file, or all files under a
62 directory, to be included in the destination repository, and the
63 directory, to be included in the destination repository, and the
63 exclusion of all other files and dirs not explicitely included.
64 exclusion of all other files and dirs not explicitely included.
64 The 'exclude' directive causes files or directories to be omitted.
65 The 'exclude' directive causes files or directories to be omitted.
65 The 'rename' directive renames a file or directory. To rename from a
66 The 'rename' directive renames a file or directory. To rename from a
66 subdirectory into the root of the repository, use '.' as the path to
67 subdirectory into the root of the repository, use '.' as the path to
67 rename to.
68 rename to.
68
69
69 Back end options:
70 Back end options:
70
71
71 --config convert.hg.clonebranches=False (boolean)
72 --config convert.hg.clonebranches=False (boolean)
72 hg target: XXX not documented
73 hg target: XXX not documented
73 --config convert.hg.saverev=True (boolean)
74 --config convert.hg.saverev=True (boolean)
74 hg source: allow target to preserve source revision ID
75 hg source: allow target to preserve source revision ID
75 --config convert.hg.tagsbranch=default (branch name)
76 --config convert.hg.tagsbranch=default (branch name)
76 hg target: XXX not documented
77 hg target: XXX not documented
77 --config convert.hg.usebranchnames=True (boolean)
78 --config convert.hg.usebranchnames=True (boolean)
78 hg target: preserve branch names
79 hg target: preserve branch names
79
80
80 --config convert.svn.branches=branches (directory name)
81 --config convert.svn.branches=branches (directory name)
81 svn source: specify the directory containing branches
82 svn source: specify the directory containing branches
82 --config convert.svn.tags=tags (directory name)
83 --config convert.svn.tags=tags (directory name)
83 svn source: specify the directory containing tags
84 svn source: specify the directory containing tags
84 --config convert.svn.trunk=trunk (directory name)
85 --config convert.svn.trunk=trunk (directory name)
85 svn source: specify the name of the trunk branch
86 svn source: specify the name of the trunk branch
86 """
87 """
87 return convcmd.convert(ui, src, dest, revmapfile, **opts)
88 return convcmd.convert(ui, src, dest, revmapfile, **opts)
88
89
89 def debugsvnlog(ui, **opts):
90 def debugsvnlog(ui, **opts):
90 return convcmd.debugsvnlog(ui, **opts)
91 return convcmd.debugsvnlog(ui, **opts)
91
92
92 commands.norepo += " convert debugsvnlog"
93 commands.norepo += " convert debugsvnlog"
93
94
94 cmdtable = {
95 cmdtable = {
95 "convert":
96 "convert":
96 (convert,
97 (convert,
97 [('A', 'authors', '', 'username mapping filename'),
98 [('A', 'authors', '', 'username mapping filename'),
98 ('d', 'dest-type', '', 'destination repository type'),
99 ('d', 'dest-type', '', 'destination repository type'),
99 ('', 'filemap', '', 'remap file names using contents of file'),
100 ('', 'filemap', '', 'remap file names using contents of file'),
100 ('r', 'rev', '', 'import up to target revision REV'),
101 ('r', 'rev', '', 'import up to target revision REV'),
101 ('s', 'source-type', '', 'source repository type'),
102 ('s', 'source-type', '', 'source repository type'),
102 ('', 'datesort', None, 'try to sort changesets by date')],
103 ('', 'datesort', None, 'try to sort changesets by date')],
103 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
104 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
104 "debugsvnlog":
105 "debugsvnlog":
105 (debugsvnlog,
106 (debugsvnlog,
106 [],
107 [],
107 'hg debugsvnlog'),
108 'hg debugsvnlog'),
108 }
109 }
@@ -1,341 +1,354 b''
1 # common code for the convert extension
1 # common code for the convert extension
2 import base64, errno
2 import base64, errno
3 import os
3 import os
4 import cPickle as pickle
4 import cPickle as pickle
5 from mercurial import util
5 from mercurial import util
6 from mercurial.i18n import _
6 from mercurial.i18n import _
7
7
8 def encodeargs(args):
8 def encodeargs(args):
9 def encodearg(s):
9 def encodearg(s):
10 lines = base64.encodestring(s)
10 lines = base64.encodestring(s)
11 lines = [l.splitlines()[0] for l in lines]
11 lines = [l.splitlines()[0] for l in lines]
12 return ''.join(lines)
12 return ''.join(lines)
13
13
14 s = pickle.dumps(args)
14 s = pickle.dumps(args)
15 return encodearg(s)
15 return encodearg(s)
16
16
17 def decodeargs(s):
17 def decodeargs(s):
18 s = base64.decodestring(s)
18 s = base64.decodestring(s)
19 return pickle.loads(s)
19 return pickle.loads(s)
20
20
21 def checktool(exe, name=None):
21 def checktool(exe, name=None):
22 name = name or exe
22 name = name or exe
23 if not util.find_exe(exe):
23 if not util.find_exe(exe):
24 raise util.Abort('cannot find required "%s" tool' % name)
24 raise util.Abort('cannot find required "%s" tool' % name)
25
25
26 class NoRepo(Exception): pass
26 class NoRepo(Exception): pass
27
27
28 SKIPREV = 'SKIP'
28 SKIPREV = 'SKIP'
29
29
30 class commit(object):
30 class commit(object):
31 def __init__(self, author, date, desc, parents, branch=None, rev=None,
31 def __init__(self, author, date, desc, parents, branch=None, rev=None,
32 extra={}):
32 extra={}):
33 self.author = author
33 self.author = author or 'unknown'
34 self.date = date
34 self.date = date or '0 0'
35 self.desc = desc
35 self.desc = desc
36 self.parents = parents
36 self.parents = parents
37 self.branch = branch
37 self.branch = branch
38 self.rev = rev
38 self.rev = rev
39 self.extra = extra
39 self.extra = extra
40
40
41 class converter_source(object):
41 class converter_source(object):
42 """Conversion source interface"""
42 """Conversion source interface"""
43
43
44 def __init__(self, ui, path=None, rev=None):
44 def __init__(self, ui, path=None, rev=None):
45 """Initialize conversion source (or raise NoRepo("message")
45 """Initialize conversion source (or raise NoRepo("message")
46 exception if path is not a valid repository)"""
46 exception if path is not a valid repository)"""
47 self.ui = ui
47 self.ui = ui
48 self.path = path
48 self.path = path
49 self.rev = rev
49 self.rev = rev
50
50
51 self.encoding = 'utf-8'
51 self.encoding = 'utf-8'
52
52
53 def before(self):
53 def before(self):
54 pass
54 pass
55
55
56 def after(self):
56 def after(self):
57 pass
57 pass
58
58
59 def setrevmap(self, revmap):
59 def setrevmap(self, revmap):
60 """set the map of already-converted revisions"""
60 """set the map of already-converted revisions"""
61 pass
61 pass
62
62
63 def getheads(self):
63 def getheads(self):
64 """Return a list of this repository's heads"""
64 """Return a list of this repository's heads"""
65 raise NotImplementedError()
65 raise NotImplementedError()
66
66
67 def getfile(self, name, rev):
67 def getfile(self, name, rev):
68 """Return file contents as a string"""
68 """Return file contents as a string"""
69 raise NotImplementedError()
69 raise NotImplementedError()
70
70
71 def getmode(self, name, rev):
71 def getmode(self, name, rev):
72 """Return file mode, eg. '', 'x', or 'l'"""
72 """Return file mode, eg. '', 'x', or 'l'"""
73 raise NotImplementedError()
73 raise NotImplementedError()
74
74
75 def getchanges(self, version):
75 def getchanges(self, version):
76 """Returns a tuple of (files, copies)
76 """Returns a tuple of (files, copies)
77 Files is a sorted list of (filename, id) tuples for all files changed
77 Files is a sorted list of (filename, id) tuples for all files changed
78 in version, where id is the source revision id of the file.
78 in version, where id is the source revision id of the file.
79
79
80 copies is a dictionary of dest: source
80 copies is a dictionary of dest: source
81 """
81 """
82 raise NotImplementedError()
82 raise NotImplementedError()
83
83
84 def getcommit(self, version):
84 def getcommit(self, version):
85 """Return the commit object for version"""
85 """Return the commit object for version"""
86 raise NotImplementedError()
86 raise NotImplementedError()
87
87
88 def gettags(self):
88 def gettags(self):
89 """Return the tags as a dictionary of name: revision"""
89 """Return the tags as a dictionary of name: revision"""
90 raise NotImplementedError()
90 raise NotImplementedError()
91
91
92 def recode(self, s, encoding=None):
92 def recode(self, s, encoding=None):
93 if not encoding:
93 if not encoding:
94 encoding = self.encoding or 'utf-8'
94 encoding = self.encoding or 'utf-8'
95
95
96 if isinstance(s, unicode):
96 if isinstance(s, unicode):
97 return s.encode("utf-8")
97 return s.encode("utf-8")
98 try:
98 try:
99 return s.decode(encoding).encode("utf-8")
99 return s.decode(encoding).encode("utf-8")
100 except:
100 except:
101 try:
101 try:
102 return s.decode("latin-1").encode("utf-8")
102 return s.decode("latin-1").encode("utf-8")
103 except:
103 except:
104 return s.decode(encoding, "replace").encode("utf-8")
104 return s.decode(encoding, "replace").encode("utf-8")
105
105
106 def getchangedfiles(self, rev, i):
106 def getchangedfiles(self, rev, i):
107 """Return the files changed by rev compared to parent[i].
107 """Return the files changed by rev compared to parent[i].
108
108
109 i is an index selecting one of the parents of rev. The return
109 i is an index selecting one of the parents of rev. The return
110 value should be the list of files that are different in rev and
110 value should be the list of files that are different in rev and
111 this parent.
111 this parent.
112
112
113 If rev has no parents, i is None.
113 If rev has no parents, i is None.
114
114
115 This function is only needed to support --filemap
115 This function is only needed to support --filemap
116 """
116 """
117 raise NotImplementedError()
117 raise NotImplementedError()
118
118
119 def converted(self, rev, sinkrev):
119 def converted(self, rev, sinkrev):
120 '''Notify the source that a revision has been converted.'''
120 '''Notify the source that a revision has been converted.'''
121 pass
121 pass
122
122
123
123
124 class converter_sink(object):
124 class converter_sink(object):
125 """Conversion sink (target) interface"""
125 """Conversion sink (target) interface"""
126
126
127 def __init__(self, ui, path):
127 def __init__(self, ui, path):
128 """Initialize conversion sink (or raise NoRepo("message")
128 """Initialize conversion sink (or raise NoRepo("message")
129 exception if path is not a valid repository)
129 exception if path is not a valid repository)
130
130
131 created is a list of paths to remove if a fatal error occurs
131 created is a list of paths to remove if a fatal error occurs
132 later"""
132 later"""
133 self.ui = ui
133 self.ui = ui
134 self.path = path
134 self.path = path
135 self.created = []
135 self.created = []
136
136
137 def getheads(self):
137 def getheads(self):
138 """Return a list of this repository's heads"""
138 """Return a list of this repository's heads"""
139 raise NotImplementedError()
139 raise NotImplementedError()
140
140
141 def revmapfile(self):
141 def revmapfile(self):
142 """Path to a file that will contain lines
142 """Path to a file that will contain lines
143 source_rev_id sink_rev_id
143 source_rev_id sink_rev_id
144 mapping equivalent revision identifiers for each system."""
144 mapping equivalent revision identifiers for each system."""
145 raise NotImplementedError()
145 raise NotImplementedError()
146
146
147 def authorfile(self):
147 def authorfile(self):
148 """Path to a file that will contain lines
148 """Path to a file that will contain lines
149 srcauthor=dstauthor
149 srcauthor=dstauthor
150 mapping equivalent authors identifiers for each system."""
150 mapping equivalent authors identifiers for each system."""
151 return None
151 return None
152
152
153 def putfile(self, f, e, data):
153 def putfile(self, f, e, data):
154 """Put file for next putcommit().
154 """Put file for next putcommit().
155 f: path to file
155 f: path to file
156 e: '', 'x', or 'l' (regular file, executable, or symlink)
156 e: '', 'x', or 'l' (regular file, executable, or symlink)
157 data: file contents"""
157 data: file contents"""
158 raise NotImplementedError()
158 raise NotImplementedError()
159
159
160 def delfile(self, f):
160 def delfile(self, f):
161 """Delete file for next putcommit().
161 """Delete file for next putcommit().
162 f: path to file"""
162 f: path to file"""
163 raise NotImplementedError()
163 raise NotImplementedError()
164
164
165 def putcommit(self, files, parents, commit):
165 def putcommit(self, files, parents, commit):
166 """Create a revision with all changed files listed in 'files'
166 """Create a revision with all changed files listed in 'files'
167 and having listed parents. 'commit' is a commit object containing
167 and having listed parents. 'commit' is a commit object containing
168 at a minimum the author, date, and message for this changeset.
168 at a minimum the author, date, and message for this changeset.
169 Called after putfile() and delfile() calls. Note that the sink
169 Called after putfile() and delfile() calls. Note that the sink
170 repository is not told to update itself to a particular revision
170 repository is not told to update itself to a particular revision
171 (or even what that revision would be) before it receives the
171 (or even what that revision would be) before it receives the
172 file data."""
172 file data."""
173 raise NotImplementedError()
173 raise NotImplementedError()
174
174
175 def puttags(self, tags):
175 def puttags(self, tags):
176 """Put tags into sink.
176 """Put tags into sink.
177 tags: {tagname: sink_rev_id, ...}"""
177 tags: {tagname: sink_rev_id, ...}"""
178 raise NotImplementedError()
178 raise NotImplementedError()
179
179
180 def setbranch(self, branch, pbranches):
180 def setbranch(self, branch, pbranches):
181 """Set the current branch name. Called before the first putfile
181 """Set the current branch name. Called before the first putfile
182 on the branch.
182 on the branch.
183 branch: branch name for subsequent commits
183 branch: branch name for subsequent commits
184 pbranches: (converted parent revision, parent branch) tuples"""
184 pbranches: (converted parent revision, parent branch) tuples"""
185 pass
185 pass
186
186
187 def setfilemapmode(self, active):
187 def setfilemapmode(self, active):
188 """Tell the destination that we're using a filemap
188 """Tell the destination that we're using a filemap
189
189
190 Some converter_sources (svn in particular) can claim that a file
190 Some converter_sources (svn in particular) can claim that a file
191 was changed in a revision, even if there was no change. This method
191 was changed in a revision, even if there was no change. This method
192 tells the destination that we're using a filemap and that it should
192 tells the destination that we're using a filemap and that it should
193 filter empty revisions.
193 filter empty revisions.
194 """
194 """
195 pass
195 pass
196
196
197 def before(self):
197 def before(self):
198 pass
198 pass
199
199
200 def after(self):
200 def after(self):
201 pass
201 pass
202
202
203
203
204 class commandline(object):
204 class commandline(object):
205 def __init__(self, ui, command):
205 def __init__(self, ui, command):
206 self.ui = ui
206 self.ui = ui
207 self.command = command
207 self.command = command
208
208
209 def prerun(self):
209 def prerun(self):
210 pass
210 pass
211
211
212 def postrun(self):
212 def postrun(self):
213 pass
213 pass
214
214
215 def _cmdline(self, cmd, *args, **kwargs):
215 def _cmdline(self, cmd, *args, **kwargs):
216 cmdline = [self.command, cmd] + list(args)
216 cmdline = [self.command, cmd] + list(args)
217 for k, v in kwargs.iteritems():
217 for k, v in kwargs.iteritems():
218 if len(k) == 1:
218 if len(k) == 1:
219 cmdline.append('-' + k)
219 cmdline.append('-' + k)
220 else:
220 else:
221 cmdline.append('--' + k.replace('_', '-'))
221 cmdline.append('--' + k.replace('_', '-'))
222 try:
222 try:
223 if len(k) == 1:
223 if len(k) == 1:
224 cmdline.append('' + v)
224 cmdline.append('' + v)
225 else:
225 else:
226 cmdline[-1] += '=' + v
226 cmdline[-1] += '=' + v
227 except TypeError:
227 except TypeError:
228 pass
228 pass
229 cmdline = [util.shellquote(arg) for arg in cmdline]
229 cmdline = [util.shellquote(arg) for arg in cmdline]
230 cmdline += ['<', util.nulldev]
230 cmdline += ['2>', util.nulldev, '<', util.nulldev]
231 cmdline = ' '.join(cmdline)
231 cmdline = ' '.join(cmdline)
232 self.ui.debug(cmdline, '\n')
232 self.ui.debug(cmdline, '\n')
233 return cmdline
233 return cmdline
234
234
235 def _run(self, cmd, *args, **kwargs):
235 def _run(self, cmd, *args, **kwargs):
236 cmdline = self._cmdline(cmd, *args, **kwargs)
236 cmdline = self._cmdline(cmd, *args, **kwargs)
237 self.prerun()
237 self.prerun()
238 try:
238 try:
239 return util.popen(cmdline)
239 return util.popen(cmdline)
240 finally:
240 finally:
241 self.postrun()
241 self.postrun()
242
242
243 def run(self, cmd, *args, **kwargs):
243 def run(self, cmd, *args, **kwargs):
244 fp = self._run(cmd, *args, **kwargs)
244 fp = self._run(cmd, *args, **kwargs)
245 output = fp.read()
245 output = fp.read()
246 self.ui.debug(output)
246 self.ui.debug(output)
247 return output, fp.close()
247 return output, fp.close()
248
248
249 def runlines(self, cmd, *args, **kwargs):
250 fp = self._run(cmd, *args, **kwargs)
251 output = fp.readlines()
252 self.ui.debug(''.join(output))
253 return output, fp.close()
254
249 def checkexit(self, status, output=''):
255 def checkexit(self, status, output=''):
250 if status:
256 if status:
251 if output:
257 if output:
252 self.ui.warn(_('%s error:\n') % self.command)
258 self.ui.warn(_('%s error:\n') % self.command)
253 self.ui.warn(output)
259 self.ui.warn(output)
254 msg = util.explain_exit(status)[0]
260 msg = util.explain_exit(status)[0]
255 raise util.Abort(_('%s %s') % (self.command, msg))
261 raise util.Abort(_('%s %s') % (self.command, msg))
256
262
257 def run0(self, cmd, *args, **kwargs):
263 def run0(self, cmd, *args, **kwargs):
258 output, status = self.run(cmd, *args, **kwargs)
264 output, status = self.run(cmd, *args, **kwargs)
259 self.checkexit(status, output)
265 self.checkexit(status, output)
260 return output
266 return output
261
267
268 def runlines0(self, cmd, *args, **kwargs):
269 output, status = self.runlines(cmd, *args, **kwargs)
270 self.checkexit(status, ''.join(output))
271 return output
272
262 def getargmax(self):
273 def getargmax(self):
263 if '_argmax' in self.__dict__:
274 if '_argmax' in self.__dict__:
264 return self._argmax
275 return self._argmax
265
276
266 # POSIX requires at least 4096 bytes for ARG_MAX
277 # POSIX requires at least 4096 bytes for ARG_MAX
267 self._argmax = 4096
278 self._argmax = 4096
268 try:
279 try:
269 self._argmax = os.sysconf("SC_ARG_MAX")
280 self._argmax = os.sysconf("SC_ARG_MAX")
270 except:
281 except:
271 pass
282 pass
272
283
273 # Windows shells impose their own limits on command line length,
284 # Windows shells impose their own limits on command line length,
274 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
285 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
275 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
286 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
276 # details about cmd.exe limitations.
287 # details about cmd.exe limitations.
277
288
278 # Since ARG_MAX is for command line _and_ environment, lower our limit
289 # Since ARG_MAX is for command line _and_ environment, lower our limit
279 # (and make happy Windows shells while doing this).
290 # (and make happy Windows shells while doing this).
280
291
281 self._argmax = self._argmax/2 - 1
292 self._argmax = self._argmax/2 - 1
282 return self._argmax
293 return self._argmax
283
294
284 def limit_arglist(self, arglist, cmd, *args, **kwargs):
295 def limit_arglist(self, arglist, cmd, *args, **kwargs):
285 limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
296 limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
286 bytes = 0
297 bytes = 0
287 fl = []
298 fl = []
288 for fn in arglist:
299 for fn in arglist:
289 b = len(fn) + 3
300 b = len(fn) + 3
290 if bytes + b < limit or len(fl) == 0:
301 if bytes + b < limit or len(fl) == 0:
291 fl.append(fn)
302 fl.append(fn)
292 bytes += b
303 bytes += b
293 else:
304 else:
294 yield fl
305 yield fl
295 fl = [fn]
306 fl = [fn]
296 bytes = b
307 bytes = b
297 if fl:
308 if fl:
298 yield fl
309 yield fl
299
310
300 def xargs(self, arglist, cmd, *args, **kwargs):
311 def xargs(self, arglist, cmd, *args, **kwargs):
301 for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
312 for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
302 self.run0(cmd, *(list(args) + l), **kwargs)
313 self.run0(cmd, *(list(args) + l), **kwargs)
303
314
304 class mapfile(dict):
315 class mapfile(dict):
305 def __init__(self, ui, path):
316 def __init__(self, ui, path):
306 super(mapfile, self).__init__()
317 super(mapfile, self).__init__()
307 self.ui = ui
318 self.ui = ui
308 self.path = path
319 self.path = path
309 self.fp = None
320 self.fp = None
310 self.order = []
321 self.order = []
311 self._read()
322 self._read()
312
323
313 def _read(self):
324 def _read(self):
325 if self.path is None:
326 return
314 try:
327 try:
315 fp = open(self.path, 'r')
328 fp = open(self.path, 'r')
316 except IOError, err:
329 except IOError, err:
317 if err.errno != errno.ENOENT:
330 if err.errno != errno.ENOENT:
318 raise
331 raise
319 return
332 return
320 for line in fp:
333 for line in fp:
321 key, value = line[:-1].split(' ', 1)
334 key, value = line[:-1].split(' ', 1)
322 if key not in self:
335 if key not in self:
323 self.order.append(key)
336 self.order.append(key)
324 super(mapfile, self).__setitem__(key, value)
337 super(mapfile, self).__setitem__(key, value)
325 fp.close()
338 fp.close()
326
339
327 def __setitem__(self, key, value):
340 def __setitem__(self, key, value):
328 if self.fp is None:
341 if self.fp is None:
329 try:
342 try:
330 self.fp = open(self.path, 'a')
343 self.fp = open(self.path, 'a')
331 except IOError, err:
344 except IOError, err:
332 raise util.Abort(_('could not open map file %r: %s') %
345 raise util.Abort(_('could not open map file %r: %s') %
333 (self.path, err.strerror))
346 (self.path, err.strerror))
334 self.fp.write('%s %s\n' % (key, value))
347 self.fp.write('%s %s\n' % (key, value))
335 self.fp.flush()
348 self.fp.flush()
336 super(mapfile, self).__setitem__(key, value)
349 super(mapfile, self).__setitem__(key, value)
337
350
338 def close(self):
351 def close(self):
339 if self.fp:
352 if self.fp:
340 self.fp.close()
353 self.fp.close()
341 self.fp = None
354 self.fp = None
@@ -1,321 +1,348 b''
1 # convcmd - convert extension commands definition
1 # convcmd - convert extension commands definition
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from common import NoRepo, SKIPREV, converter_source, converter_sink, mapfile
8 from common import NoRepo, SKIPREV, converter_source, converter_sink, mapfile
9 from cvs import convert_cvs
9 from cvs import convert_cvs
10 from darcs import darcs_source
10 from darcs import darcs_source
11 from git import convert_git
11 from git import convert_git
12 from hg import mercurial_source, mercurial_sink
12 from hg import mercurial_source, mercurial_sink
13 from subversion import debugsvnlog, svn_source, svn_sink
13 from subversion import debugsvnlog, svn_source, svn_sink
14 from gnuarch import gnuarch_source
14 import filemap
15 import filemap
15
16
16 import os, shutil
17 import os, shutil
17 from mercurial import hg, util
18 from mercurial import hg, util
18 from mercurial.i18n import _
19 from mercurial.i18n import _
19
20
21 orig_encoding = 'ascii'
22
23 def recode(s):
24 if isinstance(s, unicode):
25 return s.encode(orig_encoding, 'replace')
26 else:
27 return s.decode('utf-8').encode(orig_encoding, 'replace')
28
20 source_converters = [
29 source_converters = [
21 ('cvs', convert_cvs),
30 ('cvs', convert_cvs),
22 ('git', convert_git),
31 ('git', convert_git),
23 ('svn', svn_source),
32 ('svn', svn_source),
24 ('hg', mercurial_source),
33 ('hg', mercurial_source),
25 ('darcs', darcs_source),
34 ('darcs', darcs_source),
35 ('gnuarch', gnuarch_source),
26 ]
36 ]
27
37
28 sink_converters = [
38 sink_converters = [
29 ('hg', mercurial_sink),
39 ('hg', mercurial_sink),
30 ('svn', svn_sink),
40 ('svn', svn_sink),
31 ]
41 ]
32
42
33 def convertsource(ui, path, type, rev):
43 def convertsource(ui, path, type, rev):
34 exceptions = []
44 exceptions = []
35 for name, source in source_converters:
45 for name, source in source_converters:
36 try:
46 try:
37 if not type or name == type:
47 if not type or name == type:
38 return source(ui, path, rev)
48 return source(ui, path, rev)
39 except NoRepo, inst:
49 except NoRepo, inst:
40 exceptions.append(inst)
50 exceptions.append(inst)
41 if not ui.quiet:
51 if not ui.quiet:
42 for inst in exceptions:
52 for inst in exceptions:
43 ui.write(_("%s\n") % inst)
53 ui.write(_("%s\n") % inst)
44 raise util.Abort('%s: unknown repository type' % path)
54 raise util.Abort('%s: unknown repository type' % path)
45
55
46 def convertsink(ui, path, type):
56 def convertsink(ui, path, type):
47 for name, sink in sink_converters:
57 for name, sink in sink_converters:
48 try:
58 try:
49 if not type or name == type:
59 if not type or name == type:
50 return sink(ui, path)
60 return sink(ui, path)
51 except NoRepo, inst:
61 except NoRepo, inst:
52 ui.note(_("convert: %s\n") % inst)
62 ui.note(_("convert: %s\n") % inst)
53 raise util.Abort('%s: unknown repository type' % path)
63 raise util.Abort('%s: unknown repository type' % path)
54
64
55 class converter(object):
65 class converter(object):
56 def __init__(self, ui, source, dest, revmapfile, opts):
66 def __init__(self, ui, source, dest, revmapfile, opts):
57
67
58 self.source = source
68 self.source = source
59 self.dest = dest
69 self.dest = dest
60 self.ui = ui
70 self.ui = ui
61 self.opts = opts
71 self.opts = opts
62 self.commitcache = {}
72 self.commitcache = {}
63 self.authors = {}
73 self.authors = {}
64 self.authorfile = None
74 self.authorfile = None
65
75
66 self.map = mapfile(ui, revmapfile)
76 self.map = mapfile(ui, revmapfile)
67
77
68 # Read first the dst author map if any
78 # Read first the dst author map if any
69 authorfile = self.dest.authorfile()
79 authorfile = self.dest.authorfile()
70 if authorfile and os.path.exists(authorfile):
80 if authorfile and os.path.exists(authorfile):
71 self.readauthormap(authorfile)
81 self.readauthormap(authorfile)
72 # Extend/Override with new author map if necessary
82 # Extend/Override with new author map if necessary
73 if opts.get('authors'):
83 if opts.get('authors'):
74 self.readauthormap(opts.get('authors'))
84 self.readauthormap(opts.get('authors'))
75 self.authorfile = self.dest.authorfile()
85 self.authorfile = self.dest.authorfile()
76
86
87 self.splicemap = mapfile(ui, ui.config('convert', 'splicemap'))
88
77 def walktree(self, heads):
89 def walktree(self, heads):
78 '''Return a mapping that identifies the uncommitted parents of every
90 '''Return a mapping that identifies the uncommitted parents of every
79 uncommitted changeset.'''
91 uncommitted changeset.'''
80 visit = heads
92 visit = heads
81 known = {}
93 known = {}
82 parents = {}
94 parents = {}
83 while visit:
95 while visit:
84 n = visit.pop(0)
96 n = visit.pop(0)
85 if n in known or n in self.map: continue
97 if n in known or n in self.map: continue
86 known[n] = 1
98 known[n] = 1
87 commit = self.cachecommit(n)
99 commit = self.cachecommit(n)
88 parents[n] = []
100 parents[n] = []
89 for p in commit.parents:
101 for p in commit.parents:
90 parents[n].append(p)
102 parents[n].append(p)
91 visit.append(p)
103 visit.append(p)
92
104
93 return parents
105 return parents
94
106
95 def toposort(self, parents):
107 def toposort(self, parents):
96 '''Return an ordering such that every uncommitted changeset is
108 '''Return an ordering such that every uncommitted changeset is
97 preceeded by all its uncommitted ancestors.'''
109 preceeded by all its uncommitted ancestors.'''
98 visit = parents.keys()
110 visit = parents.keys()
99 seen = {}
111 seen = {}
100 children = {}
112 children = {}
113 actives = []
101
114
102 while visit:
115 while visit:
103 n = visit.pop(0)
116 n = visit.pop(0)
104 if n in seen: continue
117 if n in seen: continue
105 seen[n] = 1
118 seen[n] = 1
106 # Ensure that nodes without parents are present in the 'children'
119 # Ensure that nodes without parents are present in the 'children'
107 # mapping.
120 # mapping.
108 children.setdefault(n, [])
121 children.setdefault(n, [])
122 hasparent = False
109 for p in parents[n]:
123 for p in parents[n]:
110 if not p in self.map:
124 if not p in self.map:
111 visit.append(p)
125 visit.append(p)
126 hasparent = True
112 children.setdefault(p, []).append(n)
127 children.setdefault(p, []).append(n)
128 if not hasparent:
129 actives.append(n)
130
131 del seen
132 del visit
133
134 if self.opts.get('datesort'):
135 dates = {}
136 def getdate(n):
137 if n not in dates:
138 dates[n] = util.parsedate(self.commitcache[n].date)
139 return dates[n]
140
141 def picknext(nodes):
142 return min([(getdate(n), n) for n in nodes])[1]
143 else:
144 prev = [None]
145 def picknext(nodes):
146 # Return the first eligible child of the previously converted
147 # revision, or any of them.
148 next = nodes[0]
149 for n in nodes:
150 if prev[0] in parents[n]:
151 next = n
152 break
153 prev[0] = next
154 return next
113
155
114 s = []
156 s = []
115 removed = {}
157 pendings = {}
116 visit = children.keys()
158 while actives:
117 while visit:
159 n = picknext(actives)
118 n = visit.pop(0)
160 actives.remove(n)
119 if n in removed: continue
161 s.append(n)
120 dep = 0
121 if n in parents:
122 for p in parents[n]:
123 if p in self.map: continue
124 if p not in removed:
125 # we're still dependent
126 visit.append(n)
127 dep = 1
128 break
129
162
130 if not dep:
163 # Update dependents list
131 # all n's parents are in the list
164 for c in children.get(n, []):
132 removed[n] = 1
165 if c not in pendings:
133 if n not in self.map:
166 pendings[c] = [p for p in parents[c] if p not in self.map]
134 s.append(n)
167 try:
135 if n in children:
168 pendings[c].remove(n)
136 for c in children[n]:
169 except ValueError:
137 visit.insert(0, c)
170 raise util.Abort(_('cycle detected between %s and %s')
171 % (recode(c), recode(n)))
172 if not pendings[c]:
173 # Parents are converted, node is eligible
174 actives.insert(0, c)
175 pendings[c] = None
138
176
139 if self.opts.get('datesort'):
177 if len(s) != len(parents):
140 depth = {}
178 raise util.Abort(_("not all revisions were sorted"))
141 for n in s:
142 depth[n] = 0
143 pl = [p for p in self.commitcache[n].parents
144 if p not in self.map]
145 if pl:
146 depth[n] = max([depth[p] for p in pl]) + 1
147
148 s = [(depth[n], util.parsedate(self.commitcache[n].date), n)
149 for n in s]
150 s.sort()
151 s = [e[2] for e in s]
152
179
153 return s
180 return s
154
181
155 def writeauthormap(self):
182 def writeauthormap(self):
156 authorfile = self.authorfile
183 authorfile = self.authorfile
157 if authorfile:
184 if authorfile:
158 self.ui.status('Writing author map file %s\n' % authorfile)
185 self.ui.status('Writing author map file %s\n' % authorfile)
159 ofile = open(authorfile, 'w+')
186 ofile = open(authorfile, 'w+')
160 for author in self.authors:
187 for author in self.authors:
161 ofile.write("%s=%s\n" % (author, self.authors[author]))
188 ofile.write("%s=%s\n" % (author, self.authors[author]))
162 ofile.close()
189 ofile.close()
163
190
164 def readauthormap(self, authorfile):
191 def readauthormap(self, authorfile):
165 afile = open(authorfile, 'r')
192 afile = open(authorfile, 'r')
166 for line in afile:
193 for line in afile:
167 try:
194 try:
168 srcauthor = line.split('=')[0].strip()
195 srcauthor = line.split('=')[0].strip()
169 dstauthor = line.split('=')[1].strip()
196 dstauthor = line.split('=')[1].strip()
170 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
197 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
171 self.ui.status(
198 self.ui.status(
172 'Overriding mapping for author %s, was %s, will be %s\n'
199 'Overriding mapping for author %s, was %s, will be %s\n'
173 % (srcauthor, self.authors[srcauthor], dstauthor))
200 % (srcauthor, self.authors[srcauthor], dstauthor))
174 else:
201 else:
175 self.ui.debug('Mapping author %s to %s\n'
202 self.ui.debug('Mapping author %s to %s\n'
176 % (srcauthor, dstauthor))
203 % (srcauthor, dstauthor))
177 self.authors[srcauthor] = dstauthor
204 self.authors[srcauthor] = dstauthor
178 except IndexError:
205 except IndexError:
179 self.ui.warn(
206 self.ui.warn(
180 'Ignoring bad line in author file map %s: %s\n'
207 'Ignoring bad line in author file map %s: %s\n'
181 % (authorfile, line))
208 % (authorfile, line))
182 afile.close()
209 afile.close()
183
210
184 def cachecommit(self, rev):
211 def cachecommit(self, rev):
185 commit = self.source.getcommit(rev)
212 commit = self.source.getcommit(rev)
186 commit.author = self.authors.get(commit.author, commit.author)
213 commit.author = self.authors.get(commit.author, commit.author)
187 self.commitcache[rev] = commit
214 self.commitcache[rev] = commit
188 return commit
215 return commit
189
216
190 def copy(self, rev):
217 def copy(self, rev):
191 commit = self.commitcache[rev]
218 commit = self.commitcache[rev]
192 do_copies = hasattr(self.dest, 'copyfile')
219 do_copies = hasattr(self.dest, 'copyfile')
193 filenames = []
220 filenames = []
194
221
195 changes = self.source.getchanges(rev)
222 changes = self.source.getchanges(rev)
196 if isinstance(changes, basestring):
223 if isinstance(changes, basestring):
197 if changes == SKIPREV:
224 if changes == SKIPREV:
198 dest = SKIPREV
225 dest = SKIPREV
199 else:
226 else:
200 dest = self.map[changes]
227 dest = self.map[changes]
201 self.map[rev] = dest
228 self.map[rev] = dest
202 return
229 return
203 files, copies = changes
230 files, copies = changes
204 pbranches = []
231 pbranches = []
205 if commit.parents:
232 if commit.parents:
206 for prev in commit.parents:
233 for prev in commit.parents:
207 if prev not in self.commitcache:
234 if prev not in self.commitcache:
208 self.cachecommit(prev)
235 self.cachecommit(prev)
209 pbranches.append((self.map[prev],
236 pbranches.append((self.map[prev],
210 self.commitcache[prev].branch))
237 self.commitcache[prev].branch))
211 self.dest.setbranch(commit.branch, pbranches)
238 self.dest.setbranch(commit.branch, pbranches)
212 for f, v in files:
239 for f, v in files:
213 filenames.append(f)
240 filenames.append(f)
214 try:
241 try:
215 data = self.source.getfile(f, v)
242 data = self.source.getfile(f, v)
216 except IOError, inst:
243 except IOError, inst:
217 self.dest.delfile(f)
244 self.dest.delfile(f)
218 else:
245 else:
219 e = self.source.getmode(f, v)
246 e = self.source.getmode(f, v)
220 self.dest.putfile(f, e, data)
247 self.dest.putfile(f, e, data)
221 if do_copies:
248 if do_copies:
222 if f in copies:
249 if f in copies:
223 copyf = copies[f]
250 copyf = copies[f]
224 # Merely marks that a copy happened.
251 # Merely marks that a copy happened.
225 self.dest.copyfile(copyf, f)
252 self.dest.copyfile(copyf, f)
226
253
227 parents = [b[0] for b in pbranches]
254 try:
255 parents = [self.splicemap[rev]]
256 self.ui.debug('spliced in %s as parents of %s\n' %
257 (parents, rev))
258 except KeyError:
259 parents = [b[0] for b in pbranches]
228 newnode = self.dest.putcommit(filenames, parents, commit)
260 newnode = self.dest.putcommit(filenames, parents, commit)
229 self.source.converted(rev, newnode)
261 self.source.converted(rev, newnode)
230 self.map[rev] = newnode
262 self.map[rev] = newnode
231
263
232 def convert(self):
264 def convert(self):
233
265
234 def recode(s):
235 return s.decode('utf-8').encode(orig_encoding, 'replace')
236
237 try:
266 try:
238 self.source.before()
267 self.source.before()
239 self.dest.before()
268 self.dest.before()
240 self.source.setrevmap(self.map)
269 self.source.setrevmap(self.map)
241 self.ui.status("scanning source...\n")
270 self.ui.status("scanning source...\n")
242 heads = self.source.getheads()
271 heads = self.source.getheads()
243 parents = self.walktree(heads)
272 parents = self.walktree(heads)
244 self.ui.status("sorting...\n")
273 self.ui.status("sorting...\n")
245 t = self.toposort(parents)
274 t = self.toposort(parents)
246 num = len(t)
275 num = len(t)
247 c = None
276 c = None
248
277
249 self.ui.status("converting...\n")
278 self.ui.status("converting...\n")
250 for c in t:
279 for c in t:
251 num -= 1
280 num -= 1
252 desc = self.commitcache[c].desc
281 desc = self.commitcache[c].desc
253 if "\n" in desc:
282 if "\n" in desc:
254 desc = desc.splitlines()[0]
283 desc = desc.splitlines()[0]
255 # convert log message to local encoding without using
284 # convert log message to local encoding without using
256 # tolocal() because util._encoding conver() use it as
285 # tolocal() because util._encoding conver() use it as
257 # 'utf-8'
286 # 'utf-8'
258 self.ui.status("%d %s\n" % (num, recode(desc)))
287 self.ui.status("%d %s\n" % (num, recode(desc)))
259 self.ui.note(_("source: %s\n" % recode(c)))
288 self.ui.note(_("source: %s\n" % recode(c)))
260 self.copy(c)
289 self.copy(c)
261
290
262 tags = self.source.gettags()
291 tags = self.source.gettags()
263 ctags = {}
292 ctags = {}
264 for k in tags:
293 for k in tags:
265 v = tags[k]
294 v = tags[k]
266 if self.map.get(v, SKIPREV) != SKIPREV:
295 if self.map.get(v, SKIPREV) != SKIPREV:
267 ctags[k] = self.map[v]
296 ctags[k] = self.map[v]
268
297
269 if c and ctags:
298 if c and ctags:
270 nrev = self.dest.puttags(ctags)
299 nrev = self.dest.puttags(ctags)
271 # write another hash correspondence to override the previous
300 # write another hash correspondence to override the previous
272 # one so we don't end up with extra tag heads
301 # one so we don't end up with extra tag heads
273 if nrev:
302 if nrev:
274 self.map[c] = nrev
303 self.map[c] = nrev
275
304
276 self.writeauthormap()
305 self.writeauthormap()
277 finally:
306 finally:
278 self.cleanup()
307 self.cleanup()
279
308
280 def cleanup(self):
309 def cleanup(self):
281 try:
310 try:
282 self.dest.after()
311 self.dest.after()
283 finally:
312 finally:
284 self.source.after()
313 self.source.after()
285 self.map.close()
314 self.map.close()
286
315
287 orig_encoding = 'ascii'
288
289 def convert(ui, src, dest=None, revmapfile=None, **opts):
316 def convert(ui, src, dest=None, revmapfile=None, **opts):
290 global orig_encoding
317 global orig_encoding
291 orig_encoding = util._encoding
318 orig_encoding = util._encoding
292 util._encoding = 'UTF-8'
319 util._encoding = 'UTF-8'
293
320
294 if not dest:
321 if not dest:
295 dest = hg.defaultdest(src) + "-hg"
322 dest = hg.defaultdest(src) + "-hg"
296 ui.status("assuming destination %s\n" % dest)
323 ui.status("assuming destination %s\n" % dest)
297
324
298 destc = convertsink(ui, dest, opts.get('dest_type'))
325 destc = convertsink(ui, dest, opts.get('dest_type'))
299
326
300 try:
327 try:
301 srcc = convertsource(ui, src, opts.get('source_type'),
328 srcc = convertsource(ui, src, opts.get('source_type'),
302 opts.get('rev'))
329 opts.get('rev'))
303 except Exception:
330 except Exception:
304 for path in destc.created:
331 for path in destc.created:
305 shutil.rmtree(path, True)
332 shutil.rmtree(path, True)
306 raise
333 raise
307
334
308 fmap = opts.get('filemap')
335 fmap = opts.get('filemap')
309 if fmap:
336 if fmap:
310 srcc = filemap.filemap_source(ui, srcc, fmap)
337 srcc = filemap.filemap_source(ui, srcc, fmap)
311 destc.setfilemapmode(True)
338 destc.setfilemapmode(True)
312
339
313 if not revmapfile:
340 if not revmapfile:
314 try:
341 try:
315 revmapfile = destc.revmapfile()
342 revmapfile = destc.revmapfile()
316 except:
343 except:
317 revmapfile = os.path.join(destc, "map")
344 revmapfile = os.path.join(destc, "map")
318
345
319 c = converter(ui, srcc, destc, revmapfile, opts)
346 c = converter(ui, srcc, destc, revmapfile, opts)
320 c.convert()
347 c.convert()
321
348
@@ -1,312 +1,313 b''
1 # CVS conversion code inspired by hg-cvs-import and git-cvsimport
1 # CVS conversion code inspired by hg-cvs-import and git-cvsimport
2
2
3 import os, locale, re, socket
3 import os, locale, re, socket
4 from cStringIO import StringIO
4 from cStringIO import StringIO
5 from mercurial import util
5 from mercurial import util
6
6
7 from common import NoRepo, commit, converter_source, checktool
7 from common import NoRepo, commit, converter_source, checktool
8
8
9 class convert_cvs(converter_source):
9 class convert_cvs(converter_source):
10 def __init__(self, ui, path, rev=None):
10 def __init__(self, ui, path, rev=None):
11 super(convert_cvs, self).__init__(ui, path, rev=rev)
11 super(convert_cvs, self).__init__(ui, path, rev=rev)
12
12
13 cvs = os.path.join(path, "CVS")
13 cvs = os.path.join(path, "CVS")
14 if not os.path.exists(cvs):
14 if not os.path.exists(cvs):
15 raise NoRepo("%s does not look like a CVS checkout" % path)
15 raise NoRepo("%s does not look like a CVS checkout" % path)
16
16
17 for tool in ('cvsps', 'cvs'):
17 for tool in ('cvsps', 'cvs'):
18 checktool(tool)
18 checktool(tool)
19
19
20 self.changeset = {}
20 self.changeset = {}
21 self.files = {}
21 self.files = {}
22 self.tags = {}
22 self.tags = {}
23 self.lastbranch = {}
23 self.lastbranch = {}
24 self.parent = {}
24 self.parent = {}
25 self.socket = None
25 self.socket = None
26 self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1]
26 self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1]
27 self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1]
27 self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1]
28 self.encoding = locale.getpreferredencoding()
28 self.encoding = locale.getpreferredencoding()
29 self._parse()
29 self._parse()
30 self._connect()
30 self._connect()
31
31
32 def _parse(self):
32 def _parse(self):
33 if self.changeset:
33 if self.changeset:
34 return
34 return
35
35
36 maxrev = 0
36 maxrev = 0
37 cmd = 'cvsps -A -u --cvs-direct -q'
37 cmd = 'cvsps -A -u --cvs-direct -q'
38 if self.rev:
38 if self.rev:
39 # TODO: handle tags
39 # TODO: handle tags
40 try:
40 try:
41 # patchset number?
41 # patchset number?
42 maxrev = int(self.rev)
42 maxrev = int(self.rev)
43 except ValueError:
43 except ValueError:
44 try:
44 try:
45 # date
45 # date
46 util.parsedate(self.rev, ['%Y/%m/%d %H:%M:%S'])
46 util.parsedate(self.rev, ['%Y/%m/%d %H:%M:%S'])
47 cmd = '%s -d "1970/01/01 00:00:01" -d "%s"' % (cmd, self.rev)
47 cmd = '%s -d "1970/01/01 00:00:01" -d "%s"' % (cmd, self.rev)
48 except util.Abort:
48 except util.Abort:
49 raise util.Abort('revision %s is not a patchset number or date' % self.rev)
49 raise util.Abort('revision %s is not a patchset number or date' % self.rev)
50
50
51 d = os.getcwd()
51 d = os.getcwd()
52 try:
52 try:
53 os.chdir(self.path)
53 os.chdir(self.path)
54 id = None
54 id = None
55 state = 0
55 state = 0
56 filerevids = {}
56 filerevids = {}
57 for l in util.popen(cmd):
57 for l in util.popen(cmd):
58 if state == 0: # header
58 if state == 0: # header
59 if l.startswith("PatchSet"):
59 if l.startswith("PatchSet"):
60 id = l[9:-2]
60 id = l[9:-2]
61 if maxrev and int(id) > maxrev:
61 if maxrev and int(id) > maxrev:
62 # ignore everything
62 # ignore everything
63 state = 3
63 state = 3
64 elif l.startswith("Date"):
64 elif l.startswith("Date"):
65 date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
65 date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
66 date = util.datestr(date)
66 date = util.datestr(date)
67 elif l.startswith("Branch"):
67 elif l.startswith("Branch"):
68 branch = l[8:-1]
68 branch = l[8:-1]
69 self.parent[id] = self.lastbranch.get(branch, 'bad')
69 self.parent[id] = self.lastbranch.get(branch, 'bad')
70 self.lastbranch[branch] = id
70 self.lastbranch[branch] = id
71 elif l.startswith("Ancestor branch"):
71 elif l.startswith("Ancestor branch"):
72 ancestor = l[17:-1]
72 ancestor = l[17:-1]
73 # figure out the parent later
73 # figure out the parent later
74 self.parent[id] = None
74 self.parent[id] = self.lastbranch[ancestor]
75 elif l.startswith("Author"):
75 elif l.startswith("Author"):
76 author = self.recode(l[8:-1])
76 author = self.recode(l[8:-1])
77 elif l.startswith("Tag:") or l.startswith("Tags:"):
77 elif l.startswith("Tag:") or l.startswith("Tags:"):
78 t = l[l.index(':')+1:]
78 t = l[l.index(':')+1:]
79 t = [ut.strip() for ut in t.split(',')]
79 t = [ut.strip() for ut in t.split(',')]
80 if (len(t) > 1) or (t[0] and (t[0] != "(none)")):
80 if (len(t) > 1) or (t[0] and (t[0] != "(none)")):
81 self.tags.update(dict.fromkeys(t, id))
81 self.tags.update(dict.fromkeys(t, id))
82 elif l.startswith("Log:"):
82 elif l.startswith("Log:"):
83 # switch to gathering log
83 # switch to gathering log
84 state = 1
84 state = 1
85 log = ""
85 log = ""
86 elif state == 1: # log
86 elif state == 1: # log
87 if l == "Members: \n":
87 if l == "Members: \n":
88 # switch to gathering members
88 # switch to gathering members
89 files = {}
89 files = {}
90 oldrevs = []
90 oldrevs = []
91 log = self.recode(log[:-1])
91 log = self.recode(log[:-1])
92 state = 2
92 state = 2
93 else:
93 else:
94 # gather log
94 # gather log
95 log += l
95 log += l
96 elif state == 2: # members
96 elif state == 2: # members
97 if l == "\n": # start of next entry
97 if l == "\n": # start of next entry
98 state = 0
98 state = 0
99 p = [self.parent[id]]
99 p = [self.parent[id]]
100 if id == "1":
100 if id == "1":
101 p = []
101 p = []
102 if branch == "HEAD":
102 if branch == "HEAD":
103 branch = ""
103 branch = ""
104 if branch and p[0] == None:
104 if branch:
105 latest = None
105 latest = None
106 # the last changeset that contains a base
106 # the last changeset that contains a base
107 # file is our parent
107 # file is our parent
108 for r in oldrevs:
108 for r in oldrevs:
109 latest = max(filerevids[r], latest)
109 latest = max(filerevids.get(r, None), latest)
110 p = [latest]
110 if latest:
111 p = [latest]
111
112
112 # add current commit to set
113 # add current commit to set
113 c = commit(author=author, date=date, parents=p,
114 c = commit(author=author, date=date, parents=p,
114 desc=log, branch=branch)
115 desc=log, branch=branch)
115 self.changeset[id] = c
116 self.changeset[id] = c
116 self.files[id] = files
117 self.files[id] = files
117 else:
118 else:
118 colon = l.rfind(':')
119 colon = l.rfind(':')
119 file = l[1:colon]
120 file = l[1:colon]
120 rev = l[colon+1:-2]
121 rev = l[colon+1:-2]
121 oldrev, rev = rev.split("->")
122 oldrev, rev = rev.split("->")
122 files[file] = rev
123 files[file] = rev
123
124
124 # save some information for identifying branch points
125 # save some information for identifying branch points
125 oldrevs.append("%s:%s" % (oldrev, file))
126 oldrevs.append("%s:%s" % (oldrev, file))
126 filerevids["%s:%s" % (rev, file)] = id
127 filerevids["%s:%s" % (rev, file)] = id
127 elif state == 3:
128 elif state == 3:
128 # swallow all input
129 # swallow all input
129 continue
130 continue
130
131
131 self.heads = self.lastbranch.values()
132 self.heads = self.lastbranch.values()
132 finally:
133 finally:
133 os.chdir(d)
134 os.chdir(d)
134
135
135 def _connect(self):
136 def _connect(self):
136 root = self.cvsroot
137 root = self.cvsroot
137 conntype = None
138 conntype = None
138 user, host = None, None
139 user, host = None, None
139 cmd = ['cvs', 'server']
140 cmd = ['cvs', 'server']
140
141
141 self.ui.status("connecting to %s\n" % root)
142 self.ui.status("connecting to %s\n" % root)
142
143
143 if root.startswith(":pserver:"):
144 if root.startswith(":pserver:"):
144 root = root[9:]
145 root = root[9:]
145 m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)',
146 m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)',
146 root)
147 root)
147 if m:
148 if m:
148 conntype = "pserver"
149 conntype = "pserver"
149 user, passw, serv, port, root = m.groups()
150 user, passw, serv, port, root = m.groups()
150 if not user:
151 if not user:
151 user = "anonymous"
152 user = "anonymous"
152 if not port:
153 if not port:
153 port = 2401
154 port = 2401
154 else:
155 else:
155 port = int(port)
156 port = int(port)
156 format0 = ":pserver:%s@%s:%s" % (user, serv, root)
157 format0 = ":pserver:%s@%s:%s" % (user, serv, root)
157 format1 = ":pserver:%s@%s:%d%s" % (user, serv, port, root)
158 format1 = ":pserver:%s@%s:%d%s" % (user, serv, port, root)
158
159
159 if not passw:
160 if not passw:
160 passw = "A"
161 passw = "A"
161 pf = open(os.path.join(os.environ["HOME"], ".cvspass"))
162 pf = open(os.path.join(os.environ["HOME"], ".cvspass"))
162 for line in pf.read().splitlines():
163 for line in pf.read().splitlines():
163 part1, part2 = line.split(' ', 1)
164 part1, part2 = line.split(' ', 1)
164 if part1 == '/1':
165 if part1 == '/1':
165 # /1 :pserver:user@example.com:2401/cvsroot/foo Ah<Z
166 # /1 :pserver:user@example.com:2401/cvsroot/foo Ah<Z
166 part1, part2 = part2.split(' ', 1)
167 part1, part2 = part2.split(' ', 1)
167 format = format1
168 format = format1
168 else:
169 else:
169 # :pserver:user@example.com:/cvsroot/foo Ah<Z
170 # :pserver:user@example.com:/cvsroot/foo Ah<Z
170 format = format0
171 format = format0
171 if part1 == format:
172 if part1 == format:
172 passw = part2
173 passw = part2
173 break
174 break
174 pf.close()
175 pf.close()
175
176
176 sck = socket.socket()
177 sck = socket.socket()
177 sck.connect((serv, port))
178 sck.connect((serv, port))
178 sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw,
179 sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw,
179 "END AUTH REQUEST", ""]))
180 "END AUTH REQUEST", ""]))
180 if sck.recv(128) != "I LOVE YOU\n":
181 if sck.recv(128) != "I LOVE YOU\n":
181 raise util.Abort("CVS pserver authentication failed")
182 raise util.Abort("CVS pserver authentication failed")
182
183
183 self.writep = self.readp = sck.makefile('r+')
184 self.writep = self.readp = sck.makefile('r+')
184
185
185 if not conntype and root.startswith(":local:"):
186 if not conntype and root.startswith(":local:"):
186 conntype = "local"
187 conntype = "local"
187 root = root[7:]
188 root = root[7:]
188
189
189 if not conntype:
190 if not conntype:
190 # :ext:user@host/home/user/path/to/cvsroot
191 # :ext:user@host/home/user/path/to/cvsroot
191 if root.startswith(":ext:"):
192 if root.startswith(":ext:"):
192 root = root[5:]
193 root = root[5:]
193 m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
194 m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
194 # Do not take Windows path "c:\foo\bar" for a connection strings
195 # Do not take Windows path "c:\foo\bar" for a connection strings
195 if os.path.isdir(root) or not m:
196 if os.path.isdir(root) or not m:
196 conntype = "local"
197 conntype = "local"
197 else:
198 else:
198 conntype = "rsh"
199 conntype = "rsh"
199 user, host, root = m.group(1), m.group(2), m.group(3)
200 user, host, root = m.group(1), m.group(2), m.group(3)
200
201
201 if conntype != "pserver":
202 if conntype != "pserver":
202 if conntype == "rsh":
203 if conntype == "rsh":
203 rsh = os.environ.get("CVS_RSH") or "ssh"
204 rsh = os.environ.get("CVS_RSH") or "ssh"
204 if user:
205 if user:
205 cmd = [rsh, '-l', user, host] + cmd
206 cmd = [rsh, '-l', user, host] + cmd
206 else:
207 else:
207 cmd = [rsh, host] + cmd
208 cmd = [rsh, host] + cmd
208
209
209 # popen2 does not support argument lists under Windows
210 # popen2 does not support argument lists under Windows
210 cmd = [util.shellquote(arg) for arg in cmd]
211 cmd = [util.shellquote(arg) for arg in cmd]
211 cmd = util.quotecommand(' '.join(cmd))
212 cmd = util.quotecommand(' '.join(cmd))
212 self.writep, self.readp = os.popen2(cmd, 'b')
213 self.writep, self.readp = os.popen2(cmd, 'b')
213
214
214 self.realroot = root
215 self.realroot = root
215
216
216 self.writep.write("Root %s\n" % root)
217 self.writep.write("Root %s\n" % root)
217 self.writep.write("Valid-responses ok error Valid-requests Mode"
218 self.writep.write("Valid-responses ok error Valid-requests Mode"
218 " M Mbinary E Checked-in Created Updated"
219 " M Mbinary E Checked-in Created Updated"
219 " Merged Removed\n")
220 " Merged Removed\n")
220 self.writep.write("valid-requests\n")
221 self.writep.write("valid-requests\n")
221 self.writep.flush()
222 self.writep.flush()
222 r = self.readp.readline()
223 r = self.readp.readline()
223 if not r.startswith("Valid-requests"):
224 if not r.startswith("Valid-requests"):
224 raise util.Abort("server sucks")
225 raise util.Abort("server sucks")
225 if "UseUnchanged" in r:
226 if "UseUnchanged" in r:
226 self.writep.write("UseUnchanged\n")
227 self.writep.write("UseUnchanged\n")
227 self.writep.flush()
228 self.writep.flush()
228 r = self.readp.readline()
229 r = self.readp.readline()
229
230
230 def getheads(self):
231 def getheads(self):
231 return self.heads
232 return self.heads
232
233
233 def _getfile(self, name, rev):
234 def _getfile(self, name, rev):
234
235
235 def chunkedread(fp, count):
236 def chunkedread(fp, count):
236 # file-objects returned by socked.makefile() do not handle
237 # file-objects returned by socked.makefile() do not handle
237 # large read() requests very well.
238 # large read() requests very well.
238 chunksize = 65536
239 chunksize = 65536
239 output = StringIO()
240 output = StringIO()
240 while count > 0:
241 while count > 0:
241 data = fp.read(min(count, chunksize))
242 data = fp.read(min(count, chunksize))
242 if not data:
243 if not data:
243 raise util.Abort("%d bytes missing from remote file" % count)
244 raise util.Abort("%d bytes missing from remote file" % count)
244 count -= len(data)
245 count -= len(data)
245 output.write(data)
246 output.write(data)
246 return output.getvalue()
247 return output.getvalue()
247
248
248 if rev.endswith("(DEAD)"):
249 if rev.endswith("(DEAD)"):
249 raise IOError
250 raise IOError
250
251
251 args = ("-N -P -kk -r %s --" % rev).split()
252 args = ("-N -P -kk -r %s --" % rev).split()
252 args.append(self.cvsrepo + '/' + name)
253 args.append(self.cvsrepo + '/' + name)
253 for x in args:
254 for x in args:
254 self.writep.write("Argument %s\n" % x)
255 self.writep.write("Argument %s\n" % x)
255 self.writep.write("Directory .\n%s\nco\n" % self.realroot)
256 self.writep.write("Directory .\n%s\nco\n" % self.realroot)
256 self.writep.flush()
257 self.writep.flush()
257
258
258 data = ""
259 data = ""
259 while 1:
260 while 1:
260 line = self.readp.readline()
261 line = self.readp.readline()
261 if line.startswith("Created ") or line.startswith("Updated "):
262 if line.startswith("Created ") or line.startswith("Updated "):
262 self.readp.readline() # path
263 self.readp.readline() # path
263 self.readp.readline() # entries
264 self.readp.readline() # entries
264 mode = self.readp.readline()[:-1]
265 mode = self.readp.readline()[:-1]
265 count = int(self.readp.readline()[:-1])
266 count = int(self.readp.readline()[:-1])
266 data = chunkedread(self.readp, count)
267 data = chunkedread(self.readp, count)
267 elif line.startswith(" "):
268 elif line.startswith(" "):
268 data += line[1:]
269 data += line[1:]
269 elif line.startswith("M "):
270 elif line.startswith("M "):
270 pass
271 pass
271 elif line.startswith("Mbinary "):
272 elif line.startswith("Mbinary "):
272 count = int(self.readp.readline()[:-1])
273 count = int(self.readp.readline()[:-1])
273 data = chunkedread(self.readp, count)
274 data = chunkedread(self.readp, count)
274 else:
275 else:
275 if line == "ok\n":
276 if line == "ok\n":
276 return (data, "x" in mode and "x" or "")
277 return (data, "x" in mode and "x" or "")
277 elif line.startswith("E "):
278 elif line.startswith("E "):
278 self.ui.warn("cvs server: %s\n" % line[2:])
279 self.ui.warn("cvs server: %s\n" % line[2:])
279 elif line.startswith("Remove"):
280 elif line.startswith("Remove"):
280 l = self.readp.readline()
281 l = self.readp.readline()
281 l = self.readp.readline()
282 l = self.readp.readline()
282 if l != "ok\n":
283 if l != "ok\n":
283 raise util.Abort("unknown CVS response: %s" % l)
284 raise util.Abort("unknown CVS response: %s" % l)
284 else:
285 else:
285 raise util.Abort("unknown CVS response: %s" % line)
286 raise util.Abort("unknown CVS response: %s" % line)
286
287
287 def getfile(self, file, rev):
288 def getfile(self, file, rev):
288 data, mode = self._getfile(file, rev)
289 data, mode = self._getfile(file, rev)
289 self.modecache[(file, rev)] = mode
290 self.modecache[(file, rev)] = mode
290 return data
291 return data
291
292
292 def getmode(self, file, rev):
293 def getmode(self, file, rev):
293 return self.modecache[(file, rev)]
294 return self.modecache[(file, rev)]
294
295
295 def getchanges(self, rev):
296 def getchanges(self, rev):
296 self.modecache = {}
297 self.modecache = {}
297 files = self.files[rev]
298 files = self.files[rev]
298 cl = files.items()
299 cl = files.items()
299 cl.sort()
300 cl.sort()
300 return (cl, {})
301 return (cl, {})
301
302
302 def getcommit(self, rev):
303 def getcommit(self, rev):
303 return self.changeset[rev]
304 return self.changeset[rev]
304
305
305 def gettags(self):
306 def gettags(self):
306 return self.tags
307 return self.tags
307
308
308 def getchangedfiles(self, rev, i):
309 def getchangedfiles(self, rev, i):
309 files = self.files[rev].keys()
310 files = self.files[rev].keys()
310 files.sort()
311 files.sort()
311 return files
312 return files
312
313
@@ -1,143 +1,142 b''
1 # git support for the convert extension
1 # git support for the convert extension
2
2
3 import os
3 import os
4 from mercurial import util
4 from mercurial import util
5
5
6 from common import NoRepo, commit, converter_source, checktool
6 from common import NoRepo, commit, converter_source, checktool
7
7
8 class convert_git(converter_source):
8 class convert_git(converter_source):
9 # Windows does not support GIT_DIR= construct while other systems
9 # Windows does not support GIT_DIR= construct while other systems
10 # cannot remove environment variable. Just assume none have
10 # cannot remove environment variable. Just assume none have
11 # both issues.
11 # both issues.
12 if hasattr(os, 'unsetenv'):
12 if hasattr(os, 'unsetenv'):
13 def gitcmd(self, s):
13 def gitcmd(self, s):
14 prevgitdir = os.environ.get('GIT_DIR')
14 prevgitdir = os.environ.get('GIT_DIR')
15 os.environ['GIT_DIR'] = self.path
15 os.environ['GIT_DIR'] = self.path
16 try:
16 try:
17 return util.popen(s)
17 return util.popen(s)
18 finally:
18 finally:
19 if prevgitdir is None:
19 if prevgitdir is None:
20 del os.environ['GIT_DIR']
20 del os.environ['GIT_DIR']
21 else:
21 else:
22 os.environ['GIT_DIR'] = prevgitdir
22 os.environ['GIT_DIR'] = prevgitdir
23 else:
23 else:
24 def gitcmd(self, s):
24 def gitcmd(self, s):
25 return util.popen('GIT_DIR=%s %s' % (self.path, s))
25 return util.popen('GIT_DIR=%s %s' % (self.path, s))
26
26
27 def __init__(self, ui, path, rev=None):
27 def __init__(self, ui, path, rev=None):
28 super(convert_git, self).__init__(ui, path, rev=rev)
28 super(convert_git, self).__init__(ui, path, rev=rev)
29
29
30 if os.path.isdir(path + "/.git"):
30 if os.path.isdir(path + "/.git"):
31 path += "/.git"
31 path += "/.git"
32 if not os.path.exists(path + "/objects"):
32 if not os.path.exists(path + "/objects"):
33 raise NoRepo("%s does not look like a Git repo" % path)
33 raise NoRepo("%s does not look like a Git repo" % path)
34
34
35 checktool('git-rev-parse', 'git')
35 checktool('git-rev-parse', 'git')
36
36
37 self.path = path
37 self.path = path
38
38
39 def getheads(self):
39 def getheads(self):
40 if not self.rev:
40 if not self.rev:
41 return self.gitcmd('git-rev-parse --branches').read().splitlines()
41 return self.gitcmd('git-rev-parse --branches').read().splitlines()
42 else:
42 else:
43 fh = self.gitcmd("git-rev-parse --verify %s" % self.rev)
43 fh = self.gitcmd("git-rev-parse --verify %s" % self.rev)
44 return [fh.read()[:-1]]
44 return [fh.read()[:-1]]
45
45
46 def catfile(self, rev, type):
46 def catfile(self, rev, type):
47 if rev == "0" * 40: raise IOError()
47 if rev == "0" * 40: raise IOError()
48 fh = self.gitcmd("git-cat-file %s %s" % (type, rev))
48 fh = self.gitcmd("git-cat-file %s %s" % (type, rev))
49 return fh.read()
49 return fh.read()
50
50
51 def getfile(self, name, rev):
51 def getfile(self, name, rev):
52 return self.catfile(rev, "blob")
52 return self.catfile(rev, "blob")
53
53
54 def getmode(self, name, rev):
54 def getmode(self, name, rev):
55 return self.modecache[(name, rev)]
55 return self.modecache[(name, rev)]
56
56
57 def getchanges(self, version):
57 def getchanges(self, version):
58 self.modecache = {}
58 self.modecache = {}
59 fh = self.gitcmd("git-diff-tree --root -m -r %s" % version)
59 fh = self.gitcmd("git-diff-tree --root -m -r %s" % version)
60 changes = []
60 changes = []
61 seen = {}
61 seen = {}
62 for l in fh:
62 for l in fh:
63 if "\t" not in l:
63 if "\t" not in l:
64 continue
64 continue
65 m, f = l[:-1].split("\t")
65 m, f = l[:-1].split("\t")
66 if f in seen:
66 if f in seen:
67 continue
67 continue
68 seen[f] = 1
68 seen[f] = 1
69 m = m.split()
69 m = m.split()
70 h = m[3]
70 h = m[3]
71 p = (m[1] == "100755")
71 p = (m[1] == "100755")
72 s = (m[1] == "120000")
72 s = (m[1] == "120000")
73 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
73 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
74 changes.append((f, h))
74 changes.append((f, h))
75 return (changes, {})
75 return (changes, {})
76
76
77 def getcommit(self, version):
77 def getcommit(self, version):
78 c = self.catfile(version, "commit") # read the commit hash
78 c = self.catfile(version, "commit") # read the commit hash
79 end = c.find("\n\n")
79 end = c.find("\n\n")
80 message = c[end+2:]
80 message = c[end+2:]
81 message = self.recode(message)
81 message = self.recode(message)
82 l = c[:end].splitlines()
82 l = c[:end].splitlines()
83 manifest = l[0].split()[1]
83 manifest = l[0].split()[1]
84 parents = []
84 parents = []
85 for e in l[1:]:
85 for e in l[1:]:
86 n, v = e.split(" ", 1)
86 n, v = e.split(" ", 1)
87 if n == "author":
87 if n == "author":
88 p = v.split()
88 p = v.split()
89 tm, tz = p[-2:]
89 tm, tz = p[-2:]
90 author = " ".join(p[:-2])
90 author = " ".join(p[:-2])
91 if author[0] == "<": author = author[1:-1]
91 if author[0] == "<": author = author[1:-1]
92 author = self.recode(author)
92 author = self.recode(author)
93 if n == "committer":
93 if n == "committer":
94 p = v.split()
94 p = v.split()
95 tm, tz = p[-2:]
95 tm, tz = p[-2:]
96 committer = " ".join(p[:-2])
96 committer = " ".join(p[:-2])
97 if committer[0] == "<": committer = committer[1:-1]
97 if committer[0] == "<": committer = committer[1:-1]
98 committer = self.recode(committer)
98 committer = self.recode(committer)
99 message += "\ncommitter: %s\n" % committer
99 message += "\ncommitter: %s\n" % committer
100 if n == "parent": parents.append(v)
100 if n == "parent": parents.append(v)
101
101
102 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
102 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
103 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
103 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
104 date = tm + " " + str(tz)
104 date = tm + " " + str(tz)
105 author = author or "unknown"
106
105
107 c = commit(parents=parents, date=date, author=author, desc=message,
106 c = commit(parents=parents, date=date, author=author, desc=message,
108 rev=version)
107 rev=version)
109 return c
108 return c
110
109
111 def gettags(self):
110 def gettags(self):
112 tags = {}
111 tags = {}
113 fh = self.gitcmd('git-ls-remote --tags "%s"' % self.path)
112 fh = self.gitcmd('git-ls-remote --tags "%s"' % self.path)
114 prefix = 'refs/tags/'
113 prefix = 'refs/tags/'
115 for line in fh:
114 for line in fh:
116 line = line.strip()
115 line = line.strip()
117 if not line.endswith("^{}"):
116 if not line.endswith("^{}"):
118 continue
117 continue
119 node, tag = line.split(None, 1)
118 node, tag = line.split(None, 1)
120 if not tag.startswith(prefix):
119 if not tag.startswith(prefix):
121 continue
120 continue
122 tag = tag[len(prefix):-3]
121 tag = tag[len(prefix):-3]
123 tags[tag] = node
122 tags[tag] = node
124
123
125 return tags
124 return tags
126
125
127 def getchangedfiles(self, version, i):
126 def getchangedfiles(self, version, i):
128 changes = []
127 changes = []
129 if i is None:
128 if i is None:
130 fh = self.gitcmd("git-diff-tree --root -m -r %s" % version)
129 fh = self.gitcmd("git-diff-tree --root -m -r %s" % version)
131 for l in fh:
130 for l in fh:
132 if "\t" not in l:
131 if "\t" not in l:
133 continue
132 continue
134 m, f = l[:-1].split("\t")
133 m, f = l[:-1].split("\t")
135 changes.append(f)
134 changes.append(f)
136 fh.close()
135 fh.close()
137 else:
136 else:
138 fh = self.gitcmd('git-diff-tree --name-only --root -r %s "%s^%s" --'
137 fh = self.gitcmd('git-diff-tree --name-only --root -r %s "%s^%s" --'
139 % (version, version, i+1))
138 % (version, version, i+1))
140 changes = [f.rstrip('\n') for f in fh]
139 changes = [f.rstrip('\n') for f in fh]
141 fh.close()
140 fh.close()
142
141
143 return changes
142 return changes
@@ -1,1041 +1,1042 b''
1 # Subversion 1.4/1.5 Python API backend
1 # Subversion 1.4/1.5 Python API backend
2 #
2 #
3 # Copyright(C) 2007 Daniel Holth et al
3 # Copyright(C) 2007 Daniel Holth et al
4 #
4 #
5 # Configuration options:
5 # Configuration options:
6 #
6 #
7 # convert.svn.trunk
7 # convert.svn.trunk
8 # Relative path to the trunk (default: "trunk")
8 # Relative path to the trunk (default: "trunk")
9 # convert.svn.branches
9 # convert.svn.branches
10 # Relative path to tree of branches (default: "branches")
10 # Relative path to tree of branches (default: "branches")
11 # convert.svn.tags
11 # convert.svn.tags
12 # Relative path to tree of tags (default: "tags")
12 # Relative path to tree of tags (default: "tags")
13 #
13 #
14 # Set these in a hgrc, or on the command line as follows:
14 # Set these in a hgrc, or on the command line as follows:
15 #
15 #
16 # hg convert --config convert.svn.trunk=wackoname [...]
16 # hg convert --config convert.svn.trunk=wackoname [...]
17
17
18 import locale
18 import locale
19 import os
19 import os
20 import re
20 import re
21 import sys
21 import sys
22 import cPickle as pickle
22 import cPickle as pickle
23 import tempfile
23 import tempfile
24
24
25 from mercurial import strutil, util
25 from mercurial import strutil, util
26 from mercurial.i18n import _
26 from mercurial.i18n import _
27
27
28 # Subversion stuff. Works best with very recent Python SVN bindings
28 # Subversion stuff. Works best with very recent Python SVN bindings
29 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
29 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
30 # these bindings.
30 # these bindings.
31
31
32 from cStringIO import StringIO
32 from cStringIO import StringIO
33
33
34 from common import NoRepo, commit, converter_source, encodeargs, decodeargs
34 from common import NoRepo, commit, converter_source, encodeargs, decodeargs
35 from common import commandline, converter_sink, mapfile
35 from common import commandline, converter_sink, mapfile
36
36
37 try:
37 try:
38 from svn.core import SubversionException, Pool
38 from svn.core import SubversionException, Pool
39 import svn
39 import svn
40 import svn.client
40 import svn.client
41 import svn.core
41 import svn.core
42 import svn.ra
42 import svn.ra
43 import svn.delta
43 import svn.delta
44 import transport
44 import transport
45 except ImportError:
45 except ImportError:
46 pass
46 pass
47
47
48 def geturl(path):
48 def geturl(path):
49 try:
49 try:
50 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
50 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
51 except SubversionException:
51 except SubversionException:
52 pass
52 pass
53 if os.path.isdir(path):
53 if os.path.isdir(path):
54 path = os.path.normpath(os.path.abspath(path))
54 path = os.path.normpath(os.path.abspath(path))
55 if os.name == 'nt':
55 if os.name == 'nt':
56 path = '/' + util.normpath(path)
56 path = '/' + util.normpath(path)
57 return 'file://%s' % path
57 return 'file://%s' % path
58 return path
58 return path
59
59
60 def optrev(number):
60 def optrev(number):
61 optrev = svn.core.svn_opt_revision_t()
61 optrev = svn.core.svn_opt_revision_t()
62 optrev.kind = svn.core.svn_opt_revision_number
62 optrev.kind = svn.core.svn_opt_revision_number
63 optrev.value.number = number
63 optrev.value.number = number
64 return optrev
64 return optrev
65
65
66 class changedpath(object):
66 class changedpath(object):
67 def __init__(self, p):
67 def __init__(self, p):
68 self.copyfrom_path = p.copyfrom_path
68 self.copyfrom_path = p.copyfrom_path
69 self.copyfrom_rev = p.copyfrom_rev
69 self.copyfrom_rev = p.copyfrom_rev
70 self.action = p.action
70 self.action = p.action
71
71
72 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
72 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
73 strict_node_history=False):
73 strict_node_history=False):
74 protocol = -1
74 protocol = -1
75 def receiver(orig_paths, revnum, author, date, message, pool):
75 def receiver(orig_paths, revnum, author, date, message, pool):
76 if orig_paths is not None:
76 if orig_paths is not None:
77 for k, v in orig_paths.iteritems():
77 for k, v in orig_paths.iteritems():
78 orig_paths[k] = changedpath(v)
78 orig_paths[k] = changedpath(v)
79 pickle.dump((orig_paths, revnum, author, date, message),
79 pickle.dump((orig_paths, revnum, author, date, message),
80 fp, protocol)
80 fp, protocol)
81
81
82 try:
82 try:
83 # Use an ra of our own so that our parent can consume
83 # Use an ra of our own so that our parent can consume
84 # our results without confusing the server.
84 # our results without confusing the server.
85 t = transport.SvnRaTransport(url=url)
85 t = transport.SvnRaTransport(url=url)
86 svn.ra.get_log(t.ra, paths, start, end, limit,
86 svn.ra.get_log(t.ra, paths, start, end, limit,
87 discover_changed_paths,
87 discover_changed_paths,
88 strict_node_history,
88 strict_node_history,
89 receiver)
89 receiver)
90 except SubversionException, (inst, num):
90 except SubversionException, (inst, num):
91 pickle.dump(num, fp, protocol)
91 pickle.dump(num, fp, protocol)
92 except IOError:
92 except IOError:
93 # Caller may interrupt the iteration
93 # Caller may interrupt the iteration
94 pickle.dump(None, fp, protocol)
94 pickle.dump(None, fp, protocol)
95 else:
95 else:
96 pickle.dump(None, fp, protocol)
96 pickle.dump(None, fp, protocol)
97 fp.close()
97 fp.close()
98
98
99 def debugsvnlog(ui, **opts):
99 def debugsvnlog(ui, **opts):
100 """Fetch SVN log in a subprocess and channel them back to parent to
100 """Fetch SVN log in a subprocess and channel them back to parent to
101 avoid memory collection issues.
101 avoid memory collection issues.
102 """
102 """
103 util.set_binary(sys.stdin)
103 util.set_binary(sys.stdin)
104 util.set_binary(sys.stdout)
104 util.set_binary(sys.stdout)
105 args = decodeargs(sys.stdin.read())
105 args = decodeargs(sys.stdin.read())
106 get_log_child(sys.stdout, *args)
106 get_log_child(sys.stdout, *args)
107
107
108 class logstream:
108 class logstream:
109 """Interruptible revision log iterator."""
109 """Interruptible revision log iterator."""
110 def __init__(self, stdout):
110 def __init__(self, stdout):
111 self._stdout = stdout
111 self._stdout = stdout
112
112
113 def __iter__(self):
113 def __iter__(self):
114 while True:
114 while True:
115 entry = pickle.load(self._stdout)
115 entry = pickle.load(self._stdout)
116 try:
116 try:
117 orig_paths, revnum, author, date, message = entry
117 orig_paths, revnum, author, date, message = entry
118 except:
118 except:
119 if entry is None:
119 if entry is None:
120 break
120 break
121 raise SubversionException("child raised exception", entry)
121 raise SubversionException("child raised exception", entry)
122 yield entry
122 yield entry
123
123
124 def close(self):
124 def close(self):
125 if self._stdout:
125 if self._stdout:
126 self._stdout.close()
126 self._stdout.close()
127 self._stdout = None
127 self._stdout = None
128
128
129 def get_log(url, paths, start, end, limit=0, discover_changed_paths=True,
129 def get_log(url, paths, start, end, limit=0, discover_changed_paths=True,
130 strict_node_history=False):
130 strict_node_history=False):
131 args = [url, paths, start, end, limit, discover_changed_paths,
131 args = [url, paths, start, end, limit, discover_changed_paths,
132 strict_node_history]
132 strict_node_history]
133 arg = encodeargs(args)
133 arg = encodeargs(args)
134 hgexe = util.hgexecutable()
134 hgexe = util.hgexecutable()
135 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
135 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
136 stdin, stdout = os.popen2(cmd, 'b')
136 stdin, stdout = os.popen2(cmd, 'b')
137 stdin.write(arg)
137 stdin.write(arg)
138 stdin.close()
138 stdin.close()
139 return logstream(stdout)
139 return logstream(stdout)
140
140
141 # SVN conversion code stolen from bzr-svn and tailor
141 # SVN conversion code stolen from bzr-svn and tailor
142 #
142 #
143 # Subversion looks like a versioned filesystem, branches structures
143 # Subversion looks like a versioned filesystem, branches structures
144 # are defined by conventions and not enforced by the tool. First,
144 # are defined by conventions and not enforced by the tool. First,
145 # we define the potential branches (modules) as "trunk" and "branches"
145 # we define the potential branches (modules) as "trunk" and "branches"
146 # children directories. Revisions are then identified by their
146 # children directories. Revisions are then identified by their
147 # module and revision number (and a repository identifier).
147 # module and revision number (and a repository identifier).
148 #
148 #
149 # The revision graph is really a tree (or a forest). By default, a
149 # The revision graph is really a tree (or a forest). By default, a
150 # revision parent is the previous revision in the same module. If the
150 # revision parent is the previous revision in the same module. If the
151 # module directory is copied/moved from another module then the
151 # module directory is copied/moved from another module then the
152 # revision is the module root and its parent the source revision in
152 # revision is the module root and its parent the source revision in
153 # the parent module. A revision has at most one parent.
153 # the parent module. A revision has at most one parent.
154 #
154 #
155 class svn_source(converter_source):
155 class svn_source(converter_source):
156 def __init__(self, ui, url, rev=None):
156 def __init__(self, ui, url, rev=None):
157 super(svn_source, self).__init__(ui, url, rev=rev)
157 super(svn_source, self).__init__(ui, url, rev=rev)
158
158
159 try:
159 try:
160 SubversionException
160 SubversionException
161 except NameError:
161 except NameError:
162 raise NoRepo('Subversion python bindings could not be loaded')
162 raise NoRepo('Subversion python bindings could not be loaded')
163
163
164 self.encoding = locale.getpreferredencoding()
164 self.encoding = locale.getpreferredencoding()
165 self.lastrevs = {}
165 self.lastrevs = {}
166
166
167 latest = None
167 latest = None
168 try:
168 try:
169 # Support file://path@rev syntax. Useful e.g. to convert
169 # Support file://path@rev syntax. Useful e.g. to convert
170 # deleted branches.
170 # deleted branches.
171 at = url.rfind('@')
171 at = url.rfind('@')
172 if at >= 0:
172 if at >= 0:
173 latest = int(url[at+1:])
173 latest = int(url[at+1:])
174 url = url[:at]
174 url = url[:at]
175 except ValueError, e:
175 except ValueError, e:
176 pass
176 pass
177 self.url = geturl(url)
177 self.url = geturl(url)
178 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
178 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
179 try:
179 try:
180 self.transport = transport.SvnRaTransport(url=self.url)
180 self.transport = transport.SvnRaTransport(url=self.url)
181 self.ra = self.transport.ra
181 self.ra = self.transport.ra
182 self.ctx = self.transport.client
182 self.ctx = self.transport.client
183 self.base = svn.ra.get_repos_root(self.ra)
183 self.base = svn.ra.get_repos_root(self.ra)
184 self.module = self.url[len(self.base):]
184 self.module = self.url[len(self.base):]
185 self.rootmodule = self.module
185 self.rootmodule = self.module
186 self.commits = {}
186 self.commits = {}
187 self.paths = {}
187 self.paths = {}
188 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
188 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
189 except SubversionException, e:
189 except SubversionException, e:
190 ui.print_exc()
190 ui.print_exc()
191 raise NoRepo("%s does not look like a Subversion repo" % self.url)
191 raise NoRepo("%s does not look like a Subversion repo" % self.url)
192
192
193 if rev:
193 if rev:
194 try:
194 try:
195 latest = int(rev)
195 latest = int(rev)
196 except ValueError:
196 except ValueError:
197 raise util.Abort('svn: revision %s is not an integer' % rev)
197 raise util.Abort('svn: revision %s is not an integer' % rev)
198
198
199 try:
199 try:
200 self.get_blacklist()
200 self.get_blacklist()
201 except IOError, e:
201 except IOError, e:
202 pass
202 pass
203
203
204 self.head = self.latest(self.module, latest)
204 self.head = self.latest(self.module, latest)
205 if not self.head:
205 if not self.head:
206 raise util.Abort(_('no revision found in module %s') %
206 raise util.Abort(_('no revision found in module %s') %
207 self.module.encode(self.encoding))
207 self.module.encode(self.encoding))
208 self.last_changed = self.revnum(self.head)
208 self.last_changed = self.revnum(self.head)
209
209
210 self._changescache = None
210 self._changescache = None
211
211
212 if os.path.exists(os.path.join(url, '.svn/entries')):
212 if os.path.exists(os.path.join(url, '.svn/entries')):
213 self.wc = url
213 self.wc = url
214 else:
214 else:
215 self.wc = None
215 self.wc = None
216 self.convertfp = None
216 self.convertfp = None
217
217
218 def setrevmap(self, revmap):
218 def setrevmap(self, revmap):
219 lastrevs = {}
219 lastrevs = {}
220 for revid in revmap.iterkeys():
220 for revid in revmap.iterkeys():
221 uuid, module, revnum = self.revsplit(revid)
221 uuid, module, revnum = self.revsplit(revid)
222 lastrevnum = lastrevs.setdefault(module, revnum)
222 lastrevnum = lastrevs.setdefault(module, revnum)
223 if revnum > lastrevnum:
223 if revnum > lastrevnum:
224 lastrevs[module] = revnum
224 lastrevs[module] = revnum
225 self.lastrevs = lastrevs
225 self.lastrevs = lastrevs
226
226
227 def exists(self, path, optrev):
227 def exists(self, path, optrev):
228 try:
228 try:
229 svn.client.ls(self.url.rstrip('/') + '/' + path,
229 svn.client.ls(self.url.rstrip('/') + '/' + path,
230 optrev, False, self.ctx)
230 optrev, False, self.ctx)
231 return True
231 return True
232 except SubversionException, err:
232 except SubversionException, err:
233 return False
233 return False
234
234
235 def getheads(self):
235 def getheads(self):
236
236
237 def getcfgpath(name, rev):
237 def getcfgpath(name, rev):
238 cfgpath = self.ui.config('convert', 'svn.' + name)
238 cfgpath = self.ui.config('convert', 'svn.' + name)
239 path = (cfgpath or name).strip('/')
239 path = (cfgpath or name).strip('/')
240 if not self.exists(path, rev):
240 if not self.exists(path, rev):
241 if cfgpath:
241 if cfgpath:
242 raise util.Abort(_('expected %s to be at %r, but not found')
242 raise util.Abort(_('expected %s to be at %r, but not found')
243 % (name, path))
243 % (name, path))
244 return None
244 return None
245 self.ui.note(_('found %s at %r\n') % (name, path))
245 self.ui.note(_('found %s at %r\n') % (name, path))
246 return path
246 return path
247
247
248 rev = optrev(self.last_changed)
248 rev = optrev(self.last_changed)
249 oldmodule = ''
249 oldmodule = ''
250 trunk = getcfgpath('trunk', rev)
250 trunk = getcfgpath('trunk', rev)
251 tags = getcfgpath('tags', rev)
251 tags = getcfgpath('tags', rev)
252 branches = getcfgpath('branches', rev)
252 branches = getcfgpath('branches', rev)
253
253
254 # If the project has a trunk or branches, we will extract heads
254 # If the project has a trunk or branches, we will extract heads
255 # from them. We keep the project root otherwise.
255 # from them. We keep the project root otherwise.
256 if trunk:
256 if trunk:
257 oldmodule = self.module or ''
257 oldmodule = self.module or ''
258 self.module += '/' + trunk
258 self.module += '/' + trunk
259 self.head = self.latest(self.module, self.last_changed)
259 self.head = self.latest(self.module, self.last_changed)
260 if not self.head:
260 if not self.head:
261 raise util.Abort(_('no revision found in module %s') %
261 raise util.Abort(_('no revision found in module %s') %
262 self.module.encode(self.encoding))
262 self.module.encode(self.encoding))
263
263
264 # First head in the list is the module's head
264 # First head in the list is the module's head
265 self.heads = [self.head]
265 self.heads = [self.head]
266 self.tags = '%s/%s' % (oldmodule , (tags or 'tags'))
266 self.tags = '%s/%s' % (oldmodule , (tags or 'tags'))
267
267
268 # Check if branches bring a few more heads to the list
268 # Check if branches bring a few more heads to the list
269 if branches:
269 if branches:
270 rpath = self.url.strip('/')
270 rpath = self.url.strip('/')
271 branchnames = svn.client.ls(rpath + '/' + branches, rev, False,
271 branchnames = svn.client.ls(rpath + '/' + branches, rev, False,
272 self.ctx)
272 self.ctx)
273 for branch in branchnames.keys():
273 for branch in branchnames.keys():
274 module = '%s/%s/%s' % (oldmodule, branches, branch)
274 module = '%s/%s/%s' % (oldmodule, branches, branch)
275 brevid = self.latest(module, self.last_changed)
275 brevid = self.latest(module, self.last_changed)
276 if not brevid:
276 if not brevid:
277 self.ui.note(_('ignoring empty branch %s\n') %
277 self.ui.note(_('ignoring empty branch %s\n') %
278 branch.encode(self.encoding))
278 branch.encode(self.encoding))
279 continue
279 continue
280 self.ui.note('found branch %s at %d\n' %
280 self.ui.note('found branch %s at %d\n' %
281 (branch, self.revnum(brevid)))
281 (branch, self.revnum(brevid)))
282 self.heads.append(brevid)
282 self.heads.append(brevid)
283
283
284 return self.heads
284 return self.heads
285
285
286 def getfile(self, file, rev):
286 def getfile(self, file, rev):
287 data, mode = self._getfile(file, rev)
287 data, mode = self._getfile(file, rev)
288 self.modecache[(file, rev)] = mode
288 self.modecache[(file, rev)] = mode
289 return data
289 return data
290
290
291 def getmode(self, file, rev):
291 def getmode(self, file, rev):
292 return self.modecache[(file, rev)]
292 return self.modecache[(file, rev)]
293
293
294 def getchanges(self, rev):
294 def getchanges(self, rev):
295 if self._changescache and self._changescache[0] == rev:
295 if self._changescache and self._changescache[0] == rev:
296 return self._changescache[1]
296 return self._changescache[1]
297 self._changescache = None
297 self._changescache = None
298 self.modecache = {}
298 self.modecache = {}
299 (paths, parents) = self.paths[rev]
299 (paths, parents) = self.paths[rev]
300 if parents:
300 if parents:
301 files, copies = self.expandpaths(rev, paths, parents)
301 files, copies = self.expandpaths(rev, paths, parents)
302 else:
302 else:
303 # Perform a full checkout on roots
303 # Perform a full checkout on roots
304 uuid, module, revnum = self.revsplit(rev)
304 uuid, module, revnum = self.revsplit(rev)
305 entries = svn.client.ls(self.base + module, optrev(revnum),
305 entries = svn.client.ls(self.base + module, optrev(revnum),
306 True, self.ctx)
306 True, self.ctx)
307 files = [n for n,e in entries.iteritems()
307 files = [n for n,e in entries.iteritems()
308 if e.kind == svn.core.svn_node_file]
308 if e.kind == svn.core.svn_node_file]
309 copies = {}
309 copies = {}
310
310
311 files.sort()
311 files.sort()
312 files = zip(files, [rev] * len(files))
312 files = zip(files, [rev] * len(files))
313
313
314 # caller caches the result, so free it here to release memory
314 # caller caches the result, so free it here to release memory
315 del self.paths[rev]
315 del self.paths[rev]
316 return (files, copies)
316 return (files, copies)
317
317
318 def getchangedfiles(self, rev, i):
318 def getchangedfiles(self, rev, i):
319 changes = self.getchanges(rev)
319 changes = self.getchanges(rev)
320 self._changescache = (rev, changes)
320 self._changescache = (rev, changes)
321 return [f[0] for f in changes[0]]
321 return [f[0] for f in changes[0]]
322
322
323 def getcommit(self, rev):
323 def getcommit(self, rev):
324 if rev not in self.commits:
324 if rev not in self.commits:
325 uuid, module, revnum = self.revsplit(rev)
325 uuid, module, revnum = self.revsplit(rev)
326 self.module = module
326 self.module = module
327 self.reparent(module)
327 self.reparent(module)
328 # We assume that:
328 # We assume that:
329 # - requests for revisions after "stop" come from the
329 # - requests for revisions after "stop" come from the
330 # revision graph backward traversal. Cache all of them
330 # revision graph backward traversal. Cache all of them
331 # down to stop, they will be used eventually.
331 # down to stop, they will be used eventually.
332 # - requests for revisions before "stop" come to get
332 # - requests for revisions before "stop" come to get
333 # isolated branches parents. Just fetch what is needed.
333 # isolated branches parents. Just fetch what is needed.
334 stop = self.lastrevs.get(module, 0)
334 stop = self.lastrevs.get(module, 0)
335 if revnum < stop:
335 if revnum < stop:
336 stop = revnum + 1
336 stop = revnum + 1
337 self._fetch_revisions(revnum, stop)
337 self._fetch_revisions(revnum, stop)
338 commit = self.commits[rev]
338 commit = self.commits[rev]
339 # caller caches the result, so free it here to release memory
339 # caller caches the result, so free it here to release memory
340 del self.commits[rev]
340 del self.commits[rev]
341 return commit
341 return commit
342
342
343 def gettags(self):
343 def gettags(self):
344 tags = {}
344 tags = {}
345 start = self.revnum(self.head)
345 start = self.revnum(self.head)
346 try:
346 try:
347 for entry in get_log(self.url, [self.tags], 0, start):
347 for entry in get_log(self.url, [self.tags], 0, start):
348 orig_paths, revnum, author, date, message = entry
348 orig_paths, revnum, author, date, message = entry
349 for path in orig_paths:
349 for path in orig_paths:
350 if not path.startswith(self.tags+'/'):
350 if not path.startswith(self.tags+'/'):
351 continue
351 continue
352 ent = orig_paths[path]
352 ent = orig_paths[path]
353 source = ent.copyfrom_path
353 source = ent.copyfrom_path
354 rev = ent.copyfrom_rev
354 rev = ent.copyfrom_rev
355 tag = path.split('/')[-1]
355 tag = path.split('/')[-1]
356 tags[tag] = self.revid(rev, module=source)
356 tags[tag] = self.revid(rev, module=source)
357 except SubversionException, (inst, num):
357 except SubversionException, (inst, num):
358 self.ui.note('no tags found at revision %d\n' % start)
358 self.ui.note('no tags found at revision %d\n' % start)
359 return tags
359 return tags
360
360
361 def converted(self, rev, destrev):
361 def converted(self, rev, destrev):
362 if not self.wc:
362 if not self.wc:
363 return
363 return
364 if self.convertfp is None:
364 if self.convertfp is None:
365 self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'),
365 self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'),
366 'a')
366 'a')
367 self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev)))
367 self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev)))
368 self.convertfp.flush()
368 self.convertfp.flush()
369
369
370 # -- helper functions --
370 # -- helper functions --
371
371
372 def revid(self, revnum, module=None):
372 def revid(self, revnum, module=None):
373 if not module:
373 if not module:
374 module = self.module
374 module = self.module
375 return u"svn:%s%s@%s" % (self.uuid, module.decode(self.encoding),
375 return u"svn:%s%s@%s" % (self.uuid, module.decode(self.encoding),
376 revnum)
376 revnum)
377
377
378 def revnum(self, rev):
378 def revnum(self, rev):
379 return int(rev.split('@')[-1])
379 return int(rev.split('@')[-1])
380
380
381 def revsplit(self, rev):
381 def revsplit(self, rev):
382 url, revnum = rev.encode(self.encoding).split('@', 1)
382 url, revnum = rev.encode(self.encoding).split('@', 1)
383 revnum = int(revnum)
383 revnum = int(revnum)
384 parts = url.split('/', 1)
384 parts = url.split('/', 1)
385 uuid = parts.pop(0)[4:]
385 uuid = parts.pop(0)[4:]
386 mod = ''
386 mod = ''
387 if parts:
387 if parts:
388 mod = '/' + parts[0]
388 mod = '/' + parts[0]
389 return uuid, mod, revnum
389 return uuid, mod, revnum
390
390
391 def latest(self, path, stop=0):
391 def latest(self, path, stop=0):
392 """Find the latest revid affecting path, up to stop. It may return
392 """Find the latest revid affecting path, up to stop. It may return
393 a revision in a different module, since a branch may be moved without
393 a revision in a different module, since a branch may be moved without
394 a change being reported. Return None if computed module does not
394 a change being reported. Return None if computed module does not
395 belong to rootmodule subtree.
395 belong to rootmodule subtree.
396 """
396 """
397 if not stop:
397 if not stop:
398 stop = svn.ra.get_latest_revnum(self.ra)
398 stop = svn.ra.get_latest_revnum(self.ra)
399 try:
399 try:
400 self.reparent('')
400 self.reparent('')
401 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
401 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
402 self.reparent(self.module)
402 self.reparent(self.module)
403 except SubversionException:
403 except SubversionException:
404 dirent = None
404 dirent = None
405 if not dirent:
405 if not dirent:
406 raise util.Abort('%s not found up to revision %d' % (path, stop))
406 raise util.Abort('%s not found up to revision %d' % (path, stop))
407
407
408 # stat() gives us the previous revision on this line of development, but
408 # stat() gives us the previous revision on this line of development, but
409 # it might be in *another module*. Fetch the log and detect renames down
409 # it might be in *another module*. Fetch the log and detect renames down
410 # to the latest revision.
410 # to the latest revision.
411 stream = get_log(self.url, [path], stop, dirent.created_rev)
411 stream = get_log(self.url, [path], stop, dirent.created_rev)
412 try:
412 try:
413 for entry in stream:
413 for entry in stream:
414 paths, revnum, author, date, message = entry
414 paths, revnum, author, date, message = entry
415 if revnum <= dirent.created_rev:
415 if revnum <= dirent.created_rev:
416 break
416 break
417
417
418 for p in paths:
418 for p in paths:
419 if not path.startswith(p) or not paths[p].copyfrom_path:
419 if not path.startswith(p) or not paths[p].copyfrom_path:
420 continue
420 continue
421 newpath = paths[p].copyfrom_path + path[len(p):]
421 newpath = paths[p].copyfrom_path + path[len(p):]
422 self.ui.debug("branch renamed from %s to %s at %d\n" %
422 self.ui.debug("branch renamed from %s to %s at %d\n" %
423 (path, newpath, revnum))
423 (path, newpath, revnum))
424 path = newpath
424 path = newpath
425 break
425 break
426 finally:
426 finally:
427 stream.close()
427 stream.close()
428
428
429 if not path.startswith(self.rootmodule):
429 if not path.startswith(self.rootmodule):
430 self.ui.debug(_('ignoring foreign branch %r\n') % path)
430 self.ui.debug(_('ignoring foreign branch %r\n') % path)
431 return None
431 return None
432 return self.revid(dirent.created_rev, path)
432 return self.revid(dirent.created_rev, path)
433
433
434 def get_blacklist(self):
434 def get_blacklist(self):
435 """Avoid certain revision numbers.
435 """Avoid certain revision numbers.
436 It is not uncommon for two nearby revisions to cancel each other
436 It is not uncommon for two nearby revisions to cancel each other
437 out, e.g. 'I copied trunk into a subdirectory of itself instead
437 out, e.g. 'I copied trunk into a subdirectory of itself instead
438 of making a branch'. The converted repository is significantly
438 of making a branch'. The converted repository is significantly
439 smaller if we ignore such revisions."""
439 smaller if we ignore such revisions."""
440 self.blacklist = util.set()
440 self.blacklist = util.set()
441 blacklist = self.blacklist
441 blacklist = self.blacklist
442 for line in file("blacklist.txt", "r"):
442 for line in file("blacklist.txt", "r"):
443 if not line.startswith("#"):
443 if not line.startswith("#"):
444 try:
444 try:
445 svn_rev = int(line.strip())
445 svn_rev = int(line.strip())
446 blacklist.add(svn_rev)
446 blacklist.add(svn_rev)
447 except ValueError, e:
447 except ValueError, e:
448 pass # not an integer or a comment
448 pass # not an integer or a comment
449
449
450 def is_blacklisted(self, svn_rev):
450 def is_blacklisted(self, svn_rev):
451 return svn_rev in self.blacklist
451 return svn_rev in self.blacklist
452
452
453 def reparent(self, module):
453 def reparent(self, module):
454 svn_url = self.base + module
454 svn_url = self.base + module
455 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
455 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
456 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
456 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
457
457
458 def expandpaths(self, rev, paths, parents):
458 def expandpaths(self, rev, paths, parents):
459 def get_entry_from_path(path, module=self.module):
459 def get_entry_from_path(path, module=self.module):
460 # Given the repository url of this wc, say
460 # Given the repository url of this wc, say
461 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
461 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
462 # extract the "entry" portion (a relative path) from what
462 # extract the "entry" portion (a relative path) from what
463 # svn log --xml says, ie
463 # svn log --xml says, ie
464 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
464 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
465 # that is to say "tests/PloneTestCase.py"
465 # that is to say "tests/PloneTestCase.py"
466 if path.startswith(module):
466 if path.startswith(module):
467 relative = path[len(module):]
467 relative = path[len(module):]
468 if relative.startswith('/'):
468 if relative.startswith('/'):
469 return relative[1:]
469 return relative[1:]
470 else:
470 else:
471 return relative
471 return relative
472
472
473 # The path is outside our tracked tree...
473 # The path is outside our tracked tree...
474 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
474 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
475 return None
475 return None
476
476
477 entries = []
477 entries = []
478 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
478 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
479 copies = {}
479 copies = {}
480
480
481 new_module, revnum = self.revsplit(rev)[1:]
481 new_module, revnum = self.revsplit(rev)[1:]
482 if new_module != self.module:
482 if new_module != self.module:
483 self.module = new_module
483 self.module = new_module
484 self.reparent(self.module)
484 self.reparent(self.module)
485
485
486 for path, ent in paths:
486 for path, ent in paths:
487 entrypath = get_entry_from_path(path, module=self.module)
487 entrypath = get_entry_from_path(path, module=self.module)
488 entry = entrypath.decode(self.encoding)
488 entry = entrypath.decode(self.encoding)
489
489
490 kind = svn.ra.check_path(self.ra, entrypath, revnum)
490 kind = svn.ra.check_path(self.ra, entrypath, revnum)
491 if kind == svn.core.svn_node_file:
491 if kind == svn.core.svn_node_file:
492 if ent.copyfrom_path:
492 if ent.copyfrom_path:
493 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
493 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
494 if copyfrom_path:
494 if copyfrom_path:
495 self.ui.debug("Copied to %s from %s@%s\n" %
495 self.ui.debug("Copied to %s from %s@%s\n" %
496 (entrypath, copyfrom_path,
496 (entrypath, copyfrom_path,
497 ent.copyfrom_rev))
497 ent.copyfrom_rev))
498 # It's probably important for hg that the source
498 # It's probably important for hg that the source
499 # exists in the revision's parent, not just the
499 # exists in the revision's parent, not just the
500 # ent.copyfrom_rev
500 # ent.copyfrom_rev
501 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
501 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
502 if fromkind != 0:
502 if fromkind != 0:
503 copies[self.recode(entry)] = self.recode(copyfrom_path)
503 copies[self.recode(entry)] = self.recode(copyfrom_path)
504 entries.append(self.recode(entry))
504 entries.append(self.recode(entry))
505 elif kind == 0: # gone, but had better be a deleted *file*
505 elif kind == 0: # gone, but had better be a deleted *file*
506 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
506 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
507
507
508 # if a branch is created but entries are removed in the same
508 # if a branch is created but entries are removed in the same
509 # changeset, get the right fromrev
509 # changeset, get the right fromrev
510 # parents cannot be empty here, you cannot remove things from
510 # parents cannot be empty here, you cannot remove things from
511 # a root revision.
511 # a root revision.
512 uuid, old_module, fromrev = self.revsplit(parents[0])
512 uuid, old_module, fromrev = self.revsplit(parents[0])
513
513
514 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
514 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
515 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
515 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
516
516
517 def lookup_parts(p):
517 def lookup_parts(p):
518 rc = None
518 rc = None
519 parts = p.split("/")
519 parts = p.split("/")
520 for i in range(len(parts)):
520 for i in range(len(parts)):
521 part = "/".join(parts[:i])
521 part = "/".join(parts[:i])
522 info = part, copyfrom.get(part, None)
522 info = part, copyfrom.get(part, None)
523 if info[1] is not None:
523 if info[1] is not None:
524 self.ui.debug("Found parent directory %s\n" % info[1])
524 self.ui.debug("Found parent directory %s\n" % info[1])
525 rc = info
525 rc = info
526 return rc
526 return rc
527
527
528 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
528 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
529
529
530 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
530 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
531
531
532 # need to remove fragment from lookup_parts and replace with copyfrom_path
532 # need to remove fragment from lookup_parts and replace with copyfrom_path
533 if frompath is not None:
533 if frompath is not None:
534 self.ui.debug("munge-o-matic\n")
534 self.ui.debug("munge-o-matic\n")
535 self.ui.debug(entrypath + '\n')
535 self.ui.debug(entrypath + '\n')
536 self.ui.debug(entrypath[len(frompath):] + '\n')
536 self.ui.debug(entrypath[len(frompath):] + '\n')
537 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
537 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
538 fromrev = froment.copyfrom_rev
538 fromrev = froment.copyfrom_rev
539 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
539 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
540
540
541 # We can avoid the reparent calls if the module has not changed
541 # We can avoid the reparent calls if the module has not changed
542 # but it probably does not worth the pain.
542 # but it probably does not worth the pain.
543 self.reparent('')
543 self.reparent('')
544 fromkind = svn.ra.check_path(self.ra, entrypath.strip('/'), fromrev)
544 fromkind = svn.ra.check_path(self.ra, entrypath.strip('/'), fromrev)
545 self.reparent(self.module)
545 self.reparent(self.module)
546
546
547 if fromkind == svn.core.svn_node_file: # a deleted file
547 if fromkind == svn.core.svn_node_file: # a deleted file
548 entries.append(self.recode(entry))
548 entries.append(self.recode(entry))
549 elif fromkind == svn.core.svn_node_dir:
549 elif fromkind == svn.core.svn_node_dir:
550 # print "Deleted/moved non-file:", revnum, path, ent
550 # print "Deleted/moved non-file:", revnum, path, ent
551 # children = self._find_children(path, revnum - 1)
551 # children = self._find_children(path, revnum - 1)
552 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
552 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
553 # Sometimes this is tricky. For example: in
553 # Sometimes this is tricky. For example: in
554 # The Subversion Repository revision 6940 a dir
554 # The Subversion Repository revision 6940 a dir
555 # was copied and one of its files was deleted
555 # was copied and one of its files was deleted
556 # from the new location in the same commit. This
556 # from the new location in the same commit. This
557 # code can't deal with that yet.
557 # code can't deal with that yet.
558 if ent.action == 'C':
558 if ent.action == 'C':
559 children = self._find_children(path, fromrev)
559 children = self._find_children(path, fromrev)
560 else:
560 else:
561 oroot = entrypath.strip('/')
561 oroot = entrypath.strip('/')
562 nroot = path.strip('/')
562 nroot = path.strip('/')
563 children = self._find_children(oroot, fromrev)
563 children = self._find_children(oroot, fromrev)
564 children = [s.replace(oroot,nroot) for s in children]
564 children = [s.replace(oroot,nroot) for s in children]
565 # Mark all [files, not directories] as deleted.
565 # Mark all [files, not directories] as deleted.
566 for child in children:
566 for child in children:
567 # Can we move a child directory and its
567 # Can we move a child directory and its
568 # parent in the same commit? (probably can). Could
568 # parent in the same commit? (probably can). Could
569 # cause problems if instead of revnum -1,
569 # cause problems if instead of revnum -1,
570 # we have to look in (copyfrom_path, revnum - 1)
570 # we have to look in (copyfrom_path, revnum - 1)
571 entrypath = get_entry_from_path("/" + child, module=old_module)
571 entrypath = get_entry_from_path("/" + child, module=old_module)
572 if entrypath:
572 if entrypath:
573 entry = self.recode(entrypath.decode(self.encoding))
573 entry = self.recode(entrypath.decode(self.encoding))
574 if entry in copies:
574 if entry in copies:
575 # deleted file within a copy
575 # deleted file within a copy
576 del copies[entry]
576 del copies[entry]
577 else:
577 else:
578 entries.append(entry)
578 entries.append(entry)
579 else:
579 else:
580 self.ui.debug('unknown path in revision %d: %s\n' % \
580 self.ui.debug('unknown path in revision %d: %s\n' % \
581 (revnum, path))
581 (revnum, path))
582 elif kind == svn.core.svn_node_dir:
582 elif kind == svn.core.svn_node_dir:
583 # Should probably synthesize normal file entries
583 # Should probably synthesize normal file entries
584 # and handle as above to clean up copy/rename handling.
584 # and handle as above to clean up copy/rename handling.
585
585
586 # If the directory just had a prop change,
586 # If the directory just had a prop change,
587 # then we shouldn't need to look for its children.
587 # then we shouldn't need to look for its children.
588 if ent.action == 'M':
588 if ent.action == 'M':
589 continue
589 continue
590
590
591 # Also this could create duplicate entries. Not sure
591 # Also this could create duplicate entries. Not sure
592 # whether this will matter. Maybe should make entries a set.
592 # whether this will matter. Maybe should make entries a set.
593 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
593 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
594 # This will fail if a directory was copied
594 # This will fail if a directory was copied
595 # from another branch and then some of its files
595 # from another branch and then some of its files
596 # were deleted in the same transaction.
596 # were deleted in the same transaction.
597 children = self._find_children(path, revnum)
597 children = self._find_children(path, revnum)
598 children.sort()
598 children.sort()
599 for child in children:
599 for child in children:
600 # Can we move a child directory and its
600 # Can we move a child directory and its
601 # parent in the same commit? (probably can). Could
601 # parent in the same commit? (probably can). Could
602 # cause problems if instead of revnum -1,
602 # cause problems if instead of revnum -1,
603 # we have to look in (copyfrom_path, revnum - 1)
603 # we have to look in (copyfrom_path, revnum - 1)
604 entrypath = get_entry_from_path("/" + child, module=self.module)
604 entrypath = get_entry_from_path("/" + child, module=self.module)
605 # print child, self.module, entrypath
605 # print child, self.module, entrypath
606 if entrypath:
606 if entrypath:
607 # Need to filter out directories here...
607 # Need to filter out directories here...
608 kind = svn.ra.check_path(self.ra, entrypath, revnum)
608 kind = svn.ra.check_path(self.ra, entrypath, revnum)
609 if kind != svn.core.svn_node_dir:
609 if kind != svn.core.svn_node_dir:
610 entries.append(self.recode(entrypath))
610 entries.append(self.recode(entrypath))
611
611
612 # Copies here (must copy all from source)
612 # Copies here (must copy all from source)
613 # Probably not a real problem for us if
613 # Probably not a real problem for us if
614 # source does not exist
614 # source does not exist
615
615
616 # Can do this with the copy command "hg copy"
616 # Can do this with the copy command "hg copy"
617 # if ent.copyfrom_path:
617 # if ent.copyfrom_path:
618 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
618 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
619 # module=self.module)
619 # module=self.module)
620 # copyto_entry = entrypath
620 # copyto_entry = entrypath
621 #
621 #
622 # print "copy directory", copyfrom_entry, 'to', copyto_entry
622 # print "copy directory", copyfrom_entry, 'to', copyto_entry
623 #
623 #
624 # copies.append((copyfrom_entry, copyto_entry))
624 # copies.append((copyfrom_entry, copyto_entry))
625
625
626 if ent.copyfrom_path:
626 if ent.copyfrom_path:
627 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
627 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
628 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
628 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
629 if copyfrom_entry:
629 if copyfrom_entry:
630 copyfrom[path] = ent
630 copyfrom[path] = ent
631 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
631 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
632
632
633 # Good, /probably/ a regular copy. Really should check
633 # Good, /probably/ a regular copy. Really should check
634 # to see whether the parent revision actually contains
634 # to see whether the parent revision actually contains
635 # the directory in question.
635 # the directory in question.
636 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
636 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
637 children.sort()
637 children.sort()
638 for child in children:
638 for child in children:
639 entrypath = get_entry_from_path("/" + child, module=self.module)
639 entrypath = get_entry_from_path("/" + child, module=self.module)
640 if entrypath:
640 if entrypath:
641 entry = entrypath.decode(self.encoding)
641 entry = entrypath.decode(self.encoding)
642 # print "COPY COPY From", copyfrom_entry, entry
642 # print "COPY COPY From", copyfrom_entry, entry
643 copyto_path = path + entry[len(copyfrom_entry):]
643 copyto_path = path + entry[len(copyfrom_entry):]
644 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
644 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
645 # print "COPY", entry, "COPY To", copyto_entry
645 # print "COPY", entry, "COPY To", copyto_entry
646 copies[self.recode(copyto_entry)] = self.recode(entry)
646 copies[self.recode(copyto_entry)] = self.recode(entry)
647 # copy from quux splort/quuxfile
647 # copy from quux splort/quuxfile
648
648
649 return (util.unique(entries), copies)
649 return (util.unique(entries), copies)
650
650
651 def _fetch_revisions(self, from_revnum, to_revnum):
651 def _fetch_revisions(self, from_revnum, to_revnum):
652 if from_revnum < to_revnum:
652 if from_revnum < to_revnum:
653 from_revnum, to_revnum = to_revnum, from_revnum
653 from_revnum, to_revnum = to_revnum, from_revnum
654
654
655 self.child_cset = None
655 self.child_cset = None
656 def parselogentry(orig_paths, revnum, author, date, message):
656 def parselogentry(orig_paths, revnum, author, date, message):
657 """Return the parsed commit object or None, and True if
657 """Return the parsed commit object or None, and True if
658 the revision is a branch root.
658 the revision is a branch root.
659 """
659 """
660 self.ui.debug("parsing revision %d (%d changes)\n" %
660 self.ui.debug("parsing revision %d (%d changes)\n" %
661 (revnum, len(orig_paths)))
661 (revnum, len(orig_paths)))
662
662
663 branched = False
663 branched = False
664 rev = self.revid(revnum)
664 rev = self.revid(revnum)
665 # branch log might return entries for a parent we already have
665 # branch log might return entries for a parent we already have
666
666
667 if (rev in self.commits or revnum < to_revnum):
667 if (rev in self.commits or revnum < to_revnum):
668 return None, branched
668 return None, branched
669
669
670 parents = []
670 parents = []
671 # check whether this revision is the start of a branch or part
671 # check whether this revision is the start of a branch or part
672 # of a branch renaming
672 # of a branch renaming
673 orig_paths = orig_paths.items()
673 orig_paths = orig_paths.items()
674 orig_paths.sort()
674 orig_paths.sort()
675 root_paths = [(p,e) for p,e in orig_paths if self.module.startswith(p)]
675 root_paths = [(p,e) for p,e in orig_paths if self.module.startswith(p)]
676 if root_paths:
676 if root_paths:
677 path, ent = root_paths[-1]
677 path, ent = root_paths[-1]
678 if ent.copyfrom_path:
678 if ent.copyfrom_path:
679 branched = True
679 branched = True
680 newpath = ent.copyfrom_path + self.module[len(path):]
680 newpath = ent.copyfrom_path + self.module[len(path):]
681 # ent.copyfrom_rev may not be the actual last revision
681 # ent.copyfrom_rev may not be the actual last revision
682 previd = self.latest(newpath, ent.copyfrom_rev)
682 previd = self.latest(newpath, ent.copyfrom_rev)
683 if previd is not None:
683 if previd is not None:
684 parents = [previd]
684 parents = [previd]
685 prevmodule, prevnum = self.revsplit(previd)[1:]
685 prevmodule, prevnum = self.revsplit(previd)[1:]
686 self.ui.note('found parent of branch %s at %d: %s\n' %
686 self.ui.note('found parent of branch %s at %d: %s\n' %
687 (self.module, prevnum, prevmodule))
687 (self.module, prevnum, prevmodule))
688 else:
688 else:
689 self.ui.debug("No copyfrom path, don't know what to do.\n")
689 self.ui.debug("No copyfrom path, don't know what to do.\n")
690
690
691 paths = []
691 paths = []
692 # filter out unrelated paths
692 # filter out unrelated paths
693 for path, ent in orig_paths:
693 for path, ent in orig_paths:
694 if not path.startswith(self.module):
694 if not path.startswith(self.module):
695 self.ui.debug("boring@%s: %s\n" % (revnum, path))
695 self.ui.debug("boring@%s: %s\n" % (revnum, path))
696 continue
696 continue
697 paths.append((path, ent))
697 paths.append((path, ent))
698
698
699 # Example SVN datetime. Includes microseconds.
699 # Example SVN datetime. Includes microseconds.
700 # ISO-8601 conformant
700 # ISO-8601 conformant
701 # '2007-01-04T17:35:00.902377Z'
701 # '2007-01-04T17:35:00.902377Z'
702 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
702 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
703
703
704 log = message and self.recode(message) or ''
704 log = message and self.recode(message) or ''
705 author = author and self.recode(author) or ''
705 author = author and self.recode(author) or ''
706 try:
706 try:
707 branch = self.module.split("/")[-1]
707 branch = self.module.split("/")[-1]
708 if branch == 'trunk':
708 if branch == 'trunk':
709 branch = ''
709 branch = ''
710 except IndexError:
710 except IndexError:
711 branch = None
711 branch = None
712
712
713 cset = commit(author=author,
713 cset = commit(author=author,
714 date=util.datestr(date),
714 date=util.datestr(date),
715 desc=log,
715 desc=log,
716 parents=parents,
716 parents=parents,
717 branch=branch,
717 branch=branch,
718 rev=rev.encode('utf-8'))
718 rev=rev.encode('utf-8'))
719
719
720 self.commits[rev] = cset
720 self.commits[rev] = cset
721 # The parents list is *shared* among self.paths and the
721 # The parents list is *shared* among self.paths and the
722 # commit object. Both will be updated below.
722 # commit object. Both will be updated below.
723 self.paths[rev] = (paths, cset.parents)
723 self.paths[rev] = (paths, cset.parents)
724 if self.child_cset and not self.child_cset.parents:
724 if self.child_cset and not self.child_cset.parents:
725 self.child_cset.parents[:] = [rev]
725 self.child_cset.parents[:] = [rev]
726 self.child_cset = cset
726 self.child_cset = cset
727 return cset, branched
727 return cset, branched
728
728
729 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
729 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
730 (self.module, from_revnum, to_revnum))
730 (self.module, from_revnum, to_revnum))
731
731
732 try:
732 try:
733 firstcset = None
733 firstcset = None
734 branched = False
734 branched = False
735 stream = get_log(self.url, [self.module], from_revnum, to_revnum)
735 stream = get_log(self.url, [self.module], from_revnum, to_revnum)
736 try:
736 try:
737 for entry in stream:
737 for entry in stream:
738 paths, revnum, author, date, message = entry
738 paths, revnum, author, date, message = entry
739 if self.is_blacklisted(revnum):
739 if self.is_blacklisted(revnum):
740 self.ui.note('skipping blacklisted revision %d\n'
740 self.ui.note('skipping blacklisted revision %d\n'
741 % revnum)
741 % revnum)
742 continue
742 continue
743 if paths is None:
743 if paths is None:
744 self.ui.debug('revision %d has no entries\n' % revnum)
744 self.ui.debug('revision %d has no entries\n' % revnum)
745 continue
745 continue
746 cset, branched = parselogentry(paths, revnum, author,
746 cset, branched = parselogentry(paths, revnum, author,
747 date, message)
747 date, message)
748 if cset:
748 if cset:
749 firstcset = cset
749 firstcset = cset
750 if branched:
750 if branched:
751 break
751 break
752 finally:
752 finally:
753 stream.close()
753 stream.close()
754
754
755 if not branched and firstcset and not firstcset.parents:
755 if not branched and firstcset and not firstcset.parents:
756 # The first revision of the sequence (the last fetched one)
756 # The first revision of the sequence (the last fetched one)
757 # has invalid parents if not a branch root. Find the parent
757 # has invalid parents if not a branch root. Find the parent
758 # revision now, if any.
758 # revision now, if any.
759 try:
759 try:
760 firstrevnum = self.revnum(firstcset.rev)
760 firstrevnum = self.revnum(firstcset.rev)
761 if firstrevnum > 1:
761 if firstrevnum > 1:
762 latest = self.latest(self.module, firstrevnum - 1)
762 latest = self.latest(self.module, firstrevnum - 1)
763 if latest:
763 if latest:
764 firstcset.parents.append(latest)
764 firstcset.parents.append(latest)
765 except util.Abort:
765 except util.Abort:
766 pass
766 pass
767 except SubversionException, (inst, num):
767 except SubversionException, (inst, num):
768 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
768 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
769 raise NoSuchRevision(branch=self,
769 raise NoSuchRevision(branch=self,
770 revision="Revision number %d" % to_revnum)
770 revision="Revision number %d" % to_revnum)
771 raise
771 raise
772
772
773 def _getfile(self, file, rev):
773 def _getfile(self, file, rev):
774 io = StringIO()
774 io = StringIO()
775 # TODO: ra.get_file transmits the whole file instead of diffs.
775 # TODO: ra.get_file transmits the whole file instead of diffs.
776 mode = ''
776 mode = ''
777 try:
777 try:
778 new_module, revnum = self.revsplit(rev)[1:]
778 new_module, revnum = self.revsplit(rev)[1:]
779 if self.module != new_module:
779 if self.module != new_module:
780 self.module = new_module
780 self.module = new_module
781 self.reparent(self.module)
781 self.reparent(self.module)
782 info = svn.ra.get_file(self.ra, file, revnum, io)
782 info = svn.ra.get_file(self.ra, file, revnum, io)
783 if isinstance(info, list):
783 if isinstance(info, list):
784 info = info[-1]
784 info = info[-1]
785 mode = ("svn:executable" in info) and 'x' or ''
785 mode = ("svn:executable" in info) and 'x' or ''
786 mode = ("svn:special" in info) and 'l' or mode
786 mode = ("svn:special" in info) and 'l' or mode
787 except SubversionException, e:
787 except SubversionException, e:
788 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
788 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
789 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
789 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
790 if e.apr_err in notfound: # File not found
790 if e.apr_err in notfound: # File not found
791 raise IOError()
791 raise IOError()
792 raise
792 raise
793 data = io.getvalue()
793 data = io.getvalue()
794 if mode == 'l':
794 if mode == 'l':
795 link_prefix = "link "
795 link_prefix = "link "
796 if data.startswith(link_prefix):
796 if data.startswith(link_prefix):
797 data = data[len(link_prefix):]
797 data = data[len(link_prefix):]
798 return data, mode
798 return data, mode
799
799
800 def _find_children(self, path, revnum):
800 def _find_children(self, path, revnum):
801 path = path.strip('/')
801 path = path.strip('/')
802 pool = Pool()
802 pool = Pool()
803 rpath = '/'.join([self.base, path]).strip('/')
803 rpath = '/'.join([self.base, path]).strip('/')
804 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
804 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
805
805
806 pre_revprop_change = '''#!/bin/sh
806 pre_revprop_change = '''#!/bin/sh
807
807
808 REPOS="$1"
808 REPOS="$1"
809 REV="$2"
809 REV="$2"
810 USER="$3"
810 USER="$3"
811 PROPNAME="$4"
811 PROPNAME="$4"
812 ACTION="$5"
812 ACTION="$5"
813
813
814 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
814 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
815 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
815 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
816 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
816 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
817
817
818 echo "Changing prohibited revision property" >&2
818 echo "Changing prohibited revision property" >&2
819 exit 1
819 exit 1
820 '''
820 '''
821
821
822 class svn_sink(converter_sink, commandline):
822 class svn_sink(converter_sink, commandline):
823 commit_re = re.compile(r'Committed revision (\d+).', re.M)
823 commit_re = re.compile(r'Committed revision (\d+).', re.M)
824
824
825 def prerun(self):
825 def prerun(self):
826 if self.wc:
826 if self.wc:
827 os.chdir(self.wc)
827 os.chdir(self.wc)
828
828
829 def postrun(self):
829 def postrun(self):
830 if self.wc:
830 if self.wc:
831 os.chdir(self.cwd)
831 os.chdir(self.cwd)
832
832
833 def join(self, name):
833 def join(self, name):
834 return os.path.join(self.wc, '.svn', name)
834 return os.path.join(self.wc, '.svn', name)
835
835
836 def revmapfile(self):
836 def revmapfile(self):
837 return self.join('hg-shamap')
837 return self.join('hg-shamap')
838
838
839 def authorfile(self):
839 def authorfile(self):
840 return self.join('hg-authormap')
840 return self.join('hg-authormap')
841
841
842 def __init__(self, ui, path):
842 def __init__(self, ui, path):
843 converter_sink.__init__(self, ui, path)
843 converter_sink.__init__(self, ui, path)
844 commandline.__init__(self, ui, 'svn')
844 commandline.__init__(self, ui, 'svn')
845 self.delete = []
845 self.delete = []
846 self.setexec = []
846 self.setexec = []
847 self.delexec = []
847 self.delexec = []
848 self.copies = []
848 self.copies = []
849 self.wc = None
849 self.wc = None
850 self.cwd = os.getcwd()
850 self.cwd = os.getcwd()
851
851
852 path = os.path.realpath(path)
852 path = os.path.realpath(path)
853
853
854 created = False
854 created = False
855 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
855 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
856 self.wc = path
856 self.wc = path
857 self.run0('update')
857 self.run0('update')
858 else:
858 else:
859 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
859 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
860
860
861 if os.path.isdir(os.path.dirname(path)):
861 if os.path.isdir(os.path.dirname(path)):
862 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
862 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
863 ui.status(_('initializing svn repo %r\n') %
863 ui.status(_('initializing svn repo %r\n') %
864 os.path.basename(path))
864 os.path.basename(path))
865 commandline(ui, 'svnadmin').run0('create', path)
865 commandline(ui, 'svnadmin').run0('create', path)
866 created = path
866 created = path
867 path = util.normpath(path)
867 path = util.normpath(path)
868 if not path.startswith('/'):
868 if not path.startswith('/'):
869 path = '/' + path
869 path = '/' + path
870 path = 'file://' + path
870 path = 'file://' + path
871
871
872 ui.status(_('initializing svn wc %r\n') % os.path.basename(wcpath))
872 ui.status(_('initializing svn wc %r\n') % os.path.basename(wcpath))
873 self.run0('checkout', path, wcpath)
873 self.run0('checkout', path, wcpath)
874
874
875 self.wc = wcpath
875 self.wc = wcpath
876 self.opener = util.opener(self.wc)
876 self.opener = util.opener(self.wc)
877 self.wopener = util.opener(self.wc)
877 self.wopener = util.opener(self.wc)
878 self.childmap = mapfile(ui, self.join('hg-childmap'))
878 self.childmap = mapfile(ui, self.join('hg-childmap'))
879 self.is_exec = util.checkexec(self.wc) and util.is_exec or None
879 self.is_exec = util.checkexec(self.wc) and util.is_exec or None
880
880
881 if created:
881 if created:
882 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
882 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
883 fp = open(hook, 'w')
883 fp = open(hook, 'w')
884 fp.write(pre_revprop_change)
884 fp.write(pre_revprop_change)
885 fp.close()
885 fp.close()
886 util.set_flags(hook, "x")
886 util.set_flags(hook, "x")
887
887
888 xport = transport.SvnRaTransport(url=geturl(path))
888 xport = transport.SvnRaTransport(url=geturl(path))
889 self.uuid = svn.ra.get_uuid(xport.ra)
889 self.uuid = svn.ra.get_uuid(xport.ra)
890
890
891 def wjoin(self, *names):
891 def wjoin(self, *names):
892 return os.path.join(self.wc, *names)
892 return os.path.join(self.wc, *names)
893
893
894 def putfile(self, filename, flags, data):
894 def putfile(self, filename, flags, data):
895 if 'l' in flags:
895 if 'l' in flags:
896 self.wopener.symlink(data, filename)
896 self.wopener.symlink(data, filename)
897 else:
897 else:
898 try:
898 try:
899 if os.path.islink(self.wjoin(filename)):
899 if os.path.islink(self.wjoin(filename)):
900 os.unlink(filename)
900 os.unlink(filename)
901 except OSError:
901 except OSError:
902 pass
902 pass
903 self.wopener(filename, 'w').write(data)
903 self.wopener(filename, 'w').write(data)
904
904
905 if self.is_exec:
905 if self.is_exec:
906 was_exec = self.is_exec(self.wjoin(filename))
906 was_exec = self.is_exec(self.wjoin(filename))
907 else:
907 else:
908 # On filesystems not supporting execute-bit, there is no way
908 # On filesystems not supporting execute-bit, there is no way
909 # to know if it is set but asking subversion. Setting it
909 # to know if it is set but asking subversion. Setting it
910 # systematically is just as expensive and much simpler.
910 # systematically is just as expensive and much simpler.
911 was_exec = 'x' not in flags
911 was_exec = 'x' not in flags
912
912
913 util.set_flags(self.wjoin(filename), flags)
913 util.set_flags(self.wjoin(filename), flags)
914 if was_exec:
914 if was_exec:
915 if 'x' not in flags:
915 if 'x' not in flags:
916 self.delexec.append(filename)
916 self.delexec.append(filename)
917 else:
917 else:
918 if 'x' in flags:
918 if 'x' in flags:
919 self.setexec.append(filename)
919 self.setexec.append(filename)
920
920
921 def delfile(self, name):
921 def delfile(self, name):
922 self.delete.append(name)
922 self.delete.append(name)
923
923
924 def copyfile(self, source, dest):
924 def copyfile(self, source, dest):
925 self.copies.append([source, dest])
925 self.copies.append([source, dest])
926
926
927 def _copyfile(self, source, dest):
927 def _copyfile(self, source, dest):
928 # SVN's copy command pukes if the destination file exists, but
928 # SVN's copy command pukes if the destination file exists, but
929 # our copyfile method expects to record a copy that has
929 # our copyfile method expects to record a copy that has
930 # already occurred. Cross the semantic gap.
930 # already occurred. Cross the semantic gap.
931 wdest = self.wjoin(dest)
931 wdest = self.wjoin(dest)
932 exists = os.path.exists(wdest)
932 exists = os.path.exists(wdest)
933 if exists:
933 if exists:
934 fd, tempname = tempfile.mkstemp(
934 fd, tempname = tempfile.mkstemp(
935 prefix='hg-copy-', dir=os.path.dirname(wdest))
935 prefix='hg-copy-', dir=os.path.dirname(wdest))
936 os.close(fd)
936 os.close(fd)
937 os.unlink(tempname)
937 os.unlink(tempname)
938 os.rename(wdest, tempname)
938 os.rename(wdest, tempname)
939 try:
939 try:
940 self.run0('copy', source, dest)
940 self.run0('copy', source, dest)
941 finally:
941 finally:
942 if exists:
942 if exists:
943 try:
943 try:
944 os.unlink(wdest)
944 os.unlink(wdest)
945 except OSError:
945 except OSError:
946 pass
946 pass
947 os.rename(tempname, wdest)
947 os.rename(tempname, wdest)
948
948
949 def dirs_of(self, files):
949 def dirs_of(self, files):
950 dirs = set()
950 dirs = util.set()
951 for f in files:
951 for f in files:
952 if os.path.isdir(self.wjoin(f)):
952 if os.path.isdir(self.wjoin(f)):
953 dirs.add(f)
953 dirs.add(f)
954 for i in strutil.rfindall(f, '/'):
954 for i in strutil.rfindall(f, '/'):
955 dirs.add(f[:i])
955 dirs.add(f[:i])
956 return dirs
956 return dirs
957
957
958 def add_dirs(self, files):
958 def add_dirs(self, files):
959 add_dirs = [d for d in self.dirs_of(files)
959 add_dirs = [d for d in self.dirs_of(files)
960 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
960 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
961 if add_dirs:
961 if add_dirs:
962 add_dirs.sort()
962 add_dirs.sort()
963 self.xargs(add_dirs, 'add', non_recursive=True, quiet=True)
963 self.xargs(add_dirs, 'add', non_recursive=True, quiet=True)
964 return add_dirs
964 return add_dirs
965
965
966 def add_files(self, files):
966 def add_files(self, files):
967 if files:
967 if files:
968 self.xargs(files, 'add', quiet=True)
968 self.xargs(files, 'add', quiet=True)
969 return files
969 return files
970
970
971 def tidy_dirs(self, names):
971 def tidy_dirs(self, names):
972 dirs = list(self.dirs_of(names))
972 dirs = list(self.dirs_of(names))
973 dirs.sort(reverse=True)
973 dirs.sort()
974 dirs.reverse()
974 deleted = []
975 deleted = []
975 for d in dirs:
976 for d in dirs:
976 wd = self.wjoin(d)
977 wd = self.wjoin(d)
977 if os.listdir(wd) == '.svn':
978 if os.listdir(wd) == '.svn':
978 self.run0('delete', d)
979 self.run0('delete', d)
979 deleted.append(d)
980 deleted.append(d)
980 return deleted
981 return deleted
981
982
982 def addchild(self, parent, child):
983 def addchild(self, parent, child):
983 self.childmap[parent] = child
984 self.childmap[parent] = child
984
985
985 def revid(self, rev):
986 def revid(self, rev):
986 return u"svn:%s@%s" % (self.uuid, rev)
987 return u"svn:%s@%s" % (self.uuid, rev)
987
988
988 def putcommit(self, files, parents, commit):
989 def putcommit(self, files, parents, commit):
989 for parent in parents:
990 for parent in parents:
990 try:
991 try:
991 return self.revid(self.childmap[parent])
992 return self.revid(self.childmap[parent])
992 except KeyError:
993 except KeyError:
993 pass
994 pass
994 entries = set(self.delete)
995 entries = util.set(self.delete)
995 files = util.frozenset(files)
996 files = util.frozenset(files)
996 entries.update(self.add_dirs(files.difference(entries)))
997 entries.update(self.add_dirs(files.difference(entries)))
997 if self.copies:
998 if self.copies:
998 for s, d in self.copies:
999 for s, d in self.copies:
999 self._copyfile(s, d)
1000 self._copyfile(s, d)
1000 self.copies = []
1001 self.copies = []
1001 if self.delete:
1002 if self.delete:
1002 self.xargs(self.delete, 'delete')
1003 self.xargs(self.delete, 'delete')
1003 self.delete = []
1004 self.delete = []
1004 entries.update(self.add_files(files.difference(entries)))
1005 entries.update(self.add_files(files.difference(entries)))
1005 entries.update(self.tidy_dirs(entries))
1006 entries.update(self.tidy_dirs(entries))
1006 if self.delexec:
1007 if self.delexec:
1007 self.xargs(self.delexec, 'propdel', 'svn:executable')
1008 self.xargs(self.delexec, 'propdel', 'svn:executable')
1008 self.delexec = []
1009 self.delexec = []
1009 if self.setexec:
1010 if self.setexec:
1010 self.xargs(self.setexec, 'propset', 'svn:executable', '*')
1011 self.xargs(self.setexec, 'propset', 'svn:executable', '*')
1011 self.setexec = []
1012 self.setexec = []
1012
1013
1013 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
1014 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
1014 fp = os.fdopen(fd, 'w')
1015 fp = os.fdopen(fd, 'w')
1015 fp.write(commit.desc)
1016 fp.write(commit.desc)
1016 fp.close()
1017 fp.close()
1017 try:
1018 try:
1018 output = self.run0('commit',
1019 output = self.run0('commit',
1019 username=util.shortuser(commit.author),
1020 username=util.shortuser(commit.author),
1020 file=messagefile,
1021 file=messagefile,
1021 encoding='utf-8')
1022 encoding='utf-8')
1022 try:
1023 try:
1023 rev = self.commit_re.search(output).group(1)
1024 rev = self.commit_re.search(output).group(1)
1024 except AttributeError:
1025 except AttributeError:
1025 self.ui.warn(_('unexpected svn output:\n'))
1026 self.ui.warn(_('unexpected svn output:\n'))
1026 self.ui.warn(output)
1027 self.ui.warn(output)
1027 raise util.Abort(_('unable to cope with svn output'))
1028 raise util.Abort(_('unable to cope with svn output'))
1028 if commit.rev:
1029 if commit.rev:
1029 self.run('propset', 'hg:convert-rev', commit.rev,
1030 self.run('propset', 'hg:convert-rev', commit.rev,
1030 revprop=True, revision=rev)
1031 revprop=True, revision=rev)
1031 if commit.branch and commit.branch != 'default':
1032 if commit.branch and commit.branch != 'default':
1032 self.run('propset', 'hg:convert-branch', commit.branch,
1033 self.run('propset', 'hg:convert-branch', commit.branch,
1033 revprop=True, revision=rev)
1034 revprop=True, revision=rev)
1034 for parent in parents:
1035 for parent in parents:
1035 self.addchild(parent, rev)
1036 self.addchild(parent, rev)
1036 return self.revid(rev)
1037 return self.revid(rev)
1037 finally:
1038 finally:
1038 os.unlink(messagefile)
1039 os.unlink(messagefile)
1039
1040
1040 def puttags(self, tags):
1041 def puttags(self, tags):
1041 self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n'))
1042 self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n'))
@@ -1,219 +1,251 b''
1 # extdiff.py - external diff program support for mercurial
1 # extdiff.py - external diff program support for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 '''
8 '''
9 The `extdiff' Mercurial extension allows you to use external programs
9 The `extdiff' Mercurial extension allows you to use external programs
10 to compare revisions, or revision with working dir. The external diff
10 to compare revisions, or revision with working dir. The external diff
11 programs are called with a configurable set of options and two
11 programs are called with a configurable set of options and two
12 non-option arguments: paths to directories containing snapshots of
12 non-option arguments: paths to directories containing snapshots of
13 files to compare.
13 files to compare.
14
14
15 To enable this extension:
15 To enable this extension:
16
16
17 [extensions]
17 [extensions]
18 hgext.extdiff =
18 hgext.extdiff =
19
19
20 The `extdiff' extension also allows to configure new diff commands, so
20 The `extdiff' extension also allows to configure new diff commands, so
21 you do not need to type "hg extdiff -p kdiff3" always.
21 you do not need to type "hg extdiff -p kdiff3" always.
22
22
23 [extdiff]
23 [extdiff]
24 # add new command that runs GNU diff(1) in 'context diff' mode
24 # add new command that runs GNU diff(1) in 'context diff' mode
25 cdiff = gdiff -Nprc5
25 cdiff = gdiff -Nprc5
26 ## or the old way:
26 ## or the old way:
27 #cmd.cdiff = gdiff
27 #cmd.cdiff = gdiff
28 #opts.cdiff = -Nprc5
28 #opts.cdiff = -Nprc5
29
29
30 # add new command called vdiff, runs kdiff3
30 # add new command called vdiff, runs kdiff3
31 vdiff = kdiff3
31 vdiff = kdiff3
32
32
33 # add new command called meld, runs meld (no need to name twice)
33 # add new command called meld, runs meld (no need to name twice)
34 meld =
34 meld =
35
35
36 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
36 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
37 #(see http://www.vim.org/scripts/script.php?script_id=102)
37 #(see http://www.vim.org/scripts/script.php?script_id=102)
38 # Non english user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
38 # Non english user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
39 # your .vimrc
39 # your .vimrc
40 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
40 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
41
41
42 You can use -I/-X and list of file or directory names like normal
42 You can use -I/-X and list of file or directory names like normal
43 "hg diff" command. The `extdiff' extension makes snapshots of only
43 "hg diff" command. The `extdiff' extension makes snapshots of only
44 needed files, so running the external diff program will actually be
44 needed files, so running the external diff program will actually be
45 pretty fast (at least faster than having to compare the entire tree).
45 pretty fast (at least faster than having to compare the entire tree).
46 '''
46 '''
47
47
48 from mercurial.i18n import _
48 from mercurial.i18n import _
49 from mercurial.node import *
49 from mercurial.node import *
50 from mercurial import cmdutil, util, commands
50 from mercurial import cmdutil, util, commands
51 import os, shlex, shutil, tempfile
51 import os, shlex, shutil, tempfile
52
52
53 def snapshot_node(ui, repo, files, node, tmproot):
53 def snapshot_node(ui, repo, files, node, tmproot):
54 '''snapshot files as of some revision'''
54 '''snapshot files as of some revision'''
55 mf = repo.changectx(node).manifest()
55 mf = repo.changectx(node).manifest()
56 dirname = os.path.basename(repo.root)
56 dirname = os.path.basename(repo.root)
57 if dirname == "":
57 if dirname == "":
58 dirname = "root"
58 dirname = "root"
59 dirname = '%s.%s' % (dirname, short(node))
59 dirname = '%s.%s' % (dirname, short(node))
60 base = os.path.join(tmproot, dirname)
60 base = os.path.join(tmproot, dirname)
61 os.mkdir(base)
61 os.mkdir(base)
62 ui.note(_('making snapshot of %d files from rev %s\n') %
62 ui.note(_('making snapshot of %d files from rev %s\n') %
63 (len(files), short(node)))
63 (len(files), short(node)))
64 for fn in files:
64 for fn in files:
65 if not fn in mf:
65 if not fn in mf:
66 # skipping new file after a merge ?
66 # skipping new file after a merge ?
67 continue
67 continue
68 wfn = util.pconvert(fn)
68 wfn = util.pconvert(fn)
69 ui.note(' %s\n' % wfn)
69 ui.note(' %s\n' % wfn)
70 dest = os.path.join(base, wfn)
70 dest = os.path.join(base, wfn)
71 destdir = os.path.dirname(dest)
71 destdir = os.path.dirname(dest)
72 if not os.path.isdir(destdir):
72 if not os.path.isdir(destdir):
73 os.makedirs(destdir)
73 os.makedirs(destdir)
74 data = repo.wwritedata(wfn, repo.file(wfn).read(mf[wfn]))
74 data = repo.wwritedata(wfn, repo.file(wfn).read(mf[wfn]))
75 open(dest, 'wb').write(data)
75 open(dest, 'wb').write(data)
76 return dirname
76 return dirname
77
77
78
78
79 def snapshot_wdir(ui, repo, files, tmproot):
79 def snapshot_wdir(ui, repo, files, tmproot):
80 '''snapshot files from working directory.
80 '''snapshot files from working directory.
81 if not using snapshot, -I/-X does not work and recursive diff
81 if not using snapshot, -I/-X does not work and recursive diff
82 in tools like kdiff3 and meld displays too many files.'''
82 in tools like kdiff3 and meld displays too many files.'''
83 dirname = os.path.basename(repo.root)
83 repo_root = repo.root
84
85 dirname = os.path.basename(repo_root)
84 if dirname == "":
86 if dirname == "":
85 dirname = "root"
87 dirname = "root"
86 base = os.path.join(tmproot, dirname)
88 base = os.path.join(tmproot, dirname)
87 os.mkdir(base)
89 os.mkdir(base)
88 ui.note(_('making snapshot of %d files from working dir\n') %
90 ui.note(_('making snapshot of %d files from working dir\n') %
89 (len(files)))
91 (len(files)))
92
93 fns_and_mtime = []
94
90 for fn in files:
95 for fn in files:
91 wfn = util.pconvert(fn)
96 wfn = util.pconvert(fn)
92 ui.note(' %s\n' % wfn)
97 ui.note(' %s\n' % wfn)
93 dest = os.path.join(base, wfn)
98 dest = os.path.join(base, wfn)
94 destdir = os.path.dirname(dest)
99 destdir = os.path.dirname(dest)
95 if not os.path.isdir(destdir):
100 if not os.path.isdir(destdir):
96 os.makedirs(destdir)
101 os.makedirs(destdir)
102
97 fp = open(dest, 'wb')
103 fp = open(dest, 'wb')
98 for chunk in util.filechunkiter(repo.wopener(wfn)):
104 for chunk in util.filechunkiter(repo.wopener(wfn)):
99 fp.write(chunk)
105 fp.write(chunk)
100 return dirname
106 fp.close()
107
108 fns_and_mtime.append((dest, os.path.join(repo_root, fn),
109 os.path.getmtime(dest)))
110
111
112 return dirname, fns_and_mtime
101
113
102
114
103 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
115 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
116 '''Do the actuall diff:
117
118 - copy to a temp structure if diffing 2 internal revisions
119 - copy to a temp structure if diffing working revision with
120 another one and more than 1 file is changed
121 - just invoke the diff for a single file in the working dir
122 '''
104 node1, node2 = cmdutil.revpair(repo, opts['rev'])
123 node1, node2 = cmdutil.revpair(repo, opts['rev'])
105 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
124 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
106 modified, added, removed, deleted, unknown = repo.status(
125 modified, added, removed, deleted, unknown = repo.status(
107 node1, node2, files, match=matchfn)[:5]
126 node1, node2, files, match=matchfn)[:5]
108 if not (modified or added or removed):
127 if not (modified or added or removed):
109 return 0
128 return 0
110
129
111 tmproot = tempfile.mkdtemp(prefix='extdiff.')
130 tmproot = tempfile.mkdtemp(prefix='extdiff.')
112 dir2root = ''
131 dir2root = ''
113 try:
132 try:
114 # Always make a copy of node1
133 # Always make a copy of node1
115 dir1 = snapshot_node(ui, repo, modified + removed, node1, tmproot)
134 dir1 = snapshot_node(ui, repo, modified + removed, node1, tmproot)
116 changes = len(modified) + len(removed) + len(added)
135 changes = len(modified) + len(removed) + len(added)
117
136
137 fns_and_mtime = []
138
118 # If node2 in not the wc or there is >1 change, copy it
139 # If node2 in not the wc or there is >1 change, copy it
119 if node2:
140 if node2:
120 dir2 = snapshot_node(ui, repo, modified + added, node2, tmproot)
141 dir2 = snapshot_node(ui, repo, modified + added, node2, tmproot)
121 elif changes > 1:
142 elif changes > 1:
122 dir2 = snapshot_wdir(ui, repo, modified + added, tmproot)
143 #we only actually need to get the files to copy back to the working
144 #dir in this case (because the other cases are: diffing 2 revisions
145 #or single file -- in which case the file is already directly passed
146 #to the diff tool).
147 dir2, fns_and_mtime = snapshot_wdir(ui, repo, modified + added, tmproot)
123 else:
148 else:
124 # This lets the diff tool open the changed file directly
149 # This lets the diff tool open the changed file directly
125 dir2 = ''
150 dir2 = ''
126 dir2root = repo.root
151 dir2root = repo.root
127
152
128 # If only one change, diff the files instead of the directories
153 # If only one change, diff the files instead of the directories
129 if changes == 1 :
154 if changes == 1 :
130 if len(modified):
155 if len(modified):
131 dir1 = os.path.join(dir1, util.localpath(modified[0]))
156 dir1 = os.path.join(dir1, util.localpath(modified[0]))
132 dir2 = os.path.join(dir2root, dir2, util.localpath(modified[0]))
157 dir2 = os.path.join(dir2root, dir2, util.localpath(modified[0]))
133 elif len(removed) :
158 elif len(removed) :
134 dir1 = os.path.join(dir1, util.localpath(removed[0]))
159 dir1 = os.path.join(dir1, util.localpath(removed[0]))
135 dir2 = os.devnull
160 dir2 = os.devnull
136 else:
161 else:
137 dir1 = os.devnull
162 dir1 = os.devnull
138 dir2 = os.path.join(dir2root, dir2, util.localpath(added[0]))
163 dir2 = os.path.join(dir2root, dir2, util.localpath(added[0]))
139
164
140 cmdline = ('%s %s %s %s' %
165 cmdline = ('%s %s %s %s' %
141 (util.shellquote(diffcmd), ' '.join(diffopts),
166 (util.shellquote(diffcmd), ' '.join(diffopts),
142 util.shellquote(dir1), util.shellquote(dir2)))
167 util.shellquote(dir1), util.shellquote(dir2)))
143 ui.debug('running %r in %s\n' % (cmdline, tmproot))
168 ui.debug('running %r in %s\n' % (cmdline, tmproot))
144 util.system(cmdline, cwd=tmproot)
169 util.system(cmdline, cwd=tmproot)
170
171 for copy_fn, working_fn, mtime in fns_and_mtime:
172 if os.path.getmtime(copy_fn) != mtime:
173 ui.debug('File changed while diffing. '
174 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
175 util.copyfile(copy_fn, working_fn)
176
145 return 1
177 return 1
146 finally:
178 finally:
147 ui.note(_('cleaning up temp directory\n'))
179 ui.note(_('cleaning up temp directory\n'))
148 shutil.rmtree(tmproot)
180 shutil.rmtree(tmproot)
149
181
150 def extdiff(ui, repo, *pats, **opts):
182 def extdiff(ui, repo, *pats, **opts):
151 '''use external program to diff repository (or selected files)
183 '''use external program to diff repository (or selected files)
152
184
153 Show differences between revisions for the specified files, using
185 Show differences between revisions for the specified files, using
154 an external program. The default program used is diff, with
186 an external program. The default program used is diff, with
155 default options "-Npru".
187 default options "-Npru".
156
188
157 To select a different program, use the -p option. The program
189 To select a different program, use the -p option. The program
158 will be passed the names of two directories to compare. To pass
190 will be passed the names of two directories to compare. To pass
159 additional options to the program, use the -o option. These will
191 additional options to the program, use the -o option. These will
160 be passed before the names of the directories to compare.
192 be passed before the names of the directories to compare.
161
193
162 When two revision arguments are given, then changes are
194 When two revision arguments are given, then changes are
163 shown between those revisions. If only one revision is
195 shown between those revisions. If only one revision is
164 specified then that revision is compared to the working
196 specified then that revision is compared to the working
165 directory, and, when no revisions are specified, the
197 directory, and, when no revisions are specified, the
166 working directory files are compared to its parent.'''
198 working directory files are compared to its parent.'''
167 program = opts['program'] or 'diff'
199 program = opts['program'] or 'diff'
168 if opts['program']:
200 if opts['program']:
169 option = opts['option']
201 option = opts['option']
170 else:
202 else:
171 option = opts['option'] or ['-Npru']
203 option = opts['option'] or ['-Npru']
172 return dodiff(ui, repo, program, option, pats, opts)
204 return dodiff(ui, repo, program, option, pats, opts)
173
205
174 cmdtable = {
206 cmdtable = {
175 "extdiff":
207 "extdiff":
176 (extdiff,
208 (extdiff,
177 [('p', 'program', '', _('comparison program to run')),
209 [('p', 'program', '', _('comparison program to run')),
178 ('o', 'option', [], _('pass option to comparison program')),
210 ('o', 'option', [], _('pass option to comparison program')),
179 ('r', 'rev', [], _('revision')),
211 ('r', 'rev', [], _('revision')),
180 ] + commands.walkopts,
212 ] + commands.walkopts,
181 _('hg extdiff [OPT]... [FILE]...')),
213 _('hg extdiff [OPT]... [FILE]...')),
182 }
214 }
183
215
184 def uisetup(ui):
216 def uisetup(ui):
185 for cmd, path in ui.configitems('extdiff'):
217 for cmd, path in ui.configitems('extdiff'):
186 if cmd.startswith('cmd.'):
218 if cmd.startswith('cmd.'):
187 cmd = cmd[4:]
219 cmd = cmd[4:]
188 if not path: path = cmd
220 if not path: path = cmd
189 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
221 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
190 diffopts = diffopts and [diffopts] or []
222 diffopts = diffopts and [diffopts] or []
191 elif cmd.startswith('opts.'):
223 elif cmd.startswith('opts.'):
192 continue
224 continue
193 else:
225 else:
194 # command = path opts
226 # command = path opts
195 if path:
227 if path:
196 diffopts = shlex.split(path)
228 diffopts = shlex.split(path)
197 path = diffopts.pop(0)
229 path = diffopts.pop(0)
198 else:
230 else:
199 path, diffopts = cmd, []
231 path, diffopts = cmd, []
200 def save(cmd, path, diffopts):
232 def save(cmd, path, diffopts):
201 '''use closure to save diff command to use'''
233 '''use closure to save diff command to use'''
202 def mydiff(ui, repo, *pats, **opts):
234 def mydiff(ui, repo, *pats, **opts):
203 return dodiff(ui, repo, path, diffopts, pats, opts)
235 return dodiff(ui, repo, path, diffopts, pats, opts)
204 mydiff.__doc__ = '''use %(path)s to diff repository (or selected files)
236 mydiff.__doc__ = '''use %(path)s to diff repository (or selected files)
205
237
206 Show differences between revisions for the specified
238 Show differences between revisions for the specified
207 files, using the %(path)s program.
239 files, using the %(path)s program.
208
240
209 When two revision arguments are given, then changes are
241 When two revision arguments are given, then changes are
210 shown between those revisions. If only one revision is
242 shown between those revisions. If only one revision is
211 specified then that revision is compared to the working
243 specified then that revision is compared to the working
212 directory, and, when no revisions are specified, the
244 directory, and, when no revisions are specified, the
213 working directory files are compared to its parent.''' % {
245 working directory files are compared to its parent.''' % {
214 'path': util.uirepr(path),
246 'path': util.uirepr(path),
215 }
247 }
216 return mydiff
248 return mydiff
217 cmdtable[cmd] = (save(cmd, path, diffopts),
249 cmdtable[cmd] = (save(cmd, path, diffopts),
218 cmdtable['extdiff'][1][1:],
250 cmdtable['extdiff'][1][1:],
219 _('hg %s [OPTION]... [FILE]...') % cmd)
251 _('hg %s [OPTION]... [FILE]...') % cmd)
@@ -1,95 +1,99 b''
1 # fetch.py - pull and merge remote changes
1 # fetch.py - pull and merge remote changes
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from mercurial.i18n import _
8 from mercurial.i18n import _
9 from mercurial.node import *
9 from mercurial.node import *
10 from mercurial import commands, cmdutil, hg, node, util
10 from mercurial import commands, cmdutil, hg, node, util
11
11
12 def fetch(ui, repo, source='default', **opts):
12 def fetch(ui, repo, source='default', **opts):
13 '''Pull changes from a remote repository, merge new changes if needed.
13 '''Pull changes from a remote repository, merge new changes if needed.
14
14
15 This finds all changes from the repository at the specified path
15 This finds all changes from the repository at the specified path
16 or URL and adds them to the local repository.
16 or URL and adds them to the local repository.
17
17
18 If the pulled changes add a new head, the head is automatically
18 If the pulled changes add a new head, the head is automatically
19 merged, and the result of the merge is committed. Otherwise, the
19 merged, and the result of the merge is committed. Otherwise, the
20 working directory is updated.'''
20 working directory is updated.'''
21
21
22 def postincoming(other, modheads):
22 def postincoming(other, modheads):
23 if modheads == 0:
23 if modheads == 0:
24 return 0
24 return 0
25 if modheads == 1:
25 if modheads == 1:
26 return hg.clean(repo, repo.changelog.tip())
26 return hg.clean(repo, repo.changelog.tip())
27 newheads = repo.heads(parent)
27 newheads = repo.heads(parent)
28 newchildren = [n for n in repo.heads(parent) if n != parent]
28 newchildren = [n for n in repo.heads(parent) if n != parent]
29 newparent = parent
29 newparent = parent
30 if newchildren:
30 if newchildren:
31 newparent = newchildren[0]
31 newparent = newchildren[0]
32 hg.clean(repo, newparent)
32 hg.clean(repo, newparent)
33 newheads = [n for n in repo.heads() if n != newparent]
33 newheads = [n for n in repo.heads() if n != newparent]
34 err = False
34 err = False
35 if newheads:
35 if newheads:
36 ui.status(_('merging with new head %d:%s\n') %
36 ui.status(_('merging with new head %d:%s\n') %
37 (repo.changelog.rev(newheads[0]), short(newheads[0])))
37 (repo.changelog.rev(newheads[0]), short(newheads[0])))
38 err = hg.merge(repo, newheads[0], remind=False)
38 err = hg.merge(repo, newheads[0], remind=False)
39 if not err and len(newheads) > 1:
39 if not err and len(newheads) > 1:
40 ui.status(_('not merging with %d other new heads '
40 ui.status(_('not merging with %d other new heads '
41 '(use "hg heads" and "hg merge" to merge them)') %
41 '(use "hg heads" and "hg merge" to merge them)') %
42 (len(newheads) - 1))
42 (len(newheads) - 1))
43 if not err:
43 if not err:
44 mod, add, rem = repo.status()[:3]
44 mod, add, rem = repo.status()[:3]
45 message = (cmdutil.logmessage(opts) or
45 message = (cmdutil.logmessage(opts) or
46 (_('Automated merge with %s') %
46 (_('Automated merge with %s') %
47 util.removeauth(other.url())))
47 util.removeauth(other.url())))
48 n = repo.commit(mod + add + rem, message,
48 n = repo.commit(mod + add + rem, message,
49 opts['user'], opts['date'],
49 opts['user'], opts['date'],
50 force_editor=opts.get('force_editor'))
50 force_editor=opts.get('force_editor'))
51 ui.status(_('new changeset %d:%s merges remote changes '
51 ui.status(_('new changeset %d:%s merges remote changes '
52 'with local\n') % (repo.changelog.rev(n),
52 'with local\n') % (repo.changelog.rev(n),
53 short(n)))
53 short(n)))
54 def pull():
54 def pull():
55 cmdutil.setremoteconfig(ui, opts)
55 cmdutil.setremoteconfig(ui, opts)
56
56
57 other = hg.repository(ui, ui.expandpath(source))
57 other = hg.repository(ui, ui.expandpath(source))
58 ui.status(_('pulling from %s\n') %
58 ui.status(_('pulling from %s\n') %
59 util.hidepassword(ui.expandpath(source)))
59 util.hidepassword(ui.expandpath(source)))
60 revs = None
60 revs = None
61 if opts['rev'] and not other.local():
61 if opts['rev'] and not other.local():
62 raise util.Abort(_("fetch -r doesn't work for remote repositories yet"))
62 raise util.Abort(_("fetch -r doesn't work for remote repositories yet"))
63 elif opts['rev']:
63 elif opts['rev']:
64 revs = [other.lookup(rev) for rev in opts['rev']]
64 revs = [other.lookup(rev) for rev in opts['rev']]
65 modheads = repo.pull(other, heads=revs)
65 modheads = repo.pull(other, heads=revs)
66 return postincoming(other, modheads)
66 return postincoming(other, modheads)
67
67
68 date = opts.get('date')
69 if date:
70 opts['date'] = util.parsedate(date)
71
68 parent, p2 = repo.dirstate.parents()
72 parent, p2 = repo.dirstate.parents()
69 if parent != repo.changelog.tip():
73 if parent != repo.changelog.tip():
70 raise util.Abort(_('working dir not at tip '
74 raise util.Abort(_('working dir not at tip '
71 '(use "hg update" to check out tip)'))
75 '(use "hg update" to check out tip)'))
72 if p2 != nullid:
76 if p2 != nullid:
73 raise util.Abort(_('outstanding uncommitted merge'))
77 raise util.Abort(_('outstanding uncommitted merge'))
74 wlock = lock = None
78 wlock = lock = None
75 try:
79 try:
76 wlock = repo.wlock()
80 wlock = repo.wlock()
77 lock = repo.lock()
81 lock = repo.lock()
78 mod, add, rem = repo.status()[:3]
82 mod, add, rem = repo.status()[:3]
79 if mod or add or rem:
83 if mod or add or rem:
80 raise util.Abort(_('outstanding uncommitted changes'))
84 raise util.Abort(_('outstanding uncommitted changes'))
81 if len(repo.heads()) > 1:
85 if len(repo.heads()) > 1:
82 raise util.Abort(_('multiple heads in this repository '
86 raise util.Abort(_('multiple heads in this repository '
83 '(use "hg heads" and "hg merge" to merge)'))
87 '(use "hg heads" and "hg merge" to merge)'))
84 return pull()
88 return pull()
85 finally:
89 finally:
86 del lock, wlock
90 del lock, wlock
87
91
88 cmdtable = {
92 cmdtable = {
89 'fetch':
93 'fetch':
90 (fetch,
94 (fetch,
91 [('r', 'rev', [], _('a specific revision you would like to pull')),
95 [('r', 'rev', [], _('a specific revision you would like to pull')),
92 ('f', 'force-editor', None, _('edit commit message')),
96 ('f', 'force-editor', None, _('edit commit message')),
93 ] + commands.commitopts + commands.commitopts2 + commands.remoteopts,
97 ] + commands.commitopts + commands.commitopts2 + commands.remoteopts,
94 _('hg fetch [SOURCE]')),
98 _('hg fetch [SOURCE]')),
95 }
99 }
@@ -1,279 +1,284 b''
1 # GnuPG signing extension for Mercurial
1 # GnuPG signing extension for Mercurial
2 #
2 #
3 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
3 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 import os, tempfile, binascii
8 import os, tempfile, binascii
9 from mercurial import util, commands
9 from mercurial import util, commands
10 from mercurial import node as hgnode
10 from mercurial import node as hgnode
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12
12
13 class gpg:
13 class gpg:
14 def __init__(self, path, key=None):
14 def __init__(self, path, key=None):
15 self.path = path
15 self.path = path
16 self.key = (key and " --local-user \"%s\"" % key) or ""
16 self.key = (key and " --local-user \"%s\"" % key) or ""
17
17
18 def sign(self, data):
18 def sign(self, data):
19 gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key)
19 gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key)
20 return util.filter(data, gpgcmd)
20 return util.filter(data, gpgcmd)
21
21
22 def verify(self, data, sig):
22 def verify(self, data, sig):
23 """ returns of the good and bad signatures"""
23 """ returns of the good and bad signatures"""
24 sigfile = datafile = None
24 sigfile = datafile = None
25 try:
25 try:
26 # create temporary files
26 # create temporary files
27 fd, sigfile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".sig")
27 fd, sigfile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".sig")
28 fp = os.fdopen(fd, 'wb')
28 fp = os.fdopen(fd, 'wb')
29 fp.write(sig)
29 fp.write(sig)
30 fp.close()
30 fp.close()
31 fd, datafile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".txt")
31 fd, datafile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".txt")
32 fp = os.fdopen(fd, 'wb')
32 fp = os.fdopen(fd, 'wb')
33 fp.write(data)
33 fp.write(data)
34 fp.close()
34 fp.close()
35 gpgcmd = ("%s --logger-fd 1 --status-fd 1 --verify "
35 gpgcmd = ("%s --logger-fd 1 --status-fd 1 --verify "
36 "\"%s\" \"%s\"" % (self.path, sigfile, datafile))
36 "\"%s\" \"%s\"" % (self.path, sigfile, datafile))
37 ret = util.filter("", gpgcmd)
37 ret = util.filter("", gpgcmd)
38 finally:
38 finally:
39 for f in (sigfile, datafile):
39 for f in (sigfile, datafile):
40 try:
40 try:
41 if f: os.unlink(f)
41 if f: os.unlink(f)
42 except: pass
42 except: pass
43 keys = []
43 keys = []
44 key, fingerprint = None, None
44 key, fingerprint = None, None
45 err = ""
45 err = ""
46 for l in ret.splitlines():
46 for l in ret.splitlines():
47 # see DETAILS in the gnupg documentation
47 # see DETAILS in the gnupg documentation
48 # filter the logger output
48 # filter the logger output
49 if not l.startswith("[GNUPG:]"):
49 if not l.startswith("[GNUPG:]"):
50 continue
50 continue
51 l = l[9:]
51 l = l[9:]
52 if l.startswith("ERRSIG"):
52 if l.startswith("ERRSIG"):
53 err = _("error while verifying signature")
53 err = _("error while verifying signature")
54 break
54 break
55 elif l.startswith("VALIDSIG"):
55 elif l.startswith("VALIDSIG"):
56 # fingerprint of the primary key
56 # fingerprint of the primary key
57 fingerprint = l.split()[10]
57 fingerprint = l.split()[10]
58 elif (l.startswith("GOODSIG") or
58 elif (l.startswith("GOODSIG") or
59 l.startswith("EXPSIG") or
59 l.startswith("EXPSIG") or
60 l.startswith("EXPKEYSIG") or
60 l.startswith("EXPKEYSIG") or
61 l.startswith("BADSIG")):
61 l.startswith("BADSIG")):
62 if key is not None:
62 if key is not None:
63 keys.append(key + [fingerprint])
63 keys.append(key + [fingerprint])
64 key = l.split(" ", 2)
64 key = l.split(" ", 2)
65 fingerprint = None
65 fingerprint = None
66 if err:
66 if err:
67 return err, []
67 return err, []
68 if key is not None:
68 if key is not None:
69 keys.append(key + [fingerprint])
69 keys.append(key + [fingerprint])
70 return err, keys
70 return err, keys
71
71
72 def newgpg(ui, **opts):
72 def newgpg(ui, **opts):
73 """create a new gpg instance"""
73 """create a new gpg instance"""
74 gpgpath = ui.config("gpg", "cmd", "gpg")
74 gpgpath = ui.config("gpg", "cmd", "gpg")
75 gpgkey = opts.get('key')
75 gpgkey = opts.get('key')
76 if not gpgkey:
76 if not gpgkey:
77 gpgkey = ui.config("gpg", "key", None)
77 gpgkey = ui.config("gpg", "key", None)
78 return gpg(gpgpath, gpgkey)
78 return gpg(gpgpath, gpgkey)
79
79
80 def sigwalk(repo):
80 def sigwalk(repo):
81 """
81 """
82 walk over every sigs, yields a couple
82 walk over every sigs, yields a couple
83 ((node, version, sig), (filename, linenumber))
83 ((node, version, sig), (filename, linenumber))
84 """
84 """
85 def parsefile(fileiter, context):
85 def parsefile(fileiter, context):
86 ln = 1
86 ln = 1
87 for l in fileiter:
87 for l in fileiter:
88 if not l:
88 if not l:
89 continue
89 continue
90 yield (l.split(" ", 2), (context, ln))
90 yield (l.split(" ", 2), (context, ln))
91 ln +=1
91 ln +=1
92
92
93 fl = repo.file(".hgsigs")
93 fl = repo.file(".hgsigs")
94 h = fl.heads()
94 h = fl.heads()
95 h.reverse()
95 h.reverse()
96 # read the heads
96 # read the heads
97 for r in h:
97 for r in h:
98 fn = ".hgsigs|%s" % hgnode.short(r)
98 fn = ".hgsigs|%s" % hgnode.short(r)
99 for item in parsefile(fl.read(r).splitlines(), fn):
99 for item in parsefile(fl.read(r).splitlines(), fn):
100 yield item
100 yield item
101 try:
101 try:
102 # read local signatures
102 # read local signatures
103 fn = "localsigs"
103 fn = "localsigs"
104 for item in parsefile(repo.opener(fn), fn):
104 for item in parsefile(repo.opener(fn), fn):
105 yield item
105 yield item
106 except IOError:
106 except IOError:
107 pass
107 pass
108
108
109 def getkeys(ui, repo, mygpg, sigdata, context):
109 def getkeys(ui, repo, mygpg, sigdata, context):
110 """get the keys who signed a data"""
110 """get the keys who signed a data"""
111 fn, ln = context
111 fn, ln = context
112 node, version, sig = sigdata
112 node, version, sig = sigdata
113 prefix = "%s:%d" % (fn, ln)
113 prefix = "%s:%d" % (fn, ln)
114 node = hgnode.bin(node)
114 node = hgnode.bin(node)
115
115
116 data = node2txt(repo, node, version)
116 data = node2txt(repo, node, version)
117 sig = binascii.a2b_base64(sig)
117 sig = binascii.a2b_base64(sig)
118 err, keys = mygpg.verify(data, sig)
118 err, keys = mygpg.verify(data, sig)
119 if err:
119 if err:
120 ui.warn("%s:%d %s\n" % (fn, ln , err))
120 ui.warn("%s:%d %s\n" % (fn, ln , err))
121 return None
121 return None
122
122
123 validkeys = []
123 validkeys = []
124 # warn for expired key and/or sigs
124 # warn for expired key and/or sigs
125 for key in keys:
125 for key in keys:
126 if key[0] == "BADSIG":
126 if key[0] == "BADSIG":
127 ui.write(_("%s Bad signature from \"%s\"\n") % (prefix, key[2]))
127 ui.write(_("%s Bad signature from \"%s\"\n") % (prefix, key[2]))
128 continue
128 continue
129 if key[0] == "EXPSIG":
129 if key[0] == "EXPSIG":
130 ui.write(_("%s Note: Signature has expired"
130 ui.write(_("%s Note: Signature has expired"
131 " (signed by: \"%s\")\n") % (prefix, key[2]))
131 " (signed by: \"%s\")\n") % (prefix, key[2]))
132 elif key[0] == "EXPKEYSIG":
132 elif key[0] == "EXPKEYSIG":
133 ui.write(_("%s Note: This key has expired"
133 ui.write(_("%s Note: This key has expired"
134 " (signed by: \"%s\")\n") % (prefix, key[2]))
134 " (signed by: \"%s\")\n") % (prefix, key[2]))
135 validkeys.append((key[1], key[2], key[3]))
135 validkeys.append((key[1], key[2], key[3]))
136 return validkeys
136 return validkeys
137
137
138 def sigs(ui, repo):
138 def sigs(ui, repo):
139 """list signed changesets"""
139 """list signed changesets"""
140 mygpg = newgpg(ui)
140 mygpg = newgpg(ui)
141 revs = {}
141 revs = {}
142
142
143 for data, context in sigwalk(repo):
143 for data, context in sigwalk(repo):
144 node, version, sig = data
144 node, version, sig = data
145 fn, ln = context
145 fn, ln = context
146 try:
146 try:
147 n = repo.lookup(node)
147 n = repo.lookup(node)
148 except KeyError:
148 except KeyError:
149 ui.warn(_("%s:%d node does not exist\n") % (fn, ln))
149 ui.warn(_("%s:%d node does not exist\n") % (fn, ln))
150 continue
150 continue
151 r = repo.changelog.rev(n)
151 r = repo.changelog.rev(n)
152 keys = getkeys(ui, repo, mygpg, data, context)
152 keys = getkeys(ui, repo, mygpg, data, context)
153 if not keys:
153 if not keys:
154 continue
154 continue
155 revs.setdefault(r, [])
155 revs.setdefault(r, [])
156 revs[r].extend(keys)
156 revs[r].extend(keys)
157 nodes = list(revs)
157 nodes = list(revs)
158 nodes.reverse()
158 nodes.reverse()
159 for rev in nodes:
159 for rev in nodes:
160 for k in revs[rev]:
160 for k in revs[rev]:
161 r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev)))
161 r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev)))
162 ui.write("%-30s %s\n" % (keystr(ui, k), r))
162 ui.write("%-30s %s\n" % (keystr(ui, k), r))
163
163
164 def check(ui, repo, rev):
164 def check(ui, repo, rev):
165 """verify all the signatures there may be for a particular revision"""
165 """verify all the signatures there may be for a particular revision"""
166 mygpg = newgpg(ui)
166 mygpg = newgpg(ui)
167 rev = repo.lookup(rev)
167 rev = repo.lookup(rev)
168 hexrev = hgnode.hex(rev)
168 hexrev = hgnode.hex(rev)
169 keys = []
169 keys = []
170
170
171 for data, context in sigwalk(repo):
171 for data, context in sigwalk(repo):
172 node, version, sig = data
172 node, version, sig = data
173 if node == hexrev:
173 if node == hexrev:
174 k = getkeys(ui, repo, mygpg, data, context)
174 k = getkeys(ui, repo, mygpg, data, context)
175 if k:
175 if k:
176 keys.extend(k)
176 keys.extend(k)
177
177
178 if not keys:
178 if not keys:
179 ui.write(_("No valid signature for %s\n") % hgnode.short(rev))
179 ui.write(_("No valid signature for %s\n") % hgnode.short(rev))
180 return
180 return
181
181
182 # print summary
182 # print summary
183 ui.write("%s is signed by:\n" % hgnode.short(rev))
183 ui.write("%s is signed by:\n" % hgnode.short(rev))
184 for key in keys:
184 for key in keys:
185 ui.write(" %s\n" % keystr(ui, key))
185 ui.write(" %s\n" % keystr(ui, key))
186
186
187 def keystr(ui, key):
187 def keystr(ui, key):
188 """associate a string to a key (username, comment)"""
188 """associate a string to a key (username, comment)"""
189 keyid, user, fingerprint = key
189 keyid, user, fingerprint = key
190 comment = ui.config("gpg", fingerprint, None)
190 comment = ui.config("gpg", fingerprint, None)
191 if comment:
191 if comment:
192 return "%s (%s)" % (user, comment)
192 return "%s (%s)" % (user, comment)
193 else:
193 else:
194 return user
194 return user
195
195
196 def sign(ui, repo, *revs, **opts):
196 def sign(ui, repo, *revs, **opts):
197 """add a signature for the current or given revision
197 """add a signature for the current or given revision
198
198
199 If no revision is given, the parent of the working directory is used,
199 If no revision is given, the parent of the working directory is used,
200 or tip if no revision is checked out.
200 or tip if no revision is checked out.
201 """
201 """
202
202
203 mygpg = newgpg(ui, **opts)
203 mygpg = newgpg(ui, **opts)
204 sigver = "0"
204 sigver = "0"
205 sigmessage = ""
205 sigmessage = ""
206
207 date = opts.get('date')
208 if date:
209 opts['date'] = util.parsedate(date)
210
206 if revs:
211 if revs:
207 nodes = [repo.lookup(n) for n in revs]
212 nodes = [repo.lookup(n) for n in revs]
208 else:
213 else:
209 nodes = [node for node in repo.dirstate.parents()
214 nodes = [node for node in repo.dirstate.parents()
210 if node != hgnode.nullid]
215 if node != hgnode.nullid]
211 if len(nodes) > 1:
216 if len(nodes) > 1:
212 raise util.Abort(_('uncommitted merge - please provide a '
217 raise util.Abort(_('uncommitted merge - please provide a '
213 'specific revision'))
218 'specific revision'))
214 if not nodes:
219 if not nodes:
215 nodes = [repo.changelog.tip()]
220 nodes = [repo.changelog.tip()]
216
221
217 for n in nodes:
222 for n in nodes:
218 hexnode = hgnode.hex(n)
223 hexnode = hgnode.hex(n)
219 ui.write("Signing %d:%s\n" % (repo.changelog.rev(n),
224 ui.write("Signing %d:%s\n" % (repo.changelog.rev(n),
220 hgnode.short(n)))
225 hgnode.short(n)))
221 # build data
226 # build data
222 data = node2txt(repo, n, sigver)
227 data = node2txt(repo, n, sigver)
223 sig = mygpg.sign(data)
228 sig = mygpg.sign(data)
224 if not sig:
229 if not sig:
225 raise util.Abort(_("Error while signing"))
230 raise util.Abort(_("Error while signing"))
226 sig = binascii.b2a_base64(sig)
231 sig = binascii.b2a_base64(sig)
227 sig = sig.replace("\n", "")
232 sig = sig.replace("\n", "")
228 sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
233 sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
229
234
230 # write it
235 # write it
231 if opts['local']:
236 if opts['local']:
232 repo.opener("localsigs", "ab").write(sigmessage)
237 repo.opener("localsigs", "ab").write(sigmessage)
233 return
238 return
234
239
235 for x in repo.status()[:5]:
240 for x in repo.status()[:5]:
236 if ".hgsigs" in x and not opts["force"]:
241 if ".hgsigs" in x and not opts["force"]:
237 raise util.Abort(_("working copy of .hgsigs is changed "
242 raise util.Abort(_("working copy of .hgsigs is changed "
238 "(please commit .hgsigs manually "
243 "(please commit .hgsigs manually "
239 "or use --force)"))
244 "or use --force)"))
240
245
241 repo.wfile(".hgsigs", "ab").write(sigmessage)
246 repo.wfile(".hgsigs", "ab").write(sigmessage)
242
247
243 if '.hgsigs' not in repo.dirstate:
248 if '.hgsigs' not in repo.dirstate:
244 repo.add([".hgsigs"])
249 repo.add([".hgsigs"])
245
250
246 if opts["no_commit"]:
251 if opts["no_commit"]:
247 return
252 return
248
253
249 message = opts['message']
254 message = opts['message']
250 if not message:
255 if not message:
251 message = "\n".join([_("Added signature for changeset %s")
256 message = "\n".join([_("Added signature for changeset %s")
252 % hgnode.short(n)
257 % hgnode.short(n)
253 for n in nodes])
258 for n in nodes])
254 try:
259 try:
255 repo.commit([".hgsigs"], message, opts['user'], opts['date'])
260 repo.commit([".hgsigs"], message, opts['user'], opts['date'])
256 except ValueError, inst:
261 except ValueError, inst:
257 raise util.Abort(str(inst))
262 raise util.Abort(str(inst))
258
263
259 def node2txt(repo, node, ver):
264 def node2txt(repo, node, ver):
260 """map a manifest into some text"""
265 """map a manifest into some text"""
261 if ver == "0":
266 if ver == "0":
262 return "%s\n" % hgnode.hex(node)
267 return "%s\n" % hgnode.hex(node)
263 else:
268 else:
264 raise util.Abort(_("unknown signature version"))
269 raise util.Abort(_("unknown signature version"))
265
270
266 cmdtable = {
271 cmdtable = {
267 "sign":
272 "sign":
268 (sign,
273 (sign,
269 [('l', 'local', None, _('make the signature local')),
274 [('l', 'local', None, _('make the signature local')),
270 ('f', 'force', None, _('sign even if the sigfile is modified')),
275 ('f', 'force', None, _('sign even if the sigfile is modified')),
271 ('', 'no-commit', None, _('do not commit the sigfile after signing')),
276 ('', 'no-commit', None, _('do not commit the sigfile after signing')),
272 ('k', 'key', '', _('the key id to sign with')),
277 ('k', 'key', '', _('the key id to sign with')),
273 ('m', 'message', '', _('commit message')),
278 ('m', 'message', '', _('commit message')),
274 ] + commands.commitopts2,
279 ] + commands.commitopts2,
275 _('hg sign [OPTION]... [REVISION]...')),
280 _('hg sign [OPTION]... [REVISION]...')),
276 "sigcheck": (check, [], _('hg sigcheck REVISION')),
281 "sigcheck": (check, [], _('hg sigcheck REVISION')),
277 "sigs": (sigs, [], _('hg sigs')),
282 "sigs": (sigs, [], _('hg sigs')),
278 }
283 }
279
284
@@ -1,405 +1,406 b''
1 # Copyright (C) 2007 Brendan Cully <brendan@kublai.com>
1 # Copyright (C) 2007 Brendan Cully <brendan@kublai.com>
2 # Published under the GNU GPL
2 # Published under the GNU GPL
3
3
4 '''
4 '''
5 imerge - interactive merge
5 imerge - interactive merge
6 '''
6 '''
7
7
8 from mercurial.i18n import _
8 from mercurial.i18n import _
9 from mercurial.node import *
9 from mercurial.node import *
10 from mercurial import commands, cmdutil, dispatch, fancyopts, hg, merge, util
10 from mercurial import commands, cmdutil, dispatch, fancyopts
11 from mercurial import hg, filemerge, util
11 import os, tarfile
12 import os, tarfile
12
13
13 class InvalidStateFileException(Exception): pass
14 class InvalidStateFileException(Exception): pass
14
15
15 class ImergeStateFile(object):
16 class ImergeStateFile(object):
16 def __init__(self, im):
17 def __init__(self, im):
17 self.im = im
18 self.im = im
18
19
19 def save(self, dest):
20 def save(self, dest):
20 tf = tarfile.open(dest, 'w:gz')
21 tf = tarfile.open(dest, 'w:gz')
21
22
22 st = os.path.join(self.im.path, 'status')
23 st = os.path.join(self.im.path, 'status')
23 tf.add(st, os.path.join('.hg', 'imerge', 'status'))
24 tf.add(st, os.path.join('.hg', 'imerge', 'status'))
24
25
25 for f in self.im.resolved:
26 for f in self.im.resolved:
26 (fd, fo) = self.im.conflicts[f]
27 (fd, fo) = self.im.conflicts[f]
27 abssrc = self.im.repo.wjoin(fd)
28 abssrc = self.im.repo.wjoin(fd)
28 tf.add(abssrc, fd)
29 tf.add(abssrc, fd)
29
30
30 tf.close()
31 tf.close()
31
32
32 def load(self, source):
33 def load(self, source):
33 wlock = self.im.repo.wlock()
34 wlock = self.im.repo.wlock()
34 lock = self.im.repo.lock()
35 lock = self.im.repo.lock()
35
36
36 tf = tarfile.open(source, 'r')
37 tf = tarfile.open(source, 'r')
37 contents = tf.getnames()
38 contents = tf.getnames()
38 # tarfile normalizes path separators to '/'
39 # tarfile normalizes path separators to '/'
39 statusfile = '.hg/imerge/status'
40 statusfile = '.hg/imerge/status'
40 if statusfile not in contents:
41 if statusfile not in contents:
41 raise InvalidStateFileException('no status file')
42 raise InvalidStateFileException('no status file')
42
43
43 tf.extract(statusfile, self.im.repo.root)
44 tf.extract(statusfile, self.im.repo.root)
44 p1, p2 = self.im.load()
45 p1, p2 = self.im.load()
45 if self.im.repo.dirstate.parents()[0] != p1.node():
46 if self.im.repo.dirstate.parents()[0] != p1.node():
46 hg.clean(self.im.repo, p1.node())
47 hg.clean(self.im.repo, p1.node())
47 self.im.start(p2.node())
48 self.im.start(p2.node())
48 for tarinfo in tf:
49 for tarinfo in tf:
49 tf.extract(tarinfo, self.im.repo.root)
50 tf.extract(tarinfo, self.im.repo.root)
50 self.im.load()
51 self.im.load()
51
52
52 class Imerge(object):
53 class Imerge(object):
53 def __init__(self, ui, repo):
54 def __init__(self, ui, repo):
54 self.ui = ui
55 self.ui = ui
55 self.repo = repo
56 self.repo = repo
56
57
57 self.path = repo.join('imerge')
58 self.path = repo.join('imerge')
58 self.opener = util.opener(self.path)
59 self.opener = util.opener(self.path)
59
60
60 self.wctx = self.repo.workingctx()
61 self.wctx = self.repo.workingctx()
61 self.conflicts = {}
62 self.conflicts = {}
62 self.resolved = []
63 self.resolved = []
63
64
64 def merging(self):
65 def merging(self):
65 return len(self.wctx.parents()) > 1
66 return len(self.wctx.parents()) > 1
66
67
67 def load(self):
68 def load(self):
68 # status format. \0-delimited file, fields are
69 # status format. \0-delimited file, fields are
69 # p1, p2, conflict count, conflict filenames, resolved filenames
70 # p1, p2, conflict count, conflict filenames, resolved filenames
70 # conflict filenames are tuples of localname, remoteorig, remotenew
71 # conflict filenames are tuples of localname, remoteorig, remotenew
71
72
72 statusfile = self.opener('status')
73 statusfile = self.opener('status')
73
74
74 status = statusfile.read().split('\0')
75 status = statusfile.read().split('\0')
75 if len(status) < 3:
76 if len(status) < 3:
76 raise util.Abort('invalid imerge status file')
77 raise util.Abort('invalid imerge status file')
77
78
78 try:
79 try:
79 parents = [self.repo.changectx(n) for n in status[:2]]
80 parents = [self.repo.changectx(n) for n in status[:2]]
80 except LookupError:
81 except LookupError:
81 raise util.Abort('merge parent %s not in repository' % short(p))
82 raise util.Abort('merge parent %s not in repository' % short(p))
82
83
83 status = status[2:]
84 status = status[2:]
84 conflicts = int(status.pop(0)) * 3
85 conflicts = int(status.pop(0)) * 3
85 self.resolved = status[conflicts:]
86 self.resolved = status[conflicts:]
86 for i in xrange(0, conflicts, 3):
87 for i in xrange(0, conflicts, 3):
87 self.conflicts[status[i]] = (status[i+1], status[i+2])
88 self.conflicts[status[i]] = (status[i+1], status[i+2])
88
89
89 return parents
90 return parents
90
91
91 def save(self):
92 def save(self):
92 lock = self.repo.lock()
93 lock = self.repo.lock()
93
94
94 if not os.path.isdir(self.path):
95 if not os.path.isdir(self.path):
95 os.mkdir(self.path)
96 os.mkdir(self.path)
96 statusfile = self.opener('status', 'wb')
97 statusfile = self.opener('status', 'wb')
97
98
98 out = [hex(n.node()) for n in self.wctx.parents()]
99 out = [hex(n.node()) for n in self.wctx.parents()]
99 out.append(str(len(self.conflicts)))
100 out.append(str(len(self.conflicts)))
100 conflicts = self.conflicts.items()
101 conflicts = self.conflicts.items()
101 conflicts.sort()
102 conflicts.sort()
102 for fw, fd_fo in conflicts:
103 for fw, fd_fo in conflicts:
103 out.append(fw)
104 out.append(fw)
104 out.extend(fd_fo)
105 out.extend(fd_fo)
105 out.extend(self.resolved)
106 out.extend(self.resolved)
106
107
107 statusfile.write('\0'.join(out))
108 statusfile.write('\0'.join(out))
108
109
109 def remaining(self):
110 def remaining(self):
110 return [f for f in self.conflicts if f not in self.resolved]
111 return [f for f in self.conflicts if f not in self.resolved]
111
112
112 def filemerge(self, fn, interactive=True):
113 def filemerge(self, fn, interactive=True):
113 wlock = self.repo.wlock()
114 wlock = self.repo.wlock()
114
115
115 (fd, fo) = self.conflicts[fn]
116 (fd, fo) = self.conflicts[fn]
116 p1, p2 = self.wctx.parents()
117 p1, p2 = self.wctx.parents()
117
118
118 # this could be greatly improved
119 # this could be greatly improved
119 realmerge = os.environ.get('HGMERGE')
120 realmerge = os.environ.get('HGMERGE')
120 if not interactive:
121 if not interactive:
121 os.environ['HGMERGE'] = 'merge'
122 os.environ['HGMERGE'] = 'merge'
122
123
123 # The filemerge ancestor algorithm does not work if self.wctx
124 # The filemerge ancestor algorithm does not work if self.wctx
124 # already has two parents (in normal merge it doesn't yet). But
125 # already has two parents (in normal merge it doesn't yet). But
125 # this is very dirty.
126 # this is very dirty.
126 self.wctx._parents.pop()
127 self.wctx._parents.pop()
127 try:
128 try:
128 # TODO: we should probably revert the file if merge fails
129 # TODO: we should probably revert the file if merge fails
129 return merge.filemerge(self.repo, fn, fd, fo, self.wctx, p2)
130 return filemerge.filemerge(self.repo, fn, fd, fo, self.wctx, p2)
130 finally:
131 finally:
131 self.wctx._parents.append(p2)
132 self.wctx._parents.append(p2)
132 if realmerge:
133 if realmerge:
133 os.environ['HGMERGE'] = realmerge
134 os.environ['HGMERGE'] = realmerge
134 elif not interactive:
135 elif not interactive:
135 del os.environ['HGMERGE']
136 del os.environ['HGMERGE']
136
137
137 def start(self, rev=None):
138 def start(self, rev=None):
138 _filemerge = merge.filemerge
139 _filemerge = filemerge.filemerge
139 def filemerge(repo, fw, fd, fo, wctx, mctx):
140 def filemerge_(repo, fw, fd, fo, wctx, mctx):
140 self.conflicts[fw] = (fd, fo)
141 self.conflicts[fw] = (fd, fo)
141
142
142 merge.filemerge = filemerge
143 filemerge.filemerge = filemerge_
143 commands.merge(self.ui, self.repo, rev=rev)
144 commands.merge(self.ui, self.repo, rev=rev)
144 merge.filemerge = _filemerge
145 filemerge.filemerge = _filemerge
145
146
146 self.wctx = self.repo.workingctx()
147 self.wctx = self.repo.workingctx()
147 self.save()
148 self.save()
148
149
149 def resume(self):
150 def resume(self):
150 self.load()
151 self.load()
151
152
152 dp = self.repo.dirstate.parents()
153 dp = self.repo.dirstate.parents()
153 p1, p2 = self.wctx.parents()
154 p1, p2 = self.wctx.parents()
154 if p1.node() != dp[0] or p2.node() != dp[1]:
155 if p1.node() != dp[0] or p2.node() != dp[1]:
155 raise util.Abort('imerge state does not match working directory')
156 raise util.Abort('imerge state does not match working directory')
156
157
157 def next(self):
158 def next(self):
158 remaining = self.remaining()
159 remaining = self.remaining()
159 return remaining and remaining[0]
160 return remaining and remaining[0]
160
161
161 def resolve(self, files):
162 def resolve(self, files):
162 resolved = dict.fromkeys(self.resolved)
163 resolved = dict.fromkeys(self.resolved)
163 for fn in files:
164 for fn in files:
164 if fn not in self.conflicts:
165 if fn not in self.conflicts:
165 raise util.Abort('%s is not in the merge set' % fn)
166 raise util.Abort('%s is not in the merge set' % fn)
166 resolved[fn] = True
167 resolved[fn] = True
167 self.resolved = resolved.keys()
168 self.resolved = resolved.keys()
168 self.resolved.sort()
169 self.resolved.sort()
169 self.save()
170 self.save()
170 return 0
171 return 0
171
172
172 def unresolve(self, files):
173 def unresolve(self, files):
173 resolved = dict.fromkeys(self.resolved)
174 resolved = dict.fromkeys(self.resolved)
174 for fn in files:
175 for fn in files:
175 if fn not in resolved:
176 if fn not in resolved:
176 raise util.Abort('%s is not resolved' % fn)
177 raise util.Abort('%s is not resolved' % fn)
177 del resolved[fn]
178 del resolved[fn]
178 self.resolved = resolved.keys()
179 self.resolved = resolved.keys()
179 self.resolved.sort()
180 self.resolved.sort()
180 self.save()
181 self.save()
181 return 0
182 return 0
182
183
183 def pickle(self, dest):
184 def pickle(self, dest):
184 '''write current merge state to file to be resumed elsewhere'''
185 '''write current merge state to file to be resumed elsewhere'''
185 state = ImergeStateFile(self)
186 state = ImergeStateFile(self)
186 return state.save(dest)
187 return state.save(dest)
187
188
188 def unpickle(self, source):
189 def unpickle(self, source):
189 '''read merge state from file'''
190 '''read merge state from file'''
190 state = ImergeStateFile(self)
191 state = ImergeStateFile(self)
191 return state.load(source)
192 return state.load(source)
192
193
193 def load(im, source):
194 def load(im, source):
194 if im.merging():
195 if im.merging():
195 raise util.Abort('there is already a merge in progress '
196 raise util.Abort('there is already a merge in progress '
196 '(update -C <rev> to abort it)' )
197 '(update -C <rev> to abort it)' )
197 m, a, r, d = im.repo.status()[:4]
198 m, a, r, d = im.repo.status()[:4]
198 if m or a or r or d:
199 if m or a or r or d:
199 raise util.Abort('working directory has uncommitted changes')
200 raise util.Abort('working directory has uncommitted changes')
200
201
201 rc = im.unpickle(source)
202 rc = im.unpickle(source)
202 if not rc:
203 if not rc:
203 status(im)
204 status(im)
204 return rc
205 return rc
205
206
206 def merge_(im, filename=None, auto=False):
207 def merge_(im, filename=None, auto=False):
207 success = True
208 success = True
208 if auto and not filename:
209 if auto and not filename:
209 for fn in im.remaining():
210 for fn in im.remaining():
210 rc = im.filemerge(fn, interactive=False)
211 rc = im.filemerge(fn, interactive=False)
211 if rc:
212 if rc:
212 success = False
213 success = False
213 else:
214 else:
214 im.resolve([fn])
215 im.resolve([fn])
215 if success:
216 if success:
216 im.ui.write('all conflicts resolved\n')
217 im.ui.write('all conflicts resolved\n')
217 else:
218 else:
218 status(im)
219 status(im)
219 return 0
220 return 0
220
221
221 if not filename:
222 if not filename:
222 filename = im.next()
223 filename = im.next()
223 if not filename:
224 if not filename:
224 im.ui.write('all conflicts resolved\n')
225 im.ui.write('all conflicts resolved\n')
225 return 0
226 return 0
226
227
227 rc = im.filemerge(filename, interactive=not auto)
228 rc = im.filemerge(filename, interactive=not auto)
228 if not rc:
229 if not rc:
229 im.resolve([filename])
230 im.resolve([filename])
230 if not im.next():
231 if not im.next():
231 im.ui.write('all conflicts resolved\n')
232 im.ui.write('all conflicts resolved\n')
232 return rc
233 return rc
233
234
234 def next(im):
235 def next(im):
235 n = im.next()
236 n = im.next()
236 if n:
237 if n:
237 im.ui.write('%s\n' % n)
238 im.ui.write('%s\n' % n)
238 else:
239 else:
239 im.ui.write('all conflicts resolved\n')
240 im.ui.write('all conflicts resolved\n')
240 return 0
241 return 0
241
242
242 def resolve(im, *files):
243 def resolve(im, *files):
243 if not files:
244 if not files:
244 raise util.Abort('resolve requires at least one filename')
245 raise util.Abort('resolve requires at least one filename')
245 return im.resolve(files)
246 return im.resolve(files)
246
247
247 def save(im, dest):
248 def save(im, dest):
248 return im.pickle(dest)
249 return im.pickle(dest)
249
250
250 def status(im, **opts):
251 def status(im, **opts):
251 if not opts.get('resolved') and not opts.get('unresolved'):
252 if not opts.get('resolved') and not opts.get('unresolved'):
252 opts['resolved'] = True
253 opts['resolved'] = True
253 opts['unresolved'] = True
254 opts['unresolved'] = True
254
255
255 if im.ui.verbose:
256 if im.ui.verbose:
256 p1, p2 = [short(p.node()) for p in im.wctx.parents()]
257 p1, p2 = [short(p.node()) for p in im.wctx.parents()]
257 im.ui.note(_('merging %s and %s\n') % (p1, p2))
258 im.ui.note(_('merging %s and %s\n') % (p1, p2))
258
259
259 conflicts = im.conflicts.keys()
260 conflicts = im.conflicts.keys()
260 conflicts.sort()
261 conflicts.sort()
261 remaining = dict.fromkeys(im.remaining())
262 remaining = dict.fromkeys(im.remaining())
262 st = []
263 st = []
263 for fn in conflicts:
264 for fn in conflicts:
264 if opts.get('no_status'):
265 if opts.get('no_status'):
265 mode = ''
266 mode = ''
266 elif fn in remaining:
267 elif fn in remaining:
267 mode = 'U '
268 mode = 'U '
268 else:
269 else:
269 mode = 'R '
270 mode = 'R '
270 if ((opts.get('resolved') and fn not in remaining)
271 if ((opts.get('resolved') and fn not in remaining)
271 or (opts.get('unresolved') and fn in remaining)):
272 or (opts.get('unresolved') and fn in remaining)):
272 st.append((mode, fn))
273 st.append((mode, fn))
273 st.sort()
274 st.sort()
274 for (mode, fn) in st:
275 for (mode, fn) in st:
275 if im.ui.verbose:
276 if im.ui.verbose:
276 fo, fd = im.conflicts[fn]
277 fo, fd = im.conflicts[fn]
277 if fd != fn:
278 if fd != fn:
278 fn = '%s (%s)' % (fn, fd)
279 fn = '%s (%s)' % (fn, fd)
279 im.ui.write('%s%s\n' % (mode, fn))
280 im.ui.write('%s%s\n' % (mode, fn))
280 if opts.get('unresolved') and not remaining:
281 if opts.get('unresolved') and not remaining:
281 im.ui.write(_('all conflicts resolved\n'))
282 im.ui.write(_('all conflicts resolved\n'))
282
283
283 return 0
284 return 0
284
285
285 def unresolve(im, *files):
286 def unresolve(im, *files):
286 if not files:
287 if not files:
287 raise util.Abort('unresolve requires at least one filename')
288 raise util.Abort('unresolve requires at least one filename')
288 return im.unresolve(files)
289 return im.unresolve(files)
289
290
290 subcmdtable = {
291 subcmdtable = {
291 'load': (load, []),
292 'load': (load, []),
292 'merge':
293 'merge':
293 (merge_,
294 (merge_,
294 [('a', 'auto', None, _('automatically resolve if possible'))]),
295 [('a', 'auto', None, _('automatically resolve if possible'))]),
295 'next': (next, []),
296 'next': (next, []),
296 'resolve': (resolve, []),
297 'resolve': (resolve, []),
297 'save': (save, []),
298 'save': (save, []),
298 'status':
299 'status':
299 (status,
300 (status,
300 [('n', 'no-status', None, _('hide status prefix')),
301 [('n', 'no-status', None, _('hide status prefix')),
301 ('', 'resolved', None, _('only show resolved conflicts')),
302 ('', 'resolved', None, _('only show resolved conflicts')),
302 ('', 'unresolved', None, _('only show unresolved conflicts'))]),
303 ('', 'unresolved', None, _('only show unresolved conflicts'))]),
303 'unresolve': (unresolve, [])
304 'unresolve': (unresolve, [])
304 }
305 }
305
306
306 def dispatch_(im, args, opts):
307 def dispatch_(im, args, opts):
307 def complete(s, choices):
308 def complete(s, choices):
308 candidates = []
309 candidates = []
309 for choice in choices:
310 for choice in choices:
310 if choice.startswith(s):
311 if choice.startswith(s):
311 candidates.append(choice)
312 candidates.append(choice)
312 return candidates
313 return candidates
313
314
314 c, args = args[0], list(args[1:])
315 c, args = args[0], list(args[1:])
315 cmd = complete(c, subcmdtable.keys())
316 cmd = complete(c, subcmdtable.keys())
316 if not cmd:
317 if not cmd:
317 raise cmdutil.UnknownCommand('imerge ' + c)
318 raise cmdutil.UnknownCommand('imerge ' + c)
318 if len(cmd) > 1:
319 if len(cmd) > 1:
319 cmd.sort()
320 cmd.sort()
320 raise cmdutil.AmbiguousCommand('imerge ' + c, cmd)
321 raise cmdutil.AmbiguousCommand('imerge ' + c, cmd)
321 cmd = cmd[0]
322 cmd = cmd[0]
322
323
323 func, optlist = subcmdtable[cmd]
324 func, optlist = subcmdtable[cmd]
324 opts = {}
325 opts = {}
325 try:
326 try:
326 args = fancyopts.fancyopts(args, optlist, opts)
327 args = fancyopts.fancyopts(args, optlist, opts)
327 return func(im, *args, **opts)
328 return func(im, *args, **opts)
328 except fancyopts.getopt.GetoptError, inst:
329 except fancyopts.getopt.GetoptError, inst:
329 raise dispatch.ParseError('imerge', '%s: %s' % (cmd, inst))
330 raise dispatch.ParseError('imerge', '%s: %s' % (cmd, inst))
330 except TypeError:
331 except TypeError:
331 raise dispatch.ParseError('imerge', _('%s: invalid arguments') % cmd)
332 raise dispatch.ParseError('imerge', _('%s: invalid arguments') % cmd)
332
333
333 def imerge(ui, repo, *args, **opts):
334 def imerge(ui, repo, *args, **opts):
334 '''interactive merge
335 '''interactive merge
335
336
336 imerge lets you split a merge into pieces. When you start a merge
337 imerge lets you split a merge into pieces. When you start a merge
337 with imerge, the names of all files with conflicts are recorded.
338 with imerge, the names of all files with conflicts are recorded.
338 You can then merge any of these files, and if the merge is
339 You can then merge any of these files, and if the merge is
339 successful, they will be marked as resolved. When all files are
340 successful, they will be marked as resolved. When all files are
340 resolved, the merge is complete.
341 resolved, the merge is complete.
341
342
342 If no merge is in progress, hg imerge [rev] will merge the working
343 If no merge is in progress, hg imerge [rev] will merge the working
343 directory with rev (defaulting to the other head if the repository
344 directory with rev (defaulting to the other head if the repository
344 only has two heads). You may also resume a saved merge with
345 only has two heads). You may also resume a saved merge with
345 hg imerge load <file>.
346 hg imerge load <file>.
346
347
347 If a merge is in progress, hg imerge will default to merging the
348 If a merge is in progress, hg imerge will default to merging the
348 next unresolved file.
349 next unresolved file.
349
350
350 The following subcommands are available:
351 The following subcommands are available:
351
352
352 status:
353 status:
353 show the current state of the merge
354 show the current state of the merge
354 options:
355 options:
355 -n --no-status: do not print the status prefix
356 -n --no-status: do not print the status prefix
356 --resolved: only print resolved conflicts
357 --resolved: only print resolved conflicts
357 --unresolved: only print unresolved conflicts
358 --unresolved: only print unresolved conflicts
358 next:
359 next:
359 show the next unresolved file merge
360 show the next unresolved file merge
360 merge [<file>]:
361 merge [<file>]:
361 merge <file>. If the file merge is successful, the file will be
362 merge <file>. If the file merge is successful, the file will be
362 recorded as resolved. If no file is given, the next unresolved
363 recorded as resolved. If no file is given, the next unresolved
363 file will be merged.
364 file will be merged.
364 resolve <file>...:
365 resolve <file>...:
365 mark files as successfully merged
366 mark files as successfully merged
366 unresolve <file>...:
367 unresolve <file>...:
367 mark files as requiring merging.
368 mark files as requiring merging.
368 save <file>:
369 save <file>:
369 save the state of the merge to a file to be resumed elsewhere
370 save the state of the merge to a file to be resumed elsewhere
370 load <file>:
371 load <file>:
371 load the state of the merge from a file created by save
372 load the state of the merge from a file created by save
372 '''
373 '''
373
374
374 im = Imerge(ui, repo)
375 im = Imerge(ui, repo)
375
376
376 if im.merging():
377 if im.merging():
377 im.resume()
378 im.resume()
378 else:
379 else:
379 rev = opts.get('rev')
380 rev = opts.get('rev')
380 if rev and args:
381 if rev and args:
381 raise util.Abort('please specify just one revision')
382 raise util.Abort('please specify just one revision')
382
383
383 if len(args) == 2 and args[0] == 'load':
384 if len(args) == 2 and args[0] == 'load':
384 pass
385 pass
385 else:
386 else:
386 if args:
387 if args:
387 rev = args[0]
388 rev = args[0]
388 im.start(rev=rev)
389 im.start(rev=rev)
389 if opts.get('auto'):
390 if opts.get('auto'):
390 args = ['merge', '--auto']
391 args = ['merge', '--auto']
391 else:
392 else:
392 args = ['status']
393 args = ['status']
393
394
394 if not args:
395 if not args:
395 args = ['merge']
396 args = ['merge']
396
397
397 return dispatch_(im, args, opts)
398 return dispatch_(im, args, opts)
398
399
399 cmdtable = {
400 cmdtable = {
400 '^imerge':
401 '^imerge':
401 (imerge,
402 (imerge,
402 [('r', 'rev', '', _('revision to merge')),
403 [('r', 'rev', '', _('revision to merge')),
403 ('a', 'auto', None, _('automatically merge where possible'))],
404 ('a', 'auto', None, _('automatically merge where possible'))],
404 'hg imerge [command]')
405 'hg imerge [command]')
405 }
406 }
@@ -1,520 +1,556 b''
1 # keyword.py - $Keyword$ expansion for Mercurial
1 # keyword.py - $Keyword$ expansion for Mercurial
2 #
2 #
3 # Copyright 2007, 2008 Christian Ebert <blacktrash@gmx.net>
3 # Copyright 2007, 2008 Christian Ebert <blacktrash@gmx.net>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7 #
7 #
8 # $Id$
8 # $Id$
9 #
9 #
10 # Keyword expansion hack against the grain of a DSCM
10 # Keyword expansion hack against the grain of a DSCM
11 #
11 #
12 # There are many good reasons why this is not needed in a distributed
12 # There are many good reasons why this is not needed in a distributed
13 # SCM, still it may be useful in very small projects based on single
13 # SCM, still it may be useful in very small projects based on single
14 # files (like LaTeX packages), that are mostly addressed to an audience
14 # files (like LaTeX packages), that are mostly addressed to an audience
15 # not running a version control system.
15 # not running a version control system.
16 #
16 #
17 # For in-depth discussion refer to
17 # For in-depth discussion refer to
18 # <http://www.selenic.com/mercurial/wiki/index.cgi/KeywordPlan>.
18 # <http://www.selenic.com/mercurial/wiki/index.cgi/KeywordPlan>.
19 #
19 #
20 # Keyword expansion is based on Mercurial's changeset template mappings.
20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 #
21 #
22 # Binary files are not touched.
22 # Binary files are not touched.
23 #
23 #
24 # Setup in hgrc:
24 # Setup in hgrc:
25 #
25 #
26 # [extensions]
26 # [extensions]
27 # # enable extension
27 # # enable extension
28 # hgext.keyword =
28 # hgext.keyword =
29 #
29 #
30 # Files to act upon/ignore are specified in the [keyword] section.
30 # Files to act upon/ignore are specified in the [keyword] section.
31 # Customized keyword template mappings in the [keywordmaps] section.
31 # Customized keyword template mappings in the [keywordmaps] section.
32 #
32 #
33 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
33 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
34
34
35 '''keyword expansion in local repositories
35 '''keyword expansion in local repositories
36
36
37 This extension expands RCS/CVS-like or self-customized $Keywords$
37 This extension expands RCS/CVS-like or self-customized $Keywords$
38 in tracked text files selected by your configuration.
38 in tracked text files selected by your configuration.
39
39
40 Keywords are only expanded in local repositories and not stored in
40 Keywords are only expanded in local repositories and not stored in
41 the change history. The mechanism can be regarded as a convenience
41 the change history. The mechanism can be regarded as a convenience
42 for the current user or for archive distribution.
42 for the current user or for archive distribution.
43
43
44 Configuration is done in the [keyword] and [keywordmaps] sections
44 Configuration is done in the [keyword] and [keywordmaps] sections
45 of hgrc files.
45 of hgrc files.
46
46
47 Example:
47 Example:
48
48
49 [keyword]
49 [keyword]
50 # expand keywords in every python file except those matching "x*"
50 # expand keywords in every python file except those matching "x*"
51 **.py =
51 **.py =
52 x* = ignore
52 x* = ignore
53
53
54 Note: the more specific you are in your filename patterns
54 Note: the more specific you are in your filename patterns
55 the less you lose speed in huge repos.
55 the less you lose speed in huge repos.
56
56
57 For [keywordmaps] template mapping and expansion demonstration and
57 For [keywordmaps] template mapping and expansion demonstration and
58 control run "hg kwdemo".
58 control run "hg kwdemo".
59
59
60 An additional date template filter {date|utcdate} is provided.
60 An additional date template filter {date|utcdate} is provided.
61
61
62 The default template mappings (view with "hg kwdemo -d") can be replaced
62 The default template mappings (view with "hg kwdemo -d") can be replaced
63 with customized keywords and templates.
63 with customized keywords and templates.
64 Again, run "hg kwdemo" to control the results of your config changes.
64 Again, run "hg kwdemo" to control the results of your config changes.
65
65
66 Before changing/disabling active keywords, run "hg kwshrink" to avoid
66 Before changing/disabling active keywords, run "hg kwshrink" to avoid
67 the risk of inadvertedly storing expanded keywords in the change history.
67 the risk of inadvertedly storing expanded keywords in the change history.
68
68
69 To force expansion after enabling it, or a configuration change, run
69 To force expansion after enabling it, or a configuration change, run
70 "hg kwexpand".
70 "hg kwexpand".
71
71
72 Also, when committing with the record extension or using mq's qrecord, be aware
72 Also, when committing with the record extension or using mq's qrecord, be aware
73 that keywords cannot be updated. Again, run "hg kwexpand" on the files in
73 that keywords cannot be updated. Again, run "hg kwexpand" on the files in
74 question to update keyword expansions after all changes have been checked in.
74 question to update keyword expansions after all changes have been checked in.
75
75
76 Expansions spanning more than one line and incremental expansions,
76 Expansions spanning more than one line and incremental expansions,
77 like CVS' $Log$, are not supported. A keyword template map
77 like CVS' $Log$, are not supported. A keyword template map
78 "Log = {desc}" expands to the first line of the changeset description.
78 "Log = {desc}" expands to the first line of the changeset description.
79 '''
79 '''
80
80
81 from mercurial import commands, cmdutil, context, dispatch, filelog, revlog
81 from mercurial import commands, cmdutil, context, dispatch, filelog, revlog
82 from mercurial import patch, localrepo, templater, templatefilters, util
82 from mercurial import patch, localrepo, templater, templatefilters, util
83 from mercurial.hgweb import webcommands
83 from mercurial.node import *
84 from mercurial.node import *
84 from mercurial.i18n import _
85 from mercurial.i18n import _
85 import re, shutil, sys, tempfile, time
86 import re, shutil, tempfile, time
86
87
87 commands.optionalrepo += ' kwdemo'
88 commands.optionalrepo += ' kwdemo'
88
89
90 # hg commands that do not act on keywords
91 nokwcommands = ('add addremove bundle copy export grep identify incoming init'
92 ' log outgoing push remove rename rollback tip'
93 ' convert email glog')
94
89 # hg commands that trigger expansion only when writing to working dir,
95 # hg commands that trigger expansion only when writing to working dir,
90 # not when reading filelog, and unexpand when reading from working dir
96 # not when reading filelog, and unexpand when reading from working dir
91 restricted = ('diff1', 'record',
97 restricted = 'record qfold qimport qnew qpush qrefresh qrecord'
92 'qfold', 'qimport', 'qnew', 'qpush', 'qrefresh', 'qrecord')
93
98
94 def utcdate(date):
99 def utcdate(date):
95 '''Returns hgdate in cvs-like UTC format.'''
100 '''Returns hgdate in cvs-like UTC format.'''
96 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
101 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
97
102
98
103
99 _kwtemplater = None
104 # make keyword tools accessible
105 kwtools = {'templater': None, 'hgcmd': None}
106
107 # store originals of monkeypatches
108 _patchfile_init = patch.patchfile.__init__
109 _patch_diff = patch.diff
110 _dispatch_parse = dispatch._parse
111
112 def _kwpatchfile_init(self, ui, fname, missing=False):
113 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
114 rejects or conflicts due to expanded keywords in working dir.'''
115 _patchfile_init(self, ui, fname, missing=missing)
116 # shrink keywords read from working dir
117 kwt = kwtools['templater']
118 self.lines = kwt.shrinklines(self.fname, self.lines)
119
120 def _kw_diff(repo, node1=None, node2=None, files=None, match=util.always,
121 fp=None, changes=None, opts=None):
122 '''Monkeypatch patch.diff to avoid expansion except when
123 comparing against working dir.'''
124 if node2 is not None:
125 kwtools['templater'].matcher = util.never
126 elif node1 is not None and node1 != repo.changectx().node():
127 kwtools['templater'].restrict = True
128 _patch_diff(repo, node1=node1, node2=node2, files=files, match=match,
129 fp=fp, changes=changes, opts=opts)
130
131 def _kwweb_changeset(web, req, tmpl):
132 '''Wraps webcommands.changeset turning off keyword expansion.'''
133 kwtools['templater'].matcher = util.never
134 return web.changeset(tmpl, web.changectx(req))
135
136 def _kwweb_filediff(web, req, tmpl):
137 '''Wraps webcommands.filediff turning off keyword expansion.'''
138 kwtools['templater'].matcher = util.never
139 return web.filediff(tmpl, web.filectx(req))
140
141 def _kwdispatch_parse(ui, args):
142 '''Monkeypatch dispatch._parse to obtain running hg command.'''
143 cmd, func, args, options, cmdoptions = _dispatch_parse(ui, args)
144 kwtools['hgcmd'] = cmd
145 return cmd, func, args, options, cmdoptions
146
147 # dispatch._parse is run before reposetup, so wrap it here
148 dispatch._parse = _kwdispatch_parse
149
100
150
101 class kwtemplater(object):
151 class kwtemplater(object):
102 '''
152 '''
103 Sets up keyword templates, corresponding keyword regex, and
153 Sets up keyword templates, corresponding keyword regex, and
104 provides keyword substitution functions.
154 provides keyword substitution functions.
105 '''
155 '''
106 templates = {
156 templates = {
107 'Revision': '{node|short}',
157 'Revision': '{node|short}',
108 'Author': '{author|user}',
158 'Author': '{author|user}',
109 'Date': '{date|utcdate}',
159 'Date': '{date|utcdate}',
110 'RCSFile': '{file|basename},v',
160 'RCSFile': '{file|basename},v',
111 'Source': '{root}/{file},v',
161 'Source': '{root}/{file},v',
112 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
162 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
113 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
163 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
114 }
164 }
115
165
116 def __init__(self, ui, repo, inc, exc, hgcmd):
166 def __init__(self, ui, repo, inc, exc):
117 self.ui = ui
167 self.ui = ui
118 self.repo = repo
168 self.repo = repo
119 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
169 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
120 self.hgcmd = hgcmd
170 self.restrict = kwtools['hgcmd'] in restricted.split()
121 self.commitnode = None
122 self.path = ''
123
171
124 kwmaps = self.ui.configitems('keywordmaps')
172 kwmaps = self.ui.configitems('keywordmaps')
125 if kwmaps: # override default templates
173 if kwmaps: # override default templates
126 kwmaps = [(k, templater.parsestring(v, quoted=False))
174 kwmaps = [(k, templater.parsestring(v, quoted=False))
127 for (k, v) in kwmaps]
175 for (k, v) in kwmaps]
128 self.templates = dict(kwmaps)
176 self.templates = dict(kwmaps)
129 escaped = map(re.escape, self.templates.keys())
177 escaped = map(re.escape, self.templates.keys())
130 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
178 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
131 self.re_kw = re.compile(kwpat)
179 self.re_kw = re.compile(kwpat)
132
180
133 templatefilters.filters['utcdate'] = utcdate
181 templatefilters.filters['utcdate'] = utcdate
134 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
182 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
135 False, '', False)
183 False, '', False)
136
184
137 def substitute(self, node, data, subfunc):
185 def getnode(self, path, fnode):
138 '''Obtains file's changenode if commit node not given,
186 '''Derives changenode from file path and filenode.'''
139 and calls given substitution function.'''
187 # used by kwfilelog.read and kwexpand
140 if self.commitnode:
188 c = context.filectx(self.repo, path, fileid=fnode)
141 fnode = self.commitnode
189 return c.node()
142 else:
143 c = context.filectx(self.repo, self.path, fileid=node)
144 fnode = c.node()
145
190
191 def substitute(self, data, path, node, subfunc):
192 '''Replaces keywords in data with expanded template.'''
146 def kwsub(mobj):
193 def kwsub(mobj):
147 '''Substitutes keyword using corresponding template.'''
148 kw = mobj.group(1)
194 kw = mobj.group(1)
149 self.ct.use_template(self.templates[kw])
195 self.ct.use_template(self.templates[kw])
150 self.ui.pushbuffer()
196 self.ui.pushbuffer()
151 self.ct.show(changenode=fnode, root=self.repo.root, file=self.path)
197 self.ct.show(changenode=node, root=self.repo.root, file=path)
152 return '$%s: %s $' % (kw, templatefilters.firstline(
198 ekw = templatefilters.firstline(self.ui.popbuffer())
153 self.ui.popbuffer()))
199 return '$%s: %s $' % (kw, ekw)
154
155 return subfunc(kwsub, data)
200 return subfunc(kwsub, data)
156
201
157 def expand(self, node, data):
202 def expand(self, path, node, data):
158 '''Returns data with keywords expanded.'''
203 '''Returns data with keywords expanded.'''
159 if util.binary(data) or self.hgcmd in restricted:
204 if not self.restrict and self.matcher(path) and not util.binary(data):
160 return data
205 changenode = self.getnode(path, node)
161 return self.substitute(node, data, self.re_kw.sub)
206 return self.substitute(data, path, changenode, self.re_kw.sub)
207 return data
208
209 def iskwfile(self, path, islink):
210 '''Returns true if path matches [keyword] pattern
211 and is not a symbolic link.
212 Caveat: localrepository._link fails on Windows.'''
213 return self.matcher(path) and not islink(path)
162
214
163 def process(self, node, data, expand):
215 def overwrite(self, node=None, expand=True, files=None):
164 '''Returns a tuple: data, count.
216 '''Overwrites selected files expanding/shrinking keywords.'''
165 Count is number of keywords/keyword substitutions,
217 ctx = self.repo.changectx(node)
166 telling caller whether to act on file containing data.'''
218 mf = ctx.manifest()
167 if util.binary(data):
219 if node is not None: # commit
168 return data, None
220 files = [f for f in ctx.files() if f in mf]
169 if expand:
221 notify = self.ui.debug
170 return self.substitute(node, data, self.re_kw.subn)
222 else: # kwexpand/kwshrink
171 return data, self.re_kw.search(data)
223 notify = self.ui.note
224 candidates = [f for f in files if self.iskwfile(f, mf.linkf)]
225 if candidates:
226 self.restrict = True # do not expand when reading
227 candidates.sort()
228 action = expand and 'expanding' or 'shrinking'
229 for f in candidates:
230 fp = self.repo.file(f)
231 data = fp.read(mf[f])
232 if util.binary(data):
233 continue
234 if expand:
235 changenode = node or self.getnode(f, mf[f])
236 data, found = self.substitute(data, f, changenode,
237 self.re_kw.subn)
238 else:
239 found = self.re_kw.search(data)
240 if found:
241 notify(_('overwriting %s %s keywords\n') % (f, action))
242 self.repo.wwrite(f, data, mf.flags(f))
243 self.repo.dirstate.normal(f)
244 self.restrict = False
172
245
173 def shrink(self, text):
246 def shrinktext(self, text):
247 '''Unconditionally removes all keyword substitutions from text.'''
248 return self.re_kw.sub(r'$\1$', text)
249
250 def shrink(self, fname, text):
174 '''Returns text with all keyword substitutions removed.'''
251 '''Returns text with all keyword substitutions removed.'''
175 if util.binary(text):
252 if self.matcher(fname) and not util.binary(text):
176 return text
253 return self.shrinktext(text)
177 return self.re_kw.sub(r'$\1$', text)
254 return text
255
256 def shrinklines(self, fname, lines):
257 '''Returns lines with keyword substitutions removed.'''
258 if self.matcher(fname):
259 text = ''.join(lines)
260 if not util.binary(text):
261 return self.shrinktext(text).splitlines(True)
262 return lines
263
264 def wread(self, fname, data):
265 '''If in restricted mode returns data read from wdir with
266 keyword substitutions removed.'''
267 return self.restrict and self.shrink(fname, data) or data
178
268
179 class kwfilelog(filelog.filelog):
269 class kwfilelog(filelog.filelog):
180 '''
270 '''
181 Subclass of filelog to hook into its read, add, cmp methods.
271 Subclass of filelog to hook into its read, add, cmp methods.
182 Keywords are "stored" unexpanded, and processed on reading.
272 Keywords are "stored" unexpanded, and processed on reading.
183 '''
273 '''
184 def __init__(self, opener, path):
274 def __init__(self, opener, path):
185 super(kwfilelog, self).__init__(opener, path)
275 super(kwfilelog, self).__init__(opener, path)
186 _kwtemplater.path = path
276 self.kwt = kwtools['templater']
187
277 self.path = path
188 def kwctread(self, node, expand):
189 '''Reads expanding and counting keywords, called from _overwrite.'''
190 data = super(kwfilelog, self).read(node)
191 return _kwtemplater.process(node, data, expand)
192
278
193 def read(self, node):
279 def read(self, node):
194 '''Expands keywords when reading filelog.'''
280 '''Expands keywords when reading filelog.'''
195 data = super(kwfilelog, self).read(node)
281 data = super(kwfilelog, self).read(node)
196 return _kwtemplater.expand(node, data)
282 return self.kwt.expand(self.path, node, data)
197
283
198 def add(self, text, meta, tr, link, p1=None, p2=None):
284 def add(self, text, meta, tr, link, p1=None, p2=None):
199 '''Removes keyword substitutions when adding to filelog.'''
285 '''Removes keyword substitutions when adding to filelog.'''
200 text = _kwtemplater.shrink(text)
286 text = self.kwt.shrink(self.path, text)
201 return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2)
287 return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2)
202
288
203 def cmp(self, node, text):
289 def cmp(self, node, text):
204 '''Removes keyword substitutions for comparison.'''
290 '''Removes keyword substitutions for comparison.'''
205 text = _kwtemplater.shrink(text)
291 text = self.kwt.shrink(self.path, text)
206 if self.renamed(node):
292 if self.renamed(node):
207 t2 = super(kwfilelog, self).read(node)
293 t2 = super(kwfilelog, self).read(node)
208 return t2 != text
294 return t2 != text
209 return revlog.revlog.cmp(self, node, text)
295 return revlog.revlog.cmp(self, node, text)
210
296
211
297 def _status(ui, repo, kwt, *pats, **opts):
212 # store original patch.patchfile.__init__
213 _patchfile_init = patch.patchfile.__init__
214
215 def _kwpatchfile_init(self, ui, fname, missing=False):
216 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
217 rejects or conflicts due to expanded keywords in working dir.'''
218 _patchfile_init(self, ui, fname, missing=missing)
219
220 if _kwtemplater.matcher(self.fname):
221 # shrink keywords read from working dir
222 kwshrunk = _kwtemplater.shrink(''.join(self.lines))
223 self.lines = kwshrunk.splitlines(True)
224
225
226 def _iskwfile(f, link):
227 return not link(f) and _kwtemplater.matcher(f)
228
229 def _status(ui, repo, *pats, **opts):
230 '''Bails out if [keyword] configuration is not active.
298 '''Bails out if [keyword] configuration is not active.
231 Returns status of working directory.'''
299 Returns status of working directory.'''
232 if _kwtemplater:
300 if kwt:
233 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
301 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
234 return repo.status(files=files, match=match, list_clean=True)
302 return repo.status(files=files, match=match, list_clean=True)
235 if ui.configitems('keyword'):
303 if ui.configitems('keyword'):
236 raise util.Abort(_('[keyword] patterns cannot match'))
304 raise util.Abort(_('[keyword] patterns cannot match'))
237 raise util.Abort(_('no [keyword] patterns configured'))
305 raise util.Abort(_('no [keyword] patterns configured'))
238
306
239 def _overwrite(ui, repo, node=None, expand=True, files=None):
240 '''Overwrites selected files expanding/shrinking keywords.'''
241 ctx = repo.changectx(node)
242 mf = ctx.manifest()
243 if node is not None: # commit
244 _kwtemplater.commitnode = node
245 files = [f for f in ctx.files() if f in mf]
246 notify = ui.debug
247 else: # kwexpand/kwshrink
248 notify = ui.note
249 candidates = [f for f in files if _iskwfile(f, mf.linkf)]
250 if candidates:
251 candidates.sort()
252 action = expand and 'expanding' or 'shrinking'
253 for f in candidates:
254 fp = repo.file(f, kwmatch=True)
255 data, kwfound = fp.kwctread(mf[f], expand)
256 if kwfound:
257 notify(_('overwriting %s %s keywords\n') % (f, action))
258 repo.wwrite(f, data, mf.flags(f))
259 repo.dirstate.normal(f)
260
261 def _kwfwrite(ui, repo, expand, *pats, **opts):
307 def _kwfwrite(ui, repo, expand, *pats, **opts):
262 '''Selects files and passes them to _overwrite.'''
308 '''Selects files and passes them to kwtemplater.overwrite.'''
263 status = _status(ui, repo, *pats, **opts)
309 kwt = kwtools['templater']
310 status = _status(ui, repo, kwt, *pats, **opts)
264 modified, added, removed, deleted, unknown, ignored, clean = status
311 modified, added, removed, deleted, unknown, ignored, clean = status
265 if modified or added or removed or deleted:
312 if modified or added or removed or deleted:
266 raise util.Abort(_('outstanding uncommitted changes in given files'))
313 raise util.Abort(_('outstanding uncommitted changes in given files'))
267 wlock = lock = None
314 wlock = lock = None
268 try:
315 try:
269 wlock = repo.wlock()
316 wlock = repo.wlock()
270 lock = repo.lock()
317 lock = repo.lock()
271 _overwrite(ui, repo, expand=expand, files=clean)
318 kwt.overwrite(expand=expand, files=clean)
272 finally:
319 finally:
273 del wlock, lock
320 del wlock, lock
274
321
275
322
276 def demo(ui, repo, *args, **opts):
323 def demo(ui, repo, *args, **opts):
277 '''print [keywordmaps] configuration and an expansion example
324 '''print [keywordmaps] configuration and an expansion example
278
325
279 Show current, custom, or default keyword template maps
326 Show current, custom, or default keyword template maps
280 and their expansion.
327 and their expansion.
281
328
282 Extend current configuration by specifying maps as arguments
329 Extend current configuration by specifying maps as arguments
283 and optionally by reading from an additional hgrc file.
330 and optionally by reading from an additional hgrc file.
284
331
285 Override current keyword template maps with "default" option.
332 Override current keyword template maps with "default" option.
286 '''
333 '''
287 def demostatus(stat):
334 def demostatus(stat):
288 ui.status(_('\n\t%s\n') % stat)
335 ui.status(_('\n\t%s\n') % stat)
289
336
290 def demoitems(section, items):
337 def demoitems(section, items):
291 ui.write('[%s]\n' % section)
338 ui.write('[%s]\n' % section)
292 for k, v in items:
339 for k, v in items:
293 ui.write('%s = %s\n' % (k, v))
340 ui.write('%s = %s\n' % (k, v))
294
341
295 msg = 'hg keyword config and expansion example'
342 msg = 'hg keyword config and expansion example'
296 kwstatus = 'current'
343 kwstatus = 'current'
297 fn = 'demo.txt'
344 fn = 'demo.txt'
298 branchname = 'demobranch'
345 branchname = 'demobranch'
299 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
346 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
300 ui.note(_('creating temporary repo at %s\n') % tmpdir)
347 ui.note(_('creating temporary repo at %s\n') % tmpdir)
301 repo = localrepo.localrepository(ui, path=tmpdir, create=True)
348 repo = localrepo.localrepository(ui, path=tmpdir, create=True)
302 ui.setconfig('keyword', fn, '')
349 ui.setconfig('keyword', fn, '')
303 if args or opts.get('rcfile'):
350 if args or opts.get('rcfile'):
304 kwstatus = 'custom'
351 kwstatus = 'custom'
305 if opts.get('rcfile'):
352 if opts.get('rcfile'):
306 ui.readconfig(opts.get('rcfile'))
353 ui.readconfig(opts.get('rcfile'))
307 if opts.get('default'):
354 if opts.get('default'):
308 kwstatus = 'default'
355 kwstatus = 'default'
309 kwmaps = kwtemplater.templates
356 kwmaps = kwtemplater.templates
310 if ui.configitems('keywordmaps'):
357 if ui.configitems('keywordmaps'):
311 # override maps from optional rcfile
358 # override maps from optional rcfile
312 for k, v in kwmaps.iteritems():
359 for k, v in kwmaps.iteritems():
313 ui.setconfig('keywordmaps', k, v)
360 ui.setconfig('keywordmaps', k, v)
314 elif args:
361 elif args:
315 # simulate hgrc parsing
362 # simulate hgrc parsing
316 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
363 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
317 fp = repo.opener('hgrc', 'w')
364 fp = repo.opener('hgrc', 'w')
318 fp.writelines(rcmaps)
365 fp.writelines(rcmaps)
319 fp.close()
366 fp.close()
320 ui.readconfig(repo.join('hgrc'))
367 ui.readconfig(repo.join('hgrc'))
321 if not opts.get('default'):
368 if not opts.get('default'):
322 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
369 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
323 reposetup(ui, repo)
370 reposetup(ui, repo)
324 for k, v in ui.configitems('extensions'):
371 for k, v in ui.configitems('extensions'):
325 if k.endswith('keyword'):
372 if k.endswith('keyword'):
326 extension = '%s = %s' % (k, v)
373 extension = '%s = %s' % (k, v)
327 break
374 break
328 demostatus('config using %s keyword template maps' % kwstatus)
375 demostatus('config using %s keyword template maps' % kwstatus)
329 ui.write('[extensions]\n%s\n' % extension)
376 ui.write('[extensions]\n%s\n' % extension)
330 demoitems('keyword', ui.configitems('keyword'))
377 demoitems('keyword', ui.configitems('keyword'))
331 demoitems('keywordmaps', kwmaps.iteritems())
378 demoitems('keywordmaps', kwmaps.iteritems())
332 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
379 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
333 repo.wopener(fn, 'w').write(keywords)
380 repo.wopener(fn, 'w').write(keywords)
334 repo.add([fn])
381 repo.add([fn])
335 path = repo.wjoin(fn)
382 path = repo.wjoin(fn)
336 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
383 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
337 ui.note(keywords)
384 ui.note(keywords)
338 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
385 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
339 # silence branch command if not verbose
386 # silence branch command if not verbose
340 quiet = ui.quiet
387 quiet = ui.quiet
341 ui.quiet = not ui.verbose
388 ui.quiet = not ui.verbose
342 commands.branch(ui, repo, branchname)
389 commands.branch(ui, repo, branchname)
343 ui.quiet = quiet
390 ui.quiet = quiet
344 for name, cmd in ui.configitems('hooks'):
391 for name, cmd in ui.configitems('hooks'):
345 if name.split('.', 1)[0].find('commit') > -1:
392 if name.split('.', 1)[0].find('commit') > -1:
346 repo.ui.setconfig('hooks', name, '')
393 repo.ui.setconfig('hooks', name, '')
347 ui.note(_('unhooked all commit hooks\n'))
394 ui.note(_('unhooked all commit hooks\n'))
348 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
395 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
349 repo.commit(text=msg)
396 repo.commit(text=msg)
350 format = ui.verbose and ' in %s' % path or ''
397 format = ui.verbose and ' in %s' % path or ''
351 demostatus('%s keywords expanded%s' % (kwstatus, format))
398 demostatus('%s keywords expanded%s' % (kwstatus, format))
352 ui.write(repo.wread(fn))
399 ui.write(repo.wread(fn))
353 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
400 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
354 shutil.rmtree(tmpdir, ignore_errors=True)
401 shutil.rmtree(tmpdir, ignore_errors=True)
355
402
356 def expand(ui, repo, *pats, **opts):
403 def expand(ui, repo, *pats, **opts):
357 '''expand keywords in working directory
404 '''expand keywords in working directory
358
405
359 Run after (re)enabling keyword expansion.
406 Run after (re)enabling keyword expansion.
360
407
361 kwexpand refuses to run if given files contain local changes.
408 kwexpand refuses to run if given files contain local changes.
362 '''
409 '''
363 # 3rd argument sets expansion to True
410 # 3rd argument sets expansion to True
364 _kwfwrite(ui, repo, True, *pats, **opts)
411 _kwfwrite(ui, repo, True, *pats, **opts)
365
412
366 def files(ui, repo, *pats, **opts):
413 def files(ui, repo, *pats, **opts):
367 '''print files currently configured for keyword expansion
414 '''print files currently configured for keyword expansion
368
415
369 Crosscheck which files in working directory are potential targets for
416 Crosscheck which files in working directory are potential targets for
370 keyword expansion.
417 keyword expansion.
371 That is, files matched by [keyword] config patterns but not symlinks.
418 That is, files matched by [keyword] config patterns but not symlinks.
372 '''
419 '''
373 status = _status(ui, repo, *pats, **opts)
420 kwt = kwtools['templater']
421 status = _status(ui, repo, kwt, *pats, **opts)
374 modified, added, removed, deleted, unknown, ignored, clean = status
422 modified, added, removed, deleted, unknown, ignored, clean = status
375 files = modified + added + clean
423 files = modified + added + clean
376 if opts.get('untracked'):
424 if opts.get('untracked'):
377 files += unknown
425 files += unknown
378 files.sort()
426 files.sort()
379 kwfiles = [f for f in files if _iskwfile(f, repo._link)]
427 wctx = repo.workingctx()
428 islink = lambda p: 'l' in wctx.fileflags(p)
429 kwfiles = [f for f in files if kwt.iskwfile(f, islink)]
380 cwd = pats and repo.getcwd() or ''
430 cwd = pats and repo.getcwd() or ''
381 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
431 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
382 if opts.get('all') or opts.get('ignore'):
432 if opts.get('all') or opts.get('ignore'):
383 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
433 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
384 for char, filenames in kwfstats:
434 for char, filenames in kwfstats:
385 format = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
435 format = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
386 for f in filenames:
436 for f in filenames:
387 ui.write(format % repo.pathto(f, cwd))
437 ui.write(format % repo.pathto(f, cwd))
388
438
389 def shrink(ui, repo, *pats, **opts):
439 def shrink(ui, repo, *pats, **opts):
390 '''revert expanded keywords in working directory
440 '''revert expanded keywords in working directory
391
441
392 Run before changing/disabling active keywords
442 Run before changing/disabling active keywords
393 or if you experience problems with "hg import" or "hg merge".
443 or if you experience problems with "hg import" or "hg merge".
394
444
395 kwshrink refuses to run if given files contain local changes.
445 kwshrink refuses to run if given files contain local changes.
396 '''
446 '''
397 # 3rd argument sets expansion to False
447 # 3rd argument sets expansion to False
398 _kwfwrite(ui, repo, False, *pats, **opts)
448 _kwfwrite(ui, repo, False, *pats, **opts)
399
449
400
450
401 def reposetup(ui, repo):
451 def reposetup(ui, repo):
402 '''Sets up repo as kwrepo for keyword substitution.
452 '''Sets up repo as kwrepo for keyword substitution.
403 Overrides file method to return kwfilelog instead of filelog
453 Overrides file method to return kwfilelog instead of filelog
404 if file matches user configuration.
454 if file matches user configuration.
405 Wraps commit to overwrite configured files with updated
455 Wraps commit to overwrite configured files with updated
406 keyword substitutions.
456 keyword substitutions.
407 This is done for local repos only, and only if there are
457 This is done for local repos only, and only if there are
408 files configured at all for keyword substitution.'''
458 files configured at all for keyword substitution.'''
409
459
410 if not repo.local():
460 try:
411 return
461 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
462 or '.hg' in util.splitpath(repo.root)
463 or repo._url.startswith('bundle:')):
464 return
465 except AttributeError:
466 pass
412
467
413 nokwcommands = ('add', 'addremove', 'bundle', 'clone', 'copy',
468 inc, exc = [], ['.hg*']
414 'export', 'grep', 'identify', 'incoming', 'init',
415 'log', 'outgoing', 'push', 'remove', 'rename',
416 'rollback', 'tip',
417 'convert')
418 hgcmd, func, args, opts, cmdopts = dispatch._parse(ui, sys.argv[1:])
419 if hgcmd in nokwcommands:
420 return
421
422 if hgcmd == 'diff':
423 # only expand if comparing against working dir
424 node1, node2 = cmdutil.revpair(repo, cmdopts.get('rev'))
425 if node2 is not None:
426 return
427 # shrink if rev is not current node
428 if node1 is not None and node1 != repo.changectx().node():
429 hgcmd = 'diff1'
430
431 inc, exc = [], ['.hgtags']
432 for pat, opt in ui.configitems('keyword'):
469 for pat, opt in ui.configitems('keyword'):
433 if opt != 'ignore':
470 if opt != 'ignore':
434 inc.append(pat)
471 inc.append(pat)
435 else:
472 else:
436 exc.append(pat)
473 exc.append(pat)
437 if not inc:
474 if not inc:
438 return
475 return
439
476
440 global _kwtemplater
477 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
441 _kwtemplater = kwtemplater(ui, repo, inc, exc, hgcmd)
442
478
443 class kwrepo(repo.__class__):
479 class kwrepo(repo.__class__):
444 def file(self, f, kwmatch=False):
480 def file(self, f):
445 if f[0] == '/':
481 if f[0] == '/':
446 f = f[1:]
482 f = f[1:]
447 if kwmatch or _kwtemplater.matcher(f):
483 return kwfilelog(self.sopener, f)
448 return kwfilelog(self.sopener, f)
449 return filelog.filelog(self.sopener, f)
450
484
451 def wread(self, filename):
485 def wread(self, filename):
452 data = super(kwrepo, self).wread(filename)
486 data = super(kwrepo, self).wread(filename)
453 if hgcmd in restricted and _kwtemplater.matcher(filename):
487 return kwt.wread(filename, data)
454 return _kwtemplater.shrink(data)
455 return data
456
488
457 def commit(self, files=None, text='', user=None, date=None,
489 def commit(self, files=None, text='', user=None, date=None,
458 match=util.always, force=False, force_editor=False,
490 match=util.always, force=False, force_editor=False,
459 p1=None, p2=None, extra={}):
491 p1=None, p2=None, extra={}, empty_ok=False):
460 wlock = lock = None
492 wlock = lock = None
461 _p1 = _p2 = None
493 _p1 = _p2 = None
462 try:
494 try:
463 wlock = self.wlock()
495 wlock = self.wlock()
464 lock = self.lock()
496 lock = self.lock()
465 # store and postpone commit hooks
497 # store and postpone commit hooks
466 commithooks = {}
498 commithooks = {}
467 for name, cmd in ui.configitems('hooks'):
499 for name, cmd in ui.configitems('hooks'):
468 if name.split('.', 1)[0] == 'commit':
500 if name.split('.', 1)[0] == 'commit':
469 commithooks[name] = cmd
501 commithooks[name] = cmd
470 ui.setconfig('hooks', name, None)
502 ui.setconfig('hooks', name, None)
471 if commithooks:
503 if commithooks:
472 # store parents for commit hook environment
504 # store parents for commit hook environment
473 if p1 is None:
505 if p1 is None:
474 _p1, _p2 = repo.dirstate.parents()
506 _p1, _p2 = repo.dirstate.parents()
475 else:
507 else:
476 _p1, _p2 = p1, p2 or nullid
508 _p1, _p2 = p1, p2 or nullid
477 _p1 = hex(_p1)
509 _p1 = hex(_p1)
478 if _p2 == nullid:
510 if _p2 == nullid:
479 _p2 = ''
511 _p2 = ''
480 else:
512 else:
481 _p2 = hex(_p2)
513 _p2 = hex(_p2)
482
514
483 node = super(kwrepo,
515 node = super(kwrepo,
484 self).commit(files=files, text=text, user=user,
516 self).commit(files=files, text=text, user=user,
485 date=date, match=match, force=force,
517 date=date, match=match, force=force,
486 force_editor=force_editor,
518 force_editor=force_editor,
487 p1=p1, p2=p2, extra=extra)
519 p1=p1, p2=p2, extra=extra,
520 empty_ok=empty_ok)
488
521
489 # restore commit hooks
522 # restore commit hooks
490 for name, cmd in commithooks.iteritems():
523 for name, cmd in commithooks.iteritems():
491 ui.setconfig('hooks', name, cmd)
524 ui.setconfig('hooks', name, cmd)
492 if node is not None:
525 if node is not None:
493 _overwrite(ui, self, node=node)
526 kwt.overwrite(node=node)
494 repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
527 repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
495 return node
528 return node
496 finally:
529 finally:
497 del wlock, lock
530 del wlock, lock
498
531
499 repo.__class__ = kwrepo
532 repo.__class__ = kwrepo
500 patch.patchfile.__init__ = _kwpatchfile_init
533 patch.patchfile.__init__ = _kwpatchfile_init
534 patch.diff = _kw_diff
535 webcommands.changeset = webcommands.rev = _kwweb_changeset
536 webcommands.filediff = webcommands.diff = _kwweb_filediff
501
537
502
538
503 cmdtable = {
539 cmdtable = {
504 'kwdemo':
540 'kwdemo':
505 (demo,
541 (demo,
506 [('d', 'default', None, _('show default keyword template maps')),
542 [('d', 'default', None, _('show default keyword template maps')),
507 ('f', 'rcfile', [], _('read maps from rcfile'))],
543 ('f', 'rcfile', [], _('read maps from rcfile'))],
508 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
544 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
509 'kwexpand': (expand, commands.walkopts,
545 'kwexpand': (expand, commands.walkopts,
510 _('hg kwexpand [OPTION]... [FILE]...')),
546 _('hg kwexpand [OPTION]... [FILE]...')),
511 'kwfiles':
547 'kwfiles':
512 (files,
548 (files,
513 [('a', 'all', None, _('show keyword status flags of all files')),
549 [('a', 'all', None, _('show keyword status flags of all files')),
514 ('i', 'ignore', None, _('show files excluded from expansion')),
550 ('i', 'ignore', None, _('show files excluded from expansion')),
515 ('u', 'untracked', None, _('additionally show untracked files')),
551 ('u', 'untracked', None, _('additionally show untracked files')),
516 ] + commands.walkopts,
552 ] + commands.walkopts,
517 _('hg kwfiles [OPTION]... [FILE]...')),
553 _('hg kwfiles [OPTION]... [FILE]...')),
518 'kwshrink': (shrink, commands.walkopts,
554 'kwshrink': (shrink, commands.walkopts,
519 _('hg kwshrink [OPTION]... [FILE]...')),
555 _('hg kwshrink [OPTION]... [FILE]...')),
520 }
556 }
@@ -1,2315 +1,2351 b''
1 # queue.py - patch queues for mercurial
1 # queue.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 '''patch management and development
8 '''patch management and development
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use "hg help command" for more details):
17 Common tasks (use "hg help command" for more details):
18
18
19 prepare repository to work with patches qinit
19 prepare repository to work with patches qinit
20 create new patch qnew
20 create new patch qnew
21 import existing patch qimport
21 import existing patch qimport
22
22
23 print patch series qseries
23 print patch series qseries
24 print applied patches qapplied
24 print applied patches qapplied
25 print name of top applied patch qtop
25 print name of top applied patch qtop
26
26
27 add known patch to applied stack qpush
27 add known patch to applied stack qpush
28 remove patch from applied stack qpop
28 remove patch from applied stack qpop
29 refresh contents of top applied patch qrefresh
29 refresh contents of top applied patch qrefresh
30 '''
30 '''
31
31
32 from mercurial.i18n import _
32 from mercurial.i18n import _
33 from mercurial import commands, cmdutil, hg, patch, revlog, util
33 from mercurial import commands, cmdutil, hg, patch, revlog, util
34 from mercurial import repair
34 from mercurial import repair
35 import os, sys, re, errno
35 import os, sys, re, errno
36
36
37 commands.norepo += " qclone"
37 commands.norepo += " qclone"
38
38
39 # Patch names looks like unix-file names.
39 # Patch names looks like unix-file names.
40 # They must be joinable with queue directory and result in the patch path.
40 # They must be joinable with queue directory and result in the patch path.
41 normname = util.normpath
41 normname = util.normpath
42
42
43 class statusentry:
43 class statusentry:
44 def __init__(self, rev, name=None):
44 def __init__(self, rev, name=None):
45 if not name:
45 if not name:
46 fields = rev.split(':', 1)
46 fields = rev.split(':', 1)
47 if len(fields) == 2:
47 if len(fields) == 2:
48 self.rev, self.name = fields
48 self.rev, self.name = fields
49 else:
49 else:
50 self.rev, self.name = None, None
50 self.rev, self.name = None, None
51 else:
51 else:
52 self.rev, self.name = rev, name
52 self.rev, self.name = rev, name
53
53
54 def __str__(self):
54 def __str__(self):
55 return self.rev + ':' + self.name
55 return self.rev + ':' + self.name
56
56
57 class queue:
57 class queue:
58 def __init__(self, ui, path, patchdir=None):
58 def __init__(self, ui, path, patchdir=None):
59 self.basepath = path
59 self.basepath = path
60 self.path = patchdir or os.path.join(path, "patches")
60 self.path = patchdir or os.path.join(path, "patches")
61 self.opener = util.opener(self.path)
61 self.opener = util.opener(self.path)
62 self.ui = ui
62 self.ui = ui
63 self.applied = []
63 self.applied = []
64 self.full_series = []
64 self.full_series = []
65 self.applied_dirty = 0
65 self.applied_dirty = 0
66 self.series_dirty = 0
66 self.series_dirty = 0
67 self.series_path = "series"
67 self.series_path = "series"
68 self.status_path = "status"
68 self.status_path = "status"
69 self.guards_path = "guards"
69 self.guards_path = "guards"
70 self.active_guards = None
70 self.active_guards = None
71 self.guards_dirty = False
71 self.guards_dirty = False
72 self._diffopts = None
72 self._diffopts = None
73
73
74 if os.path.exists(self.join(self.series_path)):
74 if os.path.exists(self.join(self.series_path)):
75 self.full_series = self.opener(self.series_path).read().splitlines()
75 self.full_series = self.opener(self.series_path).read().splitlines()
76 self.parse_series()
76 self.parse_series()
77
77
78 if os.path.exists(self.join(self.status_path)):
78 if os.path.exists(self.join(self.status_path)):
79 lines = self.opener(self.status_path).read().splitlines()
79 lines = self.opener(self.status_path).read().splitlines()
80 self.applied = [statusentry(l) for l in lines]
80 self.applied = [statusentry(l) for l in lines]
81
81
82 def diffopts(self):
82 def diffopts(self):
83 if self._diffopts is None:
83 if self._diffopts is None:
84 self._diffopts = patch.diffopts(self.ui)
84 self._diffopts = patch.diffopts(self.ui)
85 return self._diffopts
85 return self._diffopts
86
86
87 def join(self, *p):
87 def join(self, *p):
88 return os.path.join(self.path, *p)
88 return os.path.join(self.path, *p)
89
89
90 def find_series(self, patch):
90 def find_series(self, patch):
91 pre = re.compile("(\s*)([^#]+)")
91 pre = re.compile("(\s*)([^#]+)")
92 index = 0
92 index = 0
93 for l in self.full_series:
93 for l in self.full_series:
94 m = pre.match(l)
94 m = pre.match(l)
95 if m:
95 if m:
96 s = m.group(2)
96 s = m.group(2)
97 s = s.rstrip()
97 s = s.rstrip()
98 if s == patch:
98 if s == patch:
99 return index
99 return index
100 index += 1
100 index += 1
101 return None
101 return None
102
102
103 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
103 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
104
104
105 def parse_series(self):
105 def parse_series(self):
106 self.series = []
106 self.series = []
107 self.series_guards = []
107 self.series_guards = []
108 for l in self.full_series:
108 for l in self.full_series:
109 h = l.find('#')
109 h = l.find('#')
110 if h == -1:
110 if h == -1:
111 patch = l
111 patch = l
112 comment = ''
112 comment = ''
113 elif h == 0:
113 elif h == 0:
114 continue
114 continue
115 else:
115 else:
116 patch = l[:h]
116 patch = l[:h]
117 comment = l[h:]
117 comment = l[h:]
118 patch = patch.strip()
118 patch = patch.strip()
119 if patch:
119 if patch:
120 if patch in self.series:
120 if patch in self.series:
121 raise util.Abort(_('%s appears more than once in %s') %
121 raise util.Abort(_('%s appears more than once in %s') %
122 (patch, self.join(self.series_path)))
122 (patch, self.join(self.series_path)))
123 self.series.append(patch)
123 self.series.append(patch)
124 self.series_guards.append(self.guard_re.findall(comment))
124 self.series_guards.append(self.guard_re.findall(comment))
125
125
126 def check_guard(self, guard):
126 def check_guard(self, guard):
127 bad_chars = '# \t\r\n\f'
127 bad_chars = '# \t\r\n\f'
128 first = guard[0]
128 first = guard[0]
129 for c in '-+':
129 for c in '-+':
130 if first == c:
130 if first == c:
131 return (_('guard %r starts with invalid character: %r') %
131 return (_('guard %r starts with invalid character: %r') %
132 (guard, c))
132 (guard, c))
133 for c in bad_chars:
133 for c in bad_chars:
134 if c in guard:
134 if c in guard:
135 return _('invalid character in guard %r: %r') % (guard, c)
135 return _('invalid character in guard %r: %r') % (guard, c)
136
136
137 def set_active(self, guards):
137 def set_active(self, guards):
138 for guard in guards:
138 for guard in guards:
139 bad = self.check_guard(guard)
139 bad = self.check_guard(guard)
140 if bad:
140 if bad:
141 raise util.Abort(bad)
141 raise util.Abort(bad)
142 guards = dict.fromkeys(guards).keys()
142 guards = dict.fromkeys(guards).keys()
143 guards.sort()
143 guards.sort()
144 self.ui.debug('active guards: %s\n' % ' '.join(guards))
144 self.ui.debug('active guards: %s\n' % ' '.join(guards))
145 self.active_guards = guards
145 self.active_guards = guards
146 self.guards_dirty = True
146 self.guards_dirty = True
147
147
148 def active(self):
148 def active(self):
149 if self.active_guards is None:
149 if self.active_guards is None:
150 self.active_guards = []
150 self.active_guards = []
151 try:
151 try:
152 guards = self.opener(self.guards_path).read().split()
152 guards = self.opener(self.guards_path).read().split()
153 except IOError, err:
153 except IOError, err:
154 if err.errno != errno.ENOENT: raise
154 if err.errno != errno.ENOENT: raise
155 guards = []
155 guards = []
156 for i, guard in enumerate(guards):
156 for i, guard in enumerate(guards):
157 bad = self.check_guard(guard)
157 bad = self.check_guard(guard)
158 if bad:
158 if bad:
159 self.ui.warn('%s:%d: %s\n' %
159 self.ui.warn('%s:%d: %s\n' %
160 (self.join(self.guards_path), i + 1, bad))
160 (self.join(self.guards_path), i + 1, bad))
161 else:
161 else:
162 self.active_guards.append(guard)
162 self.active_guards.append(guard)
163 return self.active_guards
163 return self.active_guards
164
164
165 def set_guards(self, idx, guards):
165 def set_guards(self, idx, guards):
166 for g in guards:
166 for g in guards:
167 if len(g) < 2:
167 if len(g) < 2:
168 raise util.Abort(_('guard %r too short') % g)
168 raise util.Abort(_('guard %r too short') % g)
169 if g[0] not in '-+':
169 if g[0] not in '-+':
170 raise util.Abort(_('guard %r starts with invalid char') % g)
170 raise util.Abort(_('guard %r starts with invalid char') % g)
171 bad = self.check_guard(g[1:])
171 bad = self.check_guard(g[1:])
172 if bad:
172 if bad:
173 raise util.Abort(bad)
173 raise util.Abort(bad)
174 drop = self.guard_re.sub('', self.full_series[idx])
174 drop = self.guard_re.sub('', self.full_series[idx])
175 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
175 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
176 self.parse_series()
176 self.parse_series()
177 self.series_dirty = True
177 self.series_dirty = True
178
178
179 def pushable(self, idx):
179 def pushable(self, idx):
180 if isinstance(idx, str):
180 if isinstance(idx, str):
181 idx = self.series.index(idx)
181 idx = self.series.index(idx)
182 patchguards = self.series_guards[idx]
182 patchguards = self.series_guards[idx]
183 if not patchguards:
183 if not patchguards:
184 return True, None
184 return True, None
185 default = False
185 default = False
186 guards = self.active()
186 guards = self.active()
187 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
187 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
188 if exactneg:
188 if exactneg:
189 return False, exactneg[0]
189 return False, exactneg[0]
190 pos = [g for g in patchguards if g[0] == '+']
190 pos = [g for g in patchguards if g[0] == '+']
191 exactpos = [g for g in pos if g[1:] in guards]
191 exactpos = [g for g in pos if g[1:] in guards]
192 if pos:
192 if pos:
193 if exactpos:
193 if exactpos:
194 return True, exactpos[0]
194 return True, exactpos[0]
195 return False, pos
195 return False, pos
196 return True, ''
196 return True, ''
197
197
198 def explain_pushable(self, idx, all_patches=False):
198 def explain_pushable(self, idx, all_patches=False):
199 write = all_patches and self.ui.write or self.ui.warn
199 write = all_patches and self.ui.write or self.ui.warn
200 if all_patches or self.ui.verbose:
200 if all_patches or self.ui.verbose:
201 if isinstance(idx, str):
201 if isinstance(idx, str):
202 idx = self.series.index(idx)
202 idx = self.series.index(idx)
203 pushable, why = self.pushable(idx)
203 pushable, why = self.pushable(idx)
204 if all_patches and pushable:
204 if all_patches and pushable:
205 if why is None:
205 if why is None:
206 write(_('allowing %s - no guards in effect\n') %
206 write(_('allowing %s - no guards in effect\n') %
207 self.series[idx])
207 self.series[idx])
208 else:
208 else:
209 if not why:
209 if not why:
210 write(_('allowing %s - no matching negative guards\n') %
210 write(_('allowing %s - no matching negative guards\n') %
211 self.series[idx])
211 self.series[idx])
212 else:
212 else:
213 write(_('allowing %s - guarded by %r\n') %
213 write(_('allowing %s - guarded by %r\n') %
214 (self.series[idx], why))
214 (self.series[idx], why))
215 if not pushable:
215 if not pushable:
216 if why:
216 if why:
217 write(_('skipping %s - guarded by %r\n') %
217 write(_('skipping %s - guarded by %r\n') %
218 (self.series[idx], why))
218 (self.series[idx], why))
219 else:
219 else:
220 write(_('skipping %s - no matching guards\n') %
220 write(_('skipping %s - no matching guards\n') %
221 self.series[idx])
221 self.series[idx])
222
222
223 def save_dirty(self):
223 def save_dirty(self):
224 def write_list(items, path):
224 def write_list(items, path):
225 fp = self.opener(path, 'w')
225 fp = self.opener(path, 'w')
226 for i in items:
226 for i in items:
227 fp.write("%s\n" % i)
227 fp.write("%s\n" % i)
228 fp.close()
228 fp.close()
229 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
229 if self.applied_dirty: write_list(map(str, self.applied), self.status_path)
230 if self.series_dirty: write_list(self.full_series, self.series_path)
230 if self.series_dirty: write_list(self.full_series, self.series_path)
231 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
231 if self.guards_dirty: write_list(self.active_guards, self.guards_path)
232
232
233 def readheaders(self, patch):
233 def readheaders(self, patch):
234 def eatdiff(lines):
234 def eatdiff(lines):
235 while lines:
235 while lines:
236 l = lines[-1]
236 l = lines[-1]
237 if (l.startswith("diff -") or
237 if (l.startswith("diff -") or
238 l.startswith("Index:") or
238 l.startswith("Index:") or
239 l.startswith("===========")):
239 l.startswith("===========")):
240 del lines[-1]
240 del lines[-1]
241 else:
241 else:
242 break
242 break
243 def eatempty(lines):
243 def eatempty(lines):
244 while lines:
244 while lines:
245 l = lines[-1]
245 l = lines[-1]
246 if re.match('\s*$', l):
246 if re.match('\s*$', l):
247 del lines[-1]
247 del lines[-1]
248 else:
248 else:
249 break
249 break
250
250
251 pf = self.join(patch)
251 pf = self.join(patch)
252 message = []
252 message = []
253 comments = []
253 comments = []
254 user = None
254 user = None
255 date = None
255 date = None
256 format = None
256 format = None
257 subject = None
257 subject = None
258 diffstart = 0
258 diffstart = 0
259
259
260 for line in file(pf):
260 for line in file(pf):
261 line = line.rstrip()
261 line = line.rstrip()
262 if line.startswith('diff --git'):
262 if line.startswith('diff --git'):
263 diffstart = 2
263 diffstart = 2
264 break
264 break
265 if diffstart:
265 if diffstart:
266 if line.startswith('+++ '):
266 if line.startswith('+++ '):
267 diffstart = 2
267 diffstart = 2
268 break
268 break
269 if line.startswith("--- "):
269 if line.startswith("--- "):
270 diffstart = 1
270 diffstart = 1
271 continue
271 continue
272 elif format == "hgpatch":
272 elif format == "hgpatch":
273 # parse values when importing the result of an hg export
273 # parse values when importing the result of an hg export
274 if line.startswith("# User "):
274 if line.startswith("# User "):
275 user = line[7:]
275 user = line[7:]
276 elif line.startswith("# Date "):
276 elif line.startswith("# Date "):
277 date = line[7:]
277 date = line[7:]
278 elif not line.startswith("# ") and line:
278 elif not line.startswith("# ") and line:
279 message.append(line)
279 message.append(line)
280 format = None
280 format = None
281 elif line == '# HG changeset patch':
281 elif line == '# HG changeset patch':
282 format = "hgpatch"
282 format = "hgpatch"
283 elif (format != "tagdone" and (line.startswith("Subject: ") or
283 elif (format != "tagdone" and (line.startswith("Subject: ") or
284 line.startswith("subject: "))):
284 line.startswith("subject: "))):
285 subject = line[9:]
285 subject = line[9:]
286 format = "tag"
286 format = "tag"
287 elif (format != "tagdone" and (line.startswith("From: ") or
287 elif (format != "tagdone" and (line.startswith("From: ") or
288 line.startswith("from: "))):
288 line.startswith("from: "))):
289 user = line[6:]
289 user = line[6:]
290 format = "tag"
290 format = "tag"
291 elif format == "tag" and line == "":
291 elif format == "tag" and line == "":
292 # when looking for tags (subject: from: etc) they
292 # when looking for tags (subject: from: etc) they
293 # end once you find a blank line in the source
293 # end once you find a blank line in the source
294 format = "tagdone"
294 format = "tagdone"
295 elif message or line:
295 elif message or line:
296 message.append(line)
296 message.append(line)
297 comments.append(line)
297 comments.append(line)
298
298
299 eatdiff(message)
299 eatdiff(message)
300 eatdiff(comments)
300 eatdiff(comments)
301 eatempty(message)
301 eatempty(message)
302 eatempty(comments)
302 eatempty(comments)
303
303
304 # make sure message isn't empty
304 # make sure message isn't empty
305 if format and format.startswith("tag") and subject:
305 if format and format.startswith("tag") and subject:
306 message.insert(0, "")
306 message.insert(0, "")
307 message.insert(0, subject)
307 message.insert(0, subject)
308 return (message, comments, user, date, diffstart > 1)
308 return (message, comments, user, date, diffstart > 1)
309
309
310 def removeundo(self, repo):
310 def removeundo(self, repo):
311 undo = repo.sjoin('undo')
311 undo = repo.sjoin('undo')
312 if not os.path.exists(undo):
312 if not os.path.exists(undo):
313 return
313 return
314 try:
314 try:
315 os.unlink(undo)
315 os.unlink(undo)
316 except OSError, inst:
316 except OSError, inst:
317 self.ui.warn('error removing undo: %s\n' % str(inst))
317 self.ui.warn('error removing undo: %s\n' % str(inst))
318
318
319 def printdiff(self, repo, node1, node2=None, files=None,
319 def printdiff(self, repo, node1, node2=None, files=None,
320 fp=None, changes=None, opts={}):
320 fp=None, changes=None, opts={}):
321 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
321 fns, matchfn, anypats = cmdutil.matchpats(repo, files, opts)
322
322
323 patch.diff(repo, node1, node2, fns, match=matchfn,
323 patch.diff(repo, node1, node2, fns, match=matchfn,
324 fp=fp, changes=changes, opts=self.diffopts())
324 fp=fp, changes=changes, opts=self.diffopts())
325
325
326 def mergeone(self, repo, mergeq, head, patch, rev):
326 def mergeone(self, repo, mergeq, head, patch, rev):
327 # first try just applying the patch
327 # first try just applying the patch
328 (err, n) = self.apply(repo, [ patch ], update_status=False,
328 (err, n) = self.apply(repo, [ patch ], update_status=False,
329 strict=True, merge=rev)
329 strict=True, merge=rev)
330
330
331 if err == 0:
331 if err == 0:
332 return (err, n)
332 return (err, n)
333
333
334 if n is None:
334 if n is None:
335 raise util.Abort(_("apply failed for patch %s") % patch)
335 raise util.Abort(_("apply failed for patch %s") % patch)
336
336
337 self.ui.warn("patch didn't work out, merging %s\n" % patch)
337 self.ui.warn("patch didn't work out, merging %s\n" % patch)
338
338
339 # apply failed, strip away that rev and merge.
339 # apply failed, strip away that rev and merge.
340 hg.clean(repo, head)
340 hg.clean(repo, head)
341 self.strip(repo, n, update=False, backup='strip')
341 self.strip(repo, n, update=False, backup='strip')
342
342
343 ctx = repo.changectx(rev)
343 ctx = repo.changectx(rev)
344 ret = hg.merge(repo, rev)
344 ret = hg.merge(repo, rev)
345 if ret:
345 if ret:
346 raise util.Abort(_("update returned %d") % ret)
346 raise util.Abort(_("update returned %d") % ret)
347 n = repo.commit(None, ctx.description(), ctx.user(), force=1)
347 n = repo.commit(None, ctx.description(), ctx.user(), force=1)
348 if n == None:
348 if n == None:
349 raise util.Abort(_("repo commit failed"))
349 raise util.Abort(_("repo commit failed"))
350 try:
350 try:
351 message, comments, user, date, patchfound = mergeq.readheaders(patch)
351 message, comments, user, date, patchfound = mergeq.readheaders(patch)
352 except:
352 except:
353 raise util.Abort(_("unable to read %s") % patch)
353 raise util.Abort(_("unable to read %s") % patch)
354
354
355 patchf = self.opener(patch, "w")
355 patchf = self.opener(patch, "w")
356 if comments:
356 if comments:
357 comments = "\n".join(comments) + '\n\n'
357 comments = "\n".join(comments) + '\n\n'
358 patchf.write(comments)
358 patchf.write(comments)
359 self.printdiff(repo, head, n, fp=patchf)
359 self.printdiff(repo, head, n, fp=patchf)
360 patchf.close()
360 patchf.close()
361 self.removeundo(repo)
361 self.removeundo(repo)
362 return (0, n)
362 return (0, n)
363
363
364 def qparents(self, repo, rev=None):
364 def qparents(self, repo, rev=None):
365 if rev is None:
365 if rev is None:
366 (p1, p2) = repo.dirstate.parents()
366 (p1, p2) = repo.dirstate.parents()
367 if p2 == revlog.nullid:
367 if p2 == revlog.nullid:
368 return p1
368 return p1
369 if len(self.applied) == 0:
369 if len(self.applied) == 0:
370 return None
370 return None
371 return revlog.bin(self.applied[-1].rev)
371 return revlog.bin(self.applied[-1].rev)
372 pp = repo.changelog.parents(rev)
372 pp = repo.changelog.parents(rev)
373 if pp[1] != revlog.nullid:
373 if pp[1] != revlog.nullid:
374 arevs = [ x.rev for x in self.applied ]
374 arevs = [ x.rev for x in self.applied ]
375 p0 = revlog.hex(pp[0])
375 p0 = revlog.hex(pp[0])
376 p1 = revlog.hex(pp[1])
376 p1 = revlog.hex(pp[1])
377 if p0 in arevs:
377 if p0 in arevs:
378 return pp[0]
378 return pp[0]
379 if p1 in arevs:
379 if p1 in arevs:
380 return pp[1]
380 return pp[1]
381 return pp[0]
381 return pp[0]
382
382
383 def mergepatch(self, repo, mergeq, series):
383 def mergepatch(self, repo, mergeq, series):
384 if len(self.applied) == 0:
384 if len(self.applied) == 0:
385 # each of the patches merged in will have two parents. This
385 # each of the patches merged in will have two parents. This
386 # can confuse the qrefresh, qdiff, and strip code because it
386 # can confuse the qrefresh, qdiff, and strip code because it
387 # needs to know which parent is actually in the patch queue.
387 # needs to know which parent is actually in the patch queue.
388 # so, we insert a merge marker with only one parent. This way
388 # so, we insert a merge marker with only one parent. This way
389 # the first patch in the queue is never a merge patch
389 # the first patch in the queue is never a merge patch
390 #
390 #
391 pname = ".hg.patches.merge.marker"
391 pname = ".hg.patches.merge.marker"
392 n = repo.commit(None, '[mq]: merge marker', user=None, force=1)
392 n = repo.commit(None, '[mq]: merge marker', user=None, force=1)
393 self.removeundo(repo)
393 self.removeundo(repo)
394 self.applied.append(statusentry(revlog.hex(n), pname))
394 self.applied.append(statusentry(revlog.hex(n), pname))
395 self.applied_dirty = 1
395 self.applied_dirty = 1
396
396
397 head = self.qparents(repo)
397 head = self.qparents(repo)
398
398
399 for patch in series:
399 for patch in series:
400 patch = mergeq.lookup(patch, strict=True)
400 patch = mergeq.lookup(patch, strict=True)
401 if not patch:
401 if not patch:
402 self.ui.warn("patch %s does not exist\n" % patch)
402 self.ui.warn("patch %s does not exist\n" % patch)
403 return (1, None)
403 return (1, None)
404 pushable, reason = self.pushable(patch)
404 pushable, reason = self.pushable(patch)
405 if not pushable:
405 if not pushable:
406 self.explain_pushable(patch, all_patches=True)
406 self.explain_pushable(patch, all_patches=True)
407 continue
407 continue
408 info = mergeq.isapplied(patch)
408 info = mergeq.isapplied(patch)
409 if not info:
409 if not info:
410 self.ui.warn("patch %s is not applied\n" % patch)
410 self.ui.warn("patch %s is not applied\n" % patch)
411 return (1, None)
411 return (1, None)
412 rev = revlog.bin(info[1])
412 rev = revlog.bin(info[1])
413 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
413 (err, head) = self.mergeone(repo, mergeq, head, patch, rev)
414 if head:
414 if head:
415 self.applied.append(statusentry(revlog.hex(head), patch))
415 self.applied.append(statusentry(revlog.hex(head), patch))
416 self.applied_dirty = 1
416 self.applied_dirty = 1
417 if err:
417 if err:
418 return (err, head)
418 return (err, head)
419 self.save_dirty()
419 self.save_dirty()
420 return (0, head)
420 return (0, head)
421
421
422 def patch(self, repo, patchfile):
422 def patch(self, repo, patchfile):
423 '''Apply patchfile to the working directory.
423 '''Apply patchfile to the working directory.
424 patchfile: file name of patch'''
424 patchfile: file name of patch'''
425 files = {}
425 files = {}
426 try:
426 try:
427 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
427 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
428 files=files)
428 files=files)
429 except Exception, inst:
429 except Exception, inst:
430 self.ui.note(str(inst) + '\n')
430 self.ui.note(str(inst) + '\n')
431 if not self.ui.verbose:
431 if not self.ui.verbose:
432 self.ui.warn("patch failed, unable to continue (try -v)\n")
432 self.ui.warn("patch failed, unable to continue (try -v)\n")
433 return (False, files, False)
433 return (False, files, False)
434
434
435 return (True, files, fuzz)
435 return (True, files, fuzz)
436
436
437 def apply(self, repo, series, list=False, update_status=True,
437 def apply(self, repo, series, list=False, update_status=True,
438 strict=False, patchdir=None, merge=None, all_files={}):
438 strict=False, patchdir=None, merge=None, all_files={}):
439 wlock = lock = tr = None
439 wlock = lock = tr = None
440 try:
440 try:
441 wlock = repo.wlock()
441 wlock = repo.wlock()
442 lock = repo.lock()
442 lock = repo.lock()
443 tr = repo.transaction()
443 tr = repo.transaction()
444 try:
444 try:
445 ret = self._apply(repo, series, list, update_status,
445 ret = self._apply(repo, series, list, update_status,
446 strict, patchdir, merge, all_files=all_files)
446 strict, patchdir, merge, all_files=all_files)
447 tr.close()
447 tr.close()
448 self.save_dirty()
448 self.save_dirty()
449 return ret
449 return ret
450 except:
450 except:
451 try:
451 try:
452 tr.abort()
452 tr.abort()
453 finally:
453 finally:
454 repo.invalidate()
454 repo.invalidate()
455 repo.dirstate.invalidate()
455 repo.dirstate.invalidate()
456 raise
456 raise
457 finally:
457 finally:
458 del tr, lock, wlock
458 del tr, lock, wlock
459 self.removeundo(repo)
459 self.removeundo(repo)
460
460
461 def _apply(self, repo, series, list=False, update_status=True,
461 def _apply(self, repo, series, list=False, update_status=True,
462 strict=False, patchdir=None, merge=None, all_files={}):
462 strict=False, patchdir=None, merge=None, all_files={}):
463 # TODO unify with commands.py
463 # TODO unify with commands.py
464 if not patchdir:
464 if not patchdir:
465 patchdir = self.path
465 patchdir = self.path
466 err = 0
466 err = 0
467 n = None
467 n = None
468 for patchname in series:
468 for patchname in series:
469 pushable, reason = self.pushable(patchname)
469 pushable, reason = self.pushable(patchname)
470 if not pushable:
470 if not pushable:
471 self.explain_pushable(patchname, all_patches=True)
471 self.explain_pushable(patchname, all_patches=True)
472 continue
472 continue
473 self.ui.warn("applying %s\n" % patchname)
473 self.ui.warn("applying %s\n" % patchname)
474 pf = os.path.join(patchdir, patchname)
474 pf = os.path.join(patchdir, patchname)
475
475
476 try:
476 try:
477 message, comments, user, date, patchfound = self.readheaders(patchname)
477 message, comments, user, date, patchfound = self.readheaders(patchname)
478 except:
478 except:
479 self.ui.warn("Unable to read %s\n" % patchname)
479 self.ui.warn("Unable to read %s\n" % patchname)
480 err = 1
480 err = 1
481 break
481 break
482
482
483 if not message:
483 if not message:
484 message = "imported patch %s\n" % patchname
484 message = "imported patch %s\n" % patchname
485 else:
485 else:
486 if list:
486 if list:
487 message.append("\nimported patch %s" % patchname)
487 message.append("\nimported patch %s" % patchname)
488 message = '\n'.join(message)
488 message = '\n'.join(message)
489
489
490 (patcherr, files, fuzz) = self.patch(repo, pf)
490 (patcherr, files, fuzz) = self.patch(repo, pf)
491 all_files.update(files)
491 all_files.update(files)
492 patcherr = not patcherr
492 patcherr = not patcherr
493
493
494 if merge and files:
494 if merge and files:
495 # Mark as removed/merged and update dirstate parent info
495 # Mark as removed/merged and update dirstate parent info
496 removed = []
496 removed = []
497 merged = []
497 merged = []
498 for f in files:
498 for f in files:
499 if os.path.exists(repo.wjoin(f)):
499 if os.path.exists(repo.wjoin(f)):
500 merged.append(f)
500 merged.append(f)
501 else:
501 else:
502 removed.append(f)
502 removed.append(f)
503 for f in removed:
503 for f in removed:
504 repo.dirstate.remove(f)
504 repo.dirstate.remove(f)
505 for f in merged:
505 for f in merged:
506 repo.dirstate.merge(f)
506 repo.dirstate.merge(f)
507 p1, p2 = repo.dirstate.parents()
507 p1, p2 = repo.dirstate.parents()
508 repo.dirstate.setparents(p1, merge)
508 repo.dirstate.setparents(p1, merge)
509 files = patch.updatedir(self.ui, repo, files)
509 files = patch.updatedir(self.ui, repo, files)
510 n = repo.commit(files, message, user, date, force=1)
510 n = repo.commit(files, message, user, date, force=1)
511
511
512 if n == None:
512 if n == None:
513 raise util.Abort(_("repo commit failed"))
513 raise util.Abort(_("repo commit failed"))
514
514
515 if update_status:
515 if update_status:
516 self.applied.append(statusentry(revlog.hex(n), patchname))
516 self.applied.append(statusentry(revlog.hex(n), patchname))
517
517
518 if patcherr:
518 if patcherr:
519 if not patchfound:
519 if not patchfound:
520 self.ui.warn("patch %s is empty\n" % patchname)
520 self.ui.warn("patch %s is empty\n" % patchname)
521 err = 0
521 err = 0
522 else:
522 else:
523 self.ui.warn("patch failed, rejects left in working dir\n")
523 self.ui.warn("patch failed, rejects left in working dir\n")
524 err = 1
524 err = 1
525 break
525 break
526
526
527 if fuzz and strict:
527 if fuzz and strict:
528 self.ui.warn("fuzz found when applying patch, stopping\n")
528 self.ui.warn("fuzz found when applying patch, stopping\n")
529 err = 1
529 err = 1
530 break
530 break
531 return (err, n)
531 return (err, n)
532
532
533 def delete(self, repo, patches, opts):
533 def delete(self, repo, patches, opts):
534 if not patches and not opts.get('rev'):
534 if not patches and not opts.get('rev'):
535 raise util.Abort(_('qdelete requires at least one revision or '
535 raise util.Abort(_('qdelete requires at least one revision or '
536 'patch name'))
536 'patch name'))
537
537
538 realpatches = []
538 realpatches = []
539 for patch in patches:
539 for patch in patches:
540 patch = self.lookup(patch, strict=True)
540 patch = self.lookup(patch, strict=True)
541 info = self.isapplied(patch)
541 info = self.isapplied(patch)
542 if info:
542 if info:
543 raise util.Abort(_("cannot delete applied patch %s") % patch)
543 raise util.Abort(_("cannot delete applied patch %s") % patch)
544 if patch not in self.series:
544 if patch not in self.series:
545 raise util.Abort(_("patch %s not in series file") % patch)
545 raise util.Abort(_("patch %s not in series file") % patch)
546 realpatches.append(patch)
546 realpatches.append(patch)
547
547
548 appliedbase = 0
548 appliedbase = 0
549 if opts.get('rev'):
549 if opts.get('rev'):
550 if not self.applied:
550 if not self.applied:
551 raise util.Abort(_('no patches applied'))
551 raise util.Abort(_('no patches applied'))
552 revs = cmdutil.revrange(repo, opts['rev'])
552 revs = cmdutil.revrange(repo, opts['rev'])
553 if len(revs) > 1 and revs[0] > revs[1]:
553 if len(revs) > 1 and revs[0] > revs[1]:
554 revs.reverse()
554 revs.reverse()
555 for rev in revs:
555 for rev in revs:
556 if appliedbase >= len(self.applied):
556 if appliedbase >= len(self.applied):
557 raise util.Abort(_("revision %d is not managed") % rev)
557 raise util.Abort(_("revision %d is not managed") % rev)
558
558
559 base = revlog.bin(self.applied[appliedbase].rev)
559 base = revlog.bin(self.applied[appliedbase].rev)
560 node = repo.changelog.node(rev)
560 node = repo.changelog.node(rev)
561 if node != base:
561 if node != base:
562 raise util.Abort(_("cannot delete revision %d above "
562 raise util.Abort(_("cannot delete revision %d above "
563 "applied patches") % rev)
563 "applied patches") % rev)
564 realpatches.append(self.applied[appliedbase].name)
564 realpatches.append(self.applied[appliedbase].name)
565 appliedbase += 1
565 appliedbase += 1
566
566
567 if not opts.get('keep'):
567 if not opts.get('keep'):
568 r = self.qrepo()
568 r = self.qrepo()
569 if r:
569 if r:
570 r.remove(realpatches, True)
570 r.remove(realpatches, True)
571 else:
571 else:
572 for p in realpatches:
572 for p in realpatches:
573 os.unlink(self.join(p))
573 os.unlink(self.join(p))
574
574
575 if appliedbase:
575 if appliedbase:
576 del self.applied[:appliedbase]
576 del self.applied[:appliedbase]
577 self.applied_dirty = 1
577 self.applied_dirty = 1
578 indices = [self.find_series(p) for p in realpatches]
578 indices = [self.find_series(p) for p in realpatches]
579 indices.sort()
579 indices.sort()
580 for i in indices[-1::-1]:
580 for i in indices[-1::-1]:
581 del self.full_series[i]
581 del self.full_series[i]
582 self.parse_series()
582 self.parse_series()
583 self.series_dirty = 1
583 self.series_dirty = 1
584
584
585 def check_toppatch(self, repo):
585 def check_toppatch(self, repo):
586 if len(self.applied) > 0:
586 if len(self.applied) > 0:
587 top = revlog.bin(self.applied[-1].rev)
587 top = revlog.bin(self.applied[-1].rev)
588 pp = repo.dirstate.parents()
588 pp = repo.dirstate.parents()
589 if top not in pp:
589 if top not in pp:
590 raise util.Abort(_("working directory revision is not qtip"))
590 raise util.Abort(_("working directory revision is not qtip"))
591 return top
591 return top
592 return None
592 return None
593 def check_localchanges(self, repo, force=False, refresh=True):
593 def check_localchanges(self, repo, force=False, refresh=True):
594 m, a, r, d = repo.status()[:4]
594 m, a, r, d = repo.status()[:4]
595 if m or a or r or d:
595 if m or a or r or d:
596 if not force:
596 if not force:
597 if refresh:
597 if refresh:
598 raise util.Abort(_("local changes found, refresh first"))
598 raise util.Abort(_("local changes found, refresh first"))
599 else:
599 else:
600 raise util.Abort(_("local changes found"))
600 raise util.Abort(_("local changes found"))
601 return m, a, r, d
601 return m, a, r, d
602
602
603 _reserved = ('series', 'status', 'guards')
604 def check_reserved_name(self, name):
605 if (name in self._reserved or name.startswith('.hg')
606 or name.startswith('.mq')):
607 raise util.Abort(_('"%s" cannot be used as the name of a patch')
608 % name)
609
603 def new(self, repo, patch, *pats, **opts):
610 def new(self, repo, patch, *pats, **opts):
604 msg = opts.get('msg')
611 msg = opts.get('msg')
605 force = opts.get('force')
612 force = opts.get('force')
606 user = opts.get('user')
613 user = opts.get('user')
607 date = opts.get('date')
614 date = opts.get('date')
615 if date:
616 date = util.parsedate(date)
617 self.check_reserved_name(patch)
608 if os.path.exists(self.join(patch)):
618 if os.path.exists(self.join(patch)):
609 raise util.Abort(_('patch "%s" already exists') % patch)
619 raise util.Abort(_('patch "%s" already exists') % patch)
610 if opts.get('include') or opts.get('exclude') or pats:
620 if opts.get('include') or opts.get('exclude') or pats:
611 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
621 fns, match, anypats = cmdutil.matchpats(repo, pats, opts)
612 m, a, r, d = repo.status(files=fns, match=match)[:4]
622 m, a, r, d = repo.status(files=fns, match=match)[:4]
613 else:
623 else:
614 m, a, r, d = self.check_localchanges(repo, force)
624 m, a, r, d = self.check_localchanges(repo, force)
615 fns, match, anypats = cmdutil.matchpats(repo, m + a + r)
625 fns, match, anypats = cmdutil.matchpats(repo, m + a + r)
616 commitfiles = m + a + r
626 commitfiles = m + a + r
617 self.check_toppatch(repo)
627 self.check_toppatch(repo)
618 wlock = repo.wlock()
628 wlock = repo.wlock()
619 try:
629 try:
620 insert = self.full_series_end()
630 insert = self.full_series_end()
621 commitmsg = msg and msg or ("[mq]: %s" % patch)
631 commitmsg = msg and msg or ("[mq]: %s" % patch)
622 n = repo.commit(commitfiles, commitmsg, user, date, match=match, force=True)
632 n = repo.commit(commitfiles, commitmsg, user, date, match=match, force=True)
623 if n == None:
633 if n == None:
624 raise util.Abort(_("repo commit failed"))
634 raise util.Abort(_("repo commit failed"))
625 self.full_series[insert:insert] = [patch]
635 self.full_series[insert:insert] = [patch]
626 self.applied.append(statusentry(revlog.hex(n), patch))
636 self.applied.append(statusentry(revlog.hex(n), patch))
627 self.parse_series()
637 self.parse_series()
628 self.series_dirty = 1
638 self.series_dirty = 1
629 self.applied_dirty = 1
639 self.applied_dirty = 1
630 p = self.opener(patch, "w")
640 p = self.opener(patch, "w")
631 if date:
641 if date:
632 p.write("# HG changeset patch\n")
642 p.write("# HG changeset patch\n")
633 if user:
643 if user:
634 p.write("# User " + user + "\n")
644 p.write("# User " + user + "\n")
635 p.write("# Date " + date + "\n")
645 p.write("# Date %d %d\n" % date)
636 p.write("\n")
646 p.write("\n")
637 elif user:
647 elif user:
638 p.write("From: " + user + "\n")
648 p.write("From: " + user + "\n")
639 p.write("\n")
649 p.write("\n")
640 if msg:
650 if msg:
641 msg = msg + "\n"
651 msg = msg + "\n"
642 p.write(msg)
652 p.write(msg)
643 p.close()
653 p.close()
644 wlock = None
654 wlock = None
645 r = self.qrepo()
655 r = self.qrepo()
646 if r: r.add([patch])
656 if r: r.add([patch])
647 if commitfiles:
657 if commitfiles:
648 self.refresh(repo, short=True, git=opts.get('git'))
658 self.refresh(repo, short=True, git=opts.get('git'))
649 self.removeundo(repo)
659 self.removeundo(repo)
650 finally:
660 finally:
651 del wlock
661 del wlock
652
662
653 def strip(self, repo, rev, update=True, backup="all"):
663 def strip(self, repo, rev, update=True, backup="all"):
654 wlock = lock = None
664 wlock = lock = None
655 try:
665 try:
656 wlock = repo.wlock()
666 wlock = repo.wlock()
657 lock = repo.lock()
667 lock = repo.lock()
658
668
659 if update:
669 if update:
660 self.check_localchanges(repo, refresh=False)
670 self.check_localchanges(repo, refresh=False)
661 urev = self.qparents(repo, rev)
671 urev = self.qparents(repo, rev)
662 hg.clean(repo, urev)
672 hg.clean(repo, urev)
663 repo.dirstate.write()
673 repo.dirstate.write()
664
674
665 self.removeundo(repo)
675 self.removeundo(repo)
666 repair.strip(self.ui, repo, rev, backup)
676 repair.strip(self.ui, repo, rev, backup)
667 # strip may have unbundled a set of backed up revisions after
677 # strip may have unbundled a set of backed up revisions after
668 # the actual strip
678 # the actual strip
669 self.removeundo(repo)
679 self.removeundo(repo)
670 finally:
680 finally:
671 del lock, wlock
681 del lock, wlock
672
682
673 def isapplied(self, patch):
683 def isapplied(self, patch):
674 """returns (index, rev, patch)"""
684 """returns (index, rev, patch)"""
675 for i in xrange(len(self.applied)):
685 for i in xrange(len(self.applied)):
676 a = self.applied[i]
686 a = self.applied[i]
677 if a.name == patch:
687 if a.name == patch:
678 return (i, a.rev, a.name)
688 return (i, a.rev, a.name)
679 return None
689 return None
680
690
681 # if the exact patch name does not exist, we try a few
691 # if the exact patch name does not exist, we try a few
682 # variations. If strict is passed, we try only #1
692 # variations. If strict is passed, we try only #1
683 #
693 #
684 # 1) a number to indicate an offset in the series file
694 # 1) a number to indicate an offset in the series file
685 # 2) a unique substring of the patch name was given
695 # 2) a unique substring of the patch name was given
686 # 3) patchname[-+]num to indicate an offset in the series file
696 # 3) patchname[-+]num to indicate an offset in the series file
687 def lookup(self, patch, strict=False):
697 def lookup(self, patch, strict=False):
688 patch = patch and str(patch)
698 patch = patch and str(patch)
689
699
690 def partial_name(s):
700 def partial_name(s):
691 if s in self.series:
701 if s in self.series:
692 return s
702 return s
693 matches = [x for x in self.series if s in x]
703 matches = [x for x in self.series if s in x]
694 if len(matches) > 1:
704 if len(matches) > 1:
695 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
705 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
696 for m in matches:
706 for m in matches:
697 self.ui.warn(' %s\n' % m)
707 self.ui.warn(' %s\n' % m)
698 return None
708 return None
699 if matches:
709 if matches:
700 return matches[0]
710 return matches[0]
701 if len(self.series) > 0 and len(self.applied) > 0:
711 if len(self.series) > 0 and len(self.applied) > 0:
702 if s == 'qtip':
712 if s == 'qtip':
703 return self.series[self.series_end(True)-1]
713 return self.series[self.series_end(True)-1]
704 if s == 'qbase':
714 if s == 'qbase':
705 return self.series[0]
715 return self.series[0]
706 return None
716 return None
707 if patch == None:
717 if patch == None:
708 return None
718 return None
709
719
710 # we don't want to return a partial match until we make
720 # we don't want to return a partial match until we make
711 # sure the file name passed in does not exist (checked below)
721 # sure the file name passed in does not exist (checked below)
712 res = partial_name(patch)
722 res = partial_name(patch)
713 if res and res == patch:
723 if res and res == patch:
714 return res
724 return res
715
725
716 if not os.path.isfile(self.join(patch)):
726 if not os.path.isfile(self.join(patch)):
717 try:
727 try:
718 sno = int(patch)
728 sno = int(patch)
719 except(ValueError, OverflowError):
729 except(ValueError, OverflowError):
720 pass
730 pass
721 else:
731 else:
722 if sno < len(self.series):
732 if sno < len(self.series):
723 return self.series[sno]
733 return self.series[sno]
724 if not strict:
734 if not strict:
725 # return any partial match made above
735 # return any partial match made above
726 if res:
736 if res:
727 return res
737 return res
728 minus = patch.rfind('-')
738 minus = patch.rfind('-')
729 if minus >= 0:
739 if minus >= 0:
730 res = partial_name(patch[:minus])
740 res = partial_name(patch[:minus])
731 if res:
741 if res:
732 i = self.series.index(res)
742 i = self.series.index(res)
733 try:
743 try:
734 off = int(patch[minus+1:] or 1)
744 off = int(patch[minus+1:] or 1)
735 except(ValueError, OverflowError):
745 except(ValueError, OverflowError):
736 pass
746 pass
737 else:
747 else:
738 if i - off >= 0:
748 if i - off >= 0:
739 return self.series[i - off]
749 return self.series[i - off]
740 plus = patch.rfind('+')
750 plus = patch.rfind('+')
741 if plus >= 0:
751 if plus >= 0:
742 res = partial_name(patch[:plus])
752 res = partial_name(patch[:plus])
743 if res:
753 if res:
744 i = self.series.index(res)
754 i = self.series.index(res)
745 try:
755 try:
746 off = int(patch[plus+1:] or 1)
756 off = int(patch[plus+1:] or 1)
747 except(ValueError, OverflowError):
757 except(ValueError, OverflowError):
748 pass
758 pass
749 else:
759 else:
750 if i + off < len(self.series):
760 if i + off < len(self.series):
751 return self.series[i + off]
761 return self.series[i + off]
752 raise util.Abort(_("patch %s not in series") % patch)
762 raise util.Abort(_("patch %s not in series") % patch)
753
763
754 def push(self, repo, patch=None, force=False, list=False,
764 def push(self, repo, patch=None, force=False, list=False,
755 mergeq=None):
765 mergeq=None):
756 wlock = repo.wlock()
766 wlock = repo.wlock()
757 try:
767 try:
758 patch = self.lookup(patch)
768 patch = self.lookup(patch)
759 # Suppose our series file is: A B C and the current 'top'
769 # Suppose our series file is: A B C and the current 'top'
760 # patch is B. qpush C should be performed (moving forward)
770 # patch is B. qpush C should be performed (moving forward)
761 # qpush B is a NOP (no change) qpush A is an error (can't
771 # qpush B is a NOP (no change) qpush A is an error (can't
762 # go backwards with qpush)
772 # go backwards with qpush)
763 if patch:
773 if patch:
764 info = self.isapplied(patch)
774 info = self.isapplied(patch)
765 if info:
775 if info:
766 if info[0] < len(self.applied) - 1:
776 if info[0] < len(self.applied) - 1:
767 raise util.Abort(
777 raise util.Abort(
768 _("cannot push to a previous patch: %s") % patch)
778 _("cannot push to a previous patch: %s") % patch)
769 if info[0] < len(self.series) - 1:
779 if info[0] < len(self.series) - 1:
770 self.ui.warn(
780 self.ui.warn(
771 _('qpush: %s is already at the top\n') % patch)
781 _('qpush: %s is already at the top\n') % patch)
772 else:
782 else:
773 self.ui.warn(_('all patches are currently applied\n'))
783 self.ui.warn(_('all patches are currently applied\n'))
774 return
784 return
775
785
776 # Following the above example, starting at 'top' of B:
786 # Following the above example, starting at 'top' of B:
777 # qpush should be performed (pushes C), but a subsequent
787 # qpush should be performed (pushes C), but a subsequent
778 # qpush without an argument is an error (nothing to
788 # qpush without an argument is an error (nothing to
779 # apply). This allows a loop of "...while hg qpush..." to
789 # apply). This allows a loop of "...while hg qpush..." to
780 # work as it detects an error when done
790 # work as it detects an error when done
781 if self.series_end() == len(self.series):
791 if self.series_end() == len(self.series):
782 self.ui.warn(_('patch series already fully applied\n'))
792 self.ui.warn(_('patch series already fully applied\n'))
783 return 1
793 return 1
784 if not force:
794 if not force:
785 self.check_localchanges(repo)
795 self.check_localchanges(repo)
786
796
787 self.applied_dirty = 1;
797 self.applied_dirty = 1;
788 start = self.series_end()
798 start = self.series_end()
789 if start > 0:
799 if start > 0:
790 self.check_toppatch(repo)
800 self.check_toppatch(repo)
791 if not patch:
801 if not patch:
792 patch = self.series[start]
802 patch = self.series[start]
793 end = start + 1
803 end = start + 1
794 else:
804 else:
795 end = self.series.index(patch, start) + 1
805 end = self.series.index(patch, start) + 1
796 s = self.series[start:end]
806 s = self.series[start:end]
797 all_files = {}
807 all_files = {}
798 try:
808 try:
799 if mergeq:
809 if mergeq:
800 ret = self.mergepatch(repo, mergeq, s)
810 ret = self.mergepatch(repo, mergeq, s)
801 else:
811 else:
802 ret = self.apply(repo, s, list, all_files=all_files)
812 ret = self.apply(repo, s, list, all_files=all_files)
803 except:
813 except:
804 self.ui.warn(_('cleaning up working directory...'))
814 self.ui.warn(_('cleaning up working directory...'))
805 node = repo.dirstate.parents()[0]
815 node = repo.dirstate.parents()[0]
806 hg.revert(repo, node, None)
816 hg.revert(repo, node, None)
807 unknown = repo.status()[4]
817 unknown = repo.status()[4]
808 # only remove unknown files that we know we touched or
818 # only remove unknown files that we know we touched or
809 # created while patching
819 # created while patching
810 for f in unknown:
820 for f in unknown:
811 if f in all_files:
821 if f in all_files:
812 util.unlink(repo.wjoin(f))
822 util.unlink(repo.wjoin(f))
813 self.ui.warn(_('done\n'))
823 self.ui.warn(_('done\n'))
814 raise
824 raise
815 top = self.applied[-1].name
825 top = self.applied[-1].name
816 if ret[0]:
826 if ret[0]:
817 self.ui.write(
827 self.ui.write(
818 "Errors during apply, please fix and refresh %s\n" % top)
828 "Errors during apply, please fix and refresh %s\n" % top)
819 else:
829 else:
820 self.ui.write("Now at: %s\n" % top)
830 self.ui.write("Now at: %s\n" % top)
821 return ret[0]
831 return ret[0]
822 finally:
832 finally:
823 del wlock
833 del wlock
824
834
825 def pop(self, repo, patch=None, force=False, update=True, all=False):
835 def pop(self, repo, patch=None, force=False, update=True, all=False):
826 def getfile(f, rev, flags):
836 def getfile(f, rev, flags):
827 t = repo.file(f).read(rev)
837 t = repo.file(f).read(rev)
828 repo.wwrite(f, t, flags)
838 repo.wwrite(f, t, flags)
829
839
830 wlock = repo.wlock()
840 wlock = repo.wlock()
831 try:
841 try:
832 if patch:
842 if patch:
833 # index, rev, patch
843 # index, rev, patch
834 info = self.isapplied(patch)
844 info = self.isapplied(patch)
835 if not info:
845 if not info:
836 patch = self.lookup(patch)
846 patch = self.lookup(patch)
837 info = self.isapplied(patch)
847 info = self.isapplied(patch)
838 if not info:
848 if not info:
839 raise util.Abort(_("patch %s is not applied") % patch)
849 raise util.Abort(_("patch %s is not applied") % patch)
840
850
841 if len(self.applied) == 0:
851 if len(self.applied) == 0:
842 # Allow qpop -a to work repeatedly,
852 # Allow qpop -a to work repeatedly,
843 # but not qpop without an argument
853 # but not qpop without an argument
844 self.ui.warn(_("no patches applied\n"))
854 self.ui.warn(_("no patches applied\n"))
845 return not all
855 return not all
846
856
847 if not update:
857 if not update:
848 parents = repo.dirstate.parents()
858 parents = repo.dirstate.parents()
849 rr = [ revlog.bin(x.rev) for x in self.applied ]
859 rr = [ revlog.bin(x.rev) for x in self.applied ]
850 for p in parents:
860 for p in parents:
851 if p in rr:
861 if p in rr:
852 self.ui.warn("qpop: forcing dirstate update\n")
862 self.ui.warn("qpop: forcing dirstate update\n")
853 update = True
863 update = True
854
864
855 if not force and update:
865 if not force and update:
856 self.check_localchanges(repo)
866 self.check_localchanges(repo)
857
867
858 self.applied_dirty = 1;
868 self.applied_dirty = 1;
859 end = len(self.applied)
869 end = len(self.applied)
860 if not patch:
870 if not patch:
861 if all:
871 if all:
862 popi = 0
872 popi = 0
863 else:
873 else:
864 popi = len(self.applied) - 1
874 popi = len(self.applied) - 1
865 else:
875 else:
866 popi = info[0] + 1
876 popi = info[0] + 1
867 if popi >= end:
877 if popi >= end:
868 self.ui.warn("qpop: %s is already at the top\n" % patch)
878 self.ui.warn("qpop: %s is already at the top\n" % patch)
869 return
879 return
870 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
880 info = [ popi ] + [self.applied[popi].rev, self.applied[popi].name]
871
881
872 start = info[0]
882 start = info[0]
873 rev = revlog.bin(info[1])
883 rev = revlog.bin(info[1])
874
884
885 if update:
886 top = self.check_toppatch(repo)
887
888 if repo.changelog.heads(rev) != [revlog.bin(self.applied[-1].rev)]:
889 raise util.Abort("popping would remove a revision not "
890 "managed by this patch queue")
891
875 # we know there are no local changes, so we can make a simplified
892 # we know there are no local changes, so we can make a simplified
876 # form of hg.update.
893 # form of hg.update.
877 if update:
894 if update:
878 top = self.check_toppatch(repo)
879 qp = self.qparents(repo, rev)
895 qp = self.qparents(repo, rev)
880 changes = repo.changelog.read(qp)
896 changes = repo.changelog.read(qp)
881 mmap = repo.manifest.read(changes[0])
897 mmap = repo.manifest.read(changes[0])
882 m, a, r, d, u = repo.status(qp, top)[:5]
898 m, a, r, d, u = repo.status(qp, top)[:5]
883 if d:
899 if d:
884 raise util.Abort("deletions found between repo revs")
900 raise util.Abort("deletions found between repo revs")
885 for f in m:
901 for f in m:
886 getfile(f, mmap[f], mmap.flags(f))
902 getfile(f, mmap[f], mmap.flags(f))
887 for f in r:
903 for f in r:
888 getfile(f, mmap[f], mmap.flags(f))
904 getfile(f, mmap[f], mmap.flags(f))
889 for f in m + r:
905 for f in m + r:
890 repo.dirstate.normal(f)
906 repo.dirstate.normal(f)
891 for f in a:
907 for f in a:
892 try:
908 try:
893 os.unlink(repo.wjoin(f))
909 os.unlink(repo.wjoin(f))
894 except OSError, e:
910 except OSError, e:
895 if e.errno != errno.ENOENT:
911 if e.errno != errno.ENOENT:
896 raise
912 raise
897 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
913 try: os.removedirs(os.path.dirname(repo.wjoin(f)))
898 except: pass
914 except: pass
899 repo.dirstate.forget(f)
915 repo.dirstate.forget(f)
900 repo.dirstate.setparents(qp, revlog.nullid)
916 repo.dirstate.setparents(qp, revlog.nullid)
917 del self.applied[start:end]
901 self.strip(repo, rev, update=False, backup='strip')
918 self.strip(repo, rev, update=False, backup='strip')
902 del self.applied[start:end]
903 if len(self.applied):
919 if len(self.applied):
904 self.ui.write("Now at: %s\n" % self.applied[-1].name)
920 self.ui.write("Now at: %s\n" % self.applied[-1].name)
905 else:
921 else:
906 self.ui.write("Patch queue now empty\n")
922 self.ui.write("Patch queue now empty\n")
907 finally:
923 finally:
908 del wlock
924 del wlock
909
925
910 def diff(self, repo, pats, opts):
926 def diff(self, repo, pats, opts):
911 top = self.check_toppatch(repo)
927 top = self.check_toppatch(repo)
912 if not top:
928 if not top:
913 self.ui.write("No patches applied\n")
929 self.ui.write("No patches applied\n")
914 return
930 return
915 qp = self.qparents(repo, top)
931 qp = self.qparents(repo, top)
916 if opts.get('git'):
932 if opts.get('git'):
917 self.diffopts().git = True
933 self.diffopts().git = True
918 self.printdiff(repo, qp, files=pats, opts=opts)
934 self.printdiff(repo, qp, files=pats, opts=opts)
919
935
920 def refresh(self, repo, pats=None, **opts):
936 def refresh(self, repo, pats=None, **opts):
921 if len(self.applied) == 0:
937 if len(self.applied) == 0:
922 self.ui.write("No patches applied\n")
938 self.ui.write("No patches applied\n")
923 return 1
939 return 1
940 newdate = opts.get('date')
941 if newdate:
942 newdate = '%d %d' % util.parsedate(newdate)
924 wlock = repo.wlock()
943 wlock = repo.wlock()
925 try:
944 try:
926 self.check_toppatch(repo)
945 self.check_toppatch(repo)
927 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
946 (top, patchfn) = (self.applied[-1].rev, self.applied[-1].name)
928 top = revlog.bin(top)
947 top = revlog.bin(top)
948 if repo.changelog.heads(top) != [top]:
949 raise util.Abort("cannot refresh a revision with children")
929 cparents = repo.changelog.parents(top)
950 cparents = repo.changelog.parents(top)
930 patchparent = self.qparents(repo, top)
951 patchparent = self.qparents(repo, top)
931 message, comments, user, date, patchfound = self.readheaders(patchfn)
952 message, comments, user, date, patchfound = self.readheaders(patchfn)
932
953
933 patchf = self.opener(patchfn, 'r+')
954 patchf = self.opener(patchfn, 'r+')
934
955
935 # if the patch was a git patch, refresh it as a git patch
956 # if the patch was a git patch, refresh it as a git patch
936 for line in patchf:
957 for line in patchf:
937 if line.startswith('diff --git'):
958 if line.startswith('diff --git'):
938 self.diffopts().git = True
959 self.diffopts().git = True
939 break
960 break
940
961
941 msg = opts.get('msg', '').rstrip()
962 msg = opts.get('msg', '').rstrip()
942 if msg and comments:
963 if msg and comments:
943 # Remove existing message, keeping the rest of the comments
964 # Remove existing message, keeping the rest of the comments
944 # fields.
965 # fields.
945 # If comments contains 'subject: ', message will prepend
966 # If comments contains 'subject: ', message will prepend
946 # the field and a blank line.
967 # the field and a blank line.
947 if message:
968 if message:
948 subj = 'subject: ' + message[0].lower()
969 subj = 'subject: ' + message[0].lower()
949 for i in xrange(len(comments)):
970 for i in xrange(len(comments)):
950 if subj == comments[i].lower():
971 if subj == comments[i].lower():
951 del comments[i]
972 del comments[i]
952 message = message[2:]
973 message = message[2:]
953 break
974 break
954 ci = 0
975 ci = 0
955 for mi in xrange(len(message)):
976 for mi in xrange(len(message)):
956 while message[mi] != comments[ci]:
977 while message[mi] != comments[ci]:
957 ci += 1
978 ci += 1
958 del comments[ci]
979 del comments[ci]
959
980
960 def setheaderfield(comments, prefixes, new):
981 def setheaderfield(comments, prefixes, new):
961 # Update all references to a field in the patch header.
982 # Update all references to a field in the patch header.
962 # If none found, add it email style.
983 # If none found, add it email style.
963 res = False
984 res = False
964 for prefix in prefixes:
985 for prefix in prefixes:
965 for i in xrange(len(comments)):
986 for i in xrange(len(comments)):
966 if comments[i].startswith(prefix):
987 if comments[i].startswith(prefix):
967 comments[i] = prefix + new
988 comments[i] = prefix + new
968 res = True
989 res = True
969 break
990 break
970 return res
991 return res
971
992
972 newuser = opts.get('user')
993 newuser = opts.get('user')
973 if newuser:
994 if newuser:
974 if not setheaderfield(comments, ['From: ', '# User '], newuser):
995 if not setheaderfield(comments, ['From: ', '# User '], newuser):
975 try:
996 try:
976 patchheaderat = comments.index('# HG changeset patch')
997 patchheaderat = comments.index('# HG changeset patch')
977 comments.insert(patchheaderat + 1,'# User ' + newuser)
998 comments.insert(patchheaderat + 1,'# User ' + newuser)
978 except ValueError:
999 except ValueError:
979 comments = ['From: ' + newuser, ''] + comments
1000 comments = ['From: ' + newuser, ''] + comments
980 user = newuser
1001 user = newuser
981
1002
982 newdate = opts.get('date')
983 if newdate:
1003 if newdate:
984 if setheaderfield(comments, ['# Date '], newdate):
1004 if setheaderfield(comments, ['# Date '], newdate):
985 date = newdate
1005 date = newdate
986
1006
987 if msg:
1007 if msg:
988 comments.append(msg)
1008 comments.append(msg)
989
1009
990 patchf.seek(0)
1010 patchf.seek(0)
991 patchf.truncate()
1011 patchf.truncate()
992
1012
993 if comments:
1013 if comments:
994 comments = "\n".join(comments) + '\n\n'
1014 comments = "\n".join(comments) + '\n\n'
995 patchf.write(comments)
1015 patchf.write(comments)
996
1016
997 if opts.get('git'):
1017 if opts.get('git'):
998 self.diffopts().git = True
1018 self.diffopts().git = True
999 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1019 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1000 tip = repo.changelog.tip()
1020 tip = repo.changelog.tip()
1001 if top == tip:
1021 if top == tip:
1002 # if the top of our patch queue is also the tip, there is an
1022 # if the top of our patch queue is also the tip, there is an
1003 # optimization here. We update the dirstate in place and strip
1023 # optimization here. We update the dirstate in place and strip
1004 # off the tip commit. Then just commit the current directory
1024 # off the tip commit. Then just commit the current directory
1005 # tree. We can also send repo.commit the list of files
1025 # tree. We can also send repo.commit the list of files
1006 # changed to speed up the diff
1026 # changed to speed up the diff
1007 #
1027 #
1008 # in short mode, we only diff the files included in the
1028 # in short mode, we only diff the files included in the
1009 # patch already
1029 # patch already
1010 #
1030 #
1011 # this should really read:
1031 # this should really read:
1012 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
1032 # mm, dd, aa, aa2, uu = repo.status(tip, patchparent)[:5]
1013 # but we do it backwards to take advantage of manifest/chlog
1033 # but we do it backwards to take advantage of manifest/chlog
1014 # caching against the next repo.status call
1034 # caching against the next repo.status call
1015 #
1035 #
1016 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
1036 mm, aa, dd, aa2, uu = repo.status(patchparent, tip)[:5]
1017 changes = repo.changelog.read(tip)
1037 changes = repo.changelog.read(tip)
1018 man = repo.manifest.read(changes[0])
1038 man = repo.manifest.read(changes[0])
1019 aaa = aa[:]
1039 aaa = aa[:]
1020 if opts.get('short'):
1040 if opts.get('short'):
1021 filelist = mm + aa + dd
1041 filelist = mm + aa + dd
1022 match = dict.fromkeys(filelist).__contains__
1042 match = dict.fromkeys(filelist).__contains__
1023 else:
1043 else:
1024 filelist = None
1044 filelist = None
1025 match = util.always
1045 match = util.always
1026 m, a, r, d, u = repo.status(files=filelist, match=match)[:5]
1046 m, a, r, d, u = repo.status(files=filelist, match=match)[:5]
1027
1047
1028 # we might end up with files that were added between
1048 # we might end up with files that were added between
1029 # tip and the dirstate parent, but then changed in the
1049 # tip and the dirstate parent, but then changed in the
1030 # local dirstate. in this case, we want them to only
1050 # local dirstate. in this case, we want them to only
1031 # show up in the added section
1051 # show up in the added section
1032 for x in m:
1052 for x in m:
1033 if x not in aa:
1053 if x not in aa:
1034 mm.append(x)
1054 mm.append(x)
1035 # we might end up with files added by the local dirstate that
1055 # we might end up with files added by the local dirstate that
1036 # were deleted by the patch. In this case, they should only
1056 # were deleted by the patch. In this case, they should only
1037 # show up in the changed section.
1057 # show up in the changed section.
1038 for x in a:
1058 for x in a:
1039 if x in dd:
1059 if x in dd:
1040 del dd[dd.index(x)]
1060 del dd[dd.index(x)]
1041 mm.append(x)
1061 mm.append(x)
1042 else:
1062 else:
1043 aa.append(x)
1063 aa.append(x)
1044 # make sure any files deleted in the local dirstate
1064 # make sure any files deleted in the local dirstate
1045 # are not in the add or change column of the patch
1065 # are not in the add or change column of the patch
1046 forget = []
1066 forget = []
1047 for x in d + r:
1067 for x in d + r:
1048 if x in aa:
1068 if x in aa:
1049 del aa[aa.index(x)]
1069 del aa[aa.index(x)]
1050 forget.append(x)
1070 forget.append(x)
1051 continue
1071 continue
1052 elif x in mm:
1072 elif x in mm:
1053 del mm[mm.index(x)]
1073 del mm[mm.index(x)]
1054 dd.append(x)
1074 dd.append(x)
1055
1075
1056 m = util.unique(mm)
1076 m = util.unique(mm)
1057 r = util.unique(dd)
1077 r = util.unique(dd)
1058 a = util.unique(aa)
1078 a = util.unique(aa)
1059 c = [filter(matchfn, l) for l in (m, a, r, [], u)]
1079 c = [filter(matchfn, l) for l in (m, a, r, [], u)]
1060 filelist = util.unique(c[0] + c[1] + c[2])
1080 filelist = util.unique(c[0] + c[1] + c[2])
1061 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1081 patch.diff(repo, patchparent, files=filelist, match=matchfn,
1062 fp=patchf, changes=c, opts=self.diffopts())
1082 fp=patchf, changes=c, opts=self.diffopts())
1063 patchf.close()
1083 patchf.close()
1064
1084
1065 repo.dirstate.setparents(*cparents)
1085 repo.dirstate.setparents(*cparents)
1066 copies = {}
1086 copies = {}
1067 for dst in a:
1087 for dst in a:
1068 src = repo.dirstate.copied(dst)
1088 src = repo.dirstate.copied(dst)
1069 if src is not None:
1089 if src is not None:
1070 copies.setdefault(src, []).append(dst)
1090 copies.setdefault(src, []).append(dst)
1071 repo.dirstate.add(dst)
1091 repo.dirstate.add(dst)
1072 # remember the copies between patchparent and tip
1092 # remember the copies between patchparent and tip
1073 # this may be slow, so don't do it if we're not tracking copies
1093 # this may be slow, so don't do it if we're not tracking copies
1074 if self.diffopts().git:
1094 if self.diffopts().git:
1075 for dst in aaa:
1095 for dst in aaa:
1076 f = repo.file(dst)
1096 f = repo.file(dst)
1077 src = f.renamed(man[dst])
1097 src = f.renamed(man[dst])
1078 if src:
1098 if src:
1079 copies[src[0]] = copies.get(dst, [])
1099 copies[src[0]] = copies.get(dst, [])
1080 if dst in a:
1100 if dst in a:
1081 copies[src[0]].append(dst)
1101 copies[src[0]].append(dst)
1082 # we can't copy a file created by the patch itself
1102 # we can't copy a file created by the patch itself
1083 if dst in copies:
1103 if dst in copies:
1084 del copies[dst]
1104 del copies[dst]
1085 for src, dsts in copies.iteritems():
1105 for src, dsts in copies.iteritems():
1086 for dst in dsts:
1106 for dst in dsts:
1087 repo.dirstate.copy(src, dst)
1107 repo.dirstate.copy(src, dst)
1088 for f in r:
1108 for f in r:
1089 repo.dirstate.remove(f)
1109 repo.dirstate.remove(f)
1090 # if the patch excludes a modified file, mark that
1110 # if the patch excludes a modified file, mark that
1091 # file with mtime=0 so status can see it.
1111 # file with mtime=0 so status can see it.
1092 mm = []
1112 mm = []
1093 for i in xrange(len(m)-1, -1, -1):
1113 for i in xrange(len(m)-1, -1, -1):
1094 if not matchfn(m[i]):
1114 if not matchfn(m[i]):
1095 mm.append(m[i])
1115 mm.append(m[i])
1096 del m[i]
1116 del m[i]
1097 for f in m:
1117 for f in m:
1098 repo.dirstate.normal(f)
1118 repo.dirstate.normal(f)
1099 for f in mm:
1119 for f in mm:
1100 repo.dirstate.normallookup(f)
1120 repo.dirstate.normallookup(f)
1101 for f in forget:
1121 for f in forget:
1102 repo.dirstate.forget(f)
1122 repo.dirstate.forget(f)
1103
1123
1104 if not msg:
1124 if not msg:
1105 if not message:
1125 if not message:
1106 message = "[mq]: %s\n" % patchfn
1126 message = "[mq]: %s\n" % patchfn
1107 else:
1127 else:
1108 message = "\n".join(message)
1128 message = "\n".join(message)
1109 else:
1129 else:
1110 message = msg
1130 message = msg
1111
1131
1112 if not user:
1132 if not user:
1113 user = changes[1]
1133 user = changes[1]
1114
1134
1135 self.applied.pop()
1136 self.applied_dirty = 1
1115 self.strip(repo, top, update=False,
1137 self.strip(repo, top, update=False,
1116 backup='strip')
1138 backup='strip')
1117 n = repo.commit(filelist, message, user, date, match=matchfn,
1139 n = repo.commit(filelist, message, user, date, match=matchfn,
1118 force=1)
1140 force=1)
1119 self.applied[-1] = statusentry(revlog.hex(n), patchfn)
1141 self.applied.append(statusentry(revlog.hex(n), patchfn))
1120 self.applied_dirty = 1
1121 self.removeundo(repo)
1142 self.removeundo(repo)
1122 else:
1143 else:
1123 self.printdiff(repo, patchparent, fp=patchf)
1144 self.printdiff(repo, patchparent, fp=patchf)
1124 patchf.close()
1145 patchf.close()
1125 added = repo.status()[1]
1146 added = repo.status()[1]
1126 for a in added:
1147 for a in added:
1127 f = repo.wjoin(a)
1148 f = repo.wjoin(a)
1128 try:
1149 try:
1129 os.unlink(f)
1150 os.unlink(f)
1130 except OSError, e:
1151 except OSError, e:
1131 if e.errno != errno.ENOENT:
1152 if e.errno != errno.ENOENT:
1132 raise
1153 raise
1133 try: os.removedirs(os.path.dirname(f))
1154 try: os.removedirs(os.path.dirname(f))
1134 except: pass
1155 except: pass
1135 # forget the file copies in the dirstate
1156 # forget the file copies in the dirstate
1136 # push should readd the files later on
1157 # push should readd the files later on
1137 repo.dirstate.forget(a)
1158 repo.dirstate.forget(a)
1138 self.pop(repo, force=True)
1159 self.pop(repo, force=True)
1139 self.push(repo, force=True)
1160 self.push(repo, force=True)
1140 finally:
1161 finally:
1141 del wlock
1162 del wlock
1142
1163
1143 def init(self, repo, create=False):
1164 def init(self, repo, create=False):
1144 if not create and os.path.isdir(self.path):
1165 if not create and os.path.isdir(self.path):
1145 raise util.Abort(_("patch queue directory already exists"))
1166 raise util.Abort(_("patch queue directory already exists"))
1146 try:
1167 try:
1147 os.mkdir(self.path)
1168 os.mkdir(self.path)
1148 except OSError, inst:
1169 except OSError, inst:
1149 if inst.errno != errno.EEXIST or not create:
1170 if inst.errno != errno.EEXIST or not create:
1150 raise
1171 raise
1151 if create:
1172 if create:
1152 return self.qrepo(create=True)
1173 return self.qrepo(create=True)
1153
1174
1154 def unapplied(self, repo, patch=None):
1175 def unapplied(self, repo, patch=None):
1155 if patch and patch not in self.series:
1176 if patch and patch not in self.series:
1156 raise util.Abort(_("patch %s is not in series file") % patch)
1177 raise util.Abort(_("patch %s is not in series file") % patch)
1157 if not patch:
1178 if not patch:
1158 start = self.series_end()
1179 start = self.series_end()
1159 else:
1180 else:
1160 start = self.series.index(patch) + 1
1181 start = self.series.index(patch) + 1
1161 unapplied = []
1182 unapplied = []
1162 for i in xrange(start, len(self.series)):
1183 for i in xrange(start, len(self.series)):
1163 pushable, reason = self.pushable(i)
1184 pushable, reason = self.pushable(i)
1164 if pushable:
1185 if pushable:
1165 unapplied.append((i, self.series[i]))
1186 unapplied.append((i, self.series[i]))
1166 self.explain_pushable(i)
1187 self.explain_pushable(i)
1167 return unapplied
1188 return unapplied
1168
1189
1169 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1190 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1170 summary=False):
1191 summary=False):
1171 def displayname(patchname):
1192 def displayname(patchname):
1172 if summary:
1193 if summary:
1173 msg = self.readheaders(patchname)[0]
1194 msg = self.readheaders(patchname)[0]
1174 msg = msg and ': ' + msg[0] or ': '
1195 msg = msg and ': ' + msg[0] or ': '
1175 else:
1196 else:
1176 msg = ''
1197 msg = ''
1177 return '%s%s' % (patchname, msg)
1198 return '%s%s' % (patchname, msg)
1178
1199
1179 applied = dict.fromkeys([p.name for p in self.applied])
1200 applied = dict.fromkeys([p.name for p in self.applied])
1180 if length is None:
1201 if length is None:
1181 length = len(self.series) - start
1202 length = len(self.series) - start
1182 if not missing:
1203 if not missing:
1183 for i in xrange(start, start+length):
1204 for i in xrange(start, start+length):
1184 patch = self.series[i]
1205 patch = self.series[i]
1185 if patch in applied:
1206 if patch in applied:
1186 stat = 'A'
1207 stat = 'A'
1187 elif self.pushable(i)[0]:
1208 elif self.pushable(i)[0]:
1188 stat = 'U'
1209 stat = 'U'
1189 else:
1210 else:
1190 stat = 'G'
1211 stat = 'G'
1191 pfx = ''
1212 pfx = ''
1192 if self.ui.verbose:
1213 if self.ui.verbose:
1193 pfx = '%d %s ' % (i, stat)
1214 pfx = '%d %s ' % (i, stat)
1194 elif status and status != stat:
1215 elif status and status != stat:
1195 continue
1216 continue
1196 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1217 self.ui.write('%s%s\n' % (pfx, displayname(patch)))
1197 else:
1218 else:
1198 msng_list = []
1219 msng_list = []
1199 for root, dirs, files in os.walk(self.path):
1220 for root, dirs, files in os.walk(self.path):
1200 d = root[len(self.path) + 1:]
1221 d = root[len(self.path) + 1:]
1201 for f in files:
1222 for f in files:
1202 fl = os.path.join(d, f)
1223 fl = os.path.join(d, f)
1203 if (fl not in self.series and
1224 if (fl not in self.series and
1204 fl not in (self.status_path, self.series_path,
1225 fl not in (self.status_path, self.series_path,
1205 self.guards_path)
1226 self.guards_path)
1206 and not fl.startswith('.')):
1227 and not fl.startswith('.')):
1207 msng_list.append(fl)
1228 msng_list.append(fl)
1208 msng_list.sort()
1229 msng_list.sort()
1209 for x in msng_list:
1230 for x in msng_list:
1210 pfx = self.ui.verbose and ('D ') or ''
1231 pfx = self.ui.verbose and ('D ') or ''
1211 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1232 self.ui.write("%s%s\n" % (pfx, displayname(x)))
1212
1233
1213 def issaveline(self, l):
1234 def issaveline(self, l):
1214 if l.name == '.hg.patches.save.line':
1235 if l.name == '.hg.patches.save.line':
1215 return True
1236 return True
1216
1237
1217 def qrepo(self, create=False):
1238 def qrepo(self, create=False):
1218 if create or os.path.isdir(self.join(".hg")):
1239 if create or os.path.isdir(self.join(".hg")):
1219 return hg.repository(self.ui, path=self.path, create=create)
1240 return hg.repository(self.ui, path=self.path, create=create)
1220
1241
1221 def restore(self, repo, rev, delete=None, qupdate=None):
1242 def restore(self, repo, rev, delete=None, qupdate=None):
1222 c = repo.changelog.read(rev)
1243 c = repo.changelog.read(rev)
1223 desc = c[4].strip()
1244 desc = c[4].strip()
1224 lines = desc.splitlines()
1245 lines = desc.splitlines()
1225 i = 0
1246 i = 0
1226 datastart = None
1247 datastart = None
1227 series = []
1248 series = []
1228 applied = []
1249 applied = []
1229 qpp = None
1250 qpp = None
1230 for i in xrange(0, len(lines)):
1251 for i in xrange(0, len(lines)):
1231 if lines[i] == 'Patch Data:':
1252 if lines[i] == 'Patch Data:':
1232 datastart = i + 1
1253 datastart = i + 1
1233 elif lines[i].startswith('Dirstate:'):
1254 elif lines[i].startswith('Dirstate:'):
1234 l = lines[i].rstrip()
1255 l = lines[i].rstrip()
1235 l = l[10:].split(' ')
1256 l = l[10:].split(' ')
1236 qpp = [ hg.bin(x) for x in l ]
1257 qpp = [ hg.bin(x) for x in l ]
1237 elif datastart != None:
1258 elif datastart != None:
1238 l = lines[i].rstrip()
1259 l = lines[i].rstrip()
1239 se = statusentry(l)
1260 se = statusentry(l)
1240 file_ = se.name
1261 file_ = se.name
1241 if se.rev:
1262 if se.rev:
1242 applied.append(se)
1263 applied.append(se)
1243 else:
1264 else:
1244 series.append(file_)
1265 series.append(file_)
1245 if datastart == None:
1266 if datastart == None:
1246 self.ui.warn("No saved patch data found\n")
1267 self.ui.warn("No saved patch data found\n")
1247 return 1
1268 return 1
1248 self.ui.warn("restoring status: %s\n" % lines[0])
1269 self.ui.warn("restoring status: %s\n" % lines[0])
1249 self.full_series = series
1270 self.full_series = series
1250 self.applied = applied
1271 self.applied = applied
1251 self.parse_series()
1272 self.parse_series()
1252 self.series_dirty = 1
1273 self.series_dirty = 1
1253 self.applied_dirty = 1
1274 self.applied_dirty = 1
1254 heads = repo.changelog.heads()
1275 heads = repo.changelog.heads()
1255 if delete:
1276 if delete:
1256 if rev not in heads:
1277 if rev not in heads:
1257 self.ui.warn("save entry has children, leaving it alone\n")
1278 self.ui.warn("save entry has children, leaving it alone\n")
1258 else:
1279 else:
1259 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1280 self.ui.warn("removing save entry %s\n" % hg.short(rev))
1260 pp = repo.dirstate.parents()
1281 pp = repo.dirstate.parents()
1261 if rev in pp:
1282 if rev in pp:
1262 update = True
1283 update = True
1263 else:
1284 else:
1264 update = False
1285 update = False
1265 self.strip(repo, rev, update=update, backup='strip')
1286 self.strip(repo, rev, update=update, backup='strip')
1266 if qpp:
1287 if qpp:
1267 self.ui.warn("saved queue repository parents: %s %s\n" %
1288 self.ui.warn("saved queue repository parents: %s %s\n" %
1268 (hg.short(qpp[0]), hg.short(qpp[1])))
1289 (hg.short(qpp[0]), hg.short(qpp[1])))
1269 if qupdate:
1290 if qupdate:
1270 self.ui.status(_("queue directory updating\n"))
1291 self.ui.status(_("queue directory updating\n"))
1271 r = self.qrepo()
1292 r = self.qrepo()
1272 if not r:
1293 if not r:
1273 self.ui.warn("Unable to load queue repository\n")
1294 self.ui.warn("Unable to load queue repository\n")
1274 return 1
1295 return 1
1275 hg.clean(r, qpp[0])
1296 hg.clean(r, qpp[0])
1276
1297
1277 def save(self, repo, msg=None):
1298 def save(self, repo, msg=None):
1278 if len(self.applied) == 0:
1299 if len(self.applied) == 0:
1279 self.ui.warn("save: no patches applied, exiting\n")
1300 self.ui.warn("save: no patches applied, exiting\n")
1280 return 1
1301 return 1
1281 if self.issaveline(self.applied[-1]):
1302 if self.issaveline(self.applied[-1]):
1282 self.ui.warn("status is already saved\n")
1303 self.ui.warn("status is already saved\n")
1283 return 1
1304 return 1
1284
1305
1285 ar = [ ':' + x for x in self.full_series ]
1306 ar = [ ':' + x for x in self.full_series ]
1286 if not msg:
1307 if not msg:
1287 msg = "hg patches saved state"
1308 msg = "hg patches saved state"
1288 else:
1309 else:
1289 msg = "hg patches: " + msg.rstrip('\r\n')
1310 msg = "hg patches: " + msg.rstrip('\r\n')
1290 r = self.qrepo()
1311 r = self.qrepo()
1291 if r:
1312 if r:
1292 pp = r.dirstate.parents()
1313 pp = r.dirstate.parents()
1293 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1314 msg += "\nDirstate: %s %s" % (hg.hex(pp[0]), hg.hex(pp[1]))
1294 msg += "\n\nPatch Data:\n"
1315 msg += "\n\nPatch Data:\n"
1295 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1316 text = msg + "\n".join([str(x) for x in self.applied]) + '\n' + (ar and
1296 "\n".join(ar) + '\n' or "")
1317 "\n".join(ar) + '\n' or "")
1297 n = repo.commit(None, text, user=None, force=1)
1318 n = repo.commit(None, text, user=None, force=1)
1298 if not n:
1319 if not n:
1299 self.ui.warn("repo commit failed\n")
1320 self.ui.warn("repo commit failed\n")
1300 return 1
1321 return 1
1301 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1322 self.applied.append(statusentry(revlog.hex(n),'.hg.patches.save.line'))
1302 self.applied_dirty = 1
1323 self.applied_dirty = 1
1303 self.removeundo(repo)
1324 self.removeundo(repo)
1304
1325
1305 def full_series_end(self):
1326 def full_series_end(self):
1306 if len(self.applied) > 0:
1327 if len(self.applied) > 0:
1307 p = self.applied[-1].name
1328 p = self.applied[-1].name
1308 end = self.find_series(p)
1329 end = self.find_series(p)
1309 if end == None:
1330 if end == None:
1310 return len(self.full_series)
1331 return len(self.full_series)
1311 return end + 1
1332 return end + 1
1312 return 0
1333 return 0
1313
1334
1314 def series_end(self, all_patches=False):
1335 def series_end(self, all_patches=False):
1315 """If all_patches is False, return the index of the next pushable patch
1336 """If all_patches is False, return the index of the next pushable patch
1316 in the series, or the series length. If all_patches is True, return the
1337 in the series, or the series length. If all_patches is True, return the
1317 index of the first patch past the last applied one.
1338 index of the first patch past the last applied one.
1318 """
1339 """
1319 end = 0
1340 end = 0
1320 def next(start):
1341 def next(start):
1321 if all_patches:
1342 if all_patches:
1322 return start
1343 return start
1323 i = start
1344 i = start
1324 while i < len(self.series):
1345 while i < len(self.series):
1325 p, reason = self.pushable(i)
1346 p, reason = self.pushable(i)
1326 if p:
1347 if p:
1327 break
1348 break
1328 self.explain_pushable(i)
1349 self.explain_pushable(i)
1329 i += 1
1350 i += 1
1330 return i
1351 return i
1331 if len(self.applied) > 0:
1352 if len(self.applied) > 0:
1332 p = self.applied[-1].name
1353 p = self.applied[-1].name
1333 try:
1354 try:
1334 end = self.series.index(p)
1355 end = self.series.index(p)
1335 except ValueError:
1356 except ValueError:
1336 return 0
1357 return 0
1337 return next(end + 1)
1358 return next(end + 1)
1338 return next(end)
1359 return next(end)
1339
1360
1340 def appliedname(self, index):
1361 def appliedname(self, index):
1341 pname = self.applied[index].name
1362 pname = self.applied[index].name
1342 if not self.ui.verbose:
1363 if not self.ui.verbose:
1343 p = pname
1364 p = pname
1344 else:
1365 else:
1345 p = str(self.series.index(pname)) + " " + pname
1366 p = str(self.series.index(pname)) + " " + pname
1346 return p
1367 return p
1347
1368
1348 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1369 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1349 force=None, git=False):
1370 force=None, git=False):
1350 def checkseries(patchname):
1371 def checkseries(patchname):
1351 if patchname in self.series:
1372 if patchname in self.series:
1352 raise util.Abort(_('patch %s is already in the series file')
1373 raise util.Abort(_('patch %s is already in the series file')
1353 % patchname)
1374 % patchname)
1354 def checkfile(patchname):
1375 def checkfile(patchname):
1355 if not force and os.path.exists(self.join(patchname)):
1376 if not force and os.path.exists(self.join(patchname)):
1356 raise util.Abort(_('patch "%s" already exists')
1377 raise util.Abort(_('patch "%s" already exists')
1357 % patchname)
1378 % patchname)
1358
1379
1359 if rev:
1380 if rev:
1360 if files:
1381 if files:
1361 raise util.Abort(_('option "-r" not valid when importing '
1382 raise util.Abort(_('option "-r" not valid when importing '
1362 'files'))
1383 'files'))
1363 rev = cmdutil.revrange(repo, rev)
1384 rev = cmdutil.revrange(repo, rev)
1364 rev.sort(lambda x, y: cmp(y, x))
1385 rev.sort(lambda x, y: cmp(y, x))
1365 if (len(files) > 1 or len(rev) > 1) and patchname:
1386 if (len(files) > 1 or len(rev) > 1) and patchname:
1366 raise util.Abort(_('option "-n" not valid when importing multiple '
1387 raise util.Abort(_('option "-n" not valid when importing multiple '
1367 'patches'))
1388 'patches'))
1368 i = 0
1389 i = 0
1369 added = []
1390 added = []
1370 if rev:
1391 if rev:
1371 # If mq patches are applied, we can only import revisions
1392 # If mq patches are applied, we can only import revisions
1372 # that form a linear path to qbase.
1393 # that form a linear path to qbase.
1373 # Otherwise, they should form a linear path to a head.
1394 # Otherwise, they should form a linear path to a head.
1374 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1395 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1375 if len(heads) > 1:
1396 if len(heads) > 1:
1376 raise util.Abort(_('revision %d is the root of more than one '
1397 raise util.Abort(_('revision %d is the root of more than one '
1377 'branch') % rev[-1])
1398 'branch') % rev[-1])
1378 if self.applied:
1399 if self.applied:
1379 base = revlog.hex(repo.changelog.node(rev[0]))
1400 base = revlog.hex(repo.changelog.node(rev[0]))
1380 if base in [n.rev for n in self.applied]:
1401 if base in [n.rev for n in self.applied]:
1381 raise util.Abort(_('revision %d is already managed')
1402 raise util.Abort(_('revision %d is already managed')
1382 % rev[0])
1403 % rev[0])
1383 if heads != [revlog.bin(self.applied[-1].rev)]:
1404 if heads != [revlog.bin(self.applied[-1].rev)]:
1384 raise util.Abort(_('revision %d is not the parent of '
1405 raise util.Abort(_('revision %d is not the parent of '
1385 'the queue') % rev[0])
1406 'the queue') % rev[0])
1386 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1407 base = repo.changelog.rev(revlog.bin(self.applied[0].rev))
1387 lastparent = repo.changelog.parentrevs(base)[0]
1408 lastparent = repo.changelog.parentrevs(base)[0]
1388 else:
1409 else:
1389 if heads != [repo.changelog.node(rev[0])]:
1410 if heads != [repo.changelog.node(rev[0])]:
1390 raise util.Abort(_('revision %d has unmanaged children')
1411 raise util.Abort(_('revision %d has unmanaged children')
1391 % rev[0])
1412 % rev[0])
1392 lastparent = None
1413 lastparent = None
1393
1414
1394 if git:
1415 if git:
1395 self.diffopts().git = True
1416 self.diffopts().git = True
1396
1417
1397 for r in rev:
1418 for r in rev:
1398 p1, p2 = repo.changelog.parentrevs(r)
1419 p1, p2 = repo.changelog.parentrevs(r)
1399 n = repo.changelog.node(r)
1420 n = repo.changelog.node(r)
1400 if p2 != revlog.nullrev:
1421 if p2 != revlog.nullrev:
1401 raise util.Abort(_('cannot import merge revision %d') % r)
1422 raise util.Abort(_('cannot import merge revision %d') % r)
1402 if lastparent and lastparent != r:
1423 if lastparent and lastparent != r:
1403 raise util.Abort(_('revision %d is not the parent of %d')
1424 raise util.Abort(_('revision %d is not the parent of %d')
1404 % (r, lastparent))
1425 % (r, lastparent))
1405 lastparent = p1
1426 lastparent = p1
1406
1427
1407 if not patchname:
1428 if not patchname:
1408 patchname = normname('%d.diff' % r)
1429 patchname = normname('%d.diff' % r)
1430 self.check_reserved_name(patchname)
1409 checkseries(patchname)
1431 checkseries(patchname)
1410 checkfile(patchname)
1432 checkfile(patchname)
1411 self.full_series.insert(0, patchname)
1433 self.full_series.insert(0, patchname)
1412
1434
1413 patchf = self.opener(patchname, "w")
1435 patchf = self.opener(patchname, "w")
1414 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1436 patch.export(repo, [n], fp=patchf, opts=self.diffopts())
1415 patchf.close()
1437 patchf.close()
1416
1438
1417 se = statusentry(revlog.hex(n), patchname)
1439 se = statusentry(revlog.hex(n), patchname)
1418 self.applied.insert(0, se)
1440 self.applied.insert(0, se)
1419
1441
1420 added.append(patchname)
1442 added.append(patchname)
1421 patchname = None
1443 patchname = None
1422 self.parse_series()
1444 self.parse_series()
1423 self.applied_dirty = 1
1445 self.applied_dirty = 1
1424
1446
1425 for filename in files:
1447 for filename in files:
1426 if existing:
1448 if existing:
1427 if filename == '-':
1449 if filename == '-':
1428 raise util.Abort(_('-e is incompatible with import from -'))
1450 raise util.Abort(_('-e is incompatible with import from -'))
1429 if not patchname:
1451 if not patchname:
1430 patchname = normname(filename)
1452 patchname = normname(filename)
1453 self.check_reserved_name(patchname)
1431 if not os.path.isfile(self.join(patchname)):
1454 if not os.path.isfile(self.join(patchname)):
1432 raise util.Abort(_("patch %s does not exist") % patchname)
1455 raise util.Abort(_("patch %s does not exist") % patchname)
1433 else:
1456 else:
1434 try:
1457 try:
1435 if filename == '-':
1458 if filename == '-':
1436 if not patchname:
1459 if not patchname:
1437 raise util.Abort(_('need --name to import a patch from -'))
1460 raise util.Abort(_('need --name to import a patch from -'))
1438 text = sys.stdin.read()
1461 text = sys.stdin.read()
1439 else:
1462 else:
1440 text = file(filename).read()
1463 text = file(filename, 'rb').read()
1441 except IOError:
1464 except IOError:
1442 raise util.Abort(_("unable to read %s") % patchname)
1465 raise util.Abort(_("unable to read %s") % patchname)
1443 if not patchname:
1466 if not patchname:
1444 patchname = normname(os.path.basename(filename))
1467 patchname = normname(os.path.basename(filename))
1468 self.check_reserved_name(patchname)
1445 checkfile(patchname)
1469 checkfile(patchname)
1446 patchf = self.opener(patchname, "w")
1470 patchf = self.opener(patchname, "w")
1447 patchf.write(text)
1471 patchf.write(text)
1448 checkseries(patchname)
1472 checkseries(patchname)
1449 index = self.full_series_end() + i
1473 index = self.full_series_end() + i
1450 self.full_series[index:index] = [patchname]
1474 self.full_series[index:index] = [patchname]
1451 self.parse_series()
1475 self.parse_series()
1452 self.ui.warn("adding %s to series file\n" % patchname)
1476 self.ui.warn("adding %s to series file\n" % patchname)
1453 i += 1
1477 i += 1
1454 added.append(patchname)
1478 added.append(patchname)
1455 patchname = None
1479 patchname = None
1456 self.series_dirty = 1
1480 self.series_dirty = 1
1457 qrepo = self.qrepo()
1481 qrepo = self.qrepo()
1458 if qrepo:
1482 if qrepo:
1459 qrepo.add(added)
1483 qrepo.add(added)
1460
1484
1461 def delete(ui, repo, *patches, **opts):
1485 def delete(ui, repo, *patches, **opts):
1462 """remove patches from queue
1486 """remove patches from queue
1463
1487
1464 The patches must not be applied, unless they are arguments to
1488 The patches must not be applied, unless they are arguments to
1465 the --rev parameter. At least one patch or revision is required.
1489 the --rev parameter. At least one patch or revision is required.
1466
1490
1467 With --rev, mq will stop managing the named revisions (converting
1491 With --rev, mq will stop managing the named revisions (converting
1468 them to regular mercurial changesets). The patches must be applied
1492 them to regular mercurial changesets). The patches must be applied
1469 and at the base of the stack. This option is useful when the patches
1493 and at the base of the stack. This option is useful when the patches
1470 have been applied upstream.
1494 have been applied upstream.
1471
1495
1472 With --keep, the patch files are preserved in the patch directory."""
1496 With --keep, the patch files are preserved in the patch directory."""
1473 q = repo.mq
1497 q = repo.mq
1474 q.delete(repo, patches, opts)
1498 q.delete(repo, patches, opts)
1475 q.save_dirty()
1499 q.save_dirty()
1476 return 0
1500 return 0
1477
1501
1478 def applied(ui, repo, patch=None, **opts):
1502 def applied(ui, repo, patch=None, **opts):
1479 """print the patches already applied"""
1503 """print the patches already applied"""
1480 q = repo.mq
1504 q = repo.mq
1481 if patch:
1505 if patch:
1482 if patch not in q.series:
1506 if patch not in q.series:
1483 raise util.Abort(_("patch %s is not in series file") % patch)
1507 raise util.Abort(_("patch %s is not in series file") % patch)
1484 end = q.series.index(patch) + 1
1508 end = q.series.index(patch) + 1
1485 else:
1509 else:
1486 end = q.series_end(True)
1510 end = q.series_end(True)
1487 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1511 return q.qseries(repo, length=end, status='A', summary=opts.get('summary'))
1488
1512
1489 def unapplied(ui, repo, patch=None, **opts):
1513 def unapplied(ui, repo, patch=None, **opts):
1490 """print the patches not yet applied"""
1514 """print the patches not yet applied"""
1491 q = repo.mq
1515 q = repo.mq
1492 if patch:
1516 if patch:
1493 if patch not in q.series:
1517 if patch not in q.series:
1494 raise util.Abort(_("patch %s is not in series file") % patch)
1518 raise util.Abort(_("patch %s is not in series file") % patch)
1495 start = q.series.index(patch) + 1
1519 start = q.series.index(patch) + 1
1496 else:
1520 else:
1497 start = q.series_end(True)
1521 start = q.series_end(True)
1498 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1522 q.qseries(repo, start=start, status='U', summary=opts.get('summary'))
1499
1523
1500 def qimport(ui, repo, *filename, **opts):
1524 def qimport(ui, repo, *filename, **opts):
1501 """import a patch
1525 """import a patch
1502
1526
1503 The patch will have the same name as its source file unless you
1527 The patch will have the same name as its source file unless you
1504 give it a new one with --name.
1528 give it a new one with --name.
1505
1529
1506 You can register an existing patch inside the patch directory
1530 You can register an existing patch inside the patch directory
1507 with the --existing flag.
1531 with the --existing flag.
1508
1532
1509 With --force, an existing patch of the same name will be overwritten.
1533 With --force, an existing patch of the same name will be overwritten.
1510
1534
1511 An existing changeset may be placed under mq control with --rev
1535 An existing changeset may be placed under mq control with --rev
1512 (e.g. qimport --rev tip -n patch will place tip under mq control).
1536 (e.g. qimport --rev tip -n patch will place tip under mq control).
1513 With --git, patches imported with --rev will use the git diff
1537 With --git, patches imported with --rev will use the git diff
1514 format.
1538 format.
1515 """
1539 """
1516 q = repo.mq
1540 q = repo.mq
1517 q.qimport(repo, filename, patchname=opts['name'],
1541 q.qimport(repo, filename, patchname=opts['name'],
1518 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1542 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1519 git=opts['git'])
1543 git=opts['git'])
1520 q.save_dirty()
1544 q.save_dirty()
1521 return 0
1545 return 0
1522
1546
1523 def init(ui, repo, **opts):
1547 def init(ui, repo, **opts):
1524 """init a new queue repository
1548 """init a new queue repository
1525
1549
1526 The queue repository is unversioned by default. If -c is
1550 The queue repository is unversioned by default. If -c is
1527 specified, qinit will create a separate nested repository
1551 specified, qinit will create a separate nested repository
1528 for patches (qinit -c may also be run later to convert
1552 for patches (qinit -c may also be run later to convert
1529 an unversioned patch repository into a versioned one).
1553 an unversioned patch repository into a versioned one).
1530 You can use qcommit to commit changes to this queue repository."""
1554 You can use qcommit to commit changes to this queue repository."""
1531 q = repo.mq
1555 q = repo.mq
1532 r = q.init(repo, create=opts['create_repo'])
1556 r = q.init(repo, create=opts['create_repo'])
1533 q.save_dirty()
1557 q.save_dirty()
1534 if r:
1558 if r:
1535 if not os.path.exists(r.wjoin('.hgignore')):
1559 if not os.path.exists(r.wjoin('.hgignore')):
1536 fp = r.wopener('.hgignore', 'w')
1560 fp = r.wopener('.hgignore', 'w')
1561 fp.write('^\\.hg\n')
1562 fp.write('^\\.mq\n')
1537 fp.write('syntax: glob\n')
1563 fp.write('syntax: glob\n')
1538 fp.write('status\n')
1564 fp.write('status\n')
1539 fp.write('guards\n')
1565 fp.write('guards\n')
1540 fp.close()
1566 fp.close()
1541 if not os.path.exists(r.wjoin('series')):
1567 if not os.path.exists(r.wjoin('series')):
1542 r.wopener('series', 'w').close()
1568 r.wopener('series', 'w').close()
1543 r.add(['.hgignore', 'series'])
1569 r.add(['.hgignore', 'series'])
1544 commands.add(ui, r)
1570 commands.add(ui, r)
1545 return 0
1571 return 0
1546
1572
1547 def clone(ui, source, dest=None, **opts):
1573 def clone(ui, source, dest=None, **opts):
1548 '''clone main and patch repository at same time
1574 '''clone main and patch repository at same time
1549
1575
1550 If source is local, destination will have no patches applied. If
1576 If source is local, destination will have no patches applied. If
1551 source is remote, this command can not check if patches are
1577 source is remote, this command can not check if patches are
1552 applied in source, so cannot guarantee that patches are not
1578 applied in source, so cannot guarantee that patches are not
1553 applied in destination. If you clone remote repository, be sure
1579 applied in destination. If you clone remote repository, be sure
1554 before that it has no patches applied.
1580 before that it has no patches applied.
1555
1581
1556 Source patch repository is looked for in <src>/.hg/patches by
1582 Source patch repository is looked for in <src>/.hg/patches by
1557 default. Use -p <url> to change.
1583 default. Use -p <url> to change.
1558
1584
1559 The patch directory must be a nested mercurial repository, as
1585 The patch directory must be a nested mercurial repository, as
1560 would be created by qinit -c.
1586 would be created by qinit -c.
1561 '''
1587 '''
1562 def patchdir(repo):
1588 def patchdir(repo):
1563 url = repo.url()
1589 url = repo.url()
1564 if url.endswith('/'):
1590 if url.endswith('/'):
1565 url = url[:-1]
1591 url = url[:-1]
1566 return url + '/.hg/patches'
1592 return url + '/.hg/patches'
1567 cmdutil.setremoteconfig(ui, opts)
1593 cmdutil.setremoteconfig(ui, opts)
1568 if dest is None:
1594 if dest is None:
1569 dest = hg.defaultdest(source)
1595 dest = hg.defaultdest(source)
1570 sr = hg.repository(ui, ui.expandpath(source))
1596 sr = hg.repository(ui, ui.expandpath(source))
1571 patchespath = opts['patches'] or patchdir(sr)
1597 patchespath = opts['patches'] or patchdir(sr)
1572 try:
1598 try:
1573 pr = hg.repository(ui, patchespath)
1599 pr = hg.repository(ui, patchespath)
1574 except hg.RepoError:
1600 except hg.RepoError:
1575 raise util.Abort(_('versioned patch repository not found'
1601 raise util.Abort(_('versioned patch repository not found'
1576 ' (see qinit -c)'))
1602 ' (see qinit -c)'))
1577 qbase, destrev = None, None
1603 qbase, destrev = None, None
1578 if sr.local():
1604 if sr.local():
1579 if sr.mq.applied:
1605 if sr.mq.applied:
1580 qbase = revlog.bin(sr.mq.applied[0].rev)
1606 qbase = revlog.bin(sr.mq.applied[0].rev)
1581 if not hg.islocal(dest):
1607 if not hg.islocal(dest):
1582 heads = dict.fromkeys(sr.heads())
1608 heads = dict.fromkeys(sr.heads())
1583 for h in sr.heads(qbase):
1609 for h in sr.heads(qbase):
1584 del heads[h]
1610 del heads[h]
1585 destrev = heads.keys()
1611 destrev = heads.keys()
1586 destrev.append(sr.changelog.parents(qbase)[0])
1612 destrev.append(sr.changelog.parents(qbase)[0])
1587 ui.note(_('cloning main repo\n'))
1613 ui.note(_('cloning main repo\n'))
1588 sr, dr = hg.clone(ui, sr.url(), dest,
1614 sr, dr = hg.clone(ui, sr.url(), dest,
1589 pull=opts['pull'],
1615 pull=opts['pull'],
1590 rev=destrev,
1616 rev=destrev,
1591 update=False,
1617 update=False,
1592 stream=opts['uncompressed'])
1618 stream=opts['uncompressed'])
1593 ui.note(_('cloning patch repo\n'))
1619 ui.note(_('cloning patch repo\n'))
1594 spr, dpr = hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1620 spr, dpr = hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1595 pull=opts['pull'], update=not opts['noupdate'],
1621 pull=opts['pull'], update=not opts['noupdate'],
1596 stream=opts['uncompressed'])
1622 stream=opts['uncompressed'])
1597 if dr.local():
1623 if dr.local():
1598 if qbase:
1624 if qbase:
1599 ui.note(_('stripping applied patches from destination repo\n'))
1625 ui.note(_('stripping applied patches from destination repo\n'))
1600 dr.mq.strip(dr, qbase, update=False, backup=None)
1626 dr.mq.strip(dr, qbase, update=False, backup=None)
1601 if not opts['noupdate']:
1627 if not opts['noupdate']:
1602 ui.note(_('updating destination repo\n'))
1628 ui.note(_('updating destination repo\n'))
1603 hg.update(dr, dr.changelog.tip())
1629 hg.update(dr, dr.changelog.tip())
1604
1630
1605 def commit(ui, repo, *pats, **opts):
1631 def commit(ui, repo, *pats, **opts):
1606 """commit changes in the queue repository"""
1632 """commit changes in the queue repository"""
1607 q = repo.mq
1633 q = repo.mq
1608 r = q.qrepo()
1634 r = q.qrepo()
1609 if not r: raise util.Abort('no queue repository')
1635 if not r: raise util.Abort('no queue repository')
1610 commands.commit(r.ui, r, *pats, **opts)
1636 commands.commit(r.ui, r, *pats, **opts)
1611
1637
1612 def series(ui, repo, **opts):
1638 def series(ui, repo, **opts):
1613 """print the entire series file"""
1639 """print the entire series file"""
1614 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1640 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1615 return 0
1641 return 0
1616
1642
1617 def top(ui, repo, **opts):
1643 def top(ui, repo, **opts):
1618 """print the name of the current patch"""
1644 """print the name of the current patch"""
1619 q = repo.mq
1645 q = repo.mq
1620 t = q.applied and q.series_end(True) or 0
1646 t = q.applied and q.series_end(True) or 0
1621 if t:
1647 if t:
1622 return q.qseries(repo, start=t-1, length=1, status='A',
1648 return q.qseries(repo, start=t-1, length=1, status='A',
1623 summary=opts.get('summary'))
1649 summary=opts.get('summary'))
1624 else:
1650 else:
1625 ui.write("No patches applied\n")
1651 ui.write("No patches applied\n")
1626 return 1
1652 return 1
1627
1653
1628 def next(ui, repo, **opts):
1654 def next(ui, repo, **opts):
1629 """print the name of the next patch"""
1655 """print the name of the next patch"""
1630 q = repo.mq
1656 q = repo.mq
1631 end = q.series_end()
1657 end = q.series_end()
1632 if end == len(q.series):
1658 if end == len(q.series):
1633 ui.write("All patches applied\n")
1659 ui.write("All patches applied\n")
1634 return 1
1660 return 1
1635 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1661 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1636
1662
1637 def prev(ui, repo, **opts):
1663 def prev(ui, repo, **opts):
1638 """print the name of the previous patch"""
1664 """print the name of the previous patch"""
1639 q = repo.mq
1665 q = repo.mq
1640 l = len(q.applied)
1666 l = len(q.applied)
1641 if l == 1:
1667 if l == 1:
1642 ui.write("Only one patch applied\n")
1668 ui.write("Only one patch applied\n")
1643 return 1
1669 return 1
1644 if not l:
1670 if not l:
1645 ui.write("No patches applied\n")
1671 ui.write("No patches applied\n")
1646 return 1
1672 return 1
1647 return q.qseries(repo, start=l-2, length=1, status='A',
1673 return q.qseries(repo, start=l-2, length=1, status='A',
1648 summary=opts.get('summary'))
1674 summary=opts.get('summary'))
1649
1675
1650 def setupheaderopts(ui, opts):
1676 def setupheaderopts(ui, opts):
1651 def do(opt,val):
1677 def do(opt,val):
1652 if not opts[opt] and opts['current' + opt]:
1678 if not opts[opt] and opts['current' + opt]:
1653 opts[opt] = val
1679 opts[opt] = val
1654 do('user', ui.username())
1680 do('user', ui.username())
1655 do('date', "%d %d" % util.makedate())
1681 do('date', "%d %d" % util.makedate())
1656
1682
1657 def new(ui, repo, patch, *args, **opts):
1683 def new(ui, repo, patch, *args, **opts):
1658 """create a new patch
1684 """create a new patch
1659
1685
1660 qnew creates a new patch on top of the currently-applied patch
1686 qnew creates a new patch on top of the currently-applied patch
1661 (if any). It will refuse to run if there are any outstanding
1687 (if any). It will refuse to run if there are any outstanding
1662 changes unless -f is specified, in which case the patch will
1688 changes unless -f is specified, in which case the patch will
1663 be initialised with them. You may also use -I, -X, and/or a list of
1689 be initialised with them. You may also use -I, -X, and/or a list of
1664 files after the patch name to add only changes to matching files
1690 files after the patch name to add only changes to matching files
1665 to the new patch, leaving the rest as uncommitted modifications.
1691 to the new patch, leaving the rest as uncommitted modifications.
1666
1692
1667 -e, -m or -l set the patch header as well as the commit message.
1693 -e, -m or -l set the patch header as well as the commit message.
1668 If none is specified, the patch header is empty and the
1694 If none is specified, the patch header is empty and the
1669 commit message is '[mq]: PATCH'"""
1695 commit message is '[mq]: PATCH'"""
1670 q = repo.mq
1696 q = repo.mq
1671 message = cmdutil.logmessage(opts)
1697 message = cmdutil.logmessage(opts)
1672 if opts['edit']:
1698 if opts['edit']:
1673 message = ui.edit(message, ui.username())
1699 message = ui.edit(message, ui.username())
1674 opts['msg'] = message
1700 opts['msg'] = message
1675 setupheaderopts(ui, opts)
1701 setupheaderopts(ui, opts)
1676 q.new(repo, patch, *args, **opts)
1702 q.new(repo, patch, *args, **opts)
1677 q.save_dirty()
1703 q.save_dirty()
1678 return 0
1704 return 0
1679
1705
1680 def refresh(ui, repo, *pats, **opts):
1706 def refresh(ui, repo, *pats, **opts):
1681 """update the current patch
1707 """update the current patch
1682
1708
1683 If any file patterns are provided, the refreshed patch will contain only
1709 If any file patterns are provided, the refreshed patch will contain only
1684 the modifications that match those patterns; the remaining modifications
1710 the modifications that match those patterns; the remaining modifications
1685 will remain in the working directory.
1711 will remain in the working directory.
1686
1712
1687 hg add/remove/copy/rename work as usual, though you might want to use
1713 hg add/remove/copy/rename work as usual, though you might want to use
1688 git-style patches (--git or [diff] git=1) to track copies and renames.
1714 git-style patches (--git or [diff] git=1) to track copies and renames.
1689 """
1715 """
1690 q = repo.mq
1716 q = repo.mq
1691 message = cmdutil.logmessage(opts)
1717 message = cmdutil.logmessage(opts)
1692 if opts['edit']:
1718 if opts['edit']:
1693 if not q.applied:
1719 if not q.applied:
1694 ui.write(_("No patches applied\n"))
1720 ui.write(_("No patches applied\n"))
1695 return 1
1721 return 1
1696 if message:
1722 if message:
1697 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1723 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1698 patch = q.applied[-1].name
1724 patch = q.applied[-1].name
1699 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1725 (message, comment, user, date, hasdiff) = q.readheaders(patch)
1700 message = ui.edit('\n'.join(message), user or ui.username())
1726 message = ui.edit('\n'.join(message), user or ui.username())
1701 setupheaderopts(ui, opts)
1727 setupheaderopts(ui, opts)
1702 ret = q.refresh(repo, pats, msg=message, **opts)
1728 ret = q.refresh(repo, pats, msg=message, **opts)
1703 q.save_dirty()
1729 q.save_dirty()
1704 return ret
1730 return ret
1705
1731
1706 def diff(ui, repo, *pats, **opts):
1732 def diff(ui, repo, *pats, **opts):
1707 """diff of the current patch"""
1733 """diff of the current patch"""
1708 repo.mq.diff(repo, pats, opts)
1734 repo.mq.diff(repo, pats, opts)
1709 return 0
1735 return 0
1710
1736
1711 def fold(ui, repo, *files, **opts):
1737 def fold(ui, repo, *files, **opts):
1712 """fold the named patches into the current patch
1738 """fold the named patches into the current patch
1713
1739
1714 Patches must not yet be applied. Each patch will be successively
1740 Patches must not yet be applied. Each patch will be successively
1715 applied to the current patch in the order given. If all the
1741 applied to the current patch in the order given. If all the
1716 patches apply successfully, the current patch will be refreshed
1742 patches apply successfully, the current patch will be refreshed
1717 with the new cumulative patch, and the folded patches will
1743 with the new cumulative patch, and the folded patches will
1718 be deleted. With -k/--keep, the folded patch files will not
1744 be deleted. With -k/--keep, the folded patch files will not
1719 be removed afterwards.
1745 be removed afterwards.
1720
1746
1721 The header for each folded patch will be concatenated with
1747 The header for each folded patch will be concatenated with
1722 the current patch header, separated by a line of '* * *'."""
1748 the current patch header, separated by a line of '* * *'."""
1723
1749
1724 q = repo.mq
1750 q = repo.mq
1725
1751
1726 if not files:
1752 if not files:
1727 raise util.Abort(_('qfold requires at least one patch name'))
1753 raise util.Abort(_('qfold requires at least one patch name'))
1728 if not q.check_toppatch(repo):
1754 if not q.check_toppatch(repo):
1729 raise util.Abort(_('No patches applied'))
1755 raise util.Abort(_('No patches applied'))
1730
1756
1731 message = cmdutil.logmessage(opts)
1757 message = cmdutil.logmessage(opts)
1732 if opts['edit']:
1758 if opts['edit']:
1733 if message:
1759 if message:
1734 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1760 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
1735
1761
1736 parent = q.lookup('qtip')
1762 parent = q.lookup('qtip')
1737 patches = []
1763 patches = []
1738 messages = []
1764 messages = []
1739 for f in files:
1765 for f in files:
1740 p = q.lookup(f)
1766 p = q.lookup(f)
1741 if p in patches or p == parent:
1767 if p in patches or p == parent:
1742 ui.warn(_('Skipping already folded patch %s') % p)
1768 ui.warn(_('Skipping already folded patch %s') % p)
1743 if q.isapplied(p):
1769 if q.isapplied(p):
1744 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1770 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
1745 patches.append(p)
1771 patches.append(p)
1746
1772
1747 for p in patches:
1773 for p in patches:
1748 if not message:
1774 if not message:
1749 messages.append(q.readheaders(p)[0])
1775 messages.append(q.readheaders(p)[0])
1750 pf = q.join(p)
1776 pf = q.join(p)
1751 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1777 (patchsuccess, files, fuzz) = q.patch(repo, pf)
1752 if not patchsuccess:
1778 if not patchsuccess:
1753 raise util.Abort(_('Error folding patch %s') % p)
1779 raise util.Abort(_('Error folding patch %s') % p)
1754 patch.updatedir(ui, repo, files)
1780 patch.updatedir(ui, repo, files)
1755
1781
1756 if not message:
1782 if not message:
1757 message, comments, user = q.readheaders(parent)[0:3]
1783 message, comments, user = q.readheaders(parent)[0:3]
1758 for msg in messages:
1784 for msg in messages:
1759 message.append('* * *')
1785 message.append('* * *')
1760 message.extend(msg)
1786 message.extend(msg)
1761 message = '\n'.join(message)
1787 message = '\n'.join(message)
1762
1788
1763 if opts['edit']:
1789 if opts['edit']:
1764 message = ui.edit(message, user or ui.username())
1790 message = ui.edit(message, user or ui.username())
1765
1791
1766 q.refresh(repo, msg=message)
1792 q.refresh(repo, msg=message)
1767 q.delete(repo, patches, opts)
1793 q.delete(repo, patches, opts)
1768 q.save_dirty()
1794 q.save_dirty()
1769
1795
1770 def goto(ui, repo, patch, **opts):
1796 def goto(ui, repo, patch, **opts):
1771 '''push or pop patches until named patch is at top of stack'''
1797 '''push or pop patches until named patch is at top of stack'''
1772 q = repo.mq
1798 q = repo.mq
1773 patch = q.lookup(patch)
1799 patch = q.lookup(patch)
1774 if q.isapplied(patch):
1800 if q.isapplied(patch):
1775 ret = q.pop(repo, patch, force=opts['force'])
1801 ret = q.pop(repo, patch, force=opts['force'])
1776 else:
1802 else:
1777 ret = q.push(repo, patch, force=opts['force'])
1803 ret = q.push(repo, patch, force=opts['force'])
1778 q.save_dirty()
1804 q.save_dirty()
1779 return ret
1805 return ret
1780
1806
1781 def guard(ui, repo, *args, **opts):
1807 def guard(ui, repo, *args, **opts):
1782 '''set or print guards for a patch
1808 '''set or print guards for a patch
1783
1809
1784 Guards control whether a patch can be pushed. A patch with no
1810 Guards control whether a patch can be pushed. A patch with no
1785 guards is always pushed. A patch with a positive guard ("+foo") is
1811 guards is always pushed. A patch with a positive guard ("+foo") is
1786 pushed only if the qselect command has activated it. A patch with
1812 pushed only if the qselect command has activated it. A patch with
1787 a negative guard ("-foo") is never pushed if the qselect command
1813 a negative guard ("-foo") is never pushed if the qselect command
1788 has activated it.
1814 has activated it.
1789
1815
1790 With no arguments, print the currently active guards.
1816 With no arguments, print the currently active guards.
1791 With arguments, set guards for the named patch.
1817 With arguments, set guards for the named patch.
1792
1818
1793 To set a negative guard "-foo" on topmost patch ("--" is needed so
1819 To set a negative guard "-foo" on topmost patch ("--" is needed so
1794 hg will not interpret "-foo" as an option):
1820 hg will not interpret "-foo" as an option):
1795 hg qguard -- -foo
1821 hg qguard -- -foo
1796
1822
1797 To set guards on another patch:
1823 To set guards on another patch:
1798 hg qguard other.patch +2.6.17 -stable
1824 hg qguard other.patch +2.6.17 -stable
1799 '''
1825 '''
1800 def status(idx):
1826 def status(idx):
1801 guards = q.series_guards[idx] or ['unguarded']
1827 guards = q.series_guards[idx] or ['unguarded']
1802 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1828 ui.write('%s: %s\n' % (q.series[idx], ' '.join(guards)))
1803 q = repo.mq
1829 q = repo.mq
1804 patch = None
1830 patch = None
1805 args = list(args)
1831 args = list(args)
1806 if opts['list']:
1832 if opts['list']:
1807 if args or opts['none']:
1833 if args or opts['none']:
1808 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1834 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
1809 for i in xrange(len(q.series)):
1835 for i in xrange(len(q.series)):
1810 status(i)
1836 status(i)
1811 return
1837 return
1812 if not args or args[0][0:1] in '-+':
1838 if not args or args[0][0:1] in '-+':
1813 if not q.applied:
1839 if not q.applied:
1814 raise util.Abort(_('no patches applied'))
1840 raise util.Abort(_('no patches applied'))
1815 patch = q.applied[-1].name
1841 patch = q.applied[-1].name
1816 if patch is None and args[0][0:1] not in '-+':
1842 if patch is None and args[0][0:1] not in '-+':
1817 patch = args.pop(0)
1843 patch = args.pop(0)
1818 if patch is None:
1844 if patch is None:
1819 raise util.Abort(_('no patch to work with'))
1845 raise util.Abort(_('no patch to work with'))
1820 if args or opts['none']:
1846 if args or opts['none']:
1821 idx = q.find_series(patch)
1847 idx = q.find_series(patch)
1822 if idx is None:
1848 if idx is None:
1823 raise util.Abort(_('no patch named %s') % patch)
1849 raise util.Abort(_('no patch named %s') % patch)
1824 q.set_guards(idx, args)
1850 q.set_guards(idx, args)
1825 q.save_dirty()
1851 q.save_dirty()
1826 else:
1852 else:
1827 status(q.series.index(q.lookup(patch)))
1853 status(q.series.index(q.lookup(patch)))
1828
1854
1829 def header(ui, repo, patch=None):
1855 def header(ui, repo, patch=None):
1830 """Print the header of the topmost or specified patch"""
1856 """Print the header of the topmost or specified patch"""
1831 q = repo.mq
1857 q = repo.mq
1832
1858
1833 if patch:
1859 if patch:
1834 patch = q.lookup(patch)
1860 patch = q.lookup(patch)
1835 else:
1861 else:
1836 if not q.applied:
1862 if not q.applied:
1837 ui.write('No patches applied\n')
1863 ui.write('No patches applied\n')
1838 return 1
1864 return 1
1839 patch = q.lookup('qtip')
1865 patch = q.lookup('qtip')
1840 message = repo.mq.readheaders(patch)[0]
1866 message = repo.mq.readheaders(patch)[0]
1841
1867
1842 ui.write('\n'.join(message) + '\n')
1868 ui.write('\n'.join(message) + '\n')
1843
1869
1844 def lastsavename(path):
1870 def lastsavename(path):
1845 (directory, base) = os.path.split(path)
1871 (directory, base) = os.path.split(path)
1846 names = os.listdir(directory)
1872 names = os.listdir(directory)
1847 namere = re.compile("%s.([0-9]+)" % base)
1873 namere = re.compile("%s.([0-9]+)" % base)
1848 maxindex = None
1874 maxindex = None
1849 maxname = None
1875 maxname = None
1850 for f in names:
1876 for f in names:
1851 m = namere.match(f)
1877 m = namere.match(f)
1852 if m:
1878 if m:
1853 index = int(m.group(1))
1879 index = int(m.group(1))
1854 if maxindex == None or index > maxindex:
1880 if maxindex == None or index > maxindex:
1855 maxindex = index
1881 maxindex = index
1856 maxname = f
1882 maxname = f
1857 if maxname:
1883 if maxname:
1858 return (os.path.join(directory, maxname), maxindex)
1884 return (os.path.join(directory, maxname), maxindex)
1859 return (None, None)
1885 return (None, None)
1860
1886
1861 def savename(path):
1887 def savename(path):
1862 (last, index) = lastsavename(path)
1888 (last, index) = lastsavename(path)
1863 if last is None:
1889 if last is None:
1864 index = 0
1890 index = 0
1865 newpath = path + ".%d" % (index + 1)
1891 newpath = path + ".%d" % (index + 1)
1866 return newpath
1892 return newpath
1867
1893
1868 def push(ui, repo, patch=None, **opts):
1894 def push(ui, repo, patch=None, **opts):
1869 """push the next patch onto the stack"""
1895 """push the next patch onto the stack"""
1870 q = repo.mq
1896 q = repo.mq
1871 mergeq = None
1897 mergeq = None
1872
1898
1873 if opts['all']:
1899 if opts['all']:
1874 if not q.series:
1900 if not q.series:
1875 ui.warn(_('no patches in series\n'))
1901 ui.warn(_('no patches in series\n'))
1876 return 0
1902 return 0
1877 patch = q.series[-1]
1903 patch = q.series[-1]
1878 if opts['merge']:
1904 if opts['merge']:
1879 if opts['name']:
1905 if opts['name']:
1880 newpath = opts['name']
1906 newpath = opts['name']
1881 else:
1907 else:
1882 newpath, i = lastsavename(q.path)
1908 newpath, i = lastsavename(q.path)
1883 if not newpath:
1909 if not newpath:
1884 ui.warn("no saved queues found, please use -n\n")
1910 ui.warn("no saved queues found, please use -n\n")
1885 return 1
1911 return 1
1886 mergeq = queue(ui, repo.join(""), newpath)
1912 mergeq = queue(ui, repo.join(""), newpath)
1887 ui.warn("merging with queue at: %s\n" % mergeq.path)
1913 ui.warn("merging with queue at: %s\n" % mergeq.path)
1888 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1914 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
1889 mergeq=mergeq)
1915 mergeq=mergeq)
1890 return ret
1916 return ret
1891
1917
1892 def pop(ui, repo, patch=None, **opts):
1918 def pop(ui, repo, patch=None, **opts):
1893 """pop the current patch off the stack"""
1919 """pop the current patch off the stack"""
1894 localupdate = True
1920 localupdate = True
1895 if opts['name']:
1921 if opts['name']:
1896 q = queue(ui, repo.join(""), repo.join(opts['name']))
1922 q = queue(ui, repo.join(""), repo.join(opts['name']))
1897 ui.warn('using patch queue: %s\n' % q.path)
1923 ui.warn('using patch queue: %s\n' % q.path)
1898 localupdate = False
1924 localupdate = False
1899 else:
1925 else:
1900 q = repo.mq
1926 q = repo.mq
1901 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
1927 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
1902 all=opts['all'])
1928 all=opts['all'])
1903 q.save_dirty()
1929 q.save_dirty()
1904 return ret
1930 return ret
1905
1931
1906 def rename(ui, repo, patch, name=None, **opts):
1932 def rename(ui, repo, patch, name=None, **opts):
1907 """rename a patch
1933 """rename a patch
1908
1934
1909 With one argument, renames the current patch to PATCH1.
1935 With one argument, renames the current patch to PATCH1.
1910 With two arguments, renames PATCH1 to PATCH2."""
1936 With two arguments, renames PATCH1 to PATCH2."""
1911
1937
1912 q = repo.mq
1938 q = repo.mq
1913
1939
1914 if not name:
1940 if not name:
1915 name = patch
1941 name = patch
1916 patch = None
1942 patch = None
1917
1943
1918 if patch:
1944 if patch:
1919 patch = q.lookup(patch)
1945 patch = q.lookup(patch)
1920 else:
1946 else:
1921 if not q.applied:
1947 if not q.applied:
1922 ui.write(_('No patches applied\n'))
1948 ui.write(_('No patches applied\n'))
1923 return
1949 return
1924 patch = q.lookup('qtip')
1950 patch = q.lookup('qtip')
1925 absdest = q.join(name)
1951 absdest = q.join(name)
1926 if os.path.isdir(absdest):
1952 if os.path.isdir(absdest):
1927 name = normname(os.path.join(name, os.path.basename(patch)))
1953 name = normname(os.path.join(name, os.path.basename(patch)))
1928 absdest = q.join(name)
1954 absdest = q.join(name)
1929 if os.path.exists(absdest):
1955 if os.path.exists(absdest):
1930 raise util.Abort(_('%s already exists') % absdest)
1956 raise util.Abort(_('%s already exists') % absdest)
1931
1957
1932 if name in q.series:
1958 if name in q.series:
1933 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1959 raise util.Abort(_('A patch named %s already exists in the series file') % name)
1934
1960
1935 if ui.verbose:
1961 if ui.verbose:
1936 ui.write('Renaming %s to %s\n' % (patch, name))
1962 ui.write('Renaming %s to %s\n' % (patch, name))
1937 i = q.find_series(patch)
1963 i = q.find_series(patch)
1938 guards = q.guard_re.findall(q.full_series[i])
1964 guards = q.guard_re.findall(q.full_series[i])
1939 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1965 q.full_series[i] = name + ''.join([' #' + g for g in guards])
1940 q.parse_series()
1966 q.parse_series()
1941 q.series_dirty = 1
1967 q.series_dirty = 1
1942
1968
1943 info = q.isapplied(patch)
1969 info = q.isapplied(patch)
1944 if info:
1970 if info:
1945 q.applied[info[0]] = statusentry(info[1], name)
1971 q.applied[info[0]] = statusentry(info[1], name)
1946 q.applied_dirty = 1
1972 q.applied_dirty = 1
1947
1973
1948 util.rename(q.join(patch), absdest)
1974 util.rename(q.join(patch), absdest)
1949 r = q.qrepo()
1975 r = q.qrepo()
1950 if r:
1976 if r:
1951 wlock = r.wlock()
1977 wlock = r.wlock()
1952 try:
1978 try:
1953 if r.dirstate[name] == 'r':
1979 if r.dirstate[name] == 'r':
1954 r.undelete([name])
1980 r.undelete([name])
1955 r.copy(patch, name)
1981 r.copy(patch, name)
1956 r.remove([patch], False)
1982 r.remove([patch], False)
1957 finally:
1983 finally:
1958 del wlock
1984 del wlock
1959
1985
1960 q.save_dirty()
1986 q.save_dirty()
1961
1987
1962 def restore(ui, repo, rev, **opts):
1988 def restore(ui, repo, rev, **opts):
1963 """restore the queue state saved by a rev"""
1989 """restore the queue state saved by a rev"""
1964 rev = repo.lookup(rev)
1990 rev = repo.lookup(rev)
1965 q = repo.mq
1991 q = repo.mq
1966 q.restore(repo, rev, delete=opts['delete'],
1992 q.restore(repo, rev, delete=opts['delete'],
1967 qupdate=opts['update'])
1993 qupdate=opts['update'])
1968 q.save_dirty()
1994 q.save_dirty()
1969 return 0
1995 return 0
1970
1996
1971 def save(ui, repo, **opts):
1997 def save(ui, repo, **opts):
1972 """save current queue state"""
1998 """save current queue state"""
1973 q = repo.mq
1999 q = repo.mq
1974 message = cmdutil.logmessage(opts)
2000 message = cmdutil.logmessage(opts)
1975 ret = q.save(repo, msg=message)
2001 ret = q.save(repo, msg=message)
1976 if ret:
2002 if ret:
1977 return ret
2003 return ret
1978 q.save_dirty()
2004 q.save_dirty()
1979 if opts['copy']:
2005 if opts['copy']:
1980 path = q.path
2006 path = q.path
1981 if opts['name']:
2007 if opts['name']:
1982 newpath = os.path.join(q.basepath, opts['name'])
2008 newpath = os.path.join(q.basepath, opts['name'])
1983 if os.path.exists(newpath):
2009 if os.path.exists(newpath):
1984 if not os.path.isdir(newpath):
2010 if not os.path.isdir(newpath):
1985 raise util.Abort(_('destination %s exists and is not '
2011 raise util.Abort(_('destination %s exists and is not '
1986 'a directory') % newpath)
2012 'a directory') % newpath)
1987 if not opts['force']:
2013 if not opts['force']:
1988 raise util.Abort(_('destination %s exists, '
2014 raise util.Abort(_('destination %s exists, '
1989 'use -f to force') % newpath)
2015 'use -f to force') % newpath)
1990 else:
2016 else:
1991 newpath = savename(path)
2017 newpath = savename(path)
1992 ui.warn("copy %s to %s\n" % (path, newpath))
2018 ui.warn("copy %s to %s\n" % (path, newpath))
1993 util.copyfiles(path, newpath)
2019 util.copyfiles(path, newpath)
1994 if opts['empty']:
2020 if opts['empty']:
1995 try:
2021 try:
1996 os.unlink(q.join(q.status_path))
2022 os.unlink(q.join(q.status_path))
1997 except:
2023 except:
1998 pass
2024 pass
1999 return 0
2025 return 0
2000
2026
2001 def strip(ui, repo, rev, **opts):
2027 def strip(ui, repo, rev, **opts):
2002 """strip a revision and all later revs on the same branch"""
2028 """strip a revision and all later revs on the same branch"""
2003 rev = repo.lookup(rev)
2029 rev = repo.lookup(rev)
2004 backup = 'all'
2030 backup = 'all'
2005 if opts['backup']:
2031 if opts['backup']:
2006 backup = 'strip'
2032 backup = 'strip'
2007 elif opts['nobackup']:
2033 elif opts['nobackup']:
2008 backup = 'none'
2034 backup = 'none'
2009 update = repo.dirstate.parents()[0] != revlog.nullid
2035 update = repo.dirstate.parents()[0] != revlog.nullid
2010 repo.mq.strip(repo, rev, backup=backup, update=update)
2036 repo.mq.strip(repo, rev, backup=backup, update=update)
2011 return 0
2037 return 0
2012
2038
2013 def select(ui, repo, *args, **opts):
2039 def select(ui, repo, *args, **opts):
2014 '''set or print guarded patches to push
2040 '''set or print guarded patches to push
2015
2041
2016 Use the qguard command to set or print guards on patch, then use
2042 Use the qguard command to set or print guards on patch, then use
2017 qselect to tell mq which guards to use. A patch will be pushed if it
2043 qselect to tell mq which guards to use. A patch will be pushed if it
2018 has no guards or any positive guards match the currently selected guard,
2044 has no guards or any positive guards match the currently selected guard,
2019 but will not be pushed if any negative guards match the current guard.
2045 but will not be pushed if any negative guards match the current guard.
2020 For example:
2046 For example:
2021
2047
2022 qguard foo.patch -stable (negative guard)
2048 qguard foo.patch -stable (negative guard)
2023 qguard bar.patch +stable (positive guard)
2049 qguard bar.patch +stable (positive guard)
2024 qselect stable
2050 qselect stable
2025
2051
2026 This activates the "stable" guard. mq will skip foo.patch (because
2052 This activates the "stable" guard. mq will skip foo.patch (because
2027 it has a negative match) but push bar.patch (because it
2053 it has a negative match) but push bar.patch (because it
2028 has a positive match).
2054 has a positive match).
2029
2055
2030 With no arguments, prints the currently active guards.
2056 With no arguments, prints the currently active guards.
2031 With one argument, sets the active guard.
2057 With one argument, sets the active guard.
2032
2058
2033 Use -n/--none to deactivate guards (no other arguments needed).
2059 Use -n/--none to deactivate guards (no other arguments needed).
2034 When no guards are active, patches with positive guards are skipped
2060 When no guards are active, patches with positive guards are skipped
2035 and patches with negative guards are pushed.
2061 and patches with negative guards are pushed.
2036
2062
2037 qselect can change the guards on applied patches. It does not pop
2063 qselect can change the guards on applied patches. It does not pop
2038 guarded patches by default. Use --pop to pop back to the last applied
2064 guarded patches by default. Use --pop to pop back to the last applied
2039 patch that is not guarded. Use --reapply (which implies --pop) to push
2065 patch that is not guarded. Use --reapply (which implies --pop) to push
2040 back to the current patch afterwards, but skip guarded patches.
2066 back to the current patch afterwards, but skip guarded patches.
2041
2067
2042 Use -s/--series to print a list of all guards in the series file (no
2068 Use -s/--series to print a list of all guards in the series file (no
2043 other arguments needed). Use -v for more information.'''
2069 other arguments needed). Use -v for more information.'''
2044
2070
2045 q = repo.mq
2071 q = repo.mq
2046 guards = q.active()
2072 guards = q.active()
2047 if args or opts['none']:
2073 if args or opts['none']:
2048 old_unapplied = q.unapplied(repo)
2074 old_unapplied = q.unapplied(repo)
2049 old_guarded = [i for i in xrange(len(q.applied)) if
2075 old_guarded = [i for i in xrange(len(q.applied)) if
2050 not q.pushable(i)[0]]
2076 not q.pushable(i)[0]]
2051 q.set_active(args)
2077 q.set_active(args)
2052 q.save_dirty()
2078 q.save_dirty()
2053 if not args:
2079 if not args:
2054 ui.status(_('guards deactivated\n'))
2080 ui.status(_('guards deactivated\n'))
2055 if not opts['pop'] and not opts['reapply']:
2081 if not opts['pop'] and not opts['reapply']:
2056 unapplied = q.unapplied(repo)
2082 unapplied = q.unapplied(repo)
2057 guarded = [i for i in xrange(len(q.applied))
2083 guarded = [i for i in xrange(len(q.applied))
2058 if not q.pushable(i)[0]]
2084 if not q.pushable(i)[0]]
2059 if len(unapplied) != len(old_unapplied):
2085 if len(unapplied) != len(old_unapplied):
2060 ui.status(_('number of unguarded, unapplied patches has '
2086 ui.status(_('number of unguarded, unapplied patches has '
2061 'changed from %d to %d\n') %
2087 'changed from %d to %d\n') %
2062 (len(old_unapplied), len(unapplied)))
2088 (len(old_unapplied), len(unapplied)))
2063 if len(guarded) != len(old_guarded):
2089 if len(guarded) != len(old_guarded):
2064 ui.status(_('number of guarded, applied patches has changed '
2090 ui.status(_('number of guarded, applied patches has changed '
2065 'from %d to %d\n') %
2091 'from %d to %d\n') %
2066 (len(old_guarded), len(guarded)))
2092 (len(old_guarded), len(guarded)))
2067 elif opts['series']:
2093 elif opts['series']:
2068 guards = {}
2094 guards = {}
2069 noguards = 0
2095 noguards = 0
2070 for gs in q.series_guards:
2096 for gs in q.series_guards:
2071 if not gs:
2097 if not gs:
2072 noguards += 1
2098 noguards += 1
2073 for g in gs:
2099 for g in gs:
2074 guards.setdefault(g, 0)
2100 guards.setdefault(g, 0)
2075 guards[g] += 1
2101 guards[g] += 1
2076 if ui.verbose:
2102 if ui.verbose:
2077 guards['NONE'] = noguards
2103 guards['NONE'] = noguards
2078 guards = guards.items()
2104 guards = guards.items()
2079 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2105 guards.sort(lambda a, b: cmp(a[0][1:], b[0][1:]))
2080 if guards:
2106 if guards:
2081 ui.note(_('guards in series file:\n'))
2107 ui.note(_('guards in series file:\n'))
2082 for guard, count in guards:
2108 for guard, count in guards:
2083 ui.note('%2d ' % count)
2109 ui.note('%2d ' % count)
2084 ui.write(guard, '\n')
2110 ui.write(guard, '\n')
2085 else:
2111 else:
2086 ui.note(_('no guards in series file\n'))
2112 ui.note(_('no guards in series file\n'))
2087 else:
2113 else:
2088 if guards:
2114 if guards:
2089 ui.note(_('active guards:\n'))
2115 ui.note(_('active guards:\n'))
2090 for g in guards:
2116 for g in guards:
2091 ui.write(g, '\n')
2117 ui.write(g, '\n')
2092 else:
2118 else:
2093 ui.write(_('no active guards\n'))
2119 ui.write(_('no active guards\n'))
2094 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2120 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2095 popped = False
2121 popped = False
2096 if opts['pop'] or opts['reapply']:
2122 if opts['pop'] or opts['reapply']:
2097 for i in xrange(len(q.applied)):
2123 for i in xrange(len(q.applied)):
2098 pushable, reason = q.pushable(i)
2124 pushable, reason = q.pushable(i)
2099 if not pushable:
2125 if not pushable:
2100 ui.status(_('popping guarded patches\n'))
2126 ui.status(_('popping guarded patches\n'))
2101 popped = True
2127 popped = True
2102 if i == 0:
2128 if i == 0:
2103 q.pop(repo, all=True)
2129 q.pop(repo, all=True)
2104 else:
2130 else:
2105 q.pop(repo, i-1)
2131 q.pop(repo, i-1)
2106 break
2132 break
2107 if popped:
2133 if popped:
2108 try:
2134 try:
2109 if reapply:
2135 if reapply:
2110 ui.status(_('reapplying unguarded patches\n'))
2136 ui.status(_('reapplying unguarded patches\n'))
2111 q.push(repo, reapply)
2137 q.push(repo, reapply)
2112 finally:
2138 finally:
2113 q.save_dirty()
2139 q.save_dirty()
2114
2140
2115 def reposetup(ui, repo):
2141 def reposetup(ui, repo):
2116 class mqrepo(repo.__class__):
2142 class mqrepo(repo.__class__):
2117 def abort_if_wdir_patched(self, errmsg, force=False):
2143 def abort_if_wdir_patched(self, errmsg, force=False):
2118 if self.mq.applied and not force:
2144 if self.mq.applied and not force:
2119 parent = revlog.hex(self.dirstate.parents()[0])
2145 parent = revlog.hex(self.dirstate.parents()[0])
2120 if parent in [s.rev for s in self.mq.applied]:
2146 if parent in [s.rev for s in self.mq.applied]:
2121 raise util.Abort(errmsg)
2147 raise util.Abort(errmsg)
2122
2148
2123 def commit(self, *args, **opts):
2149 def commit(self, *args, **opts):
2124 if len(args) >= 6:
2150 if len(args) >= 6:
2125 force = args[5]
2151 force = args[5]
2126 else:
2152 else:
2127 force = opts.get('force')
2153 force = opts.get('force')
2128 self.abort_if_wdir_patched(
2154 self.abort_if_wdir_patched(
2129 _('cannot commit over an applied mq patch'),
2155 _('cannot commit over an applied mq patch'),
2130 force)
2156 force)
2131
2157
2132 return super(mqrepo, self).commit(*args, **opts)
2158 return super(mqrepo, self).commit(*args, **opts)
2133
2159
2134 def push(self, remote, force=False, revs=None):
2160 def push(self, remote, force=False, revs=None):
2135 if self.mq.applied and not force and not revs:
2161 if self.mq.applied and not force and not revs:
2136 raise util.Abort(_('source has mq patches applied'))
2162 raise util.Abort(_('source has mq patches applied'))
2137 return super(mqrepo, self).push(remote, force, revs)
2163 return super(mqrepo, self).push(remote, force, revs)
2138
2164
2139 def tags(self):
2165 def tags(self):
2140 if self.tagscache:
2166 if self.tagscache:
2141 return self.tagscache
2167 return self.tagscache
2142
2168
2143 tagscache = super(mqrepo, self).tags()
2169 tagscache = super(mqrepo, self).tags()
2144
2170
2145 q = self.mq
2171 q = self.mq
2146 if not q.applied:
2172 if not q.applied:
2147 return tagscache
2173 return tagscache
2148
2174
2149 mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied]
2175 mqtags = [(revlog.bin(patch.rev), patch.name) for patch in q.applied]
2176
2177 if mqtags[-1][0] not in self.changelog.nodemap:
2178 self.ui.warn('mq status file refers to unknown node %s\n'
2179 % revlog.short(mqtags[-1][0]))
2180 return tagscache
2181
2150 mqtags.append((mqtags[-1][0], 'qtip'))
2182 mqtags.append((mqtags[-1][0], 'qtip'))
2151 mqtags.append((mqtags[0][0], 'qbase'))
2183 mqtags.append((mqtags[0][0], 'qbase'))
2152 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2184 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2153 for patch in mqtags:
2185 for patch in mqtags:
2154 if patch[1] in tagscache:
2186 if patch[1] in tagscache:
2155 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2187 self.ui.warn('Tag %s overrides mq patch of the same name\n' % patch[1])
2156 else:
2188 else:
2157 tagscache[patch[1]] = patch[0]
2189 tagscache[patch[1]] = patch[0]
2158
2190
2159 return tagscache
2191 return tagscache
2160
2192
2161 def _branchtags(self):
2193 def _branchtags(self, partial, lrev):
2162 q = self.mq
2194 q = self.mq
2163 if not q.applied:
2195 if not q.applied:
2164 return super(mqrepo, self)._branchtags()
2196 return super(mqrepo, self)._branchtags(partial, lrev)
2165
2197
2166 self.branchcache = {} # avoid recursion in changectx
2167 cl = self.changelog
2198 cl = self.changelog
2168 partial, last, lrev = self._readbranchcache()
2199 qbasenode = revlog.bin(q.applied[0].rev)
2200 if qbasenode not in cl.nodemap:
2201 self.ui.warn('mq status file refers to unknown node %s\n'
2202 % revlog.short(qbasenode))
2203 return super(mqrepo, self)._branchtags(partial, lrev)
2169
2204
2170 qbase = cl.rev(revlog.bin(q.applied[0].rev))
2205 qbase = cl.rev(qbasenode)
2171 start = lrev + 1
2206 start = lrev + 1
2172 if start < qbase:
2207 if start < qbase:
2173 # update the cache (excluding the patches) and save it
2208 # update the cache (excluding the patches) and save it
2174 self._updatebranchcache(partial, lrev+1, qbase)
2209 self._updatebranchcache(partial, lrev+1, qbase)
2175 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2210 self._writebranchcache(partial, cl.node(qbase-1), qbase-1)
2176 start = qbase
2211 start = qbase
2177 # if start = qbase, the cache is as updated as it should be.
2212 # if start = qbase, the cache is as updated as it should be.
2178 # if start > qbase, the cache includes (part of) the patches.
2213 # if start > qbase, the cache includes (part of) the patches.
2179 # we might as well use it, but we won't save it.
2214 # we might as well use it, but we won't save it.
2180
2215
2181 # update the cache up to the tip
2216 # update the cache up to the tip
2182 self._updatebranchcache(partial, start, cl.count())
2217 self._updatebranchcache(partial, start, cl.count())
2183
2218
2184 return partial
2219 return partial
2185
2220
2186 if repo.local():
2221 if repo.local():
2187 repo.__class__ = mqrepo
2222 repo.__class__ = mqrepo
2188 repo.mq = queue(ui, repo.join(""))
2223 repo.mq = queue(ui, repo.join(""))
2189
2224
2190 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2225 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2191
2226
2192 headeropts = [
2227 headeropts = [
2193 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2228 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2194 ('u', 'user', '', _('add "From: <given user>" to patch')),
2229 ('u', 'user', '', _('add "From: <given user>" to patch')),
2195 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2230 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2196 ('d', 'date', '', _('add "Date: <given date>" to patch'))]
2231 ('d', 'date', '', _('add "Date: <given date>" to patch'))]
2197
2232
2198 cmdtable = {
2233 cmdtable = {
2199 "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
2234 "qapplied": (applied, [] + seriesopts, _('hg qapplied [-s] [PATCH]')),
2200 "qclone":
2235 "qclone":
2201 (clone,
2236 (clone,
2202 [('', 'pull', None, _('use pull protocol to copy metadata')),
2237 [('', 'pull', None, _('use pull protocol to copy metadata')),
2203 ('U', 'noupdate', None, _('do not update the new working directories')),
2238 ('U', 'noupdate', None, _('do not update the new working directories')),
2204 ('', 'uncompressed', None,
2239 ('', 'uncompressed', None,
2205 _('use uncompressed transfer (fast over LAN)')),
2240 _('use uncompressed transfer (fast over LAN)')),
2206 ('p', 'patches', '', _('location of source patch repo')),
2241 ('p', 'patches', '', _('location of source patch repo')),
2207 ] + commands.remoteopts,
2242 ] + commands.remoteopts,
2208 _('hg qclone [OPTION]... SOURCE [DEST]')),
2243 _('hg qclone [OPTION]... SOURCE [DEST]')),
2209 "qcommit|qci":
2244 "qcommit|qci":
2210 (commit,
2245 (commit,
2211 commands.table["^commit|ci"][1],
2246 commands.table["^commit|ci"][1],
2212 _('hg qcommit [OPTION]... [FILE]...')),
2247 _('hg qcommit [OPTION]... [FILE]...')),
2213 "^qdiff":
2248 "^qdiff":
2214 (diff,
2249 (diff,
2215 [('g', 'git', None, _('use git extended diff format')),
2250 [('g', 'git', None, _('use git extended diff format')),
2251 ('U', 'unified', 3, _('number of lines of context to show')),
2216 ] + commands.walkopts,
2252 ] + commands.walkopts,
2217 _('hg qdiff [-I] [-X] [-g] [FILE]...')),
2253 _('hg qdiff [-I] [-X] [-U NUM] [-g] [FILE]...')),
2218 "qdelete|qremove|qrm":
2254 "qdelete|qremove|qrm":
2219 (delete,
2255 (delete,
2220 [('k', 'keep', None, _('keep patch file')),
2256 [('k', 'keep', None, _('keep patch file')),
2221 ('r', 'rev', [], _('stop managing a revision'))],
2257 ('r', 'rev', [], _('stop managing a revision'))],
2222 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2258 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2223 'qfold':
2259 'qfold':
2224 (fold,
2260 (fold,
2225 [('e', 'edit', None, _('edit patch header')),
2261 [('e', 'edit', None, _('edit patch header')),
2226 ('k', 'keep', None, _('keep folded patch files')),
2262 ('k', 'keep', None, _('keep folded patch files')),
2227 ] + commands.commitopts,
2263 ] + commands.commitopts,
2228 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2264 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2229 'qgoto':
2265 'qgoto':
2230 (goto,
2266 (goto,
2231 [('f', 'force', None, _('overwrite any local changes'))],
2267 [('f', 'force', None, _('overwrite any local changes'))],
2232 _('hg qgoto [OPTION]... PATCH')),
2268 _('hg qgoto [OPTION]... PATCH')),
2233 'qguard':
2269 'qguard':
2234 (guard,
2270 (guard,
2235 [('l', 'list', None, _('list all patches and guards')),
2271 [('l', 'list', None, _('list all patches and guards')),
2236 ('n', 'none', None, _('drop all guards'))],
2272 ('n', 'none', None, _('drop all guards'))],
2237 _('hg qguard [-l] [-n] [PATCH] [+GUARD]... [-GUARD]...')),
2273 _('hg qguard [-l] [-n] [PATCH] [+GUARD]... [-GUARD]...')),
2238 'qheader': (header, [], _('hg qheader [PATCH]')),
2274 'qheader': (header, [], _('hg qheader [PATCH]')),
2239 "^qimport":
2275 "^qimport":
2240 (qimport,
2276 (qimport,
2241 [('e', 'existing', None, 'import file in patch dir'),
2277 [('e', 'existing', None, 'import file in patch dir'),
2242 ('n', 'name', '', 'patch file name'),
2278 ('n', 'name', '', 'patch file name'),
2243 ('f', 'force', None, 'overwrite existing files'),
2279 ('f', 'force', None, 'overwrite existing files'),
2244 ('r', 'rev', [], 'place existing revisions under mq control'),
2280 ('r', 'rev', [], 'place existing revisions under mq control'),
2245 ('g', 'git', None, _('use git extended diff format'))],
2281 ('g', 'git', None, _('use git extended diff format'))],
2246 _('hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...')),
2282 _('hg qimport [-e] [-n NAME] [-f] [-g] [-r REV]... FILE...')),
2247 "^qinit":
2283 "^qinit":
2248 (init,
2284 (init,
2249 [('c', 'create-repo', None, 'create queue repository')],
2285 [('c', 'create-repo', None, 'create queue repository')],
2250 _('hg qinit [-c]')),
2286 _('hg qinit [-c]')),
2251 "qnew":
2287 "qnew":
2252 (new,
2288 (new,
2253 [('e', 'edit', None, _('edit commit message')),
2289 [('e', 'edit', None, _('edit commit message')),
2254 ('f', 'force', None, _('import uncommitted changes into patch')),
2290 ('f', 'force', None, _('import uncommitted changes into patch')),
2255 ('g', 'git', None, _('use git extended diff format')),
2291 ('g', 'git', None, _('use git extended diff format')),
2256 ] + commands.walkopts + commands.commitopts + headeropts,
2292 ] + commands.walkopts + commands.commitopts + headeropts,
2257 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2293 _('hg qnew [-e] [-m TEXT] [-l FILE] [-f] PATCH [FILE]...')),
2258 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2294 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2259 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2295 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2260 "^qpop":
2296 "^qpop":
2261 (pop,
2297 (pop,
2262 [('a', 'all', None, _('pop all patches')),
2298 [('a', 'all', None, _('pop all patches')),
2263 ('n', 'name', '', _('queue name to pop')),
2299 ('n', 'name', '', _('queue name to pop')),
2264 ('f', 'force', None, _('forget any local changes'))],
2300 ('f', 'force', None, _('forget any local changes'))],
2265 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2301 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2266 "^qpush":
2302 "^qpush":
2267 (push,
2303 (push,
2268 [('f', 'force', None, _('apply if the patch has rejects')),
2304 [('f', 'force', None, _('apply if the patch has rejects')),
2269 ('l', 'list', None, _('list patch name in commit text')),
2305 ('l', 'list', None, _('list patch name in commit text')),
2270 ('a', 'all', None, _('apply all patches')),
2306 ('a', 'all', None, _('apply all patches')),
2271 ('m', 'merge', None, _('merge from another queue')),
2307 ('m', 'merge', None, _('merge from another queue')),
2272 ('n', 'name', '', _('merge queue name'))],
2308 ('n', 'name', '', _('merge queue name'))],
2273 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2309 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [PATCH | INDEX]')),
2274 "^qrefresh":
2310 "^qrefresh":
2275 (refresh,
2311 (refresh,
2276 [('e', 'edit', None, _('edit commit message')),
2312 [('e', 'edit', None, _('edit commit message')),
2277 ('g', 'git', None, _('use git extended diff format')),
2313 ('g', 'git', None, _('use git extended diff format')),
2278 ('s', 'short', None, _('refresh only files already in the patch')),
2314 ('s', 'short', None, _('refresh only files already in the patch')),
2279 ] + commands.walkopts + commands.commitopts + headeropts,
2315 ] + commands.walkopts + commands.commitopts + headeropts,
2280 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2316 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2281 'qrename|qmv':
2317 'qrename|qmv':
2282 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2318 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2283 "qrestore":
2319 "qrestore":
2284 (restore,
2320 (restore,
2285 [('d', 'delete', None, _('delete save entry')),
2321 [('d', 'delete', None, _('delete save entry')),
2286 ('u', 'update', None, _('update queue working dir'))],
2322 ('u', 'update', None, _('update queue working dir'))],
2287 _('hg qrestore [-d] [-u] REV')),
2323 _('hg qrestore [-d] [-u] REV')),
2288 "qsave":
2324 "qsave":
2289 (save,
2325 (save,
2290 [('c', 'copy', None, _('copy patch directory')),
2326 [('c', 'copy', None, _('copy patch directory')),
2291 ('n', 'name', '', _('copy directory name')),
2327 ('n', 'name', '', _('copy directory name')),
2292 ('e', 'empty', None, _('clear queue status file')),
2328 ('e', 'empty', None, _('clear queue status file')),
2293 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2329 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2294 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2330 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2295 "qselect":
2331 "qselect":
2296 (select,
2332 (select,
2297 [('n', 'none', None, _('disable all guards')),
2333 [('n', 'none', None, _('disable all guards')),
2298 ('s', 'series', None, _('list all guards in series file')),
2334 ('s', 'series', None, _('list all guards in series file')),
2299 ('', 'pop', None, _('pop to before first guarded applied patch')),
2335 ('', 'pop', None, _('pop to before first guarded applied patch')),
2300 ('', 'reapply', None, _('pop, then reapply patches'))],
2336 ('', 'reapply', None, _('pop, then reapply patches'))],
2301 _('hg qselect [OPTION]... [GUARD]...')),
2337 _('hg qselect [OPTION]... [GUARD]...')),
2302 "qseries":
2338 "qseries":
2303 (series,
2339 (series,
2304 [('m', 'missing', None, _('print patches not in series')),
2340 [('m', 'missing', None, _('print patches not in series')),
2305 ] + seriesopts,
2341 ] + seriesopts,
2306 _('hg qseries [-ms]')),
2342 _('hg qseries [-ms]')),
2307 "^strip":
2343 "^strip":
2308 (strip,
2344 (strip,
2309 [('f', 'force', None, _('force multi-head removal')),
2345 [('f', 'force', None, _('force multi-head removal')),
2310 ('b', 'backup', None, _('bundle unrelated changesets')),
2346 ('b', 'backup', None, _('bundle unrelated changesets')),
2311 ('n', 'nobackup', None, _('no backups'))],
2347 ('n', 'nobackup', None, _('no backups'))],
2312 _('hg strip [-f] [-b] [-n] REV')),
2348 _('hg strip [-f] [-b] [-n] REV')),
2313 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2349 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2314 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2350 "qunapplied": (unapplied, [] + seriesopts, _('hg qunapplied [-s] [PATCH]')),
2315 }
2351 }
@@ -1,28 +1,28 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # An example CGI script to use hgweb, edit as necessary
3 # An example CGI script to use hgweb, edit as necessary
4
4
5 # adjust python path if not a system-wide install:
5 # adjust python path if not a system-wide install:
6 #import sys
6 #import sys
7 #sys.path.insert(0, "/path/to/python/lib")
7 #sys.path.insert(0, "/path/to/python/lib")
8
8
9 # enable importing on demand to reduce startup time
9 # enable importing on demand to reduce startup time
10 from mercurial import demandimport; demandimport.enable()
10 from mercurial import demandimport; demandimport.enable()
11
11
12 # send python tracebacks to the browser if an error occurs:
12 # Uncomment to send python tracebacks to the browser if an error occurs:
13 import cgitb
13 #import cgitb
14 cgitb.enable()
14 #cgitb.enable()
15
15
16 # If you'd like to serve pages with UTF-8 instead of your default
16 # If you'd like to serve pages with UTF-8 instead of your default
17 # locale charset, you can do so by uncommenting the following lines.
17 # locale charset, you can do so by uncommenting the following lines.
18 # Note that this will cause your .hgrc files to be interpreted in
18 # Note that this will cause your .hgrc files to be interpreted in
19 # UTF-8 and all your repo files to be displayed using UTF-8.
19 # UTF-8 and all your repo files to be displayed using UTF-8.
20 #
20 #
21 #import os
21 #import os
22 #os.environ["HGENCODING"] = "UTF-8"
22 #os.environ["HGENCODING"] = "UTF-8"
23
23
24 from mercurial.hgweb.hgweb_mod import hgweb
24 from mercurial.hgweb.hgweb_mod import hgweb
25 import mercurial.hgweb.wsgicgi as wsgicgi
25 import mercurial.hgweb.wsgicgi as wsgicgi
26
26
27 application = hgweb("/path/to/repo", "repository name")
27 application = hgweb("/path/to/repo", "repository name")
28 wsgicgi.launch(application)
28 wsgicgi.launch(application)
@@ -1,47 +1,47 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # An example CGI script to export multiple hgweb repos, edit as necessary
3 # An example CGI script to export multiple hgweb repos, edit as necessary
4
4
5 # adjust python path if not a system-wide install:
5 # adjust python path if not a system-wide install:
6 #import sys
6 #import sys
7 #sys.path.insert(0, "/path/to/python/lib")
7 #sys.path.insert(0, "/path/to/python/lib")
8
8
9 # enable importing on demand to reduce startup time
9 # enable importing on demand to reduce startup time
10 from mercurial import demandimport; demandimport.enable()
10 from mercurial import demandimport; demandimport.enable()
11
11
12 # send python tracebacks to the browser if an error occurs:
12 # Uncomment to send python tracebacks to the browser if an error occurs:
13 import cgitb
13 #import cgitb
14 cgitb.enable()
14 #cgitb.enable()
15
15
16 # If you'd like to serve pages with UTF-8 instead of your default
16 # If you'd like to serve pages with UTF-8 instead of your default
17 # locale charset, you can do so by uncommenting the following lines.
17 # locale charset, you can do so by uncommenting the following lines.
18 # Note that this will cause your .hgrc files to be interpreted in
18 # Note that this will cause your .hgrc files to be interpreted in
19 # UTF-8 and all your repo files to be displayed using UTF-8.
19 # UTF-8 and all your repo files to be displayed using UTF-8.
20 #
20 #
21 #import os
21 #import os
22 #os.environ["HGENCODING"] = "UTF-8"
22 #os.environ["HGENCODING"] = "UTF-8"
23
23
24 from mercurial.hgweb.hgwebdir_mod import hgwebdir
24 from mercurial.hgweb.hgwebdir_mod import hgwebdir
25 import mercurial.hgweb.wsgicgi as wsgicgi
25 import mercurial.hgweb.wsgicgi as wsgicgi
26
26
27 # The config file looks like this. You can have paths to individual
27 # The config file looks like this. You can have paths to individual
28 # repos, collections of repos in a directory tree, or both.
28 # repos, collections of repos in a directory tree, or both.
29 #
29 #
30 # [paths]
30 # [paths]
31 # virtual/path = /real/path
31 # virtual/path = /real/path
32 # virtual/path = /real/path
32 # virtual/path = /real/path
33 #
33 #
34 # [collections]
34 # [collections]
35 # /prefix/to/strip/off = /root/of/tree/full/of/repos
35 # /prefix/to/strip/off = /root/of/tree/full/of/repos
36 #
36 #
37 # collections example: say directory tree /foo contains repos /foo/bar,
37 # collections example: say directory tree /foo contains repos /foo/bar,
38 # /foo/quux/baz. Give this config section:
38 # /foo/quux/baz. Give this config section:
39 # [collections]
39 # [collections]
40 # /foo = /foo
40 # /foo = /foo
41 # Then repos will list as bar and quux/baz.
41 # Then repos will list as bar and quux/baz.
42 #
42 #
43 # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
43 # Alternatively you can pass a list of ('virtual/path', '/real/path') tuples
44 # or use a dictionary with entries like 'virtual/path': '/real/path'
44 # or use a dictionary with entries like 'virtual/path': '/real/path'
45
45
46 application = hgwebdir('hgweb.config')
46 application = hgwebdir('hgweb.config')
47 wsgicgi.launch(application)
47 wsgicgi.launch(application)
@@ -1,220 +1,222 b''
1 # archival.py - revision archival for mercurial
1 # archival.py - revision archival for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of
5 # This software may be used and distributed according to the terms of
6 # the GNU General Public License, incorporated herein by reference.
6 # the GNU General Public License, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 from node import *
9 from node import *
10 import cStringIO, os, stat, tarfile, time, util, zipfile
10 import cStringIO, os, stat, tarfile, time, util, zipfile
11 import zlib, gzip
11 import zlib, gzip
12
12
13 def tidyprefix(dest, prefix, suffixes):
13 def tidyprefix(dest, prefix, suffixes):
14 '''choose prefix to use for names in archive. make sure prefix is
14 '''choose prefix to use for names in archive. make sure prefix is
15 safe for consumers.'''
15 safe for consumers.'''
16
16
17 if prefix:
17 if prefix:
18 prefix = util.normpath(prefix)
18 prefix = util.normpath(prefix)
19 else:
19 else:
20 if not isinstance(dest, str):
20 if not isinstance(dest, str):
21 raise ValueError('dest must be string if no prefix')
21 raise ValueError('dest must be string if no prefix')
22 prefix = os.path.basename(dest)
22 prefix = os.path.basename(dest)
23 lower = prefix.lower()
23 lower = prefix.lower()
24 for sfx in suffixes:
24 for sfx in suffixes:
25 if lower.endswith(sfx):
25 if lower.endswith(sfx):
26 prefix = prefix[:-len(sfx)]
26 prefix = prefix[:-len(sfx)]
27 break
27 break
28 lpfx = os.path.normpath(util.localpath(prefix))
28 lpfx = os.path.normpath(util.localpath(prefix))
29 prefix = util.pconvert(lpfx)
29 prefix = util.pconvert(lpfx)
30 if not prefix.endswith('/'):
30 if not prefix.endswith('/'):
31 prefix += '/'
31 prefix += '/'
32 if prefix.startswith('../') or os.path.isabs(lpfx) or '/../' in prefix:
32 if prefix.startswith('../') or os.path.isabs(lpfx) or '/../' in prefix:
33 raise util.Abort(_('archive prefix contains illegal components'))
33 raise util.Abort(_('archive prefix contains illegal components'))
34 return prefix
34 return prefix
35
35
36 class tarit:
36 class tarit:
37 '''write archive to tar file or stream. can write uncompressed,
37 '''write archive to tar file or stream. can write uncompressed,
38 or compress with gzip or bzip2.'''
38 or compress with gzip or bzip2.'''
39
39
40 class GzipFileWithTime(gzip.GzipFile):
40 class GzipFileWithTime(gzip.GzipFile):
41
41
42 def __init__(self, *args, **kw):
42 def __init__(self, *args, **kw):
43 timestamp = None
43 timestamp = None
44 if 'timestamp' in kw:
44 if 'timestamp' in kw:
45 timestamp = kw.pop('timestamp')
45 timestamp = kw.pop('timestamp')
46 if timestamp == None:
46 if timestamp == None:
47 self.timestamp = time.time()
47 self.timestamp = time.time()
48 else:
48 else:
49 self.timestamp = timestamp
49 self.timestamp = timestamp
50 gzip.GzipFile.__init__(self, *args, **kw)
50 gzip.GzipFile.__init__(self, *args, **kw)
51
51
52 def _write_gzip_header(self):
52 def _write_gzip_header(self):
53 self.fileobj.write('\037\213') # magic header
53 self.fileobj.write('\037\213') # magic header
54 self.fileobj.write('\010') # compression method
54 self.fileobj.write('\010') # compression method
55 fname = self.filename[:-3]
55 fname = self.filename[:-3]
56 flags = 0
56 flags = 0
57 if fname:
57 if fname:
58 flags = gzip.FNAME
58 flags = gzip.FNAME
59 self.fileobj.write(chr(flags))
59 self.fileobj.write(chr(flags))
60 gzip.write32u(self.fileobj, long(self.timestamp))
60 gzip.write32u(self.fileobj, long(self.timestamp))
61 self.fileobj.write('\002')
61 self.fileobj.write('\002')
62 self.fileobj.write('\377')
62 self.fileobj.write('\377')
63 if fname:
63 if fname:
64 self.fileobj.write(fname + '\000')
64 self.fileobj.write(fname + '\000')
65
65
66 def __init__(self, dest, prefix, mtime, kind=''):
66 def __init__(self, dest, prefix, mtime, kind=''):
67 self.prefix = tidyprefix(dest, prefix, ['.tar', '.tar.bz2', '.tar.gz',
67 self.prefix = tidyprefix(dest, prefix, ['.tar', '.tar.bz2', '.tar.gz',
68 '.tgz', '.tbz2'])
68 '.tgz', '.tbz2'])
69 self.mtime = mtime
69 self.mtime = mtime
70
70
71 def taropen(name, mode, fileobj=None):
71 def taropen(name, mode, fileobj=None):
72 if kind == 'gz':
72 if kind == 'gz':
73 mode = mode[0]
73 mode = mode[0]
74 if not fileobj:
74 if not fileobj:
75 fileobj = open(name, mode + 'b')
75 fileobj = open(name, mode + 'b')
76 gzfileobj = self.GzipFileWithTime(name, mode + 'b',
76 gzfileobj = self.GzipFileWithTime(name, mode + 'b',
77 zlib.Z_BEST_COMPRESSION,
77 zlib.Z_BEST_COMPRESSION,
78 fileobj, timestamp=mtime)
78 fileobj, timestamp=mtime)
79 return tarfile.TarFile.taropen(name, mode, gzfileobj)
79 return tarfile.TarFile.taropen(name, mode, gzfileobj)
80 else:
80 else:
81 return tarfile.open(name, mode + kind, fileobj)
81 return tarfile.open(name, mode + kind, fileobj)
82
82
83 if isinstance(dest, str):
83 if isinstance(dest, str):
84 self.z = taropen(dest, mode='w:')
84 self.z = taropen(dest, mode='w:')
85 else:
85 else:
86 # Python 2.5-2.5.1 have a regression that requires a name arg
86 # Python 2.5-2.5.1 have a regression that requires a name arg
87 self.z = taropen(name='', mode='w|', fileobj=dest)
87 self.z = taropen(name='', mode='w|', fileobj=dest)
88
88
89 def addfile(self, name, mode, islink, data):
89 def addfile(self, name, mode, islink, data):
90 i = tarfile.TarInfo(self.prefix + name)
90 i = tarfile.TarInfo(self.prefix + name)
91 i.mtime = self.mtime
91 i.mtime = self.mtime
92 i.size = len(data)
92 i.size = len(data)
93 if islink:
93 if islink:
94 i.type = tarfile.SYMTYPE
94 i.type = tarfile.SYMTYPE
95 i.mode = 0777
95 i.mode = 0777
96 i.linkname = data
96 i.linkname = data
97 data = None
97 data = None
98 else:
98 else:
99 i.mode = mode
99 i.mode = mode
100 data = cStringIO.StringIO(data)
100 data = cStringIO.StringIO(data)
101 self.z.addfile(i, data)
101 self.z.addfile(i, data)
102
102
103 def done(self):
103 def done(self):
104 self.z.close()
104 self.z.close()
105
105
106 class tellable:
106 class tellable:
107 '''provide tell method for zipfile.ZipFile when writing to http
107 '''provide tell method for zipfile.ZipFile when writing to http
108 response file object.'''
108 response file object.'''
109
109
110 def __init__(self, fp):
110 def __init__(self, fp):
111 self.fp = fp
111 self.fp = fp
112 self.offset = 0
112 self.offset = 0
113
113
114 def __getattr__(self, key):
114 def __getattr__(self, key):
115 return getattr(self.fp, key)
115 return getattr(self.fp, key)
116
116
117 def write(self, s):
117 def write(self, s):
118 self.fp.write(s)
118 self.fp.write(s)
119 self.offset += len(s)
119 self.offset += len(s)
120
120
121 def tell(self):
121 def tell(self):
122 return self.offset
122 return self.offset
123
123
124 class zipit:
124 class zipit:
125 '''write archive to zip file or stream. can write uncompressed,
125 '''write archive to zip file or stream. can write uncompressed,
126 or compressed with deflate.'''
126 or compressed with deflate.'''
127
127
128 def __init__(self, dest, prefix, mtime, compress=True):
128 def __init__(self, dest, prefix, mtime, compress=True):
129 self.prefix = tidyprefix(dest, prefix, ('.zip',))
129 self.prefix = tidyprefix(dest, prefix, ('.zip',))
130 if not isinstance(dest, str):
130 if not isinstance(dest, str):
131 try:
131 try:
132 dest.tell()
132 dest.tell()
133 except (AttributeError, IOError):
133 except (AttributeError, IOError):
134 dest = tellable(dest)
134 dest = tellable(dest)
135 self.z = zipfile.ZipFile(dest, 'w',
135 self.z = zipfile.ZipFile(dest, 'w',
136 compress and zipfile.ZIP_DEFLATED or
136 compress and zipfile.ZIP_DEFLATED or
137 zipfile.ZIP_STORED)
137 zipfile.ZIP_STORED)
138 self.date_time = time.gmtime(mtime)[:6]
138 self.date_time = time.gmtime(mtime)[:6]
139
139
140 def addfile(self, name, mode, islink, data):
140 def addfile(self, name, mode, islink, data):
141 i = zipfile.ZipInfo(self.prefix + name, self.date_time)
141 i = zipfile.ZipInfo(self.prefix + name, self.date_time)
142 i.compress_type = self.z.compression
142 i.compress_type = self.z.compression
143 # unzip will not honor unix file modes unless file creator is
143 # unzip will not honor unix file modes unless file creator is
144 # set to unix (id 3).
144 # set to unix (id 3).
145 i.create_system = 3
145 i.create_system = 3
146 ftype = stat.S_IFREG
146 ftype = stat.S_IFREG
147 if islink:
147 if islink:
148 mode = 0777
148 mode = 0777
149 ftype = stat.S_IFLNK
149 ftype = stat.S_IFLNK
150 i.external_attr = (mode | ftype) << 16L
150 i.external_attr = (mode | ftype) << 16L
151 self.z.writestr(i, data)
151 self.z.writestr(i, data)
152
152
153 def done(self):
153 def done(self):
154 self.z.close()
154 self.z.close()
155
155
156 class fileit:
156 class fileit:
157 '''write archive as files in directory.'''
157 '''write archive as files in directory.'''
158
158
159 def __init__(self, name, prefix, mtime):
159 def __init__(self, name, prefix, mtime):
160 if prefix:
160 if prefix:
161 raise util.Abort(_('cannot give prefix when archiving to files'))
161 raise util.Abort(_('cannot give prefix when archiving to files'))
162 self.basedir = name
162 self.basedir = name
163 self.opener = util.opener(self.basedir)
163 self.opener = util.opener(self.basedir)
164
164
165 def addfile(self, name, mode, islink, data):
165 def addfile(self, name, mode, islink, data):
166 if islink:
166 if islink:
167 self.opener.symlink(data, name)
167 self.opener.symlink(data, name)
168 return
168 return
169 f = self.opener(name, "w", atomictemp=True)
169 f = self.opener(name, "w", atomictemp=True)
170 f.write(data)
170 f.write(data)
171 f.rename()
171 f.rename()
172 destfile = os.path.join(self.basedir, name)
172 destfile = os.path.join(self.basedir, name)
173 os.chmod(destfile, mode)
173 os.chmod(destfile, mode)
174
174
175 def done(self):
175 def done(self):
176 pass
176 pass
177
177
178 archivers = {
178 archivers = {
179 'files': fileit,
179 'files': fileit,
180 'tar': tarit,
180 'tar': tarit,
181 'tbz2': lambda name, prefix, mtime: tarit(name, prefix, mtime, 'bz2'),
181 'tbz2': lambda name, prefix, mtime: tarit(name, prefix, mtime, 'bz2'),
182 'tgz': lambda name, prefix, mtime: tarit(name, prefix, mtime, 'gz'),
182 'tgz': lambda name, prefix, mtime: tarit(name, prefix, mtime, 'gz'),
183 'uzip': lambda name, prefix, mtime: zipit(name, prefix, mtime, False),
183 'uzip': lambda name, prefix, mtime: zipit(name, prefix, mtime, False),
184 'zip': zipit,
184 'zip': zipit,
185 }
185 }
186
186
187 def archive(repo, dest, node, kind, decode=True, matchfn=None,
187 def archive(repo, dest, node, kind, decode=True, matchfn=None,
188 prefix=None, mtime=None):
188 prefix=None, mtime=None):
189 '''create archive of repo as it was at node.
189 '''create archive of repo as it was at node.
190
190
191 dest can be name of directory, name of archive file, or file
191 dest can be name of directory, name of archive file, or file
192 object to write archive to.
192 object to write archive to.
193
193
194 kind is type of archive to create.
194 kind is type of archive to create.
195
195
196 decode tells whether to put files through decode filters from
196 decode tells whether to put files through decode filters from
197 hgrc.
197 hgrc.
198
198
199 matchfn is function to filter names of files to write to archive.
199 matchfn is function to filter names of files to write to archive.
200
200
201 prefix is name of path to put before every archive member.'''
201 prefix is name of path to put before every archive member.'''
202
202
203 def write(name, mode, islink, getdata):
203 def write(name, mode, islink, getdata):
204 if matchfn and not matchfn(name): return
204 if matchfn and not matchfn(name): return
205 data = getdata()
205 data = getdata()
206 if decode:
206 if decode:
207 data = repo.wwritedata(name, data)
207 data = repo.wwritedata(name, data)
208 archiver.addfile(name, mode, islink, data)
208 archiver.addfile(name, mode, islink, data)
209
209
210 ctx = repo.changectx(node)
210 ctx = repo.changectx(node)
211 if kind not in archivers:
212 raise util.Abort(_("unknown archive type '%s'" % kind))
211 archiver = archivers[kind](dest, prefix, mtime or ctx.date()[0])
213 archiver = archivers[kind](dest, prefix, mtime or ctx.date()[0])
212 m = ctx.manifest()
214 m = ctx.manifest()
213 items = m.items()
215 items = m.items()
214 items.sort()
216 items.sort()
215 write('.hg_archival.txt', 0644, False,
217 write('.hg_archival.txt', 0644, False,
216 lambda: 'repo: %s\nnode: %s\n' % (hex(repo.changelog.node(0)), hex(node)))
218 lambda: 'repo: %s\nnode: %s\n' % (hex(repo.changelog.node(0)), hex(node)))
217 for filename, filenode in items:
219 for filename, filenode in items:
218 write(filename, m.execf(filename) and 0755 or 0644, m.linkf(filename),
220 write(filename, m.execf(filename) and 0755 or 0644, m.linkf(filename),
219 lambda: repo.file(filename).read(filenode))
221 lambda: repo.file(filename).read(filenode))
220 archiver.done()
222 archiver.done()
@@ -1,280 +1,282 b''
1 """
1 """
2 bundlerepo.py - repository class for viewing uncompressed bundles
2 bundlerepo.py - repository class for viewing uncompressed bundles
3
3
4 This provides a read-only repository interface to bundles as if
4 This provides a read-only repository interface to bundles as if
5 they were part of the actual repository.
5 they were part of the actual repository.
6
6
7 Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
7 Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
8
8
9 This software may be used and distributed according to the terms
9 This software may be used and distributed according to the terms
10 of the GNU General Public License, incorporated herein by reference.
10 of the GNU General Public License, incorporated herein by reference.
11 """
11 """
12
12
13 from node import *
13 from node import *
14 from i18n import _
14 from i18n import _
15 import changegroup, util, os, struct, bz2, tempfile, mdiff
15 import changegroup, util, os, struct, bz2, tempfile, mdiff
16 import localrepo, changelog, manifest, filelog, revlog
16 import localrepo, changelog, manifest, filelog, revlog
17
17
18 class bundlerevlog(revlog.revlog):
18 class bundlerevlog(revlog.revlog):
19 def __init__(self, opener, indexfile, bundlefile,
19 def __init__(self, opener, indexfile, bundlefile,
20 linkmapper=None):
20 linkmapper=None):
21 # How it works:
21 # How it works:
22 # to retrieve a revision, we need to know the offset of
22 # to retrieve a revision, we need to know the offset of
23 # the revision in the bundlefile (an opened file).
23 # the revision in the bundlefile (an opened file).
24 #
24 #
25 # We store this offset in the index (start), to differentiate a
25 # We store this offset in the index (start), to differentiate a
26 # rev in the bundle and from a rev in the revlog, we check
26 # rev in the bundle and from a rev in the revlog, we check
27 # len(index[r]). If the tuple is bigger than 7, it is a bundle
27 # len(index[r]). If the tuple is bigger than 7, it is a bundle
28 # (it is bigger since we store the node to which the delta is)
28 # (it is bigger since we store the node to which the delta is)
29 #
29 #
30 revlog.revlog.__init__(self, opener, indexfile)
30 revlog.revlog.__init__(self, opener, indexfile)
31 self.bundlefile = bundlefile
31 self.bundlefile = bundlefile
32 self.basemap = {}
32 self.basemap = {}
33 def chunkpositer():
33 def chunkpositer():
34 for chunk in changegroup.chunkiter(bundlefile):
34 for chunk in changegroup.chunkiter(bundlefile):
35 pos = bundlefile.tell()
35 pos = bundlefile.tell()
36 yield chunk, pos - len(chunk)
36 yield chunk, pos - len(chunk)
37 n = self.count()
37 n = self.count()
38 prev = None
38 prev = None
39 for chunk, start in chunkpositer():
39 for chunk, start in chunkpositer():
40 size = len(chunk)
40 size = len(chunk)
41 if size < 80:
41 if size < 80:
42 raise util.Abort("invalid changegroup")
42 raise util.Abort("invalid changegroup")
43 start += 80
43 start += 80
44 size -= 80
44 size -= 80
45 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
45 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
46 if node in self.nodemap:
46 if node in self.nodemap:
47 prev = node
47 prev = node
48 continue
48 continue
49 for p in (p1, p2):
49 for p in (p1, p2):
50 if not p in self.nodemap:
50 if not p in self.nodemap:
51 raise revlog.LookupError(hex(p1), _("unknown parent %s") % short(p1))
51 raise revlog.LookupError(hex(p1), _("unknown parent %s") % short(p1))
52 if linkmapper is None:
52 if linkmapper is None:
53 link = n
53 link = n
54 else:
54 else:
55 link = linkmapper(cs)
55 link = linkmapper(cs)
56
56
57 if not prev:
57 if not prev:
58 prev = p1
58 prev = p1
59 # start, size, full unc. size, base (unused), link, p1, p2, node
59 # start, size, full unc. size, base (unused), link, p1, p2, node
60 e = (revlog.offset_type(start, 0), size, -1, -1, link,
60 e = (revlog.offset_type(start, 0), size, -1, -1, link,
61 self.rev(p1), self.rev(p2), node)
61 self.rev(p1), self.rev(p2), node)
62 self.basemap[n] = prev
62 self.basemap[n] = prev
63 self.index.insert(-1, e)
63 self.index.insert(-1, e)
64 self.nodemap[node] = n
64 self.nodemap[node] = n
65 prev = node
65 prev = node
66 n += 1
66 n += 1
67
67
68 def bundle(self, rev):
68 def bundle(self, rev):
69 """is rev from the bundle"""
69 """is rev from the bundle"""
70 if rev < 0:
70 if rev < 0:
71 return False
71 return False
72 return rev in self.basemap
72 return rev in self.basemap
73 def bundlebase(self, rev): return self.basemap[rev]
73 def bundlebase(self, rev): return self.basemap[rev]
74 def chunk(self, rev, df=None, cachelen=4096):
74 def chunk(self, rev, df=None, cachelen=4096):
75 # Warning: in case of bundle, the diff is against bundlebase,
75 # Warning: in case of bundle, the diff is against bundlebase,
76 # not against rev - 1
76 # not against rev - 1
77 # XXX: could use some caching
77 # XXX: could use some caching
78 if not self.bundle(rev):
78 if not self.bundle(rev):
79 return revlog.revlog.chunk(self, rev, df)
79 return revlog.revlog.chunk(self, rev, df)
80 self.bundlefile.seek(self.start(rev))
80 self.bundlefile.seek(self.start(rev))
81 return self.bundlefile.read(self.length(rev))
81 return self.bundlefile.read(self.length(rev))
82
82
83 def revdiff(self, rev1, rev2):
83 def revdiff(self, rev1, rev2):
84 """return or calculate a delta between two revisions"""
84 """return or calculate a delta between two revisions"""
85 if self.bundle(rev1) and self.bundle(rev2):
85 if self.bundle(rev1) and self.bundle(rev2):
86 # hot path for bundle
86 # hot path for bundle
87 revb = self.rev(self.bundlebase(rev2))
87 revb = self.rev(self.bundlebase(rev2))
88 if revb == rev1:
88 if revb == rev1:
89 return self.chunk(rev2)
89 return self.chunk(rev2)
90 elif not self.bundle(rev1) and not self.bundle(rev2):
90 elif not self.bundle(rev1) and not self.bundle(rev2):
91 return revlog.revlog.revdiff(self, rev1, rev2)
91 return revlog.revlog.revdiff(self, rev1, rev2)
92
92
93 return mdiff.textdiff(self.revision(self.node(rev1)),
93 return mdiff.textdiff(self.revision(self.node(rev1)),
94 self.revision(self.node(rev2)))
94 self.revision(self.node(rev2)))
95
95
96 def revision(self, node):
96 def revision(self, node):
97 """return an uncompressed revision of a given"""
97 """return an uncompressed revision of a given"""
98 if node == nullid: return ""
98 if node == nullid: return ""
99
99
100 text = None
100 text = None
101 chain = []
101 chain = []
102 iter_node = node
102 iter_node = node
103 rev = self.rev(iter_node)
103 rev = self.rev(iter_node)
104 # reconstruct the revision if it is from a changegroup
104 # reconstruct the revision if it is from a changegroup
105 while self.bundle(rev):
105 while self.bundle(rev):
106 if self._cache and self._cache[0] == iter_node:
106 if self._cache and self._cache[0] == iter_node:
107 text = self._cache[2]
107 text = self._cache[2]
108 break
108 break
109 chain.append(rev)
109 chain.append(rev)
110 iter_node = self.bundlebase(rev)
110 iter_node = self.bundlebase(rev)
111 rev = self.rev(iter_node)
111 rev = self.rev(iter_node)
112 if text is None:
112 if text is None:
113 text = revlog.revlog.revision(self, iter_node)
113 text = revlog.revlog.revision(self, iter_node)
114
114
115 while chain:
115 while chain:
116 delta = self.chunk(chain.pop())
116 delta = self.chunk(chain.pop())
117 text = mdiff.patches(text, [delta])
117 text = mdiff.patches(text, [delta])
118
118
119 p1, p2 = self.parents(node)
119 p1, p2 = self.parents(node)
120 if node != revlog.hash(text, p1, p2):
120 if node != revlog.hash(text, p1, p2):
121 raise revlog.RevlogError(_("integrity check failed on %s:%d")
121 raise revlog.RevlogError(_("integrity check failed on %s:%d")
122 % (self.datafile, self.rev(node)))
122 % (self.datafile, self.rev(node)))
123
123
124 self._cache = (node, self.rev(node), text)
124 self._cache = (node, self.rev(node), text)
125 return text
125 return text
126
126
127 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
127 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
128 raise NotImplementedError
128 raise NotImplementedError
129 def addgroup(self, revs, linkmapper, transaction, unique=0):
129 def addgroup(self, revs, linkmapper, transaction, unique=0):
130 raise NotImplementedError
130 raise NotImplementedError
131 def strip(self, rev, minlink):
131 def strip(self, rev, minlink):
132 raise NotImplementedError
132 raise NotImplementedError
133 def checksize(self):
133 def checksize(self):
134 raise NotImplementedError
134 raise NotImplementedError
135
135
136 class bundlechangelog(bundlerevlog, changelog.changelog):
136 class bundlechangelog(bundlerevlog, changelog.changelog):
137 def __init__(self, opener, bundlefile):
137 def __init__(self, opener, bundlefile):
138 changelog.changelog.__init__(self, opener)
138 changelog.changelog.__init__(self, opener)
139 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile)
139 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile)
140
140
141 class bundlemanifest(bundlerevlog, manifest.manifest):
141 class bundlemanifest(bundlerevlog, manifest.manifest):
142 def __init__(self, opener, bundlefile, linkmapper):
142 def __init__(self, opener, bundlefile, linkmapper):
143 manifest.manifest.__init__(self, opener)
143 manifest.manifest.__init__(self, opener)
144 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
144 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
145 linkmapper)
145 linkmapper)
146
146
147 class bundlefilelog(bundlerevlog, filelog.filelog):
147 class bundlefilelog(bundlerevlog, filelog.filelog):
148 def __init__(self, opener, path, bundlefile, linkmapper):
148 def __init__(self, opener, path, bundlefile, linkmapper):
149 filelog.filelog.__init__(self, opener, path)
149 filelog.filelog.__init__(self, opener, path)
150 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
150 bundlerevlog.__init__(self, opener, self.indexfile, bundlefile,
151 linkmapper)
151 linkmapper)
152
152
153 class bundlerepository(localrepo.localrepository):
153 class bundlerepository(localrepo.localrepository):
154 def __init__(self, ui, path, bundlename):
154 def __init__(self, ui, path, bundlename):
155 localrepo.localrepository.__init__(self, ui, path)
155 localrepo.localrepository.__init__(self, ui, path)
156
156
157 self._url = 'bundle:' + bundlename
157 if path:
158 if path: self._url += '+' + path
158 self._url = 'bundle:' + path + '+' + bundlename
159 else:
160 self._url = 'bundle:' + bundlename
159
161
160 self.tempfile = None
162 self.tempfile = None
161 self.bundlefile = open(bundlename, "rb")
163 self.bundlefile = open(bundlename, "rb")
162 header = self.bundlefile.read(6)
164 header = self.bundlefile.read(6)
163 if not header.startswith("HG"):
165 if not header.startswith("HG"):
164 raise util.Abort(_("%s: not a Mercurial bundle file") % bundlename)
166 raise util.Abort(_("%s: not a Mercurial bundle file") % bundlename)
165 elif not header.startswith("HG10"):
167 elif not header.startswith("HG10"):
166 raise util.Abort(_("%s: unknown bundle version") % bundlename)
168 raise util.Abort(_("%s: unknown bundle version") % bundlename)
167 elif header == "HG10BZ":
169 elif header == "HG10BZ":
168 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
170 fdtemp, temp = tempfile.mkstemp(prefix="hg-bundle-",
169 suffix=".hg10un", dir=self.path)
171 suffix=".hg10un", dir=self.path)
170 self.tempfile = temp
172 self.tempfile = temp
171 fptemp = os.fdopen(fdtemp, 'wb')
173 fptemp = os.fdopen(fdtemp, 'wb')
172 def generator(f):
174 def generator(f):
173 zd = bz2.BZ2Decompressor()
175 zd = bz2.BZ2Decompressor()
174 zd.decompress("BZ")
176 zd.decompress("BZ")
175 for chunk in f:
177 for chunk in f:
176 yield zd.decompress(chunk)
178 yield zd.decompress(chunk)
177 gen = generator(util.filechunkiter(self.bundlefile, 4096))
179 gen = generator(util.filechunkiter(self.bundlefile, 4096))
178
180
179 try:
181 try:
180 fptemp.write("HG10UN")
182 fptemp.write("HG10UN")
181 for chunk in gen:
183 for chunk in gen:
182 fptemp.write(chunk)
184 fptemp.write(chunk)
183 finally:
185 finally:
184 fptemp.close()
186 fptemp.close()
185 self.bundlefile.close()
187 self.bundlefile.close()
186
188
187 self.bundlefile = open(self.tempfile, "rb")
189 self.bundlefile = open(self.tempfile, "rb")
188 # seek right after the header
190 # seek right after the header
189 self.bundlefile.seek(6)
191 self.bundlefile.seek(6)
190 elif header == "HG10UN":
192 elif header == "HG10UN":
191 # nothing to do
193 # nothing to do
192 pass
194 pass
193 else:
195 else:
194 raise util.Abort(_("%s: unknown bundle compression type")
196 raise util.Abort(_("%s: unknown bundle compression type")
195 % bundlename)
197 % bundlename)
196 # dict with the mapping 'filename' -> position in the bundle
198 # dict with the mapping 'filename' -> position in the bundle
197 self.bundlefilespos = {}
199 self.bundlefilespos = {}
198
200
199 def __getattr__(self, name):
201 def __getattr__(self, name):
200 if name == 'changelog':
202 if name == 'changelog':
201 self.changelog = bundlechangelog(self.sopener, self.bundlefile)
203 self.changelog = bundlechangelog(self.sopener, self.bundlefile)
202 self.manstart = self.bundlefile.tell()
204 self.manstart = self.bundlefile.tell()
203 return self.changelog
205 return self.changelog
204 if name == 'manifest':
206 if name == 'manifest':
205 self.bundlefile.seek(self.manstart)
207 self.bundlefile.seek(self.manstart)
206 self.manifest = bundlemanifest(self.sopener, self.bundlefile,
208 self.manifest = bundlemanifest(self.sopener, self.bundlefile,
207 self.changelog.rev)
209 self.changelog.rev)
208 self.filestart = self.bundlefile.tell()
210 self.filestart = self.bundlefile.tell()
209 return self.manifest
211 return self.manifest
210 if name == 'manstart':
212 if name == 'manstart':
211 self.changelog
213 self.changelog
212 return self.manstart
214 return self.manstart
213 if name == 'filestart':
215 if name == 'filestart':
214 self.manifest
216 self.manifest
215 return self.filestart
217 return self.filestart
216 return localrepo.localrepository.__getattr__(self, name)
218 return localrepo.localrepository.__getattr__(self, name)
217
219
218 def url(self):
220 def url(self):
219 return self._url
221 return self._url
220
222
221 def dev(self):
223 def dev(self):
222 return -1
224 return -1
223
225
224 def file(self, f):
226 def file(self, f):
225 if not self.bundlefilespos:
227 if not self.bundlefilespos:
226 self.bundlefile.seek(self.filestart)
228 self.bundlefile.seek(self.filestart)
227 while 1:
229 while 1:
228 chunk = changegroup.getchunk(self.bundlefile)
230 chunk = changegroup.getchunk(self.bundlefile)
229 if not chunk:
231 if not chunk:
230 break
232 break
231 self.bundlefilespos[chunk] = self.bundlefile.tell()
233 self.bundlefilespos[chunk] = self.bundlefile.tell()
232 for c in changegroup.chunkiter(self.bundlefile):
234 for c in changegroup.chunkiter(self.bundlefile):
233 pass
235 pass
234
236
235 if f[0] == '/':
237 if f[0] == '/':
236 f = f[1:]
238 f = f[1:]
237 if f in self.bundlefilespos:
239 if f in self.bundlefilespos:
238 self.bundlefile.seek(self.bundlefilespos[f])
240 self.bundlefile.seek(self.bundlefilespos[f])
239 return bundlefilelog(self.sopener, f, self.bundlefile,
241 return bundlefilelog(self.sopener, f, self.bundlefile,
240 self.changelog.rev)
242 self.changelog.rev)
241 else:
243 else:
242 return filelog.filelog(self.sopener, f)
244 return filelog.filelog(self.sopener, f)
243
245
244 def close(self):
246 def close(self):
245 """Close assigned bundle file immediately."""
247 """Close assigned bundle file immediately."""
246 self.bundlefile.close()
248 self.bundlefile.close()
247
249
248 def __del__(self):
250 def __del__(self):
249 bundlefile = getattr(self, 'bundlefile', None)
251 bundlefile = getattr(self, 'bundlefile', None)
250 if bundlefile and not bundlefile.closed:
252 if bundlefile and not bundlefile.closed:
251 bundlefile.close()
253 bundlefile.close()
252 tempfile = getattr(self, 'tempfile', None)
254 tempfile = getattr(self, 'tempfile', None)
253 if tempfile is not None:
255 if tempfile is not None:
254 os.unlink(tempfile)
256 os.unlink(tempfile)
255
257
256 def instance(ui, path, create):
258 def instance(ui, path, create):
257 if create:
259 if create:
258 raise util.Abort(_('cannot create new bundle repository'))
260 raise util.Abort(_('cannot create new bundle repository'))
259 parentpath = ui.config("bundle", "mainreporoot", "")
261 parentpath = ui.config("bundle", "mainreporoot", "")
260 if parentpath:
262 if parentpath:
261 # Try to make the full path relative so we get a nice, short URL.
263 # Try to make the full path relative so we get a nice, short URL.
262 # In particular, we don't want temp dir names in test outputs.
264 # In particular, we don't want temp dir names in test outputs.
263 cwd = os.getcwd()
265 cwd = os.getcwd()
264 if parentpath == cwd:
266 if parentpath == cwd:
265 parentpath = ''
267 parentpath = ''
266 else:
268 else:
267 cwd = os.path.join(cwd,'')
269 cwd = os.path.join(cwd,'')
268 if parentpath.startswith(cwd):
270 if parentpath.startswith(cwd):
269 parentpath = parentpath[len(cwd):]
271 parentpath = parentpath[len(cwd):]
270 path = util.drop_scheme('file', path)
272 path = util.drop_scheme('file', path)
271 if path.startswith('bundle:'):
273 if path.startswith('bundle:'):
272 path = util.drop_scheme('bundle', path)
274 path = util.drop_scheme('bundle', path)
273 s = path.split("+", 1)
275 s = path.split("+", 1)
274 if len(s) == 1:
276 if len(s) == 1:
275 repopath, bundlename = parentpath, s[0]
277 repopath, bundlename = parentpath, s[0]
276 else:
278 else:
277 repopath, bundlename = s
279 repopath, bundlename = s
278 else:
280 else:
279 repopath, bundlename = parentpath, path
281 repopath, bundlename = parentpath, path
280 return bundlerepository(ui, repopath, bundlename)
282 return bundlerepository(ui, repopath, bundlename)
@@ -1,1159 +1,1163 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from node import *
8 from node import *
9 from i18n import _
9 from i18n import _
10 import os, sys, bisect, stat
10 import os, sys, bisect, stat
11 import mdiff, bdiff, util, templater, templatefilters, patch, errno
11 import mdiff, bdiff, util, templater, templatefilters, patch, errno
12
12
13 revrangesep = ':'
13 revrangesep = ':'
14
14
15 class UnknownCommand(Exception):
15 class UnknownCommand(Exception):
16 """Exception raised if command is not in the command table."""
16 """Exception raised if command is not in the command table."""
17 class AmbiguousCommand(Exception):
17 class AmbiguousCommand(Exception):
18 """Exception raised if command shortcut matches more than one command."""
18 """Exception raised if command shortcut matches more than one command."""
19
19
20 def findpossible(ui, cmd, table):
20 def findpossible(ui, cmd, table):
21 """
21 """
22 Return cmd -> (aliases, command table entry)
22 Return cmd -> (aliases, command table entry)
23 for each matching command.
23 for each matching command.
24 Return debug commands (or their aliases) only if no normal command matches.
24 Return debug commands (or their aliases) only if no normal command matches.
25 """
25 """
26 choice = {}
26 choice = {}
27 debugchoice = {}
27 debugchoice = {}
28 for e in table.keys():
28 for e in table.keys():
29 aliases = e.lstrip("^").split("|")
29 aliases = e.lstrip("^").split("|")
30 found = None
30 found = None
31 if cmd in aliases:
31 if cmd in aliases:
32 found = cmd
32 found = cmd
33 elif not ui.config("ui", "strict"):
33 elif not ui.config("ui", "strict"):
34 for a in aliases:
34 for a in aliases:
35 if a.startswith(cmd):
35 if a.startswith(cmd):
36 found = a
36 found = a
37 break
37 break
38 if found is not None:
38 if found is not None:
39 if aliases[0].startswith("debug") or found.startswith("debug"):
39 if aliases[0].startswith("debug") or found.startswith("debug"):
40 debugchoice[found] = (aliases, table[e])
40 debugchoice[found] = (aliases, table[e])
41 else:
41 else:
42 choice[found] = (aliases, table[e])
42 choice[found] = (aliases, table[e])
43
43
44 if not choice and debugchoice:
44 if not choice and debugchoice:
45 choice = debugchoice
45 choice = debugchoice
46
46
47 return choice
47 return choice
48
48
49 def findcmd(ui, cmd, table):
49 def findcmd(ui, cmd, table):
50 """Return (aliases, command table entry) for command string."""
50 """Return (aliases, command table entry) for command string."""
51 choice = findpossible(ui, cmd, table)
51 choice = findpossible(ui, cmd, table)
52
52
53 if cmd in choice:
53 if cmd in choice:
54 return choice[cmd]
54 return choice[cmd]
55
55
56 if len(choice) > 1:
56 if len(choice) > 1:
57 clist = choice.keys()
57 clist = choice.keys()
58 clist.sort()
58 clist.sort()
59 raise AmbiguousCommand(cmd, clist)
59 raise AmbiguousCommand(cmd, clist)
60
60
61 if choice:
61 if choice:
62 return choice.values()[0]
62 return choice.values()[0]
63
63
64 raise UnknownCommand(cmd)
64 raise UnknownCommand(cmd)
65
65
66 def bail_if_changed(repo):
66 def bail_if_changed(repo):
67 if repo.dirstate.parents()[1] != nullid:
67 if repo.dirstate.parents()[1] != nullid:
68 raise util.Abort(_('outstanding uncommitted merge'))
68 raise util.Abort(_('outstanding uncommitted merge'))
69 modified, added, removed, deleted = repo.status()[:4]
69 modified, added, removed, deleted = repo.status()[:4]
70 if modified or added or removed or deleted:
70 if modified or added or removed or deleted:
71 raise util.Abort(_("outstanding uncommitted changes"))
71 raise util.Abort(_("outstanding uncommitted changes"))
72
72
73 def logmessage(opts):
73 def logmessage(opts):
74 """ get the log message according to -m and -l option """
74 """ get the log message according to -m and -l option """
75 message = opts['message']
75 message = opts['message']
76 logfile = opts['logfile']
76 logfile = opts['logfile']
77
77
78 if message and logfile:
78 if message and logfile:
79 raise util.Abort(_('options --message and --logfile are mutually '
79 raise util.Abort(_('options --message and --logfile are mutually '
80 'exclusive'))
80 'exclusive'))
81 if not message and logfile:
81 if not message and logfile:
82 try:
82 try:
83 if logfile == '-':
83 if logfile == '-':
84 message = sys.stdin.read()
84 message = sys.stdin.read()
85 else:
85 else:
86 message = open(logfile).read()
86 message = open(logfile).read()
87 except IOError, inst:
87 except IOError, inst:
88 raise util.Abort(_("can't read commit message '%s': %s") %
88 raise util.Abort(_("can't read commit message '%s': %s") %
89 (logfile, inst.strerror))
89 (logfile, inst.strerror))
90 return message
90 return message
91
91
92 def setremoteconfig(ui, opts):
92 def setremoteconfig(ui, opts):
93 "copy remote options to ui tree"
93 "copy remote options to ui tree"
94 if opts.get('ssh'):
94 if opts.get('ssh'):
95 ui.setconfig("ui", "ssh", opts['ssh'])
95 ui.setconfig("ui", "ssh", opts['ssh'])
96 if opts.get('remotecmd'):
96 if opts.get('remotecmd'):
97 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
97 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
98
98
99 def revpair(repo, revs):
99 def revpair(repo, revs):
100 '''return pair of nodes, given list of revisions. second item can
100 '''return pair of nodes, given list of revisions. second item can
101 be None, meaning use working dir.'''
101 be None, meaning use working dir.'''
102
102
103 def revfix(repo, val, defval):
103 def revfix(repo, val, defval):
104 if not val and val != 0 and defval is not None:
104 if not val and val != 0 and defval is not None:
105 val = defval
105 val = defval
106 return repo.lookup(val)
106 return repo.lookup(val)
107
107
108 if not revs:
108 if not revs:
109 return repo.dirstate.parents()[0], None
109 return repo.dirstate.parents()[0], None
110 end = None
110 end = None
111 if len(revs) == 1:
111 if len(revs) == 1:
112 if revrangesep in revs[0]:
112 if revrangesep in revs[0]:
113 start, end = revs[0].split(revrangesep, 1)
113 start, end = revs[0].split(revrangesep, 1)
114 start = revfix(repo, start, 0)
114 start = revfix(repo, start, 0)
115 end = revfix(repo, end, repo.changelog.count() - 1)
115 end = revfix(repo, end, repo.changelog.count() - 1)
116 else:
116 else:
117 start = revfix(repo, revs[0], None)
117 start = revfix(repo, revs[0], None)
118 elif len(revs) == 2:
118 elif len(revs) == 2:
119 if revrangesep in revs[0] or revrangesep in revs[1]:
119 if revrangesep in revs[0] or revrangesep in revs[1]:
120 raise util.Abort(_('too many revisions specified'))
120 raise util.Abort(_('too many revisions specified'))
121 start = revfix(repo, revs[0], None)
121 start = revfix(repo, revs[0], None)
122 end = revfix(repo, revs[1], None)
122 end = revfix(repo, revs[1], None)
123 else:
123 else:
124 raise util.Abort(_('too many revisions specified'))
124 raise util.Abort(_('too many revisions specified'))
125 return start, end
125 return start, end
126
126
127 def revrange(repo, revs):
127 def revrange(repo, revs):
128 """Yield revision as strings from a list of revision specifications."""
128 """Yield revision as strings from a list of revision specifications."""
129
129
130 def revfix(repo, val, defval):
130 def revfix(repo, val, defval):
131 if not val and val != 0 and defval is not None:
131 if not val and val != 0 and defval is not None:
132 return defval
132 return defval
133 return repo.changelog.rev(repo.lookup(val))
133 return repo.changelog.rev(repo.lookup(val))
134
134
135 seen, l = {}, []
135 seen, l = {}, []
136 for spec in revs:
136 for spec in revs:
137 if revrangesep in spec:
137 if revrangesep in spec:
138 start, end = spec.split(revrangesep, 1)
138 start, end = spec.split(revrangesep, 1)
139 start = revfix(repo, start, 0)
139 start = revfix(repo, start, 0)
140 end = revfix(repo, end, repo.changelog.count() - 1)
140 end = revfix(repo, end, repo.changelog.count() - 1)
141 step = start > end and -1 or 1
141 step = start > end and -1 or 1
142 for rev in xrange(start, end+step, step):
142 for rev in xrange(start, end+step, step):
143 if rev in seen:
143 if rev in seen:
144 continue
144 continue
145 seen[rev] = 1
145 seen[rev] = 1
146 l.append(rev)
146 l.append(rev)
147 else:
147 else:
148 rev = revfix(repo, spec, None)
148 rev = revfix(repo, spec, None)
149 if rev in seen:
149 if rev in seen:
150 continue
150 continue
151 seen[rev] = 1
151 seen[rev] = 1
152 l.append(rev)
152 l.append(rev)
153
153
154 return l
154 return l
155
155
156 def make_filename(repo, pat, node,
156 def make_filename(repo, pat, node,
157 total=None, seqno=None, revwidth=None, pathname=None):
157 total=None, seqno=None, revwidth=None, pathname=None):
158 node_expander = {
158 node_expander = {
159 'H': lambda: hex(node),
159 'H': lambda: hex(node),
160 'R': lambda: str(repo.changelog.rev(node)),
160 'R': lambda: str(repo.changelog.rev(node)),
161 'h': lambda: short(node),
161 'h': lambda: short(node),
162 }
162 }
163 expander = {
163 expander = {
164 '%': lambda: '%',
164 '%': lambda: '%',
165 'b': lambda: os.path.basename(repo.root),
165 'b': lambda: os.path.basename(repo.root),
166 }
166 }
167
167
168 try:
168 try:
169 if node:
169 if node:
170 expander.update(node_expander)
170 expander.update(node_expander)
171 if node:
171 if node:
172 expander['r'] = (lambda:
172 expander['r'] = (lambda:
173 str(repo.changelog.rev(node)).zfill(revwidth or 0))
173 str(repo.changelog.rev(node)).zfill(revwidth or 0))
174 if total is not None:
174 if total is not None:
175 expander['N'] = lambda: str(total)
175 expander['N'] = lambda: str(total)
176 if seqno is not None:
176 if seqno is not None:
177 expander['n'] = lambda: str(seqno)
177 expander['n'] = lambda: str(seqno)
178 if total is not None and seqno is not None:
178 if total is not None and seqno is not None:
179 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
179 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
180 if pathname is not None:
180 if pathname is not None:
181 expander['s'] = lambda: os.path.basename(pathname)
181 expander['s'] = lambda: os.path.basename(pathname)
182 expander['d'] = lambda: os.path.dirname(pathname) or '.'
182 expander['d'] = lambda: os.path.dirname(pathname) or '.'
183 expander['p'] = lambda: pathname
183 expander['p'] = lambda: pathname
184
184
185 newname = []
185 newname = []
186 patlen = len(pat)
186 patlen = len(pat)
187 i = 0
187 i = 0
188 while i < patlen:
188 while i < patlen:
189 c = pat[i]
189 c = pat[i]
190 if c == '%':
190 if c == '%':
191 i += 1
191 i += 1
192 c = pat[i]
192 c = pat[i]
193 c = expander[c]()
193 c = expander[c]()
194 newname.append(c)
194 newname.append(c)
195 i += 1
195 i += 1
196 return ''.join(newname)
196 return ''.join(newname)
197 except KeyError, inst:
197 except KeyError, inst:
198 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
198 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
199 inst.args[0])
199 inst.args[0])
200
200
201 def make_file(repo, pat, node=None,
201 def make_file(repo, pat, node=None,
202 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
202 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
203 if not pat or pat == '-':
203 if not pat or pat == '-':
204 return 'w' in mode and sys.stdout or sys.stdin
204 return 'w' in mode and sys.stdout or sys.stdin
205 if hasattr(pat, 'write') and 'w' in mode:
205 if hasattr(pat, 'write') and 'w' in mode:
206 return pat
206 return pat
207 if hasattr(pat, 'read') and 'r' in mode:
207 if hasattr(pat, 'read') and 'r' in mode:
208 return pat
208 return pat
209 return open(make_filename(repo, pat, node, total, seqno, revwidth,
209 return open(make_filename(repo, pat, node, total, seqno, revwidth,
210 pathname),
210 pathname),
211 mode)
211 mode)
212
212
213 def matchpats(repo, pats=[], opts={}, globbed=False, default=None):
213 def matchpats(repo, pats=[], opts={}, globbed=False, default=None):
214 cwd = repo.getcwd()
214 cwd = repo.getcwd()
215 return util.cmdmatcher(repo.root, cwd, pats or [], opts.get('include'),
215 return util.cmdmatcher(repo.root, cwd, pats or [], opts.get('include'),
216 opts.get('exclude'), globbed=globbed,
216 opts.get('exclude'), globbed=globbed,
217 default=default)
217 default=default)
218
218
219 def walk(repo, pats=[], opts={}, node=None, badmatch=None, globbed=False,
219 def walk(repo, pats=[], opts={}, node=None, badmatch=None, globbed=False,
220 default=None):
220 default=None):
221 files, matchfn, anypats = matchpats(repo, pats, opts, globbed=globbed,
221 files, matchfn, anypats = matchpats(repo, pats, opts, globbed=globbed,
222 default=default)
222 default=default)
223 exact = dict.fromkeys(files)
223 exact = dict.fromkeys(files)
224 cwd = repo.getcwd()
224 cwd = repo.getcwd()
225 for src, fn in repo.walk(node=node, files=files, match=matchfn,
225 for src, fn in repo.walk(node=node, files=files, match=matchfn,
226 badmatch=badmatch):
226 badmatch=badmatch):
227 yield src, fn, repo.pathto(fn, cwd), fn in exact
227 yield src, fn, repo.pathto(fn, cwd), fn in exact
228
228
229 def findrenames(repo, added=None, removed=None, threshold=0.5):
229 def findrenames(repo, added=None, removed=None, threshold=0.5):
230 '''find renamed files -- yields (before, after, score) tuples'''
230 '''find renamed files -- yields (before, after, score) tuples'''
231 if added is None or removed is None:
231 if added is None or removed is None:
232 added, removed = repo.status()[1:3]
232 added, removed = repo.status()[1:3]
233 ctx = repo.changectx()
233 ctx = repo.changectx()
234 for a in added:
234 for a in added:
235 aa = repo.wread(a)
235 aa = repo.wread(a)
236 bestname, bestscore = None, threshold
236 bestname, bestscore = None, threshold
237 for r in removed:
237 for r in removed:
238 rr = ctx.filectx(r).data()
238 rr = ctx.filectx(r).data()
239
239
240 # bdiff.blocks() returns blocks of matching lines
240 # bdiff.blocks() returns blocks of matching lines
241 # count the number of bytes in each
241 # count the number of bytes in each
242 equal = 0
242 equal = 0
243 alines = mdiff.splitnewlines(aa)
243 alines = mdiff.splitnewlines(aa)
244 matches = bdiff.blocks(aa, rr)
244 matches = bdiff.blocks(aa, rr)
245 for x1,x2,y1,y2 in matches:
245 for x1,x2,y1,y2 in matches:
246 for line in alines[x1:x2]:
246 for line in alines[x1:x2]:
247 equal += len(line)
247 equal += len(line)
248
248
249 lengths = len(aa) + len(rr)
249 lengths = len(aa) + len(rr)
250 if lengths:
250 if lengths:
251 myscore = equal*2.0 / lengths
251 myscore = equal*2.0 / lengths
252 if myscore >= bestscore:
252 if myscore >= bestscore:
253 bestname, bestscore = r, myscore
253 bestname, bestscore = r, myscore
254 if bestname:
254 if bestname:
255 yield bestname, a, bestscore
255 yield bestname, a, bestscore
256
256
257 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
257 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
258 if dry_run is None:
258 if dry_run is None:
259 dry_run = opts.get('dry_run')
259 dry_run = opts.get('dry_run')
260 if similarity is None:
260 if similarity is None:
261 similarity = float(opts.get('similarity') or 0)
261 similarity = float(opts.get('similarity') or 0)
262 add, remove = [], []
262 add, remove = [], []
263 mapping = {}
263 mapping = {}
264 for src, abs, rel, exact in walk(repo, pats, opts):
264 for src, abs, rel, exact in walk(repo, pats, opts):
265 target = repo.wjoin(abs)
265 target = repo.wjoin(abs)
266 if src == 'f' and abs not in repo.dirstate:
266 if src == 'f' and abs not in repo.dirstate:
267 add.append(abs)
267 add.append(abs)
268 mapping[abs] = rel, exact
268 mapping[abs] = rel, exact
269 if repo.ui.verbose or not exact:
269 if repo.ui.verbose or not exact:
270 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
270 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
271 if repo.dirstate[abs] != 'r' and (not util.lexists(target)
271 if repo.dirstate[abs] != 'r' and (not util.lexists(target)
272 or (os.path.isdir(target) and not os.path.islink(target))):
272 or (os.path.isdir(target) and not os.path.islink(target))):
273 remove.append(abs)
273 remove.append(abs)
274 mapping[abs] = rel, exact
274 mapping[abs] = rel, exact
275 if repo.ui.verbose or not exact:
275 if repo.ui.verbose or not exact:
276 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
276 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
277 if not dry_run:
277 if not dry_run:
278 repo.remove(remove)
278 repo.remove(remove)
279 repo.add(add)
279 repo.add(add)
280 if similarity > 0:
280 if similarity > 0:
281 for old, new, score in findrenames(repo, add, remove, similarity):
281 for old, new, score in findrenames(repo, add, remove, similarity):
282 oldrel, oldexact = mapping[old]
282 oldrel, oldexact = mapping[old]
283 newrel, newexact = mapping[new]
283 newrel, newexact = mapping[new]
284 if repo.ui.verbose or not oldexact or not newexact:
284 if repo.ui.verbose or not oldexact or not newexact:
285 repo.ui.status(_('recording removal of %s as rename to %s '
285 repo.ui.status(_('recording removal of %s as rename to %s '
286 '(%d%% similar)\n') %
286 '(%d%% similar)\n') %
287 (oldrel, newrel, score * 100))
287 (oldrel, newrel, score * 100))
288 if not dry_run:
288 if not dry_run:
289 repo.copy(old, new)
289 repo.copy(old, new)
290
290
291 def copy(ui, repo, pats, opts, rename=False):
291 def copy(ui, repo, pats, opts, rename=False):
292 # called with the repo lock held
292 # called with the repo lock held
293 #
293 #
294 # hgsep => pathname that uses "/" to separate directories
294 # hgsep => pathname that uses "/" to separate directories
295 # ossep => pathname that uses os.sep to separate directories
295 # ossep => pathname that uses os.sep to separate directories
296 cwd = repo.getcwd()
296 cwd = repo.getcwd()
297 targets = {}
297 targets = {}
298 after = opts.get("after")
298 after = opts.get("after")
299 dryrun = opts.get("dry_run")
299 dryrun = opts.get("dry_run")
300
300
301 def walkpat(pat):
301 def walkpat(pat):
302 srcs = []
302 srcs = []
303 for tag, abs, rel, exact in walk(repo, [pat], opts, globbed=True):
303 for tag, abs, rel, exact in walk(repo, [pat], opts, globbed=True):
304 state = repo.dirstate[abs]
304 state = repo.dirstate[abs]
305 if state in '?r':
305 if state in '?r':
306 if exact and state == '?':
306 if exact and state == '?':
307 ui.warn(_('%s: not copying - file is not managed\n') % rel)
307 ui.warn(_('%s: not copying - file is not managed\n') % rel)
308 if exact and state == 'r':
308 if exact and state == 'r':
309 ui.warn(_('%s: not copying - file has been marked for'
309 ui.warn(_('%s: not copying - file has been marked for'
310 ' remove\n') % rel)
310 ' remove\n') % rel)
311 continue
311 continue
312 # abs: hgsep
312 # abs: hgsep
313 # rel: ossep
313 # rel: ossep
314 srcs.append((abs, rel, exact))
314 srcs.append((abs, rel, exact))
315 return srcs
315 return srcs
316
316
317 # abssrc: hgsep
317 # abssrc: hgsep
318 # relsrc: ossep
318 # relsrc: ossep
319 # otarget: ossep
319 # otarget: ossep
320 def copyfile(abssrc, relsrc, otarget, exact):
320 def copyfile(abssrc, relsrc, otarget, exact):
321 abstarget = util.canonpath(repo.root, cwd, otarget)
321 abstarget = util.canonpath(repo.root, cwd, otarget)
322 reltarget = repo.pathto(abstarget, cwd)
322 reltarget = repo.pathto(abstarget, cwd)
323 target = repo.wjoin(abstarget)
323 target = repo.wjoin(abstarget)
324 src = repo.wjoin(abssrc)
324 src = repo.wjoin(abssrc)
325 state = repo.dirstate[abstarget]
325 state = repo.dirstate[abstarget]
326
326
327 # check for collisions
327 # check for collisions
328 prevsrc = targets.get(abstarget)
328 prevsrc = targets.get(abstarget)
329 if prevsrc is not None:
329 if prevsrc is not None:
330 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
330 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
331 (reltarget, repo.pathto(abssrc, cwd),
331 (reltarget, repo.pathto(abssrc, cwd),
332 repo.pathto(prevsrc, cwd)))
332 repo.pathto(prevsrc, cwd)))
333 return
333 return
334
334
335 # check for overwrites
335 # check for overwrites
336 exists = os.path.exists(target)
336 exists = os.path.exists(target)
337 if (not after and exists or after and state in 'mn'):
337 if (not after and exists or after and state in 'mn'):
338 if not opts['force']:
338 if not opts['force']:
339 ui.warn(_('%s: not overwriting - file exists\n') %
339 ui.warn(_('%s: not overwriting - file exists\n') %
340 reltarget)
340 reltarget)
341 return
341 return
342
342
343 if after:
343 if after:
344 if not exists:
344 if not exists:
345 return
345 return
346 elif not dryrun:
346 elif not dryrun:
347 try:
347 try:
348 if exists:
348 if exists:
349 os.unlink(target)
349 os.unlink(target)
350 targetdir = os.path.dirname(target) or '.'
350 targetdir = os.path.dirname(target) or '.'
351 if not os.path.isdir(targetdir):
351 if not os.path.isdir(targetdir):
352 os.makedirs(targetdir)
352 os.makedirs(targetdir)
353 util.copyfile(src, target)
353 util.copyfile(src, target)
354 except IOError, inst:
354 except IOError, inst:
355 if inst.errno == errno.ENOENT:
355 if inst.errno == errno.ENOENT:
356 ui.warn(_('%s: deleted in working copy\n') % relsrc)
356 ui.warn(_('%s: deleted in working copy\n') % relsrc)
357 else:
357 else:
358 ui.warn(_('%s: cannot copy - %s\n') %
358 ui.warn(_('%s: cannot copy - %s\n') %
359 (relsrc, inst.strerror))
359 (relsrc, inst.strerror))
360 return True # report a failure
360 return True # report a failure
361
361
362 if ui.verbose or not exact:
362 if ui.verbose or not exact:
363 action = rename and "moving" or "copying"
363 action = rename and "moving" or "copying"
364 ui.status(_('%s %s to %s\n') % (action, relsrc, reltarget))
364 ui.status(_('%s %s to %s\n') % (action, relsrc, reltarget))
365
365
366 targets[abstarget] = abssrc
366 targets[abstarget] = abssrc
367
367
368 # fix up dirstate
368 # fix up dirstate
369 origsrc = repo.dirstate.copied(abssrc) or abssrc
369 origsrc = repo.dirstate.copied(abssrc) or abssrc
370 if abstarget == origsrc: # copying back a copy?
370 if abstarget == origsrc: # copying back a copy?
371 if state not in 'mn' and not dryrun:
371 if state not in 'mn' and not dryrun:
372 repo.dirstate.normallookup(abstarget)
372 repo.dirstate.normallookup(abstarget)
373 else:
373 else:
374 if repo.dirstate[origsrc] == 'a':
374 if repo.dirstate[origsrc] == 'a':
375 if not ui.quiet:
375 if not ui.quiet:
376 ui.warn(_("%s has not been committed yet, so no copy "
376 ui.warn(_("%s has not been committed yet, so no copy "
377 "data will be stored for %s.\n")
377 "data will be stored for %s.\n")
378 % (repo.pathto(origsrc, cwd), reltarget))
378 % (repo.pathto(origsrc, cwd), reltarget))
379 if abstarget not in repo.dirstate and not dryrun:
379 if abstarget not in repo.dirstate and not dryrun:
380 repo.add([abstarget])
380 repo.add([abstarget])
381 elif not dryrun:
381 elif not dryrun:
382 repo.copy(origsrc, abstarget)
382 repo.copy(origsrc, abstarget)
383
383
384 if rename and not dryrun:
384 if rename and not dryrun:
385 repo.remove([abssrc], True)
385 repo.remove([abssrc], True)
386
386
387 # pat: ossep
387 # pat: ossep
388 # dest ossep
388 # dest ossep
389 # srcs: list of (hgsep, hgsep, ossep, bool)
389 # srcs: list of (hgsep, hgsep, ossep, bool)
390 # return: function that takes hgsep and returns ossep
390 # return: function that takes hgsep and returns ossep
391 def targetpathfn(pat, dest, srcs):
391 def targetpathfn(pat, dest, srcs):
392 if os.path.isdir(pat):
392 if os.path.isdir(pat):
393 abspfx = util.canonpath(repo.root, cwd, pat)
393 abspfx = util.canonpath(repo.root, cwd, pat)
394 abspfx = util.localpath(abspfx)
394 abspfx = util.localpath(abspfx)
395 if destdirexists:
395 if destdirexists:
396 striplen = len(os.path.split(abspfx)[0])
396 striplen = len(os.path.split(abspfx)[0])
397 else:
397 else:
398 striplen = len(abspfx)
398 striplen = len(abspfx)
399 if striplen:
399 if striplen:
400 striplen += len(os.sep)
400 striplen += len(os.sep)
401 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
401 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
402 elif destdirexists:
402 elif destdirexists:
403 res = lambda p: os.path.join(dest,
403 res = lambda p: os.path.join(dest,
404 os.path.basename(util.localpath(p)))
404 os.path.basename(util.localpath(p)))
405 else:
405 else:
406 res = lambda p: dest
406 res = lambda p: dest
407 return res
407 return res
408
408
409 # pat: ossep
409 # pat: ossep
410 # dest ossep
410 # dest ossep
411 # srcs: list of (hgsep, hgsep, ossep, bool)
411 # srcs: list of (hgsep, hgsep, ossep, bool)
412 # return: function that takes hgsep and returns ossep
412 # return: function that takes hgsep and returns ossep
413 def targetpathafterfn(pat, dest, srcs):
413 def targetpathafterfn(pat, dest, srcs):
414 if util.patkind(pat, None)[0]:
414 if util.patkind(pat, None)[0]:
415 # a mercurial pattern
415 # a mercurial pattern
416 res = lambda p: os.path.join(dest,
416 res = lambda p: os.path.join(dest,
417 os.path.basename(util.localpath(p)))
417 os.path.basename(util.localpath(p)))
418 else:
418 else:
419 abspfx = util.canonpath(repo.root, cwd, pat)
419 abspfx = util.canonpath(repo.root, cwd, pat)
420 if len(abspfx) < len(srcs[0][0]):
420 if len(abspfx) < len(srcs[0][0]):
421 # A directory. Either the target path contains the last
421 # A directory. Either the target path contains the last
422 # component of the source path or it does not.
422 # component of the source path or it does not.
423 def evalpath(striplen):
423 def evalpath(striplen):
424 score = 0
424 score = 0
425 for s in srcs:
425 for s in srcs:
426 t = os.path.join(dest, util.localpath(s[0])[striplen:])
426 t = os.path.join(dest, util.localpath(s[0])[striplen:])
427 if os.path.exists(t):
427 if os.path.exists(t):
428 score += 1
428 score += 1
429 return score
429 return score
430
430
431 abspfx = util.localpath(abspfx)
431 abspfx = util.localpath(abspfx)
432 striplen = len(abspfx)
432 striplen = len(abspfx)
433 if striplen:
433 if striplen:
434 striplen += len(os.sep)
434 striplen += len(os.sep)
435 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
435 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
436 score = evalpath(striplen)
436 score = evalpath(striplen)
437 striplen1 = len(os.path.split(abspfx)[0])
437 striplen1 = len(os.path.split(abspfx)[0])
438 if striplen1:
438 if striplen1:
439 striplen1 += len(os.sep)
439 striplen1 += len(os.sep)
440 if evalpath(striplen1) > score:
440 if evalpath(striplen1) > score:
441 striplen = striplen1
441 striplen = striplen1
442 res = lambda p: os.path.join(dest,
442 res = lambda p: os.path.join(dest,
443 util.localpath(p)[striplen:])
443 util.localpath(p)[striplen:])
444 else:
444 else:
445 # a file
445 # a file
446 if destdirexists:
446 if destdirexists:
447 res = lambda p: os.path.join(dest,
447 res = lambda p: os.path.join(dest,
448 os.path.basename(util.localpath(p)))
448 os.path.basename(util.localpath(p)))
449 else:
449 else:
450 res = lambda p: dest
450 res = lambda p: dest
451 return res
451 return res
452
452
453
453
454 pats = util.expand_glob(pats)
454 pats = util.expand_glob(pats)
455 if not pats:
455 if not pats:
456 raise util.Abort(_('no source or destination specified'))
456 raise util.Abort(_('no source or destination specified'))
457 if len(pats) == 1:
457 if len(pats) == 1:
458 raise util.Abort(_('no destination specified'))
458 raise util.Abort(_('no destination specified'))
459 dest = pats.pop()
459 dest = pats.pop()
460 destdirexists = os.path.isdir(dest)
460 destdirexists = os.path.isdir(dest)
461 if not destdirexists:
461 if not destdirexists:
462 if len(pats) > 1 or util.patkind(pats[0], None)[0]:
462 if len(pats) > 1 or util.patkind(pats[0], None)[0]:
463 raise util.Abort(_('with multiple sources, destination must be an '
463 raise util.Abort(_('with multiple sources, destination must be an '
464 'existing directory'))
464 'existing directory'))
465 if util.endswithsep(dest):
465 if util.endswithsep(dest):
466 raise util.Abort(_('destination %s is not a directory') % dest)
466 raise util.Abort(_('destination %s is not a directory') % dest)
467
467
468 tfn = targetpathfn
468 tfn = targetpathfn
469 if after:
469 if after:
470 tfn = targetpathafterfn
470 tfn = targetpathafterfn
471 copylist = []
471 copylist = []
472 for pat in pats:
472 for pat in pats:
473 srcs = walkpat(pat)
473 srcs = walkpat(pat)
474 if not srcs:
474 if not srcs:
475 continue
475 continue
476 copylist.append((tfn(pat, dest, srcs), srcs))
476 copylist.append((tfn(pat, dest, srcs), srcs))
477 if not copylist:
477 if not copylist:
478 raise util.Abort(_('no files to copy'))
478 raise util.Abort(_('no files to copy'))
479
479
480 errors = 0
480 errors = 0
481 for targetpath, srcs in copylist:
481 for targetpath, srcs in copylist:
482 for abssrc, relsrc, exact in srcs:
482 for abssrc, relsrc, exact in srcs:
483 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
483 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
484 errors += 1
484 errors += 1
485
485
486 if errors:
486 if errors:
487 ui.warn(_('(consider using --after)\n'))
487 ui.warn(_('(consider using --after)\n'))
488
488
489 return errors
489 return errors
490
490
491 def service(opts, parentfn=None, initfn=None, runfn=None):
491 def service(opts, parentfn=None, initfn=None, runfn=None):
492 '''Run a command as a service.'''
492 '''Run a command as a service.'''
493
493
494 if opts['daemon'] and not opts['daemon_pipefds']:
494 if opts['daemon'] and not opts['daemon_pipefds']:
495 rfd, wfd = os.pipe()
495 rfd, wfd = os.pipe()
496 args = sys.argv[:]
496 args = sys.argv[:]
497 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
497 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
498 # Don't pass --cwd to the child process, because we've already
498 # Don't pass --cwd to the child process, because we've already
499 # changed directory.
499 # changed directory.
500 for i in xrange(1,len(args)):
500 for i in xrange(1,len(args)):
501 if args[i].startswith('--cwd='):
501 if args[i].startswith('--cwd='):
502 del args[i]
502 del args[i]
503 break
503 break
504 elif args[i].startswith('--cwd'):
504 elif args[i].startswith('--cwd'):
505 del args[i:i+2]
505 del args[i:i+2]
506 break
506 break
507 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
507 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
508 args[0], args)
508 args[0], args)
509 os.close(wfd)
509 os.close(wfd)
510 os.read(rfd, 1)
510 os.read(rfd, 1)
511 if parentfn:
511 if parentfn:
512 return parentfn(pid)
512 return parentfn(pid)
513 else:
513 else:
514 os._exit(0)
514 os._exit(0)
515
515
516 if initfn:
516 if initfn:
517 initfn()
517 initfn()
518
518
519 if opts['pid_file']:
519 if opts['pid_file']:
520 fp = open(opts['pid_file'], 'w')
520 fp = open(opts['pid_file'], 'w')
521 fp.write(str(os.getpid()) + '\n')
521 fp.write(str(os.getpid()) + '\n')
522 fp.close()
522 fp.close()
523
523
524 if opts['daemon_pipefds']:
524 if opts['daemon_pipefds']:
525 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
525 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
526 os.close(rfd)
526 os.close(rfd)
527 try:
527 try:
528 os.setsid()
528 os.setsid()
529 except AttributeError:
529 except AttributeError:
530 pass
530 pass
531 os.write(wfd, 'y')
531 os.write(wfd, 'y')
532 os.close(wfd)
532 os.close(wfd)
533 sys.stdout.flush()
533 sys.stdout.flush()
534 sys.stderr.flush()
534 sys.stderr.flush()
535 fd = os.open(util.nulldev, os.O_RDWR)
535 fd = os.open(util.nulldev, os.O_RDWR)
536 if fd != 0: os.dup2(fd, 0)
536 if fd != 0: os.dup2(fd, 0)
537 if fd != 1: os.dup2(fd, 1)
537 if fd != 1: os.dup2(fd, 1)
538 if fd != 2: os.dup2(fd, 2)
538 if fd != 2: os.dup2(fd, 2)
539 if fd not in (0, 1, 2): os.close(fd)
539 if fd not in (0, 1, 2): os.close(fd)
540
540
541 if runfn:
541 if runfn:
542 return runfn()
542 return runfn()
543
543
544 class changeset_printer(object):
544 class changeset_printer(object):
545 '''show changeset information when templating not requested.'''
545 '''show changeset information when templating not requested.'''
546
546
547 def __init__(self, ui, repo, patch, buffered):
547 def __init__(self, ui, repo, patch, buffered):
548 self.ui = ui
548 self.ui = ui
549 self.repo = repo
549 self.repo = repo
550 self.buffered = buffered
550 self.buffered = buffered
551 self.patch = patch
551 self.patch = patch
552 self.header = {}
552 self.header = {}
553 self.hunk = {}
553 self.hunk = {}
554 self.lastheader = None
554 self.lastheader = None
555
555
556 def flush(self, rev):
556 def flush(self, rev):
557 if rev in self.header:
557 if rev in self.header:
558 h = self.header[rev]
558 h = self.header[rev]
559 if h != self.lastheader:
559 if h != self.lastheader:
560 self.lastheader = h
560 self.lastheader = h
561 self.ui.write(h)
561 self.ui.write(h)
562 del self.header[rev]
562 del self.header[rev]
563 if rev in self.hunk:
563 if rev in self.hunk:
564 self.ui.write(self.hunk[rev])
564 self.ui.write(self.hunk[rev])
565 del self.hunk[rev]
565 del self.hunk[rev]
566 return 1
566 return 1
567 return 0
567 return 0
568
568
569 def show(self, rev=0, changenode=None, copies=(), **props):
569 def show(self, rev=0, changenode=None, copies=(), **props):
570 if self.buffered:
570 if self.buffered:
571 self.ui.pushbuffer()
571 self.ui.pushbuffer()
572 self._show(rev, changenode, copies, props)
572 self._show(rev, changenode, copies, props)
573 self.hunk[rev] = self.ui.popbuffer()
573 self.hunk[rev] = self.ui.popbuffer()
574 else:
574 else:
575 self._show(rev, changenode, copies, props)
575 self._show(rev, changenode, copies, props)
576
576
577 def _show(self, rev, changenode, copies, props):
577 def _show(self, rev, changenode, copies, props):
578 '''show a single changeset or file revision'''
578 '''show a single changeset or file revision'''
579 log = self.repo.changelog
579 log = self.repo.changelog
580 if changenode is None:
580 if changenode is None:
581 changenode = log.node(rev)
581 changenode = log.node(rev)
582 elif not rev:
582 elif not rev:
583 rev = log.rev(changenode)
583 rev = log.rev(changenode)
584
584
585 if self.ui.quiet:
585 if self.ui.quiet:
586 self.ui.write("%d:%s\n" % (rev, short(changenode)))
586 self.ui.write("%d:%s\n" % (rev, short(changenode)))
587 return
587 return
588
588
589 changes = log.read(changenode)
589 changes = log.read(changenode)
590 date = util.datestr(changes[2])
590 date = util.datestr(changes[2])
591 extra = changes[5]
591 extra = changes[5]
592 branch = extra.get("branch")
592 branch = extra.get("branch")
593
593
594 hexfunc = self.ui.debugflag and hex or short
594 hexfunc = self.ui.debugflag and hex or short
595
595
596 parents = [(p, hexfunc(log.node(p)))
596 parents = [(p, hexfunc(log.node(p)))
597 for p in self._meaningful_parentrevs(log, rev)]
597 for p in self._meaningful_parentrevs(log, rev)]
598
598
599 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
599 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
600
600
601 # don't show the default branch name
601 # don't show the default branch name
602 if branch != 'default':
602 if branch != 'default':
603 branch = util.tolocal(branch)
603 branch = util.tolocal(branch)
604 self.ui.write(_("branch: %s\n") % branch)
604 self.ui.write(_("branch: %s\n") % branch)
605 for tag in self.repo.nodetags(changenode):
605 for tag in self.repo.nodetags(changenode):
606 self.ui.write(_("tag: %s\n") % tag)
606 self.ui.write(_("tag: %s\n") % tag)
607 for parent in parents:
607 for parent in parents:
608 self.ui.write(_("parent: %d:%s\n") % parent)
608 self.ui.write(_("parent: %d:%s\n") % parent)
609
609
610 if self.ui.debugflag:
610 if self.ui.debugflag:
611 self.ui.write(_("manifest: %d:%s\n") %
611 self.ui.write(_("manifest: %d:%s\n") %
612 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
612 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
613 self.ui.write(_("user: %s\n") % changes[1])
613 self.ui.write(_("user: %s\n") % changes[1])
614 self.ui.write(_("date: %s\n") % date)
614 self.ui.write(_("date: %s\n") % date)
615
615
616 if self.ui.debugflag:
616 if self.ui.debugflag:
617 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
617 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
618 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
618 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
619 files):
619 files):
620 if value:
620 if value:
621 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
621 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
622 elif changes[3] and self.ui.verbose:
622 elif changes[3] and self.ui.verbose:
623 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
623 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
624 if copies and self.ui.verbose:
624 if copies and self.ui.verbose:
625 copies = ['%s (%s)' % c for c in copies]
625 copies = ['%s (%s)' % c for c in copies]
626 self.ui.write(_("copies: %s\n") % ' '.join(copies))
626 self.ui.write(_("copies: %s\n") % ' '.join(copies))
627
627
628 if extra and self.ui.debugflag:
628 if extra and self.ui.debugflag:
629 extraitems = extra.items()
629 extraitems = extra.items()
630 extraitems.sort()
630 extraitems.sort()
631 for key, value in extraitems:
631 for key, value in extraitems:
632 self.ui.write(_("extra: %s=%s\n")
632 self.ui.write(_("extra: %s=%s\n")
633 % (key, value.encode('string_escape')))
633 % (key, value.encode('string_escape')))
634
634
635 description = changes[4].strip()
635 description = changes[4].strip()
636 if description:
636 if description:
637 if self.ui.verbose:
637 if self.ui.verbose:
638 self.ui.write(_("description:\n"))
638 self.ui.write(_("description:\n"))
639 self.ui.write(description)
639 self.ui.write(description)
640 self.ui.write("\n\n")
640 self.ui.write("\n\n")
641 else:
641 else:
642 self.ui.write(_("summary: %s\n") %
642 self.ui.write(_("summary: %s\n") %
643 description.splitlines()[0])
643 description.splitlines()[0])
644 self.ui.write("\n")
644 self.ui.write("\n")
645
645
646 self.showpatch(changenode)
646 self.showpatch(changenode)
647
647
648 def showpatch(self, node):
648 def showpatch(self, node):
649 if self.patch:
649 if self.patch:
650 prev = self.repo.changelog.parents(node)[0]
650 prev = self.repo.changelog.parents(node)[0]
651 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui,
651 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui,
652 opts=patch.diffopts(self.ui))
652 opts=patch.diffopts(self.ui))
653 self.ui.write("\n")
653 self.ui.write("\n")
654
654
655 def _meaningful_parentrevs(self, log, rev):
655 def _meaningful_parentrevs(self, log, rev):
656 """Return list of meaningful (or all if debug) parentrevs for rev.
656 """Return list of meaningful (or all if debug) parentrevs for rev.
657
657
658 For merges (two non-nullrev revisions) both parents are meaningful.
658 For merges (two non-nullrev revisions) both parents are meaningful.
659 Otherwise the first parent revision is considered meaningful if it
659 Otherwise the first parent revision is considered meaningful if it
660 is not the preceding revision.
660 is not the preceding revision.
661 """
661 """
662 parents = log.parentrevs(rev)
662 parents = log.parentrevs(rev)
663 if not self.ui.debugflag and parents[1] == nullrev:
663 if not self.ui.debugflag and parents[1] == nullrev:
664 if parents[0] >= rev - 1:
664 if parents[0] >= rev - 1:
665 parents = []
665 parents = []
666 else:
666 else:
667 parents = [parents[0]]
667 parents = [parents[0]]
668 return parents
668 return parents
669
669
670
670
671 class changeset_templater(changeset_printer):
671 class changeset_templater(changeset_printer):
672 '''format changeset information.'''
672 '''format changeset information.'''
673
673
674 def __init__(self, ui, repo, patch, mapfile, buffered):
674 def __init__(self, ui, repo, patch, mapfile, buffered):
675 changeset_printer.__init__(self, ui, repo, patch, buffered)
675 changeset_printer.__init__(self, ui, repo, patch, buffered)
676 filters = templatefilters.filters.copy()
676 filters = templatefilters.filters.copy()
677 filters['formatnode'] = (ui.debugflag and (lambda x: x)
677 filters['formatnode'] = (ui.debugflag and (lambda x: x)
678 or (lambda x: x[:12]))
678 or (lambda x: x[:12]))
679 self.t = templater.templater(mapfile, filters,
679 self.t = templater.templater(mapfile, filters,
680 cache={
680 cache={
681 'parent': '{rev}:{node|formatnode} ',
681 'parent': '{rev}:{node|formatnode} ',
682 'manifest': '{rev}:{node|formatnode}',
682 'manifest': '{rev}:{node|formatnode}',
683 'filecopy': '{name} ({source})'})
683 'filecopy': '{name} ({source})'})
684
684
685 def use_template(self, t):
685 def use_template(self, t):
686 '''set template string to use'''
686 '''set template string to use'''
687 self.t.cache['changeset'] = t
687 self.t.cache['changeset'] = t
688
688
689 def _show(self, rev, changenode, copies, props):
689 def _show(self, rev, changenode, copies, props):
690 '''show a single changeset or file revision'''
690 '''show a single changeset or file revision'''
691 log = self.repo.changelog
691 log = self.repo.changelog
692 if changenode is None:
692 if changenode is None:
693 changenode = log.node(rev)
693 changenode = log.node(rev)
694 elif not rev:
694 elif not rev:
695 rev = log.rev(changenode)
695 rev = log.rev(changenode)
696
696
697 changes = log.read(changenode)
697 changes = log.read(changenode)
698
698
699 def showlist(name, values, plural=None, **args):
699 def showlist(name, values, plural=None, **args):
700 '''expand set of values.
700 '''expand set of values.
701 name is name of key in template map.
701 name is name of key in template map.
702 values is list of strings or dicts.
702 values is list of strings or dicts.
703 plural is plural of name, if not simply name + 's'.
703 plural is plural of name, if not simply name + 's'.
704
704
705 expansion works like this, given name 'foo'.
705 expansion works like this, given name 'foo'.
706
706
707 if values is empty, expand 'no_foos'.
707 if values is empty, expand 'no_foos'.
708
708
709 if 'foo' not in template map, return values as a string,
709 if 'foo' not in template map, return values as a string,
710 joined by space.
710 joined by space.
711
711
712 expand 'start_foos'.
712 expand 'start_foos'.
713
713
714 for each value, expand 'foo'. if 'last_foo' in template
714 for each value, expand 'foo'. if 'last_foo' in template
715 map, expand it instead of 'foo' for last key.
715 map, expand it instead of 'foo' for last key.
716
716
717 expand 'end_foos'.
717 expand 'end_foos'.
718 '''
718 '''
719 if plural: names = plural
719 if plural: names = plural
720 else: names = name + 's'
720 else: names = name + 's'
721 if not values:
721 if not values:
722 noname = 'no_' + names
722 noname = 'no_' + names
723 if noname in self.t:
723 if noname in self.t:
724 yield self.t(noname, **args)
724 yield self.t(noname, **args)
725 return
725 return
726 if name not in self.t:
726 if name not in self.t:
727 if isinstance(values[0], str):
727 if isinstance(values[0], str):
728 yield ' '.join(values)
728 yield ' '.join(values)
729 else:
729 else:
730 for v in values:
730 for v in values:
731 yield dict(v, **args)
731 yield dict(v, **args)
732 return
732 return
733 startname = 'start_' + names
733 startname = 'start_' + names
734 if startname in self.t:
734 if startname in self.t:
735 yield self.t(startname, **args)
735 yield self.t(startname, **args)
736 vargs = args.copy()
736 vargs = args.copy()
737 def one(v, tag=name):
737 def one(v, tag=name):
738 try:
738 try:
739 vargs.update(v)
739 vargs.update(v)
740 except (AttributeError, ValueError):
740 except (AttributeError, ValueError):
741 try:
741 try:
742 for a, b in v:
742 for a, b in v:
743 vargs[a] = b
743 vargs[a] = b
744 except ValueError:
744 except ValueError:
745 vargs[name] = v
745 vargs[name] = v
746 return self.t(tag, **vargs)
746 return self.t(tag, **vargs)
747 lastname = 'last_' + name
747 lastname = 'last_' + name
748 if lastname in self.t:
748 if lastname in self.t:
749 last = values.pop()
749 last = values.pop()
750 else:
750 else:
751 last = None
751 last = None
752 for v in values:
752 for v in values:
753 yield one(v)
753 yield one(v)
754 if last is not None:
754 if last is not None:
755 yield one(last, tag=lastname)
755 yield one(last, tag=lastname)
756 endname = 'end_' + names
756 endname = 'end_' + names
757 if endname in self.t:
757 if endname in self.t:
758 yield self.t(endname, **args)
758 yield self.t(endname, **args)
759
759
760 def showbranches(**args):
760 def showbranches(**args):
761 branch = changes[5].get("branch")
761 branch = changes[5].get("branch")
762 if branch != 'default':
762 if branch != 'default':
763 branch = util.tolocal(branch)
763 branch = util.tolocal(branch)
764 return showlist('branch', [branch], plural='branches', **args)
764 return showlist('branch', [branch], plural='branches', **args)
765
765
766 def showparents(**args):
766 def showparents(**args):
767 parents = [[('rev', p), ('node', hex(log.node(p)))]
767 parents = [[('rev', p), ('node', hex(log.node(p)))]
768 for p in self._meaningful_parentrevs(log, rev)]
768 for p in self._meaningful_parentrevs(log, rev)]
769 return showlist('parent', parents, **args)
769 return showlist('parent', parents, **args)
770
770
771 def showtags(**args):
771 def showtags(**args):
772 return showlist('tag', self.repo.nodetags(changenode), **args)
772 return showlist('tag', self.repo.nodetags(changenode), **args)
773
773
774 def showextras(**args):
774 def showextras(**args):
775 extras = changes[5].items()
775 extras = changes[5].items()
776 extras.sort()
776 extras.sort()
777 for key, value in extras:
777 for key, value in extras:
778 args = args.copy()
778 args = args.copy()
779 args.update(dict(key=key, value=value))
779 args.update(dict(key=key, value=value))
780 yield self.t('extra', **args)
780 yield self.t('extra', **args)
781
781
782 def showcopies(**args):
782 def showcopies(**args):
783 c = [{'name': x[0], 'source': x[1]} for x in copies]
783 c = [{'name': x[0], 'source': x[1]} for x in copies]
784 return showlist('file_copy', c, plural='file_copies', **args)
784 return showlist('file_copy', c, plural='file_copies', **args)
785
785
786 files = []
786 files = []
787 def getfiles():
787 def getfiles():
788 if not files:
788 if not files:
789 files[:] = self.repo.status(
789 files[:] = self.repo.status(
790 log.parents(changenode)[0], changenode)[:3]
790 log.parents(changenode)[0], changenode)[:3]
791 return files
791 return files
792 def showfiles(**args):
792 def showfiles(**args):
793 return showlist('file', changes[3], **args)
793 return showlist('file', changes[3], **args)
794 def showmods(**args):
794 def showmods(**args):
795 return showlist('file_mod', getfiles()[0], **args)
795 return showlist('file_mod', getfiles()[0], **args)
796 def showadds(**args):
796 def showadds(**args):
797 return showlist('file_add', getfiles()[1], **args)
797 return showlist('file_add', getfiles()[1], **args)
798 def showdels(**args):
798 def showdels(**args):
799 return showlist('file_del', getfiles()[2], **args)
799 return showlist('file_del', getfiles()[2], **args)
800 def showmanifest(**args):
800 def showmanifest(**args):
801 args = args.copy()
801 args = args.copy()
802 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
802 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
803 node=hex(changes[0])))
803 node=hex(changes[0])))
804 return self.t('manifest', **args)
804 return self.t('manifest', **args)
805
805
806 defprops = {
806 defprops = {
807 'author': changes[1],
807 'author': changes[1],
808 'branches': showbranches,
808 'branches': showbranches,
809 'date': changes[2],
809 'date': changes[2],
810 'desc': changes[4].strip(),
810 'desc': changes[4].strip(),
811 'file_adds': showadds,
811 'file_adds': showadds,
812 'file_dels': showdels,
812 'file_dels': showdels,
813 'file_mods': showmods,
813 'file_mods': showmods,
814 'files': showfiles,
814 'files': showfiles,
815 'file_copies': showcopies,
815 'file_copies': showcopies,
816 'manifest': showmanifest,
816 'manifest': showmanifest,
817 'node': hex(changenode),
817 'node': hex(changenode),
818 'parents': showparents,
818 'parents': showparents,
819 'rev': rev,
819 'rev': rev,
820 'tags': showtags,
820 'tags': showtags,
821 'extras': showextras,
821 'extras': showextras,
822 }
822 }
823 props = props.copy()
823 props = props.copy()
824 props.update(defprops)
824 props.update(defprops)
825
825
826 try:
826 try:
827 if self.ui.debugflag and 'header_debug' in self.t:
827 if self.ui.debugflag and 'header_debug' in self.t:
828 key = 'header_debug'
828 key = 'header_debug'
829 elif self.ui.quiet and 'header_quiet' in self.t:
829 elif self.ui.quiet and 'header_quiet' in self.t:
830 key = 'header_quiet'
830 key = 'header_quiet'
831 elif self.ui.verbose and 'header_verbose' in self.t:
831 elif self.ui.verbose and 'header_verbose' in self.t:
832 key = 'header_verbose'
832 key = 'header_verbose'
833 elif 'header' in self.t:
833 elif 'header' in self.t:
834 key = 'header'
834 key = 'header'
835 else:
835 else:
836 key = ''
836 key = ''
837 if key:
837 if key:
838 h = templater.stringify(self.t(key, **props))
838 h = templater.stringify(self.t(key, **props))
839 if self.buffered:
839 if self.buffered:
840 self.header[rev] = h
840 self.header[rev] = h
841 else:
841 else:
842 self.ui.write(h)
842 self.ui.write(h)
843 if self.ui.debugflag and 'changeset_debug' in self.t:
843 if self.ui.debugflag and 'changeset_debug' in self.t:
844 key = 'changeset_debug'
844 key = 'changeset_debug'
845 elif self.ui.quiet and 'changeset_quiet' in self.t:
845 elif self.ui.quiet and 'changeset_quiet' in self.t:
846 key = 'changeset_quiet'
846 key = 'changeset_quiet'
847 elif self.ui.verbose and 'changeset_verbose' in self.t:
847 elif self.ui.verbose and 'changeset_verbose' in self.t:
848 key = 'changeset_verbose'
848 key = 'changeset_verbose'
849 else:
849 else:
850 key = 'changeset'
850 key = 'changeset'
851 self.ui.write(templater.stringify(self.t(key, **props)))
851 self.ui.write(templater.stringify(self.t(key, **props)))
852 self.showpatch(changenode)
852 self.showpatch(changenode)
853 except KeyError, inst:
853 except KeyError, inst:
854 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
854 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
855 inst.args[0]))
855 inst.args[0]))
856 except SyntaxError, inst:
856 except SyntaxError, inst:
857 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
857 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
858
858
859 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
859 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
860 """show one changeset using template or regular display.
860 """show one changeset using template or regular display.
861
861
862 Display format will be the first non-empty hit of:
862 Display format will be the first non-empty hit of:
863 1. option 'template'
863 1. option 'template'
864 2. option 'style'
864 2. option 'style'
865 3. [ui] setting 'logtemplate'
865 3. [ui] setting 'logtemplate'
866 4. [ui] setting 'style'
866 4. [ui] setting 'style'
867 If all of these values are either the unset or the empty string,
867 If all of these values are either the unset or the empty string,
868 regular display via changeset_printer() is done.
868 regular display via changeset_printer() is done.
869 """
869 """
870 # options
870 # options
871 patch = False
871 patch = False
872 if opts.get('patch'):
872 if opts.get('patch'):
873 patch = matchfn or util.always
873 patch = matchfn or util.always
874
874
875 tmpl = opts.get('template')
875 tmpl = opts.get('template')
876 mapfile = None
876 mapfile = None
877 if tmpl:
877 if tmpl:
878 tmpl = templater.parsestring(tmpl, quoted=False)
878 tmpl = templater.parsestring(tmpl, quoted=False)
879 else:
879 else:
880 mapfile = opts.get('style')
880 mapfile = opts.get('style')
881 # ui settings
881 # ui settings
882 if not mapfile:
882 if not mapfile:
883 tmpl = ui.config('ui', 'logtemplate')
883 tmpl = ui.config('ui', 'logtemplate')
884 if tmpl:
884 if tmpl:
885 tmpl = templater.parsestring(tmpl)
885 tmpl = templater.parsestring(tmpl)
886 else:
886 else:
887 mapfile = ui.config('ui', 'style')
887 mapfile = ui.config('ui', 'style')
888
888
889 if tmpl or mapfile:
889 if tmpl or mapfile:
890 if mapfile:
890 if mapfile:
891 if not os.path.split(mapfile)[0]:
891 if not os.path.split(mapfile)[0]:
892 mapname = (templater.templatepath('map-cmdline.' + mapfile)
892 mapname = (templater.templatepath('map-cmdline.' + mapfile)
893 or templater.templatepath(mapfile))
893 or templater.templatepath(mapfile))
894 if mapname: mapfile = mapname
894 if mapname: mapfile = mapname
895 try:
895 try:
896 t = changeset_templater(ui, repo, patch, mapfile, buffered)
896 t = changeset_templater(ui, repo, patch, mapfile, buffered)
897 except SyntaxError, inst:
897 except SyntaxError, inst:
898 raise util.Abort(inst.args[0])
898 raise util.Abort(inst.args[0])
899 if tmpl: t.use_template(tmpl)
899 if tmpl: t.use_template(tmpl)
900 return t
900 return t
901 return changeset_printer(ui, repo, patch, buffered)
901 return changeset_printer(ui, repo, patch, buffered)
902
902
903 def finddate(ui, repo, date):
903 def finddate(ui, repo, date):
904 """Find the tipmost changeset that matches the given date spec"""
904 """Find the tipmost changeset that matches the given date spec"""
905 df = util.matchdate(date)
905 df = util.matchdate(date)
906 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
906 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
907 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
907 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
908 results = {}
908 results = {}
909 for st, rev, fns in changeiter:
909 for st, rev, fns in changeiter:
910 if st == 'add':
910 if st == 'add':
911 d = get(rev)[2]
911 d = get(rev)[2]
912 if df(d[0]):
912 if df(d[0]):
913 results[rev] = d
913 results[rev] = d
914 elif st == 'iter':
914 elif st == 'iter':
915 if rev in results:
915 if rev in results:
916 ui.status("Found revision %s from %s\n" %
916 ui.status("Found revision %s from %s\n" %
917 (rev, util.datestr(results[rev])))
917 (rev, util.datestr(results[rev])))
918 return str(rev)
918 return str(rev)
919
919
920 raise util.Abort(_("revision matching date not found"))
920 raise util.Abort(_("revision matching date not found"))
921
921
922 def walkchangerevs(ui, repo, pats, change, opts):
922 def walkchangerevs(ui, repo, pats, change, opts):
923 '''Iterate over files and the revs they changed in.
923 '''Iterate over files and the revs they changed in.
924
924
925 Callers most commonly need to iterate backwards over the history
925 Callers most commonly need to iterate backwards over the history
926 it is interested in. Doing so has awful (quadratic-looking)
926 it is interested in. Doing so has awful (quadratic-looking)
927 performance, so we use iterators in a "windowed" way.
927 performance, so we use iterators in a "windowed" way.
928
928
929 We walk a window of revisions in the desired order. Within the
929 We walk a window of revisions in the desired order. Within the
930 window, we first walk forwards to gather data, then in the desired
930 window, we first walk forwards to gather data, then in the desired
931 order (usually backwards) to display it.
931 order (usually backwards) to display it.
932
932
933 This function returns an (iterator, matchfn) tuple. The iterator
933 This function returns an (iterator, matchfn) tuple. The iterator
934 yields 3-tuples. They will be of one of the following forms:
934 yields 3-tuples. They will be of one of the following forms:
935
935
936 "window", incrementing, lastrev: stepping through a window,
936 "window", incrementing, lastrev: stepping through a window,
937 positive if walking forwards through revs, last rev in the
937 positive if walking forwards through revs, last rev in the
938 sequence iterated over - use to reset state for the current window
938 sequence iterated over - use to reset state for the current window
939
939
940 "add", rev, fns: out-of-order traversal of the given file names
940 "add", rev, fns: out-of-order traversal of the given file names
941 fns, which changed during revision rev - use to gather data for
941 fns, which changed during revision rev - use to gather data for
942 possible display
942 possible display
943
943
944 "iter", rev, None: in-order traversal of the revs earlier iterated
944 "iter", rev, None: in-order traversal of the revs earlier iterated
945 over with "add" - use to display data'''
945 over with "add" - use to display data'''
946
946
947 def increasing_windows(start, end, windowsize=8, sizelimit=512):
947 def increasing_windows(start, end, windowsize=8, sizelimit=512):
948 if start < end:
948 if start < end:
949 while start < end:
949 while start < end:
950 yield start, min(windowsize, end-start)
950 yield start, min(windowsize, end-start)
951 start += windowsize
951 start += windowsize
952 if windowsize < sizelimit:
952 if windowsize < sizelimit:
953 windowsize *= 2
953 windowsize *= 2
954 else:
954 else:
955 while start > end:
955 while start > end:
956 yield start, min(windowsize, start-end-1)
956 yield start, min(windowsize, start-end-1)
957 start -= windowsize
957 start -= windowsize
958 if windowsize < sizelimit:
958 if windowsize < sizelimit:
959 windowsize *= 2
959 windowsize *= 2
960
960
961 files, matchfn, anypats = matchpats(repo, pats, opts)
961 files, matchfn, anypats = matchpats(repo, pats, opts)
962 follow = opts.get('follow') or opts.get('follow_first')
962 follow = opts.get('follow') or opts.get('follow_first')
963
963
964 if repo.changelog.count() == 0:
964 if repo.changelog.count() == 0:
965 return [], matchfn
965 return [], matchfn
966
966
967 if follow:
967 if follow:
968 defrange = '%s:0' % repo.changectx().rev()
968 defrange = '%s:0' % repo.changectx().rev()
969 else:
969 else:
970 defrange = 'tip:0'
970 defrange = 'tip:0'
971 revs = revrange(repo, opts['rev'] or [defrange])
971 revs = revrange(repo, opts['rev'] or [defrange])
972 wanted = {}
972 wanted = {}
973 slowpath = anypats or opts.get('removed')
973 slowpath = anypats or opts.get('removed')
974 fncache = {}
974 fncache = {}
975
975
976 if not slowpath and not files:
976 if not slowpath and not files:
977 # No files, no patterns. Display all revs.
977 # No files, no patterns. Display all revs.
978 wanted = dict.fromkeys(revs)
978 wanted = dict.fromkeys(revs)
979 copies = []
979 copies = []
980 if not slowpath:
980 if not slowpath:
981 # Only files, no patterns. Check the history of each file.
981 # Only files, no patterns. Check the history of each file.
982 def filerevgen(filelog, node):
982 def filerevgen(filelog, node):
983 cl_count = repo.changelog.count()
983 cl_count = repo.changelog.count()
984 if node is None:
984 if node is None:
985 last = filelog.count() - 1
985 last = filelog.count() - 1
986 else:
986 else:
987 last = filelog.rev(node)
987 last = filelog.rev(node)
988 for i, window in increasing_windows(last, nullrev):
988 for i, window in increasing_windows(last, nullrev):
989 revs = []
989 revs = []
990 for j in xrange(i - window, i + 1):
990 for j in xrange(i - window, i + 1):
991 n = filelog.node(j)
991 n = filelog.node(j)
992 revs.append((filelog.linkrev(n),
992 revs.append((filelog.linkrev(n),
993 follow and filelog.renamed(n)))
993 follow and filelog.renamed(n)))
994 revs.reverse()
994 revs.reverse()
995 for rev in revs:
995 for rev in revs:
996 # only yield rev for which we have the changelog, it can
996 # only yield rev for which we have the changelog, it can
997 # happen while doing "hg log" during a pull or commit
997 # happen while doing "hg log" during a pull or commit
998 if rev[0] < cl_count:
998 if rev[0] < cl_count:
999 yield rev
999 yield rev
1000 def iterfiles():
1000 def iterfiles():
1001 for filename in files:
1001 for filename in files:
1002 yield filename, None
1002 yield filename, None
1003 for filename_node in copies:
1003 for filename_node in copies:
1004 yield filename_node
1004 yield filename_node
1005 minrev, maxrev = min(revs), max(revs)
1005 minrev, maxrev = min(revs), max(revs)
1006 for file_, node in iterfiles():
1006 for file_, node in iterfiles():
1007 filelog = repo.file(file_)
1007 filelog = repo.file(file_)
1008 # A zero count may be a directory or deleted file, so
1008 # A zero count may be a directory or deleted file, so
1009 # try to find matching entries on the slow path.
1009 # try to find matching entries on the slow path.
1010 if filelog.count() == 0:
1010 if filelog.count() == 0:
1011 slowpath = True
1011 slowpath = True
1012 break
1012 break
1013 for rev, copied in filerevgen(filelog, node):
1013 for rev, copied in filerevgen(filelog, node):
1014 if rev <= maxrev:
1014 if rev <= maxrev:
1015 if rev < minrev:
1015 if rev < minrev:
1016 break
1016 break
1017 fncache.setdefault(rev, [])
1017 fncache.setdefault(rev, [])
1018 fncache[rev].append(file_)
1018 fncache[rev].append(file_)
1019 wanted[rev] = 1
1019 wanted[rev] = 1
1020 if follow and copied:
1020 if follow and copied:
1021 copies.append(copied)
1021 copies.append(copied)
1022 if slowpath:
1022 if slowpath:
1023 if follow:
1023 if follow:
1024 raise util.Abort(_('can only follow copies/renames for explicit '
1024 raise util.Abort(_('can only follow copies/renames for explicit '
1025 'file names'))
1025 'file names'))
1026
1026
1027 # The slow path checks files modified in every changeset.
1027 # The slow path checks files modified in every changeset.
1028 def changerevgen():
1028 def changerevgen():
1029 for i, window in increasing_windows(repo.changelog.count()-1,
1029 for i, window in increasing_windows(repo.changelog.count()-1,
1030 nullrev):
1030 nullrev):
1031 for j in xrange(i - window, i + 1):
1031 for j in xrange(i - window, i + 1):
1032 yield j, change(j)[3]
1032 yield j, change(j)[3]
1033
1033
1034 for rev, changefiles in changerevgen():
1034 for rev, changefiles in changerevgen():
1035 matches = filter(matchfn, changefiles)
1035 matches = filter(matchfn, changefiles)
1036 if matches:
1036 if matches:
1037 fncache[rev] = matches
1037 fncache[rev] = matches
1038 wanted[rev] = 1
1038 wanted[rev] = 1
1039
1039
1040 class followfilter:
1040 class followfilter:
1041 def __init__(self, onlyfirst=False):
1041 def __init__(self, onlyfirst=False):
1042 self.startrev = nullrev
1042 self.startrev = nullrev
1043 self.roots = []
1043 self.roots = []
1044 self.onlyfirst = onlyfirst
1044 self.onlyfirst = onlyfirst
1045
1045
1046 def match(self, rev):
1046 def match(self, rev):
1047 def realparents(rev):
1047 def realparents(rev):
1048 if self.onlyfirst:
1048 if self.onlyfirst:
1049 return repo.changelog.parentrevs(rev)[0:1]
1049 return repo.changelog.parentrevs(rev)[0:1]
1050 else:
1050 else:
1051 return filter(lambda x: x != nullrev,
1051 return filter(lambda x: x != nullrev,
1052 repo.changelog.parentrevs(rev))
1052 repo.changelog.parentrevs(rev))
1053
1053
1054 if self.startrev == nullrev:
1054 if self.startrev == nullrev:
1055 self.startrev = rev
1055 self.startrev = rev
1056 return True
1056 return True
1057
1057
1058 if rev > self.startrev:
1058 if rev > self.startrev:
1059 # forward: all descendants
1059 # forward: all descendants
1060 if not self.roots:
1060 if not self.roots:
1061 self.roots.append(self.startrev)
1061 self.roots.append(self.startrev)
1062 for parent in realparents(rev):
1062 for parent in realparents(rev):
1063 if parent in self.roots:
1063 if parent in self.roots:
1064 self.roots.append(rev)
1064 self.roots.append(rev)
1065 return True
1065 return True
1066 else:
1066 else:
1067 # backwards: all parents
1067 # backwards: all parents
1068 if not self.roots:
1068 if not self.roots:
1069 self.roots.extend(realparents(self.startrev))
1069 self.roots.extend(realparents(self.startrev))
1070 if rev in self.roots:
1070 if rev in self.roots:
1071 self.roots.remove(rev)
1071 self.roots.remove(rev)
1072 self.roots.extend(realparents(rev))
1072 self.roots.extend(realparents(rev))
1073 return True
1073 return True
1074
1074
1075 return False
1075 return False
1076
1076
1077 # it might be worthwhile to do this in the iterator if the rev range
1077 # it might be worthwhile to do this in the iterator if the rev range
1078 # is descending and the prune args are all within that range
1078 # is descending and the prune args are all within that range
1079 for rev in opts.get('prune', ()):
1079 for rev in opts.get('prune', ()):
1080 rev = repo.changelog.rev(repo.lookup(rev))
1080 rev = repo.changelog.rev(repo.lookup(rev))
1081 ff = followfilter()
1081 ff = followfilter()
1082 stop = min(revs[0], revs[-1])
1082 stop = min(revs[0], revs[-1])
1083 for x in xrange(rev, stop-1, -1):
1083 for x in xrange(rev, stop-1, -1):
1084 if ff.match(x) and x in wanted:
1084 if ff.match(x) and x in wanted:
1085 del wanted[x]
1085 del wanted[x]
1086
1086
1087 def iterate():
1087 def iterate():
1088 if follow and not files:
1088 if follow and not files:
1089 ff = followfilter(onlyfirst=opts.get('follow_first'))
1089 ff = followfilter(onlyfirst=opts.get('follow_first'))
1090 def want(rev):
1090 def want(rev):
1091 if ff.match(rev) and rev in wanted:
1091 if ff.match(rev) and rev in wanted:
1092 return True
1092 return True
1093 return False
1093 return False
1094 else:
1094 else:
1095 def want(rev):
1095 def want(rev):
1096 return rev in wanted
1096 return rev in wanted
1097
1097
1098 for i, window in increasing_windows(0, len(revs)):
1098 for i, window in increasing_windows(0, len(revs)):
1099 yield 'window', revs[0] < revs[-1], revs[-1]
1099 yield 'window', revs[0] < revs[-1], revs[-1]
1100 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1100 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
1101 srevs = list(nrevs)
1101 srevs = list(nrevs)
1102 srevs.sort()
1102 srevs.sort()
1103 for rev in srevs:
1103 for rev in srevs:
1104 fns = fncache.get(rev)
1104 fns = fncache.get(rev)
1105 if not fns:
1105 if not fns:
1106 def fns_generator():
1106 def fns_generator():
1107 for f in change(rev)[3]:
1107 for f in change(rev)[3]:
1108 if matchfn(f):
1108 if matchfn(f):
1109 yield f
1109 yield f
1110 fns = fns_generator()
1110 fns = fns_generator()
1111 yield 'add', rev, fns
1111 yield 'add', rev, fns
1112 for rev in nrevs:
1112 for rev in nrevs:
1113 yield 'iter', rev, None
1113 yield 'iter', rev, None
1114 return iterate(), matchfn
1114 return iterate(), matchfn
1115
1115
1116 def commit(ui, repo, commitfunc, pats, opts):
1116 def commit(ui, repo, commitfunc, pats, opts):
1117 '''commit the specified files or all outstanding changes'''
1117 '''commit the specified files or all outstanding changes'''
1118 date = opts.get('date')
1119 if date:
1120 opts['date'] = util.parsedate(date)
1118 message = logmessage(opts)
1121 message = logmessage(opts)
1119
1122
1120 # extract addremove carefully -- this function can be called from a command
1123 # extract addremove carefully -- this function can be called from a command
1121 # that doesn't support addremove
1124 # that doesn't support addremove
1122 if opts.get('addremove'):
1125 if opts.get('addremove'):
1123 addremove(repo, pats, opts)
1126 addremove(repo, pats, opts)
1124
1127
1125 fns, match, anypats = matchpats(repo, pats, opts)
1128 fns, match, anypats = matchpats(repo, pats, opts)
1126 if pats:
1129 if pats:
1127 status = repo.status(files=fns, match=match)
1130 status = repo.status(files=fns, match=match)
1128 modified, added, removed, deleted, unknown = status[:5]
1131 modified, added, removed, deleted, unknown = status[:5]
1129 files = modified + added + removed
1132 files = modified + added + removed
1130 slist = None
1133 slist = None
1131 for f in fns:
1134 for f in fns:
1132 if f == '.':
1135 if f == '.':
1133 continue
1136 continue
1134 if f not in files:
1137 if f not in files:
1135 rf = repo.wjoin(f)
1138 rf = repo.wjoin(f)
1139 rel = repo.pathto(f)
1136 try:
1140 try:
1137 mode = os.lstat(rf)[stat.ST_MODE]
1141 mode = os.lstat(rf)[stat.ST_MODE]
1138 except OSError:
1142 except OSError:
1139 raise util.Abort(_("file %s not found!") % rf)
1143 raise util.Abort(_("file %s not found!") % rel)
1140 if stat.S_ISDIR(mode):
1144 if stat.S_ISDIR(mode):
1141 name = f + '/'
1145 name = f + '/'
1142 if slist is None:
1146 if slist is None:
1143 slist = list(files)
1147 slist = list(files)
1144 slist.sort()
1148 slist.sort()
1145 i = bisect.bisect(slist, name)
1149 i = bisect.bisect(slist, name)
1146 if i >= len(slist) or not slist[i].startswith(name):
1150 if i >= len(slist) or not slist[i].startswith(name):
1147 raise util.Abort(_("no match under directory %s!")
1151 raise util.Abort(_("no match under directory %s!")
1148 % rf)
1152 % rel)
1149 elif not (stat.S_ISREG(mode) or stat.S_ISLNK(mode)):
1153 elif not (stat.S_ISREG(mode) or stat.S_ISLNK(mode)):
1150 raise util.Abort(_("can't commit %s: "
1154 raise util.Abort(_("can't commit %s: "
1151 "unsupported file type!") % rf)
1155 "unsupported file type!") % rel)
1152 elif f not in repo.dirstate:
1156 elif f not in repo.dirstate:
1153 raise util.Abort(_("file %s not tracked!") % rf)
1157 raise util.Abort(_("file %s not tracked!") % rel)
1154 else:
1158 else:
1155 files = []
1159 files = []
1156 try:
1160 try:
1157 return commitfunc(ui, repo, files, message, match, opts)
1161 return commitfunc(ui, repo, files, message, match, opts)
1158 except ValueError, inst:
1162 except ValueError, inst:
1159 raise util.Abort(str(inst))
1163 raise util.Abort(str(inst))
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file copied from contrib/simplemerge to mercurial/simplemerge.py
NO CONTENT: file copied from contrib/simplemerge to mercurial/simplemerge.py
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: file was removed, binary diff hidden
NO CONTENT: file was removed, binary diff hidden
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now