##// END OF EJS Templates
i18n: calculate terminal columns by width information of each characters...
FUJIWARA Katsunori -
r15066:24efa83d stable
parent child Browse files
Show More
@@ -0,0 +1,257 b''
1 Test text wrapping for multibyte characters
2
3 $ mkdir t
4 $ cd t
5
6 define commands to display help text
7
8 $ cat << EOF > show.py
9 > # Japanese full-width characters:
10 > def show_full_ja(ui, **opts):
11 > u'''\u3042\u3044\u3046\u3048\u304a\u304b\u304d\u304f\u3051 \u3042\u3044\u3046\u3048\u304a\u304b\u304d\u304f\u3051 \u3042\u3044\u3046\u3048\u304a\u304b\u304d\u304f\u3051
12 >
13 > \u3042\u3044\u3046\u3048\u304a\u304b\u304d\u304f\u3051 \u3042\u3044\u3046\u3048\u304a\u304b\u304d\u304f\u3051 \u3042\u3044\u3046\u3048\u304a\u304b\u304d\u304f\u3051 \u3042\u3044\u3046\u3048\u304a\u304b\u304d\u304f\u3051
14 >
15 > \u3042\u3044\u3046\u3048\u304a\u304b\u304d\u304f\u3051\u3042\u3044\u3046\u3048\u304a\u304b\u304d\u304f\u3051\u3042\u3044\u3046\u3048\u304a\u304b\u304d\u304f\u3051\u3042\u3044\u3046\u3048\u304a\u304b\u304d\u304f\u3051
16 > '''
17 >
18 > # Japanese half-width characters:
19 > def show_half_ja(ui, *opts):
20 > u'''\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79 \uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79 \uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79 \uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79
21 >
22 > \uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79 \uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79 \uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79 \uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79 \uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79 \uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79 \uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79 \uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79
23 >
24 > \uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79
25 > '''
26 >
27 > # Japanese ambiguous-width characters:
28 > def show_ambig_ja(ui, **opts):
29 > u'''\u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb \u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb \u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb
30 >
31 > \u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb \u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb \u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb \u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb \u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb \u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb \u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb
32 >
33 > \u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb\u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb\u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb\u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb\u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb\u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb\u03b1\u03b2\u03b3\u03b4\u03c5\u03b6\u03b7\u03b8\u25cb
34 > '''
35 >
36 > # Russian ambiguous-width characters:
37 > def show_ambig_ru(ui, **opts):
38 > u'''\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438
39 >
40 > \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438
41 >
42 > \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438
43 > '''
44 >
45 > cmdtable = {
46 > 'show_full_ja': (show_full_ja, [], ""),
47 > 'show_half_ja': (show_half_ja, [], ""),
48 > 'show_ambig_ja': (show_ambig_ja, [], ""),
49 > 'show_ambig_ru': (show_ambig_ru, [], ""),
50 > }
51 > EOF
52
53 "COLUMNS=60" means that there is no lines which has grater than 58 width
54
55 (1) test text wrapping for non-ambiguous-width characters
56
57 (1-1) display Japanese full-width characters in cp932
58
59 $ COLUMNS=60 hg --encoding cp932 --config extensions.show=./show.py help show_full_ja
60 hg show_full_ja
61
62 \x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf \x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf \x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf (esc)
63
64 \x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf \x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf (esc)
65 \x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf \x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf (esc)
66
67 \x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf\x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf\x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf (esc)
68 \x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf (esc)
69
70 use "hg -v help show_full_ja" to show global options
71
72 (1-2) display Japanese full-width characters in utf-8
73
74 $ COLUMNS=60 hg --encoding utf-8 --config extensions.show=./show.py help show_full_ja
75 hg show_full_ja
76
77 \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91 \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91 \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91 (esc)
78
79 \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91 \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91 (esc)
80 \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91 \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91 (esc)
81
82 \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91 (esc)
83 \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91 (esc)
84
85 use "hg -v help show_full_ja" to show global options
86
87
88 (1-3) display Japanese half-width characters in cp932
89
90 $ COLUMNS=60 hg --encoding cp932 --config extensions.show=./show.py help show_half_ja
91 hg show_half_ja
92
93 \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 (esc)
94
95 \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 (esc)
96 \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 (esc)
97
98 \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 (esc)
99 \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 (esc)
100
101 use "hg -v help show_half_ja" to show global options
102
103 (1-4) display Japanese half-width characters in utf-8
104
105 $ COLUMNS=60 hg --encoding utf-8 --config extensions.show=./show.py help show_half_ja
106 hg show_half_ja
107
108 \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 (esc)
109
110 \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 (esc)
111 \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 (esc)
112
113 \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 (esc)
114 \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 (esc)
115
116 use "hg -v help show_half_ja" to show global options
117
118
119
120 (2) test text wrapping for ambiguous-width characters
121
122 (2-1) treat width of ambiguous characters as narrow (default)
123
124 (2-1-1) display Japanese ambiguous-width characters in cp932
125
126 $ COLUMNS=60 hg --encoding cp932 --config extensions.show=./show.py help show_ambig_ja
127 hg show_ambig_ja
128
129 \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
130
131 \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
132 \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
133
134 \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
135 \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
136
137 use "hg -v help show_ambig_ja" to show global options
138
139 (2-1-2) display Japanese ambiguous-width characters in utf-8
140
141 $ COLUMNS=60 hg --encoding utf-8 --config extensions.show=./show.py help show_ambig_ja
142 hg show_ambig_ja
143
144 \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
145
146 \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
147 \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
148
149 \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
150 \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
151
152 use "hg -v help show_ambig_ja" to show global options
153
154 (2-1-3) display Russian ambiguous-width characters in cp1251
155
156 $ COLUMNS=60 hg --encoding cp1251 --config extensions.show=./show.py help show_ambig_ru
157 hg show_ambig_ru
158
159 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
160
161 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
162 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
163
164 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
165 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
166
167 use "hg -v help show_ambig_ru" to show global options
168
169 (2-1-4) display Russian ambiguous-width characters in utf-8
170
171 $ COLUMNS=60 hg --encoding utf-8 --config extensions.show=./show.py help show_ambig_ru
172 hg show_ambig_ru
173
174 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
175
176 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
177 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
178
179 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
180 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
181
182 use "hg -v help show_ambig_ru" to show global options
183
184
185 (2-2) treat width of ambiguous characters as wide
186
187 (2-2-1) display Japanese ambiguous-width characters in cp932
188
189 $ COLUMNS=60 HGENCODINGAMBIGUOUS=wide hg --encoding cp932 --config extensions.show=./show.py help show_ambig_ja
190 hg show_ambig_ja
191
192 \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
193
194 \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
195 \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
196 \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
197 \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
198
199 \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
200 \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
201 \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
202
203 use "hg -v help show_ambig_ja" to show global options
204
205 (2-2-2) display Japanese ambiguous-width characters in utf-8
206
207 $ COLUMNS=60 HGENCODINGAMBIGUOUS=wide hg --encoding utf-8 --config extensions.show=./show.py help show_ambig_ja
208 hg show_ambig_ja
209
210 \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
211
212 \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
213 \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
214 \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
215 \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
216
217 \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
218 \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
219 \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
220
221 use "hg -v help show_ambig_ja" to show global options
222
223 (2-2-3) display Russian ambiguous-width characters in cp1251
224
225 $ COLUMNS=60 HGENCODINGAMBIGUOUS=wide hg --encoding cp1251 --config extensions.show=./show.py help show_ambig_ru
226 hg show_ambig_ru
227
228 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
229 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
230
231 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
232 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
233 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
234
235 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
236 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
237 \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
238
239 use "hg -v help show_ambig_ru" to show global options
240
241 (2-2-4) display Russian ambiguous-width charactes in utf-8
242
243 $ COLUMNS=60 HGENCODINGAMBIGUOUS=wide hg --encoding utf-8 --config extensions.show=./show.py help show_ambig_ru
244 hg show_ambig_ru
245
246 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
247 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
248
249 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
250 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
251 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
252
253 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
254 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
255 \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
256
257 use "hg -v help show_ambig_ru" to show global options
@@ -1,164 +1,165 b''
1 # encoding.py - character transcoding support for Mercurial
1 # encoding.py - character transcoding support for Mercurial
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import error
8 import error
9 import unicodedata, locale, os
9 import unicodedata, locale, os
10
10
11 def _getpreferredencoding():
11 def _getpreferredencoding():
12 '''
12 '''
13 On darwin, getpreferredencoding ignores the locale environment and
13 On darwin, getpreferredencoding ignores the locale environment and
14 always returns mac-roman. http://bugs.python.org/issue6202 fixes this
14 always returns mac-roman. http://bugs.python.org/issue6202 fixes this
15 for Python 2.7 and up. This is the same corrected code for earlier
15 for Python 2.7 and up. This is the same corrected code for earlier
16 Python versions.
16 Python versions.
17
17
18 However, we can't use a version check for this method, as some distributions
18 However, we can't use a version check for this method, as some distributions
19 patch Python to fix this. Instead, we use it as a 'fixer' for the mac-roman
19 patch Python to fix this. Instead, we use it as a 'fixer' for the mac-roman
20 encoding, as it is unlikely that this encoding is the actually expected.
20 encoding, as it is unlikely that this encoding is the actually expected.
21 '''
21 '''
22 try:
22 try:
23 locale.CODESET
23 locale.CODESET
24 except AttributeError:
24 except AttributeError:
25 # Fall back to parsing environment variables :-(
25 # Fall back to parsing environment variables :-(
26 return locale.getdefaultlocale()[1]
26 return locale.getdefaultlocale()[1]
27
27
28 oldloc = locale.setlocale(locale.LC_CTYPE)
28 oldloc = locale.setlocale(locale.LC_CTYPE)
29 locale.setlocale(locale.LC_CTYPE, "")
29 locale.setlocale(locale.LC_CTYPE, "")
30 result = locale.nl_langinfo(locale.CODESET)
30 result = locale.nl_langinfo(locale.CODESET)
31 locale.setlocale(locale.LC_CTYPE, oldloc)
31 locale.setlocale(locale.LC_CTYPE, oldloc)
32
32
33 return result
33 return result
34
34
35 _encodingfixers = {
35 _encodingfixers = {
36 '646': lambda: 'ascii',
36 '646': lambda: 'ascii',
37 'ANSI_X3.4-1968': lambda: 'ascii',
37 'ANSI_X3.4-1968': lambda: 'ascii',
38 'mac-roman': _getpreferredencoding
38 'mac-roman': _getpreferredencoding
39 }
39 }
40
40
41 try:
41 try:
42 encoding = os.environ.get("HGENCODING")
42 encoding = os.environ.get("HGENCODING")
43 if not encoding:
43 if not encoding:
44 encoding = locale.getpreferredencoding() or 'ascii'
44 encoding = locale.getpreferredencoding() or 'ascii'
45 encoding = _encodingfixers.get(encoding, lambda: encoding)()
45 encoding = _encodingfixers.get(encoding, lambda: encoding)()
46 except locale.Error:
46 except locale.Error:
47 encoding = 'ascii'
47 encoding = 'ascii'
48 encodingmode = os.environ.get("HGENCODINGMODE", "strict")
48 encodingmode = os.environ.get("HGENCODINGMODE", "strict")
49 fallbackencoding = 'ISO-8859-1'
49 fallbackencoding = 'ISO-8859-1'
50
50
51 class localstr(str):
51 class localstr(str):
52 '''This class allows strings that are unmodified to be
52 '''This class allows strings that are unmodified to be
53 round-tripped to the local encoding and back'''
53 round-tripped to the local encoding and back'''
54 def __new__(cls, u, l):
54 def __new__(cls, u, l):
55 s = str.__new__(cls, l)
55 s = str.__new__(cls, l)
56 s._utf8 = u
56 s._utf8 = u
57 return s
57 return s
58 def __hash__(self):
58 def __hash__(self):
59 return hash(self._utf8) # avoid collisions in local string space
59 return hash(self._utf8) # avoid collisions in local string space
60
60
61 def tolocal(s):
61 def tolocal(s):
62 """
62 """
63 Convert a string from internal UTF-8 to local encoding
63 Convert a string from internal UTF-8 to local encoding
64
64
65 All internal strings should be UTF-8 but some repos before the
65 All internal strings should be UTF-8 but some repos before the
66 implementation of locale support may contain latin1 or possibly
66 implementation of locale support may contain latin1 or possibly
67 other character sets. We attempt to decode everything strictly
67 other character sets. We attempt to decode everything strictly
68 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
68 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
69 replace unknown characters.
69 replace unknown characters.
70
70
71 The localstr class is used to cache the known UTF-8 encoding of
71 The localstr class is used to cache the known UTF-8 encoding of
72 strings next to their local representation to allow lossless
72 strings next to their local representation to allow lossless
73 round-trip conversion back to UTF-8.
73 round-trip conversion back to UTF-8.
74
74
75 >>> u = 'foo: \\xc3\\xa4' # utf-8
75 >>> u = 'foo: \\xc3\\xa4' # utf-8
76 >>> l = tolocal(u)
76 >>> l = tolocal(u)
77 >>> l
77 >>> l
78 'foo: ?'
78 'foo: ?'
79 >>> fromlocal(l)
79 >>> fromlocal(l)
80 'foo: \\xc3\\xa4'
80 'foo: \\xc3\\xa4'
81 >>> u2 = 'foo: \\xc3\\xa1'
81 >>> u2 = 'foo: \\xc3\\xa1'
82 >>> d = { l: 1, tolocal(u2): 2 }
82 >>> d = { l: 1, tolocal(u2): 2 }
83 >>> d # no collision
83 >>> d # no collision
84 {'foo: ?': 1, 'foo: ?': 2}
84 {'foo: ?': 1, 'foo: ?': 2}
85 >>> 'foo: ?' in d
85 >>> 'foo: ?' in d
86 False
86 False
87 >>> l1 = 'foo: \\xe4' # historical latin1 fallback
87 >>> l1 = 'foo: \\xe4' # historical latin1 fallback
88 >>> l = tolocal(l1)
88 >>> l = tolocal(l1)
89 >>> l
89 >>> l
90 'foo: ?'
90 'foo: ?'
91 >>> fromlocal(l) # magically in utf-8
91 >>> fromlocal(l) # magically in utf-8
92 'foo: \\xc3\\xa4'
92 'foo: \\xc3\\xa4'
93 """
93 """
94
94
95 for e in ('UTF-8', fallbackencoding):
95 for e in ('UTF-8', fallbackencoding):
96 try:
96 try:
97 u = s.decode(e) # attempt strict decoding
97 u = s.decode(e) # attempt strict decoding
98 r = u.encode(encoding, "replace")
98 r = u.encode(encoding, "replace")
99 if u == r.decode(encoding):
99 if u == r.decode(encoding):
100 # r is a safe, non-lossy encoding of s
100 # r is a safe, non-lossy encoding of s
101 return r
101 return r
102 elif e == 'UTF-8':
102 elif e == 'UTF-8':
103 return localstr(s, r)
103 return localstr(s, r)
104 else:
104 else:
105 return localstr(u.encode('UTF-8'), r)
105 return localstr(u.encode('UTF-8'), r)
106
106
107 except LookupError, k:
107 except LookupError, k:
108 raise error.Abort("%s, please check your locale settings" % k)
108 raise error.Abort("%s, please check your locale settings" % k)
109 except UnicodeDecodeError:
109 except UnicodeDecodeError:
110 pass
110 pass
111 u = s.decode("utf-8", "replace") # last ditch
111 u = s.decode("utf-8", "replace") # last ditch
112 return u.encode(encoding, "replace") # can't round-trip
112 return u.encode(encoding, "replace") # can't round-trip
113
113
114 def fromlocal(s):
114 def fromlocal(s):
115 """
115 """
116 Convert a string from the local character encoding to UTF-8
116 Convert a string from the local character encoding to UTF-8
117
117
118 We attempt to decode strings using the encoding mode set by
118 We attempt to decode strings using the encoding mode set by
119 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
119 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
120 characters will cause an error message. Other modes include
120 characters will cause an error message. Other modes include
121 'replace', which replaces unknown characters with a special
121 'replace', which replaces unknown characters with a special
122 Unicode character, and 'ignore', which drops the character.
122 Unicode character, and 'ignore', which drops the character.
123 """
123 """
124
124
125 # can we do a lossless round-trip?
125 # can we do a lossless round-trip?
126 if isinstance(s, localstr):
126 if isinstance(s, localstr):
127 return s._utf8
127 return s._utf8
128
128
129 try:
129 try:
130 return s.decode(encoding, encodingmode).encode("utf-8")
130 return s.decode(encoding, encodingmode).encode("utf-8")
131 except UnicodeDecodeError, inst:
131 except UnicodeDecodeError, inst:
132 sub = s[max(0, inst.start - 10):inst.start + 10]
132 sub = s[max(0, inst.start - 10):inst.start + 10]
133 raise error.Abort("decoding near '%s': %s!" % (sub, inst))
133 raise error.Abort("decoding near '%s': %s!" % (sub, inst))
134 except LookupError, k:
134 except LookupError, k:
135 raise error.Abort("%s, please check your locale settings" % k)
135 raise error.Abort("%s, please check your locale settings" % k)
136
136
137 # How to treat ambiguous-width characters. Set to 'wide' to treat as wide.
137 # How to treat ambiguous-width characters. Set to 'wide' to treat as wide.
138 ambiguous = os.environ.get("HGENCODINGAMBIGUOUS", "narrow")
138 wide = (os.environ.get("HGENCODINGAMBIGUOUS", "narrow") == "wide"
139 and "WFA" or "WF")
139
140
140 def colwidth(s):
141 def colwidth(s):
141 "Find the column width of a UTF-8 string for display"
142 "Find the column width of a UTF-8 string for display"
142 d = s.decode(encoding, 'replace')
143 return ucolwidth(s.decode(encoding, 'replace'))
144
145 def ucolwidth(d):
146 "Find the column width of a Unicode string for display"
143 eaw = getattr(unicodedata, 'east_asian_width', None)
147 eaw = getattr(unicodedata, 'east_asian_width', None)
144 if eaw is not None:
148 if eaw is not None:
145 wide = "WF"
146 if ambiguous == "wide":
147 wide = "WFA"
148 return sum([eaw(c) in wide and 2 or 1 for c in d])
149 return sum([eaw(c) in wide and 2 or 1 for c in d])
149 return len(d)
150 return len(d)
150
151
151 def lower(s):
152 def lower(s):
152 "best-effort encoding-aware case-folding of local string s"
153 "best-effort encoding-aware case-folding of local string s"
153 try:
154 try:
154 if isinstance(s, localstr):
155 if isinstance(s, localstr):
155 u = s._utf8.decode("utf-8")
156 u = s._utf8.decode("utf-8")
156 else:
157 else:
157 u = s.decode(encoding, encodingmode)
158 u = s.decode(encoding, encodingmode)
158
159
159 lu = u.lower()
160 lu = u.lower()
160 if u == lu:
161 if u == lu:
161 return s # preserve localstring
162 return s # preserve localstring
162 return lu.encode(encoding)
163 return lu.encode(encoding)
163 except UnicodeError:
164 except UnicodeError:
164 return s.lower() # we don't know how to fold this except in ASCII
165 return s.lower() # we don't know how to fold this except in ASCII
@@ -1,1623 +1,1693 b''
1 # util.py - Mercurial utility functions and platform specfic implementations
1 # util.py - Mercurial utility functions and platform specfic implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specfic implementations.
10 """Mercurial utility functions and platform specfic implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from i18n import _
16 from i18n import _
17 import error, osutil, encoding
17 import error, osutil, encoding
18 import errno, re, shutil, sys, tempfile, traceback
18 import errno, re, shutil, sys, tempfile, traceback
19 import os, time, calendar, textwrap, unicodedata, signal
19 import os, time, calendar, textwrap, signal
20 import imp, socket, urllib
20 import imp, socket, urllib
21
21
22 # Python compatibility
22 # Python compatibility
23
23
24 def sha1(s):
24 def sha1(s):
25 return _fastsha1(s)
25 return _fastsha1(s)
26
26
27 def _fastsha1(s):
27 def _fastsha1(s):
28 # This function will import sha1 from hashlib or sha (whichever is
28 # This function will import sha1 from hashlib or sha (whichever is
29 # available) and overwrite itself with it on the first call.
29 # available) and overwrite itself with it on the first call.
30 # Subsequent calls will go directly to the imported function.
30 # Subsequent calls will go directly to the imported function.
31 if sys.version_info >= (2, 5):
31 if sys.version_info >= (2, 5):
32 from hashlib import sha1 as _sha1
32 from hashlib import sha1 as _sha1
33 else:
33 else:
34 from sha import sha as _sha1
34 from sha import sha as _sha1
35 global _fastsha1, sha1
35 global _fastsha1, sha1
36 _fastsha1 = sha1 = _sha1
36 _fastsha1 = sha1 = _sha1
37 return _sha1(s)
37 return _sha1(s)
38
38
39 import __builtin__
39 import __builtin__
40
40
41 if sys.version_info[0] < 3:
41 if sys.version_info[0] < 3:
42 def fakebuffer(sliceable, offset=0):
42 def fakebuffer(sliceable, offset=0):
43 return sliceable[offset:]
43 return sliceable[offset:]
44 else:
44 else:
45 def fakebuffer(sliceable, offset=0):
45 def fakebuffer(sliceable, offset=0):
46 return memoryview(sliceable)[offset:]
46 return memoryview(sliceable)[offset:]
47 try:
47 try:
48 buffer
48 buffer
49 except NameError:
49 except NameError:
50 __builtin__.buffer = fakebuffer
50 __builtin__.buffer = fakebuffer
51
51
52 import subprocess
52 import subprocess
53 closefds = os.name == 'posix'
53 closefds = os.name == 'posix'
54
54
55 def popen2(cmd, env=None, newlines=False):
55 def popen2(cmd, env=None, newlines=False):
56 # Setting bufsize to -1 lets the system decide the buffer size.
56 # Setting bufsize to -1 lets the system decide the buffer size.
57 # The default for bufsize is 0, meaning unbuffered. This leads to
57 # The default for bufsize is 0, meaning unbuffered. This leads to
58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
60 close_fds=closefds,
60 close_fds=closefds,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
62 universal_newlines=newlines,
62 universal_newlines=newlines,
63 env=env)
63 env=env)
64 return p.stdin, p.stdout
64 return p.stdin, p.stdout
65
65
66 def popen3(cmd, env=None, newlines=False):
66 def popen3(cmd, env=None, newlines=False):
67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
68 close_fds=closefds,
68 close_fds=closefds,
69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
70 stderr=subprocess.PIPE,
70 stderr=subprocess.PIPE,
71 universal_newlines=newlines,
71 universal_newlines=newlines,
72 env=env)
72 env=env)
73 return p.stdin, p.stdout, p.stderr
73 return p.stdin, p.stdout, p.stderr
74
74
75 def version():
75 def version():
76 """Return version information if available."""
76 """Return version information if available."""
77 try:
77 try:
78 import __version__
78 import __version__
79 return __version__.version
79 return __version__.version
80 except ImportError:
80 except ImportError:
81 return 'unknown'
81 return 'unknown'
82
82
83 # used by parsedate
83 # used by parsedate
84 defaultdateformats = (
84 defaultdateformats = (
85 '%Y-%m-%d %H:%M:%S',
85 '%Y-%m-%d %H:%M:%S',
86 '%Y-%m-%d %I:%M:%S%p',
86 '%Y-%m-%d %I:%M:%S%p',
87 '%Y-%m-%d %H:%M',
87 '%Y-%m-%d %H:%M',
88 '%Y-%m-%d %I:%M%p',
88 '%Y-%m-%d %I:%M%p',
89 '%Y-%m-%d',
89 '%Y-%m-%d',
90 '%m-%d',
90 '%m-%d',
91 '%m/%d',
91 '%m/%d',
92 '%m/%d/%y',
92 '%m/%d/%y',
93 '%m/%d/%Y',
93 '%m/%d/%Y',
94 '%a %b %d %H:%M:%S %Y',
94 '%a %b %d %H:%M:%S %Y',
95 '%a %b %d %I:%M:%S%p %Y',
95 '%a %b %d %I:%M:%S%p %Y',
96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
97 '%b %d %H:%M:%S %Y',
97 '%b %d %H:%M:%S %Y',
98 '%b %d %I:%M:%S%p %Y',
98 '%b %d %I:%M:%S%p %Y',
99 '%b %d %H:%M:%S',
99 '%b %d %H:%M:%S',
100 '%b %d %I:%M:%S%p',
100 '%b %d %I:%M:%S%p',
101 '%b %d %H:%M',
101 '%b %d %H:%M',
102 '%b %d %I:%M%p',
102 '%b %d %I:%M%p',
103 '%b %d %Y',
103 '%b %d %Y',
104 '%b %d',
104 '%b %d',
105 '%H:%M:%S',
105 '%H:%M:%S',
106 '%I:%M:%S%p',
106 '%I:%M:%S%p',
107 '%H:%M',
107 '%H:%M',
108 '%I:%M%p',
108 '%I:%M%p',
109 )
109 )
110
110
111 extendeddateformats = defaultdateformats + (
111 extendeddateformats = defaultdateformats + (
112 "%Y",
112 "%Y",
113 "%Y-%m",
113 "%Y-%m",
114 "%b",
114 "%b",
115 "%b %Y",
115 "%b %Y",
116 )
116 )
117
117
118 def cachefunc(func):
118 def cachefunc(func):
119 '''cache the result of function calls'''
119 '''cache the result of function calls'''
120 # XXX doesn't handle keywords args
120 # XXX doesn't handle keywords args
121 cache = {}
121 cache = {}
122 if func.func_code.co_argcount == 1:
122 if func.func_code.co_argcount == 1:
123 # we gain a small amount of time because
123 # we gain a small amount of time because
124 # we don't need to pack/unpack the list
124 # we don't need to pack/unpack the list
125 def f(arg):
125 def f(arg):
126 if arg not in cache:
126 if arg not in cache:
127 cache[arg] = func(arg)
127 cache[arg] = func(arg)
128 return cache[arg]
128 return cache[arg]
129 else:
129 else:
130 def f(*args):
130 def f(*args):
131 if args not in cache:
131 if args not in cache:
132 cache[args] = func(*args)
132 cache[args] = func(*args)
133 return cache[args]
133 return cache[args]
134
134
135 return f
135 return f
136
136
137 def lrucachefunc(func):
137 def lrucachefunc(func):
138 '''cache most recent results of function calls'''
138 '''cache most recent results of function calls'''
139 cache = {}
139 cache = {}
140 order = []
140 order = []
141 if func.func_code.co_argcount == 1:
141 if func.func_code.co_argcount == 1:
142 def f(arg):
142 def f(arg):
143 if arg not in cache:
143 if arg not in cache:
144 if len(cache) > 20:
144 if len(cache) > 20:
145 del cache[order.pop(0)]
145 del cache[order.pop(0)]
146 cache[arg] = func(arg)
146 cache[arg] = func(arg)
147 else:
147 else:
148 order.remove(arg)
148 order.remove(arg)
149 order.append(arg)
149 order.append(arg)
150 return cache[arg]
150 return cache[arg]
151 else:
151 else:
152 def f(*args):
152 def f(*args):
153 if args not in cache:
153 if args not in cache:
154 if len(cache) > 20:
154 if len(cache) > 20:
155 del cache[order.pop(0)]
155 del cache[order.pop(0)]
156 cache[args] = func(*args)
156 cache[args] = func(*args)
157 else:
157 else:
158 order.remove(args)
158 order.remove(args)
159 order.append(args)
159 order.append(args)
160 return cache[args]
160 return cache[args]
161
161
162 return f
162 return f
163
163
164 class propertycache(object):
164 class propertycache(object):
165 def __init__(self, func):
165 def __init__(self, func):
166 self.func = func
166 self.func = func
167 self.name = func.__name__
167 self.name = func.__name__
168 def __get__(self, obj, type=None):
168 def __get__(self, obj, type=None):
169 result = self.func(obj)
169 result = self.func(obj)
170 setattr(obj, self.name, result)
170 setattr(obj, self.name, result)
171 return result
171 return result
172
172
173 def pipefilter(s, cmd):
173 def pipefilter(s, cmd):
174 '''filter string S through command CMD, returning its output'''
174 '''filter string S through command CMD, returning its output'''
175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
177 pout, perr = p.communicate(s)
177 pout, perr = p.communicate(s)
178 return pout
178 return pout
179
179
180 def tempfilter(s, cmd):
180 def tempfilter(s, cmd):
181 '''filter string S through a pair of temporary files with CMD.
181 '''filter string S through a pair of temporary files with CMD.
182 CMD is used as a template to create the real command to be run,
182 CMD is used as a template to create the real command to be run,
183 with the strings INFILE and OUTFILE replaced by the real names of
183 with the strings INFILE and OUTFILE replaced by the real names of
184 the temporary files generated.'''
184 the temporary files generated.'''
185 inname, outname = None, None
185 inname, outname = None, None
186 try:
186 try:
187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
188 fp = os.fdopen(infd, 'wb')
188 fp = os.fdopen(infd, 'wb')
189 fp.write(s)
189 fp.write(s)
190 fp.close()
190 fp.close()
191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
192 os.close(outfd)
192 os.close(outfd)
193 cmd = cmd.replace('INFILE', inname)
193 cmd = cmd.replace('INFILE', inname)
194 cmd = cmd.replace('OUTFILE', outname)
194 cmd = cmd.replace('OUTFILE', outname)
195 code = os.system(cmd)
195 code = os.system(cmd)
196 if sys.platform == 'OpenVMS' and code & 1:
196 if sys.platform == 'OpenVMS' and code & 1:
197 code = 0
197 code = 0
198 if code:
198 if code:
199 raise Abort(_("command '%s' failed: %s") %
199 raise Abort(_("command '%s' failed: %s") %
200 (cmd, explainexit(code)))
200 (cmd, explainexit(code)))
201 fp = open(outname, 'rb')
201 fp = open(outname, 'rb')
202 r = fp.read()
202 r = fp.read()
203 fp.close()
203 fp.close()
204 return r
204 return r
205 finally:
205 finally:
206 try:
206 try:
207 if inname:
207 if inname:
208 os.unlink(inname)
208 os.unlink(inname)
209 except OSError:
209 except OSError:
210 pass
210 pass
211 try:
211 try:
212 if outname:
212 if outname:
213 os.unlink(outname)
213 os.unlink(outname)
214 except OSError:
214 except OSError:
215 pass
215 pass
216
216
217 filtertable = {
217 filtertable = {
218 'tempfile:': tempfilter,
218 'tempfile:': tempfilter,
219 'pipe:': pipefilter,
219 'pipe:': pipefilter,
220 }
220 }
221
221
222 def filter(s, cmd):
222 def filter(s, cmd):
223 "filter a string through a command that transforms its input to its output"
223 "filter a string through a command that transforms its input to its output"
224 for name, fn in filtertable.iteritems():
224 for name, fn in filtertable.iteritems():
225 if cmd.startswith(name):
225 if cmd.startswith(name):
226 return fn(s, cmd[len(name):].lstrip())
226 return fn(s, cmd[len(name):].lstrip())
227 return pipefilter(s, cmd)
227 return pipefilter(s, cmd)
228
228
229 def binary(s):
229 def binary(s):
230 """return true if a string is binary data"""
230 """return true if a string is binary data"""
231 return bool(s and '\0' in s)
231 return bool(s and '\0' in s)
232
232
233 def increasingchunks(source, min=1024, max=65536):
233 def increasingchunks(source, min=1024, max=65536):
234 '''return no less than min bytes per chunk while data remains,
234 '''return no less than min bytes per chunk while data remains,
235 doubling min after each chunk until it reaches max'''
235 doubling min after each chunk until it reaches max'''
236 def log2(x):
236 def log2(x):
237 if not x:
237 if not x:
238 return 0
238 return 0
239 i = 0
239 i = 0
240 while x:
240 while x:
241 x >>= 1
241 x >>= 1
242 i += 1
242 i += 1
243 return i - 1
243 return i - 1
244
244
245 buf = []
245 buf = []
246 blen = 0
246 blen = 0
247 for chunk in source:
247 for chunk in source:
248 buf.append(chunk)
248 buf.append(chunk)
249 blen += len(chunk)
249 blen += len(chunk)
250 if blen >= min:
250 if blen >= min:
251 if min < max:
251 if min < max:
252 min = min << 1
252 min = min << 1
253 nmin = 1 << log2(blen)
253 nmin = 1 << log2(blen)
254 if nmin > min:
254 if nmin > min:
255 min = nmin
255 min = nmin
256 if min > max:
256 if min > max:
257 min = max
257 min = max
258 yield ''.join(buf)
258 yield ''.join(buf)
259 blen = 0
259 blen = 0
260 buf = []
260 buf = []
261 if buf:
261 if buf:
262 yield ''.join(buf)
262 yield ''.join(buf)
263
263
264 Abort = error.Abort
264 Abort = error.Abort
265
265
266 def always(fn):
266 def always(fn):
267 return True
267 return True
268
268
269 def never(fn):
269 def never(fn):
270 return False
270 return False
271
271
272 def pathto(root, n1, n2):
272 def pathto(root, n1, n2):
273 '''return the relative path from one place to another.
273 '''return the relative path from one place to another.
274 root should use os.sep to separate directories
274 root should use os.sep to separate directories
275 n1 should use os.sep to separate directories
275 n1 should use os.sep to separate directories
276 n2 should use "/" to separate directories
276 n2 should use "/" to separate directories
277 returns an os.sep-separated path.
277 returns an os.sep-separated path.
278
278
279 If n1 is a relative path, it's assumed it's
279 If n1 is a relative path, it's assumed it's
280 relative to root.
280 relative to root.
281 n2 should always be relative to root.
281 n2 should always be relative to root.
282 '''
282 '''
283 if not n1:
283 if not n1:
284 return localpath(n2)
284 return localpath(n2)
285 if os.path.isabs(n1):
285 if os.path.isabs(n1):
286 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
286 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
287 return os.path.join(root, localpath(n2))
287 return os.path.join(root, localpath(n2))
288 n2 = '/'.join((pconvert(root), n2))
288 n2 = '/'.join((pconvert(root), n2))
289 a, b = splitpath(n1), n2.split('/')
289 a, b = splitpath(n1), n2.split('/')
290 a.reverse()
290 a.reverse()
291 b.reverse()
291 b.reverse()
292 while a and b and a[-1] == b[-1]:
292 while a and b and a[-1] == b[-1]:
293 a.pop()
293 a.pop()
294 b.pop()
294 b.pop()
295 b.reverse()
295 b.reverse()
296 return os.sep.join((['..'] * len(a)) + b) or '.'
296 return os.sep.join((['..'] * len(a)) + b) or '.'
297
297
298 _hgexecutable = None
298 _hgexecutable = None
299
299
300 def mainfrozen():
300 def mainfrozen():
301 """return True if we are a frozen executable.
301 """return True if we are a frozen executable.
302
302
303 The code supports py2exe (most common, Windows only) and tools/freeze
303 The code supports py2exe (most common, Windows only) and tools/freeze
304 (portable, not much used).
304 (portable, not much used).
305 """
305 """
306 return (hasattr(sys, "frozen") or # new py2exe
306 return (hasattr(sys, "frozen") or # new py2exe
307 hasattr(sys, "importers") or # old py2exe
307 hasattr(sys, "importers") or # old py2exe
308 imp.is_frozen("__main__")) # tools/freeze
308 imp.is_frozen("__main__")) # tools/freeze
309
309
310 def hgexecutable():
310 def hgexecutable():
311 """return location of the 'hg' executable.
311 """return location of the 'hg' executable.
312
312
313 Defaults to $HG or 'hg' in the search path.
313 Defaults to $HG or 'hg' in the search path.
314 """
314 """
315 if _hgexecutable is None:
315 if _hgexecutable is None:
316 hg = os.environ.get('HG')
316 hg = os.environ.get('HG')
317 if hg:
317 if hg:
318 _sethgexecutable(hg)
318 _sethgexecutable(hg)
319 elif mainfrozen():
319 elif mainfrozen():
320 _sethgexecutable(sys.executable)
320 _sethgexecutable(sys.executable)
321 else:
321 else:
322 exe = findexe('hg') or os.path.basename(sys.argv[0])
322 exe = findexe('hg') or os.path.basename(sys.argv[0])
323 _sethgexecutable(exe)
323 _sethgexecutable(exe)
324 return _hgexecutable
324 return _hgexecutable
325
325
326 def _sethgexecutable(path):
326 def _sethgexecutable(path):
327 """set location of the 'hg' executable"""
327 """set location of the 'hg' executable"""
328 global _hgexecutable
328 global _hgexecutable
329 _hgexecutable = path
329 _hgexecutable = path
330
330
331 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
331 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
332 '''enhanced shell command execution.
332 '''enhanced shell command execution.
333 run with environment maybe modified, maybe in different dir.
333 run with environment maybe modified, maybe in different dir.
334
334
335 if command fails and onerr is None, return status. if ui object,
335 if command fails and onerr is None, return status. if ui object,
336 print error message and return status, else raise onerr object as
336 print error message and return status, else raise onerr object as
337 exception.
337 exception.
338
338
339 if out is specified, it is assumed to be a file-like object that has a
339 if out is specified, it is assumed to be a file-like object that has a
340 write() method. stdout and stderr will be redirected to out.'''
340 write() method. stdout and stderr will be redirected to out.'''
341 try:
341 try:
342 sys.stdout.flush()
342 sys.stdout.flush()
343 except Exception:
343 except Exception:
344 pass
344 pass
345 def py2shell(val):
345 def py2shell(val):
346 'convert python object into string that is useful to shell'
346 'convert python object into string that is useful to shell'
347 if val is None or val is False:
347 if val is None or val is False:
348 return '0'
348 return '0'
349 if val is True:
349 if val is True:
350 return '1'
350 return '1'
351 return str(val)
351 return str(val)
352 origcmd = cmd
352 origcmd = cmd
353 cmd = quotecommand(cmd)
353 cmd = quotecommand(cmd)
354 env = dict(os.environ)
354 env = dict(os.environ)
355 env.update((k, py2shell(v)) for k, v in environ.iteritems())
355 env.update((k, py2shell(v)) for k, v in environ.iteritems())
356 env['HG'] = hgexecutable()
356 env['HG'] = hgexecutable()
357 if out is None or out == sys.__stdout__:
357 if out is None or out == sys.__stdout__:
358 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
358 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
359 env=env, cwd=cwd)
359 env=env, cwd=cwd)
360 else:
360 else:
361 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
361 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
362 env=env, cwd=cwd, stdout=subprocess.PIPE,
362 env=env, cwd=cwd, stdout=subprocess.PIPE,
363 stderr=subprocess.STDOUT)
363 stderr=subprocess.STDOUT)
364 for line in proc.stdout:
364 for line in proc.stdout:
365 out.write(line)
365 out.write(line)
366 proc.wait()
366 proc.wait()
367 rc = proc.returncode
367 rc = proc.returncode
368 if sys.platform == 'OpenVMS' and rc & 1:
368 if sys.platform == 'OpenVMS' and rc & 1:
369 rc = 0
369 rc = 0
370 if rc and onerr:
370 if rc and onerr:
371 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
371 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
372 explainexit(rc)[0])
372 explainexit(rc)[0])
373 if errprefix:
373 if errprefix:
374 errmsg = '%s: %s' % (errprefix, errmsg)
374 errmsg = '%s: %s' % (errprefix, errmsg)
375 try:
375 try:
376 onerr.warn(errmsg + '\n')
376 onerr.warn(errmsg + '\n')
377 except AttributeError:
377 except AttributeError:
378 raise onerr(errmsg)
378 raise onerr(errmsg)
379 return rc
379 return rc
380
380
381 def checksignature(func):
381 def checksignature(func):
382 '''wrap a function with code to check for calling errors'''
382 '''wrap a function with code to check for calling errors'''
383 def check(*args, **kwargs):
383 def check(*args, **kwargs):
384 try:
384 try:
385 return func(*args, **kwargs)
385 return func(*args, **kwargs)
386 except TypeError:
386 except TypeError:
387 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
387 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
388 raise error.SignatureError
388 raise error.SignatureError
389 raise
389 raise
390
390
391 return check
391 return check
392
392
393 def makedir(path, notindexed):
393 def makedir(path, notindexed):
394 os.mkdir(path)
394 os.mkdir(path)
395
395
396 def unlinkpath(f):
396 def unlinkpath(f):
397 """unlink and remove the directory if it is empty"""
397 """unlink and remove the directory if it is empty"""
398 os.unlink(f)
398 os.unlink(f)
399 # try removing directories that might now be empty
399 # try removing directories that might now be empty
400 try:
400 try:
401 os.removedirs(os.path.dirname(f))
401 os.removedirs(os.path.dirname(f))
402 except OSError:
402 except OSError:
403 pass
403 pass
404
404
405 def copyfile(src, dest):
405 def copyfile(src, dest):
406 "copy a file, preserving mode and atime/mtime"
406 "copy a file, preserving mode and atime/mtime"
407 if os.path.islink(src):
407 if os.path.islink(src):
408 try:
408 try:
409 os.unlink(dest)
409 os.unlink(dest)
410 except OSError:
410 except OSError:
411 pass
411 pass
412 os.symlink(os.readlink(src), dest)
412 os.symlink(os.readlink(src), dest)
413 else:
413 else:
414 try:
414 try:
415 shutil.copyfile(src, dest)
415 shutil.copyfile(src, dest)
416 shutil.copymode(src, dest)
416 shutil.copymode(src, dest)
417 except shutil.Error, inst:
417 except shutil.Error, inst:
418 raise Abort(str(inst))
418 raise Abort(str(inst))
419
419
420 def copyfiles(src, dst, hardlink=None):
420 def copyfiles(src, dst, hardlink=None):
421 """Copy a directory tree using hardlinks if possible"""
421 """Copy a directory tree using hardlinks if possible"""
422
422
423 if hardlink is None:
423 if hardlink is None:
424 hardlink = (os.stat(src).st_dev ==
424 hardlink = (os.stat(src).st_dev ==
425 os.stat(os.path.dirname(dst)).st_dev)
425 os.stat(os.path.dirname(dst)).st_dev)
426
426
427 num = 0
427 num = 0
428 if os.path.isdir(src):
428 if os.path.isdir(src):
429 os.mkdir(dst)
429 os.mkdir(dst)
430 for name, kind in osutil.listdir(src):
430 for name, kind in osutil.listdir(src):
431 srcname = os.path.join(src, name)
431 srcname = os.path.join(src, name)
432 dstname = os.path.join(dst, name)
432 dstname = os.path.join(dst, name)
433 hardlink, n = copyfiles(srcname, dstname, hardlink)
433 hardlink, n = copyfiles(srcname, dstname, hardlink)
434 num += n
434 num += n
435 else:
435 else:
436 if hardlink:
436 if hardlink:
437 try:
437 try:
438 oslink(src, dst)
438 oslink(src, dst)
439 except (IOError, OSError):
439 except (IOError, OSError):
440 hardlink = False
440 hardlink = False
441 shutil.copy(src, dst)
441 shutil.copy(src, dst)
442 else:
442 else:
443 shutil.copy(src, dst)
443 shutil.copy(src, dst)
444 num += 1
444 num += 1
445
445
446 return hardlink, num
446 return hardlink, num
447
447
448 _winreservednames = '''con prn aux nul
448 _winreservednames = '''con prn aux nul
449 com1 com2 com3 com4 com5 com6 com7 com8 com9
449 com1 com2 com3 com4 com5 com6 com7 com8 com9
450 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
450 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
451 _winreservedchars = ':*?"<>|'
451 _winreservedchars = ':*?"<>|'
452 def checkwinfilename(path):
452 def checkwinfilename(path):
453 '''Check that the base-relative path is a valid filename on Windows.
453 '''Check that the base-relative path is a valid filename on Windows.
454 Returns None if the path is ok, or a UI string describing the problem.
454 Returns None if the path is ok, or a UI string describing the problem.
455
455
456 >>> checkwinfilename("just/a/normal/path")
456 >>> checkwinfilename("just/a/normal/path")
457 >>> checkwinfilename("foo/bar/con.xml")
457 >>> checkwinfilename("foo/bar/con.xml")
458 "filename contains 'con', which is reserved on Windows"
458 "filename contains 'con', which is reserved on Windows"
459 >>> checkwinfilename("foo/con.xml/bar")
459 >>> checkwinfilename("foo/con.xml/bar")
460 "filename contains 'con', which is reserved on Windows"
460 "filename contains 'con', which is reserved on Windows"
461 >>> checkwinfilename("foo/bar/xml.con")
461 >>> checkwinfilename("foo/bar/xml.con")
462 >>> checkwinfilename("foo/bar/AUX/bla.txt")
462 >>> checkwinfilename("foo/bar/AUX/bla.txt")
463 "filename contains 'AUX', which is reserved on Windows"
463 "filename contains 'AUX', which is reserved on Windows"
464 >>> checkwinfilename("foo/bar/bla:.txt")
464 >>> checkwinfilename("foo/bar/bla:.txt")
465 "filename contains ':', which is reserved on Windows"
465 "filename contains ':', which is reserved on Windows"
466 >>> checkwinfilename("foo/bar/b\07la.txt")
466 >>> checkwinfilename("foo/bar/b\07la.txt")
467 "filename contains '\\\\x07', which is invalid on Windows"
467 "filename contains '\\\\x07', which is invalid on Windows"
468 >>> checkwinfilename("foo/bar/bla ")
468 >>> checkwinfilename("foo/bar/bla ")
469 "filename ends with ' ', which is not allowed on Windows"
469 "filename ends with ' ', which is not allowed on Windows"
470 '''
470 '''
471 for n in path.replace('\\', '/').split('/'):
471 for n in path.replace('\\', '/').split('/'):
472 if not n:
472 if not n:
473 continue
473 continue
474 for c in n:
474 for c in n:
475 if c in _winreservedchars:
475 if c in _winreservedchars:
476 return _("filename contains '%s', which is reserved "
476 return _("filename contains '%s', which is reserved "
477 "on Windows") % c
477 "on Windows") % c
478 if ord(c) <= 31:
478 if ord(c) <= 31:
479 return _("filename contains %r, which is invalid "
479 return _("filename contains %r, which is invalid "
480 "on Windows") % c
480 "on Windows") % c
481 base = n.split('.')[0]
481 base = n.split('.')[0]
482 if base and base.lower() in _winreservednames:
482 if base and base.lower() in _winreservednames:
483 return _("filename contains '%s', which is reserved "
483 return _("filename contains '%s', which is reserved "
484 "on Windows") % base
484 "on Windows") % base
485 t = n[-1]
485 t = n[-1]
486 if t in '. ':
486 if t in '. ':
487 return _("filename ends with '%s', which is not allowed "
487 return _("filename ends with '%s', which is not allowed "
488 "on Windows") % t
488 "on Windows") % t
489
489
490 def lookupreg(key, name=None, scope=None):
490 def lookupreg(key, name=None, scope=None):
491 return None
491 return None
492
492
493 def hidewindow():
493 def hidewindow():
494 """Hide current shell window.
494 """Hide current shell window.
495
495
496 Used to hide the window opened when starting asynchronous
496 Used to hide the window opened when starting asynchronous
497 child process under Windows, unneeded on other systems.
497 child process under Windows, unneeded on other systems.
498 """
498 """
499 pass
499 pass
500
500
501 if os.name == 'nt':
501 if os.name == 'nt':
502 checkosfilename = checkwinfilename
502 checkosfilename = checkwinfilename
503 from windows import *
503 from windows import *
504 else:
504 else:
505 from posix import *
505 from posix import *
506
506
507 def makelock(info, pathname):
507 def makelock(info, pathname):
508 try:
508 try:
509 return os.symlink(info, pathname)
509 return os.symlink(info, pathname)
510 except OSError, why:
510 except OSError, why:
511 if why.errno == errno.EEXIST:
511 if why.errno == errno.EEXIST:
512 raise
512 raise
513 except AttributeError: # no symlink in os
513 except AttributeError: # no symlink in os
514 pass
514 pass
515
515
516 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
516 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
517 os.write(ld, info)
517 os.write(ld, info)
518 os.close(ld)
518 os.close(ld)
519
519
520 def readlock(pathname):
520 def readlock(pathname):
521 try:
521 try:
522 return os.readlink(pathname)
522 return os.readlink(pathname)
523 except OSError, why:
523 except OSError, why:
524 if why.errno not in (errno.EINVAL, errno.ENOSYS):
524 if why.errno not in (errno.EINVAL, errno.ENOSYS):
525 raise
525 raise
526 except AttributeError: # no symlink in os
526 except AttributeError: # no symlink in os
527 pass
527 pass
528 fp = posixfile(pathname)
528 fp = posixfile(pathname)
529 r = fp.read()
529 r = fp.read()
530 fp.close()
530 fp.close()
531 return r
531 return r
532
532
533 def fstat(fp):
533 def fstat(fp):
534 '''stat file object that may not have fileno method.'''
534 '''stat file object that may not have fileno method.'''
535 try:
535 try:
536 return os.fstat(fp.fileno())
536 return os.fstat(fp.fileno())
537 except AttributeError:
537 except AttributeError:
538 return os.stat(fp.name)
538 return os.stat(fp.name)
539
539
540 # File system features
540 # File system features
541
541
542 def checkcase(path):
542 def checkcase(path):
543 """
543 """
544 Check whether the given path is on a case-sensitive filesystem
544 Check whether the given path is on a case-sensitive filesystem
545
545
546 Requires a path (like /foo/.hg) ending with a foldable final
546 Requires a path (like /foo/.hg) ending with a foldable final
547 directory component.
547 directory component.
548 """
548 """
549 s1 = os.stat(path)
549 s1 = os.stat(path)
550 d, b = os.path.split(path)
550 d, b = os.path.split(path)
551 p2 = os.path.join(d, b.upper())
551 p2 = os.path.join(d, b.upper())
552 if path == p2:
552 if path == p2:
553 p2 = os.path.join(d, b.lower())
553 p2 = os.path.join(d, b.lower())
554 try:
554 try:
555 s2 = os.stat(p2)
555 s2 = os.stat(p2)
556 if s2 == s1:
556 if s2 == s1:
557 return False
557 return False
558 return True
558 return True
559 except OSError:
559 except OSError:
560 return True
560 return True
561
561
562 _fspathcache = {}
562 _fspathcache = {}
563 def fspath(name, root):
563 def fspath(name, root):
564 '''Get name in the case stored in the filesystem
564 '''Get name in the case stored in the filesystem
565
565
566 The name is either relative to root, or it is an absolute path starting
566 The name is either relative to root, or it is an absolute path starting
567 with root. Note that this function is unnecessary, and should not be
567 with root. Note that this function is unnecessary, and should not be
568 called, for case-sensitive filesystems (simply because it's expensive).
568 called, for case-sensitive filesystems (simply because it's expensive).
569 '''
569 '''
570 # If name is absolute, make it relative
570 # If name is absolute, make it relative
571 if name.lower().startswith(root.lower()):
571 if name.lower().startswith(root.lower()):
572 l = len(root)
572 l = len(root)
573 if name[l] == os.sep or name[l] == os.altsep:
573 if name[l] == os.sep or name[l] == os.altsep:
574 l = l + 1
574 l = l + 1
575 name = name[l:]
575 name = name[l:]
576
576
577 if not os.path.lexists(os.path.join(root, name)):
577 if not os.path.lexists(os.path.join(root, name)):
578 return None
578 return None
579
579
580 seps = os.sep
580 seps = os.sep
581 if os.altsep:
581 if os.altsep:
582 seps = seps + os.altsep
582 seps = seps + os.altsep
583 # Protect backslashes. This gets silly very quickly.
583 # Protect backslashes. This gets silly very quickly.
584 seps.replace('\\','\\\\')
584 seps.replace('\\','\\\\')
585 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
585 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
586 dir = os.path.normcase(os.path.normpath(root))
586 dir = os.path.normcase(os.path.normpath(root))
587 result = []
587 result = []
588 for part, sep in pattern.findall(name):
588 for part, sep in pattern.findall(name):
589 if sep:
589 if sep:
590 result.append(sep)
590 result.append(sep)
591 continue
591 continue
592
592
593 if dir not in _fspathcache:
593 if dir not in _fspathcache:
594 _fspathcache[dir] = os.listdir(dir)
594 _fspathcache[dir] = os.listdir(dir)
595 contents = _fspathcache[dir]
595 contents = _fspathcache[dir]
596
596
597 lpart = part.lower()
597 lpart = part.lower()
598 lenp = len(part)
598 lenp = len(part)
599 for n in contents:
599 for n in contents:
600 if lenp == len(n) and n.lower() == lpart:
600 if lenp == len(n) and n.lower() == lpart:
601 result.append(n)
601 result.append(n)
602 break
602 break
603 else:
603 else:
604 # Cannot happen, as the file exists!
604 # Cannot happen, as the file exists!
605 result.append(part)
605 result.append(part)
606 dir = os.path.join(dir, lpart)
606 dir = os.path.join(dir, lpart)
607
607
608 return ''.join(result)
608 return ''.join(result)
609
609
610 def checknlink(testfile):
610 def checknlink(testfile):
611 '''check whether hardlink count reporting works properly'''
611 '''check whether hardlink count reporting works properly'''
612
612
613 # testfile may be open, so we need a separate file for checking to
613 # testfile may be open, so we need a separate file for checking to
614 # work around issue2543 (or testfile may get lost on Samba shares)
614 # work around issue2543 (or testfile may get lost on Samba shares)
615 f1 = testfile + ".hgtmp1"
615 f1 = testfile + ".hgtmp1"
616 if os.path.lexists(f1):
616 if os.path.lexists(f1):
617 return False
617 return False
618 try:
618 try:
619 posixfile(f1, 'w').close()
619 posixfile(f1, 'w').close()
620 except IOError:
620 except IOError:
621 return False
621 return False
622
622
623 f2 = testfile + ".hgtmp2"
623 f2 = testfile + ".hgtmp2"
624 fd = None
624 fd = None
625 try:
625 try:
626 try:
626 try:
627 oslink(f1, f2)
627 oslink(f1, f2)
628 except OSError:
628 except OSError:
629 return False
629 return False
630
630
631 # nlinks() may behave differently for files on Windows shares if
631 # nlinks() may behave differently for files on Windows shares if
632 # the file is open.
632 # the file is open.
633 fd = posixfile(f2)
633 fd = posixfile(f2)
634 return nlinks(f2) > 1
634 return nlinks(f2) > 1
635 finally:
635 finally:
636 if fd is not None:
636 if fd is not None:
637 fd.close()
637 fd.close()
638 for f in (f1, f2):
638 for f in (f1, f2):
639 try:
639 try:
640 os.unlink(f)
640 os.unlink(f)
641 except OSError:
641 except OSError:
642 pass
642 pass
643
643
644 return False
644 return False
645
645
646 def endswithsep(path):
646 def endswithsep(path):
647 '''Check path ends with os.sep or os.altsep.'''
647 '''Check path ends with os.sep or os.altsep.'''
648 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
648 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
649
649
650 def splitpath(path):
650 def splitpath(path):
651 '''Split path by os.sep.
651 '''Split path by os.sep.
652 Note that this function does not use os.altsep because this is
652 Note that this function does not use os.altsep because this is
653 an alternative of simple "xxx.split(os.sep)".
653 an alternative of simple "xxx.split(os.sep)".
654 It is recommended to use os.path.normpath() before using this
654 It is recommended to use os.path.normpath() before using this
655 function if need.'''
655 function if need.'''
656 return path.split(os.sep)
656 return path.split(os.sep)
657
657
658 def gui():
658 def gui():
659 '''Are we running in a GUI?'''
659 '''Are we running in a GUI?'''
660 if sys.platform == 'darwin':
660 if sys.platform == 'darwin':
661 if 'SSH_CONNECTION' in os.environ:
661 if 'SSH_CONNECTION' in os.environ:
662 # handle SSH access to a box where the user is logged in
662 # handle SSH access to a box where the user is logged in
663 return False
663 return False
664 elif getattr(osutil, 'isgui', None):
664 elif getattr(osutil, 'isgui', None):
665 # check if a CoreGraphics session is available
665 # check if a CoreGraphics session is available
666 return osutil.isgui()
666 return osutil.isgui()
667 else:
667 else:
668 # pure build; use a safe default
668 # pure build; use a safe default
669 return True
669 return True
670 else:
670 else:
671 return os.name == "nt" or os.environ.get("DISPLAY")
671 return os.name == "nt" or os.environ.get("DISPLAY")
672
672
673 def mktempcopy(name, emptyok=False, createmode=None):
673 def mktempcopy(name, emptyok=False, createmode=None):
674 """Create a temporary file with the same contents from name
674 """Create a temporary file with the same contents from name
675
675
676 The permission bits are copied from the original file.
676 The permission bits are copied from the original file.
677
677
678 If the temporary file is going to be truncated immediately, you
678 If the temporary file is going to be truncated immediately, you
679 can use emptyok=True as an optimization.
679 can use emptyok=True as an optimization.
680
680
681 Returns the name of the temporary file.
681 Returns the name of the temporary file.
682 """
682 """
683 d, fn = os.path.split(name)
683 d, fn = os.path.split(name)
684 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
684 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
685 os.close(fd)
685 os.close(fd)
686 # Temporary files are created with mode 0600, which is usually not
686 # Temporary files are created with mode 0600, which is usually not
687 # what we want. If the original file already exists, just copy
687 # what we want. If the original file already exists, just copy
688 # its mode. Otherwise, manually obey umask.
688 # its mode. Otherwise, manually obey umask.
689 try:
689 try:
690 st_mode = os.lstat(name).st_mode & 0777
690 st_mode = os.lstat(name).st_mode & 0777
691 except OSError, inst:
691 except OSError, inst:
692 if inst.errno != errno.ENOENT:
692 if inst.errno != errno.ENOENT:
693 raise
693 raise
694 st_mode = createmode
694 st_mode = createmode
695 if st_mode is None:
695 if st_mode is None:
696 st_mode = ~umask
696 st_mode = ~umask
697 st_mode &= 0666
697 st_mode &= 0666
698 os.chmod(temp, st_mode)
698 os.chmod(temp, st_mode)
699 if emptyok:
699 if emptyok:
700 return temp
700 return temp
701 try:
701 try:
702 try:
702 try:
703 ifp = posixfile(name, "rb")
703 ifp = posixfile(name, "rb")
704 except IOError, inst:
704 except IOError, inst:
705 if inst.errno == errno.ENOENT:
705 if inst.errno == errno.ENOENT:
706 return temp
706 return temp
707 if not getattr(inst, 'filename', None):
707 if not getattr(inst, 'filename', None):
708 inst.filename = name
708 inst.filename = name
709 raise
709 raise
710 ofp = posixfile(temp, "wb")
710 ofp = posixfile(temp, "wb")
711 for chunk in filechunkiter(ifp):
711 for chunk in filechunkiter(ifp):
712 ofp.write(chunk)
712 ofp.write(chunk)
713 ifp.close()
713 ifp.close()
714 ofp.close()
714 ofp.close()
715 except:
715 except:
716 try: os.unlink(temp)
716 try: os.unlink(temp)
717 except: pass
717 except: pass
718 raise
718 raise
719 return temp
719 return temp
720
720
721 class atomictempfile(object):
721 class atomictempfile(object):
722 '''writeable file object that atomically updates a file
722 '''writeable file object that atomically updates a file
723
723
724 All writes will go to a temporary copy of the original file. Call
724 All writes will go to a temporary copy of the original file. Call
725 rename() when you are done writing, and atomictempfile will rename
725 rename() when you are done writing, and atomictempfile will rename
726 the temporary copy to the original name, making the changes visible.
726 the temporary copy to the original name, making the changes visible.
727
727
728 Unlike other file-like objects, close() discards your writes by
728 Unlike other file-like objects, close() discards your writes by
729 simply deleting the temporary file.
729 simply deleting the temporary file.
730 '''
730 '''
731 def __init__(self, name, mode='w+b', createmode=None):
731 def __init__(self, name, mode='w+b', createmode=None):
732 self.__name = name # permanent name
732 self.__name = name # permanent name
733 self._tempname = mktempcopy(name, emptyok=('w' in mode),
733 self._tempname = mktempcopy(name, emptyok=('w' in mode),
734 createmode=createmode)
734 createmode=createmode)
735 self._fp = posixfile(self._tempname, mode)
735 self._fp = posixfile(self._tempname, mode)
736
736
737 # delegated methods
737 # delegated methods
738 self.write = self._fp.write
738 self.write = self._fp.write
739 self.fileno = self._fp.fileno
739 self.fileno = self._fp.fileno
740
740
741 def rename(self):
741 def rename(self):
742 if not self._fp.closed:
742 if not self._fp.closed:
743 self._fp.close()
743 self._fp.close()
744 rename(self._tempname, localpath(self.__name))
744 rename(self._tempname, localpath(self.__name))
745
745
746 def close(self):
746 def close(self):
747 if not self._fp.closed:
747 if not self._fp.closed:
748 try:
748 try:
749 os.unlink(self._tempname)
749 os.unlink(self._tempname)
750 except OSError:
750 except OSError:
751 pass
751 pass
752 self._fp.close()
752 self._fp.close()
753
753
754 def __del__(self):
754 def __del__(self):
755 if hasattr(self, '_fp'): # constructor actually did something
755 if hasattr(self, '_fp'): # constructor actually did something
756 self.close()
756 self.close()
757
757
758 def makedirs(name, mode=None):
758 def makedirs(name, mode=None):
759 """recursive directory creation with parent mode inheritance"""
759 """recursive directory creation with parent mode inheritance"""
760 parent = os.path.abspath(os.path.dirname(name))
760 parent = os.path.abspath(os.path.dirname(name))
761 try:
761 try:
762 os.mkdir(name)
762 os.mkdir(name)
763 if mode is not None:
763 if mode is not None:
764 os.chmod(name, mode)
764 os.chmod(name, mode)
765 return
765 return
766 except OSError, err:
766 except OSError, err:
767 if err.errno == errno.EEXIST:
767 if err.errno == errno.EEXIST:
768 return
768 return
769 if not name or parent == name or err.errno != errno.ENOENT:
769 if not name or parent == name or err.errno != errno.ENOENT:
770 raise
770 raise
771 makedirs(parent, mode)
771 makedirs(parent, mode)
772 makedirs(name, mode)
772 makedirs(name, mode)
773
773
774 def readfile(path):
774 def readfile(path):
775 fp = open(path, 'rb')
775 fp = open(path, 'rb')
776 try:
776 try:
777 return fp.read()
777 return fp.read()
778 finally:
778 finally:
779 fp.close()
779 fp.close()
780
780
781 def writefile(path, text):
781 def writefile(path, text):
782 fp = open(path, 'wb')
782 fp = open(path, 'wb')
783 try:
783 try:
784 fp.write(text)
784 fp.write(text)
785 finally:
785 finally:
786 fp.close()
786 fp.close()
787
787
788 def appendfile(path, text):
788 def appendfile(path, text):
789 fp = open(path, 'ab')
789 fp = open(path, 'ab')
790 try:
790 try:
791 fp.write(text)
791 fp.write(text)
792 finally:
792 finally:
793 fp.close()
793 fp.close()
794
794
795 class chunkbuffer(object):
795 class chunkbuffer(object):
796 """Allow arbitrary sized chunks of data to be efficiently read from an
796 """Allow arbitrary sized chunks of data to be efficiently read from an
797 iterator over chunks of arbitrary size."""
797 iterator over chunks of arbitrary size."""
798
798
799 def __init__(self, in_iter):
799 def __init__(self, in_iter):
800 """in_iter is the iterator that's iterating over the input chunks.
800 """in_iter is the iterator that's iterating over the input chunks.
801 targetsize is how big a buffer to try to maintain."""
801 targetsize is how big a buffer to try to maintain."""
802 def splitbig(chunks):
802 def splitbig(chunks):
803 for chunk in chunks:
803 for chunk in chunks:
804 if len(chunk) > 2**20:
804 if len(chunk) > 2**20:
805 pos = 0
805 pos = 0
806 while pos < len(chunk):
806 while pos < len(chunk):
807 end = pos + 2 ** 18
807 end = pos + 2 ** 18
808 yield chunk[pos:end]
808 yield chunk[pos:end]
809 pos = end
809 pos = end
810 else:
810 else:
811 yield chunk
811 yield chunk
812 self.iter = splitbig(in_iter)
812 self.iter = splitbig(in_iter)
813 self._queue = []
813 self._queue = []
814
814
815 def read(self, l):
815 def read(self, l):
816 """Read L bytes of data from the iterator of chunks of data.
816 """Read L bytes of data from the iterator of chunks of data.
817 Returns less than L bytes if the iterator runs dry."""
817 Returns less than L bytes if the iterator runs dry."""
818 left = l
818 left = l
819 buf = ''
819 buf = ''
820 queue = self._queue
820 queue = self._queue
821 while left > 0:
821 while left > 0:
822 # refill the queue
822 # refill the queue
823 if not queue:
823 if not queue:
824 target = 2**18
824 target = 2**18
825 for chunk in self.iter:
825 for chunk in self.iter:
826 queue.append(chunk)
826 queue.append(chunk)
827 target -= len(chunk)
827 target -= len(chunk)
828 if target <= 0:
828 if target <= 0:
829 break
829 break
830 if not queue:
830 if not queue:
831 break
831 break
832
832
833 chunk = queue.pop(0)
833 chunk = queue.pop(0)
834 left -= len(chunk)
834 left -= len(chunk)
835 if left < 0:
835 if left < 0:
836 queue.insert(0, chunk[left:])
836 queue.insert(0, chunk[left:])
837 buf += chunk[:left]
837 buf += chunk[:left]
838 else:
838 else:
839 buf += chunk
839 buf += chunk
840
840
841 return buf
841 return buf
842
842
843 def filechunkiter(f, size=65536, limit=None):
843 def filechunkiter(f, size=65536, limit=None):
844 """Create a generator that produces the data in the file size
844 """Create a generator that produces the data in the file size
845 (default 65536) bytes at a time, up to optional limit (default is
845 (default 65536) bytes at a time, up to optional limit (default is
846 to read all data). Chunks may be less than size bytes if the
846 to read all data). Chunks may be less than size bytes if the
847 chunk is the last chunk in the file, or the file is a socket or
847 chunk is the last chunk in the file, or the file is a socket or
848 some other type of file that sometimes reads less data than is
848 some other type of file that sometimes reads less data than is
849 requested."""
849 requested."""
850 assert size >= 0
850 assert size >= 0
851 assert limit is None or limit >= 0
851 assert limit is None or limit >= 0
852 while True:
852 while True:
853 if limit is None:
853 if limit is None:
854 nbytes = size
854 nbytes = size
855 else:
855 else:
856 nbytes = min(limit, size)
856 nbytes = min(limit, size)
857 s = nbytes and f.read(nbytes)
857 s = nbytes and f.read(nbytes)
858 if not s:
858 if not s:
859 break
859 break
860 if limit:
860 if limit:
861 limit -= len(s)
861 limit -= len(s)
862 yield s
862 yield s
863
863
864 def makedate():
864 def makedate():
865 lt = time.localtime()
865 lt = time.localtime()
866 if lt[8] == 1 and time.daylight:
866 if lt[8] == 1 and time.daylight:
867 tz = time.altzone
867 tz = time.altzone
868 else:
868 else:
869 tz = time.timezone
869 tz = time.timezone
870 t = time.mktime(lt)
870 t = time.mktime(lt)
871 if t < 0:
871 if t < 0:
872 hint = _("check your clock")
872 hint = _("check your clock")
873 raise Abort(_("negative timestamp: %d") % t, hint=hint)
873 raise Abort(_("negative timestamp: %d") % t, hint=hint)
874 return t, tz
874 return t, tz
875
875
876 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
876 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
877 """represent a (unixtime, offset) tuple as a localized time.
877 """represent a (unixtime, offset) tuple as a localized time.
878 unixtime is seconds since the epoch, and offset is the time zone's
878 unixtime is seconds since the epoch, and offset is the time zone's
879 number of seconds away from UTC. if timezone is false, do not
879 number of seconds away from UTC. if timezone is false, do not
880 append time zone to string."""
880 append time zone to string."""
881 t, tz = date or makedate()
881 t, tz = date or makedate()
882 if t < 0:
882 if t < 0:
883 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
883 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
884 tz = 0
884 tz = 0
885 if "%1" in format or "%2" in format:
885 if "%1" in format or "%2" in format:
886 sign = (tz > 0) and "-" or "+"
886 sign = (tz > 0) and "-" or "+"
887 minutes = abs(tz) // 60
887 minutes = abs(tz) // 60
888 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
888 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
889 format = format.replace("%2", "%02d" % (minutes % 60))
889 format = format.replace("%2", "%02d" % (minutes % 60))
890 s = time.strftime(format, time.gmtime(float(t) - tz))
890 s = time.strftime(format, time.gmtime(float(t) - tz))
891 return s
891 return s
892
892
893 def shortdate(date=None):
893 def shortdate(date=None):
894 """turn (timestamp, tzoff) tuple into iso 8631 date."""
894 """turn (timestamp, tzoff) tuple into iso 8631 date."""
895 return datestr(date, format='%Y-%m-%d')
895 return datestr(date, format='%Y-%m-%d')
896
896
897 def strdate(string, format, defaults=[]):
897 def strdate(string, format, defaults=[]):
898 """parse a localized time string and return a (unixtime, offset) tuple.
898 """parse a localized time string and return a (unixtime, offset) tuple.
899 if the string cannot be parsed, ValueError is raised."""
899 if the string cannot be parsed, ValueError is raised."""
900 def timezone(string):
900 def timezone(string):
901 tz = string.split()[-1]
901 tz = string.split()[-1]
902 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
902 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
903 sign = (tz[0] == "+") and 1 or -1
903 sign = (tz[0] == "+") and 1 or -1
904 hours = int(tz[1:3])
904 hours = int(tz[1:3])
905 minutes = int(tz[3:5])
905 minutes = int(tz[3:5])
906 return -sign * (hours * 60 + minutes) * 60
906 return -sign * (hours * 60 + minutes) * 60
907 if tz == "GMT" or tz == "UTC":
907 if tz == "GMT" or tz == "UTC":
908 return 0
908 return 0
909 return None
909 return None
910
910
911 # NOTE: unixtime = localunixtime + offset
911 # NOTE: unixtime = localunixtime + offset
912 offset, date = timezone(string), string
912 offset, date = timezone(string), string
913 if offset is not None:
913 if offset is not None:
914 date = " ".join(string.split()[:-1])
914 date = " ".join(string.split()[:-1])
915
915
916 # add missing elements from defaults
916 # add missing elements from defaults
917 usenow = False # default to using biased defaults
917 usenow = False # default to using biased defaults
918 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
918 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
919 found = [True for p in part if ("%"+p) in format]
919 found = [True for p in part if ("%"+p) in format]
920 if not found:
920 if not found:
921 date += "@" + defaults[part][usenow]
921 date += "@" + defaults[part][usenow]
922 format += "@%" + part[0]
922 format += "@%" + part[0]
923 else:
923 else:
924 # We've found a specific time element, less specific time
924 # We've found a specific time element, less specific time
925 # elements are relative to today
925 # elements are relative to today
926 usenow = True
926 usenow = True
927
927
928 timetuple = time.strptime(date, format)
928 timetuple = time.strptime(date, format)
929 localunixtime = int(calendar.timegm(timetuple))
929 localunixtime = int(calendar.timegm(timetuple))
930 if offset is None:
930 if offset is None:
931 # local timezone
931 # local timezone
932 unixtime = int(time.mktime(timetuple))
932 unixtime = int(time.mktime(timetuple))
933 offset = unixtime - localunixtime
933 offset = unixtime - localunixtime
934 else:
934 else:
935 unixtime = localunixtime + offset
935 unixtime = localunixtime + offset
936 return unixtime, offset
936 return unixtime, offset
937
937
938 def parsedate(date, formats=None, bias={}):
938 def parsedate(date, formats=None, bias={}):
939 """parse a localized date/time and return a (unixtime, offset) tuple.
939 """parse a localized date/time and return a (unixtime, offset) tuple.
940
940
941 The date may be a "unixtime offset" string or in one of the specified
941 The date may be a "unixtime offset" string or in one of the specified
942 formats. If the date already is a (unixtime, offset) tuple, it is returned.
942 formats. If the date already is a (unixtime, offset) tuple, it is returned.
943 """
943 """
944 if not date:
944 if not date:
945 return 0, 0
945 return 0, 0
946 if isinstance(date, tuple) and len(date) == 2:
946 if isinstance(date, tuple) and len(date) == 2:
947 return date
947 return date
948 if not formats:
948 if not formats:
949 formats = defaultdateformats
949 formats = defaultdateformats
950 date = date.strip()
950 date = date.strip()
951 try:
951 try:
952 when, offset = map(int, date.split(' '))
952 when, offset = map(int, date.split(' '))
953 except ValueError:
953 except ValueError:
954 # fill out defaults
954 # fill out defaults
955 now = makedate()
955 now = makedate()
956 defaults = {}
956 defaults = {}
957 for part in ("d", "mb", "yY", "HI", "M", "S"):
957 for part in ("d", "mb", "yY", "HI", "M", "S"):
958 # this piece is for rounding the specific end of unknowns
958 # this piece is for rounding the specific end of unknowns
959 b = bias.get(part)
959 b = bias.get(part)
960 if b is None:
960 if b is None:
961 if part[0] in "HMS":
961 if part[0] in "HMS":
962 b = "00"
962 b = "00"
963 else:
963 else:
964 b = "0"
964 b = "0"
965
965
966 # this piece is for matching the generic end to today's date
966 # this piece is for matching the generic end to today's date
967 n = datestr(now, "%" + part[0])
967 n = datestr(now, "%" + part[0])
968
968
969 defaults[part] = (b, n)
969 defaults[part] = (b, n)
970
970
971 for format in formats:
971 for format in formats:
972 try:
972 try:
973 when, offset = strdate(date, format, defaults)
973 when, offset = strdate(date, format, defaults)
974 except (ValueError, OverflowError):
974 except (ValueError, OverflowError):
975 pass
975 pass
976 else:
976 else:
977 break
977 break
978 else:
978 else:
979 raise Abort(_('invalid date: %r') % date)
979 raise Abort(_('invalid date: %r') % date)
980 # validate explicit (probably user-specified) date and
980 # validate explicit (probably user-specified) date and
981 # time zone offset. values must fit in signed 32 bits for
981 # time zone offset. values must fit in signed 32 bits for
982 # current 32-bit linux runtimes. timezones go from UTC-12
982 # current 32-bit linux runtimes. timezones go from UTC-12
983 # to UTC+14
983 # to UTC+14
984 if abs(when) > 0x7fffffff:
984 if abs(when) > 0x7fffffff:
985 raise Abort(_('date exceeds 32 bits: %d') % when)
985 raise Abort(_('date exceeds 32 bits: %d') % when)
986 if when < 0:
986 if when < 0:
987 raise Abort(_('negative date value: %d') % when)
987 raise Abort(_('negative date value: %d') % when)
988 if offset < -50400 or offset > 43200:
988 if offset < -50400 or offset > 43200:
989 raise Abort(_('impossible time zone offset: %d') % offset)
989 raise Abort(_('impossible time zone offset: %d') % offset)
990 return when, offset
990 return when, offset
991
991
992 def matchdate(date):
992 def matchdate(date):
993 """Return a function that matches a given date match specifier
993 """Return a function that matches a given date match specifier
994
994
995 Formats include:
995 Formats include:
996
996
997 '{date}' match a given date to the accuracy provided
997 '{date}' match a given date to the accuracy provided
998
998
999 '<{date}' on or before a given date
999 '<{date}' on or before a given date
1000
1000
1001 '>{date}' on or after a given date
1001 '>{date}' on or after a given date
1002
1002
1003 >>> p1 = parsedate("10:29:59")
1003 >>> p1 = parsedate("10:29:59")
1004 >>> p2 = parsedate("10:30:00")
1004 >>> p2 = parsedate("10:30:00")
1005 >>> p3 = parsedate("10:30:59")
1005 >>> p3 = parsedate("10:30:59")
1006 >>> p4 = parsedate("10:31:00")
1006 >>> p4 = parsedate("10:31:00")
1007 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1007 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1008 >>> f = matchdate("10:30")
1008 >>> f = matchdate("10:30")
1009 >>> f(p1[0])
1009 >>> f(p1[0])
1010 False
1010 False
1011 >>> f(p2[0])
1011 >>> f(p2[0])
1012 True
1012 True
1013 >>> f(p3[0])
1013 >>> f(p3[0])
1014 True
1014 True
1015 >>> f(p4[0])
1015 >>> f(p4[0])
1016 False
1016 False
1017 >>> f(p5[0])
1017 >>> f(p5[0])
1018 False
1018 False
1019 """
1019 """
1020
1020
1021 def lower(date):
1021 def lower(date):
1022 d = dict(mb="1", d="1")
1022 d = dict(mb="1", d="1")
1023 return parsedate(date, extendeddateformats, d)[0]
1023 return parsedate(date, extendeddateformats, d)[0]
1024
1024
1025 def upper(date):
1025 def upper(date):
1026 d = dict(mb="12", HI="23", M="59", S="59")
1026 d = dict(mb="12", HI="23", M="59", S="59")
1027 for days in ("31", "30", "29"):
1027 for days in ("31", "30", "29"):
1028 try:
1028 try:
1029 d["d"] = days
1029 d["d"] = days
1030 return parsedate(date, extendeddateformats, d)[0]
1030 return parsedate(date, extendeddateformats, d)[0]
1031 except:
1031 except:
1032 pass
1032 pass
1033 d["d"] = "28"
1033 d["d"] = "28"
1034 return parsedate(date, extendeddateformats, d)[0]
1034 return parsedate(date, extendeddateformats, d)[0]
1035
1035
1036 date = date.strip()
1036 date = date.strip()
1037
1037
1038 if not date:
1038 if not date:
1039 raise Abort(_("dates cannot consist entirely of whitespace"))
1039 raise Abort(_("dates cannot consist entirely of whitespace"))
1040 elif date[0] == "<":
1040 elif date[0] == "<":
1041 if not date[1:]:
1041 if not date[1:]:
1042 raise Abort(_("invalid day spec, use '<DATE'"))
1042 raise Abort(_("invalid day spec, use '<DATE'"))
1043 when = upper(date[1:])
1043 when = upper(date[1:])
1044 return lambda x: x <= when
1044 return lambda x: x <= when
1045 elif date[0] == ">":
1045 elif date[0] == ">":
1046 if not date[1:]:
1046 if not date[1:]:
1047 raise Abort(_("invalid day spec, use '>DATE'"))
1047 raise Abort(_("invalid day spec, use '>DATE'"))
1048 when = lower(date[1:])
1048 when = lower(date[1:])
1049 return lambda x: x >= when
1049 return lambda x: x >= when
1050 elif date[0] == "-":
1050 elif date[0] == "-":
1051 try:
1051 try:
1052 days = int(date[1:])
1052 days = int(date[1:])
1053 except ValueError:
1053 except ValueError:
1054 raise Abort(_("invalid day spec: %s") % date[1:])
1054 raise Abort(_("invalid day spec: %s") % date[1:])
1055 if days < 0:
1055 if days < 0:
1056 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1056 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1057 % date[1:])
1057 % date[1:])
1058 when = makedate()[0] - days * 3600 * 24
1058 when = makedate()[0] - days * 3600 * 24
1059 return lambda x: x >= when
1059 return lambda x: x >= when
1060 elif " to " in date:
1060 elif " to " in date:
1061 a, b = date.split(" to ")
1061 a, b = date.split(" to ")
1062 start, stop = lower(a), upper(b)
1062 start, stop = lower(a), upper(b)
1063 return lambda x: x >= start and x <= stop
1063 return lambda x: x >= start and x <= stop
1064 else:
1064 else:
1065 start, stop = lower(date), upper(date)
1065 start, stop = lower(date), upper(date)
1066 return lambda x: x >= start and x <= stop
1066 return lambda x: x >= start and x <= stop
1067
1067
1068 def shortuser(user):
1068 def shortuser(user):
1069 """Return a short representation of a user name or email address."""
1069 """Return a short representation of a user name or email address."""
1070 f = user.find('@')
1070 f = user.find('@')
1071 if f >= 0:
1071 if f >= 0:
1072 user = user[:f]
1072 user = user[:f]
1073 f = user.find('<')
1073 f = user.find('<')
1074 if f >= 0:
1074 if f >= 0:
1075 user = user[f + 1:]
1075 user = user[f + 1:]
1076 f = user.find(' ')
1076 f = user.find(' ')
1077 if f >= 0:
1077 if f >= 0:
1078 user = user[:f]
1078 user = user[:f]
1079 f = user.find('.')
1079 f = user.find('.')
1080 if f >= 0:
1080 if f >= 0:
1081 user = user[:f]
1081 user = user[:f]
1082 return user
1082 return user
1083
1083
1084 def email(author):
1084 def email(author):
1085 '''get email of author.'''
1085 '''get email of author.'''
1086 r = author.find('>')
1086 r = author.find('>')
1087 if r == -1:
1087 if r == -1:
1088 r = None
1088 r = None
1089 return author[author.find('<') + 1:r]
1089 return author[author.find('<') + 1:r]
1090
1090
1091 def _ellipsis(text, maxlength):
1091 def _ellipsis(text, maxlength):
1092 if len(text) <= maxlength:
1092 if len(text) <= maxlength:
1093 return text, False
1093 return text, False
1094 else:
1094 else:
1095 return "%s..." % (text[:maxlength - 3]), True
1095 return "%s..." % (text[:maxlength - 3]), True
1096
1096
1097 def ellipsis(text, maxlength=400):
1097 def ellipsis(text, maxlength=400):
1098 """Trim string to at most maxlength (default: 400) characters."""
1098 """Trim string to at most maxlength (default: 400) characters."""
1099 try:
1099 try:
1100 # use unicode not to split at intermediate multi-byte sequence
1100 # use unicode not to split at intermediate multi-byte sequence
1101 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1101 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1102 maxlength)
1102 maxlength)
1103 if not truncated:
1103 if not truncated:
1104 return text
1104 return text
1105 return utext.encode(encoding.encoding)
1105 return utext.encode(encoding.encoding)
1106 except (UnicodeDecodeError, UnicodeEncodeError):
1106 except (UnicodeDecodeError, UnicodeEncodeError):
1107 return _ellipsis(text, maxlength)[0]
1107 return _ellipsis(text, maxlength)[0]
1108
1108
1109 def bytecount(nbytes):
1109 def bytecount(nbytes):
1110 '''return byte count formatted as readable string, with units'''
1110 '''return byte count formatted as readable string, with units'''
1111
1111
1112 units = (
1112 units = (
1113 (100, 1 << 30, _('%.0f GB')),
1113 (100, 1 << 30, _('%.0f GB')),
1114 (10, 1 << 30, _('%.1f GB')),
1114 (10, 1 << 30, _('%.1f GB')),
1115 (1, 1 << 30, _('%.2f GB')),
1115 (1, 1 << 30, _('%.2f GB')),
1116 (100, 1 << 20, _('%.0f MB')),
1116 (100, 1 << 20, _('%.0f MB')),
1117 (10, 1 << 20, _('%.1f MB')),
1117 (10, 1 << 20, _('%.1f MB')),
1118 (1, 1 << 20, _('%.2f MB')),
1118 (1, 1 << 20, _('%.2f MB')),
1119 (100, 1 << 10, _('%.0f KB')),
1119 (100, 1 << 10, _('%.0f KB')),
1120 (10, 1 << 10, _('%.1f KB')),
1120 (10, 1 << 10, _('%.1f KB')),
1121 (1, 1 << 10, _('%.2f KB')),
1121 (1, 1 << 10, _('%.2f KB')),
1122 (1, 1, _('%.0f bytes')),
1122 (1, 1, _('%.0f bytes')),
1123 )
1123 )
1124
1124
1125 for multiplier, divisor, format in units:
1125 for multiplier, divisor, format in units:
1126 if nbytes >= divisor * multiplier:
1126 if nbytes >= divisor * multiplier:
1127 return format % (nbytes / float(divisor))
1127 return format % (nbytes / float(divisor))
1128 return units[-1][2] % nbytes
1128 return units[-1][2] % nbytes
1129
1129
1130 def uirepr(s):
1130 def uirepr(s):
1131 # Avoid double backslash in Windows path repr()
1131 # Avoid double backslash in Windows path repr()
1132 return repr(s).replace('\\\\', '\\')
1132 return repr(s).replace('\\\\', '\\')
1133
1133
1134 # delay import of textwrap
1134 # delay import of textwrap
1135 def MBTextWrapper(**kwargs):
1135 def MBTextWrapper(**kwargs):
1136 class tw(textwrap.TextWrapper):
1136 class tw(textwrap.TextWrapper):
1137 """
1137 """
1138 Extend TextWrapper for double-width characters.
1138 Extend TextWrapper for width-awareness.
1139
1140 Neither number of 'bytes' in any encoding nor 'characters' is
1141 appropriate to calculate terminal columns for specified string.
1139
1142
1140 Some Asian characters use two terminal columns instead of one.
1143 Original TextWrapper implementation uses built-in 'len()' directly,
1141 A good example of this behavior can be seen with u'\u65e5\u672c',
1144 so overriding is needed to use width information of each characters.
1142 the two Japanese characters for "Japan":
1143 len() returns 2, but when printed to a terminal, they eat 4 columns.
1144
1145
1145 (Note that this has nothing to do whatsoever with unicode
1146 In addition, characters classified into 'ambiguous' width are
1146 representation, or encoding of the underlying string)
1147 treated as wide in east asian area, but as narrow in other.
1148
1149 This requires use decision to determine width of such characters.
1147 """
1150 """
1148 def __init__(self, **kwargs):
1151 def __init__(self, **kwargs):
1149 textwrap.TextWrapper.__init__(self, **kwargs)
1152 textwrap.TextWrapper.__init__(self, **kwargs)
1150
1153
1154 # for compatibility between 2.4 and 2.6
1155 if getattr(self, 'drop_whitespace', None) is None:
1156 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1157
1151 def _cutdown(self, ucstr, space_left):
1158 def _cutdown(self, ucstr, space_left):
1152 l = 0
1159 l = 0
1153 colwidth = unicodedata.east_asian_width
1160 colwidth = encoding.ucolwidth
1154 for i in xrange(len(ucstr)):
1161 for i in xrange(len(ucstr)):
1155 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
1162 l += colwidth(ucstr[i])
1156 if space_left < l:
1163 if space_left < l:
1157 return (ucstr[:i], ucstr[i:])
1164 return (ucstr[:i], ucstr[i:])
1158 return ucstr, ''
1165 return ucstr, ''
1159
1166
1160 # overriding of base class
1167 # overriding of base class
1161 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1168 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1162 space_left = max(width - cur_len, 1)
1169 space_left = max(width - cur_len, 1)
1163
1170
1164 if self.break_long_words:
1171 if self.break_long_words:
1165 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1172 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1166 cur_line.append(cut)
1173 cur_line.append(cut)
1167 reversed_chunks[-1] = res
1174 reversed_chunks[-1] = res
1168 elif not cur_line:
1175 elif not cur_line:
1169 cur_line.append(reversed_chunks.pop())
1176 cur_line.append(reversed_chunks.pop())
1170
1177
1178 # this overriding code is imported from TextWrapper of python 2.6
1179 # to calculate columns of string by 'encoding.ucolwidth()'
1180 def _wrap_chunks(self, chunks):
1181 colwidth = encoding.ucolwidth
1182
1183 lines = []
1184 if self.width <= 0:
1185 raise ValueError("invalid width %r (must be > 0)" % self.width)
1186
1187 # Arrange in reverse order so items can be efficiently popped
1188 # from a stack of chucks.
1189 chunks.reverse()
1190
1191 while chunks:
1192
1193 # Start the list of chunks that will make up the current line.
1194 # cur_len is just the length of all the chunks in cur_line.
1195 cur_line = []
1196 cur_len = 0
1197
1198 # Figure out which static string will prefix this line.
1199 if lines:
1200 indent = self.subsequent_indent
1201 else:
1202 indent = self.initial_indent
1203
1204 # Maximum width for this line.
1205 width = self.width - len(indent)
1206
1207 # First chunk on line is whitespace -- drop it, unless this
1208 # is the very beginning of the text (ie. no lines started yet).
1209 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1210 del chunks[-1]
1211
1212 while chunks:
1213 l = colwidth(chunks[-1])
1214
1215 # Can at least squeeze this chunk onto the current line.
1216 if cur_len + l <= width:
1217 cur_line.append(chunks.pop())
1218 cur_len += l
1219
1220 # Nope, this line is full.
1221 else:
1222 break
1223
1224 # The current line is full, and the next chunk is too big to
1225 # fit on *any* line (not just this one).
1226 if chunks and colwidth(chunks[-1]) > width:
1227 self._handle_long_word(chunks, cur_line, cur_len, width)
1228
1229 # If the last chunk on this line is all whitespace, drop it.
1230 if (self.drop_whitespace and
1231 cur_line and cur_line[-1].strip() == ''):
1232 del cur_line[-1]
1233
1234 # Convert current line back to a string and store it in list
1235 # of all lines (return value).
1236 if cur_line:
1237 lines.append(indent + ''.join(cur_line))
1238
1239 return lines
1240
1171 global MBTextWrapper
1241 global MBTextWrapper
1172 MBTextWrapper = tw
1242 MBTextWrapper = tw
1173 return tw(**kwargs)
1243 return tw(**kwargs)
1174
1244
1175 def wrap(line, width, initindent='', hangindent=''):
1245 def wrap(line, width, initindent='', hangindent=''):
1176 maxindent = max(len(hangindent), len(initindent))
1246 maxindent = max(len(hangindent), len(initindent))
1177 if width <= maxindent:
1247 if width <= maxindent:
1178 # adjust for weird terminal size
1248 # adjust for weird terminal size
1179 width = max(78, maxindent + 1)
1249 width = max(78, maxindent + 1)
1180 line = line.decode(encoding.encoding, encoding.encodingmode)
1250 line = line.decode(encoding.encoding, encoding.encodingmode)
1181 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1251 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1182 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1252 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1183 wrapper = MBTextWrapper(width=width,
1253 wrapper = MBTextWrapper(width=width,
1184 initial_indent=initindent,
1254 initial_indent=initindent,
1185 subsequent_indent=hangindent)
1255 subsequent_indent=hangindent)
1186 return wrapper.fill(line).encode(encoding.encoding)
1256 return wrapper.fill(line).encode(encoding.encoding)
1187
1257
1188 def iterlines(iterator):
1258 def iterlines(iterator):
1189 for chunk in iterator:
1259 for chunk in iterator:
1190 for line in chunk.splitlines():
1260 for line in chunk.splitlines():
1191 yield line
1261 yield line
1192
1262
1193 def expandpath(path):
1263 def expandpath(path):
1194 return os.path.expanduser(os.path.expandvars(path))
1264 return os.path.expanduser(os.path.expandvars(path))
1195
1265
1196 def hgcmd():
1266 def hgcmd():
1197 """Return the command used to execute current hg
1267 """Return the command used to execute current hg
1198
1268
1199 This is different from hgexecutable() because on Windows we want
1269 This is different from hgexecutable() because on Windows we want
1200 to avoid things opening new shell windows like batch files, so we
1270 to avoid things opening new shell windows like batch files, so we
1201 get either the python call or current executable.
1271 get either the python call or current executable.
1202 """
1272 """
1203 if mainfrozen():
1273 if mainfrozen():
1204 return [sys.executable]
1274 return [sys.executable]
1205 return gethgcmd()
1275 return gethgcmd()
1206
1276
1207 def rundetached(args, condfn):
1277 def rundetached(args, condfn):
1208 """Execute the argument list in a detached process.
1278 """Execute the argument list in a detached process.
1209
1279
1210 condfn is a callable which is called repeatedly and should return
1280 condfn is a callable which is called repeatedly and should return
1211 True once the child process is known to have started successfully.
1281 True once the child process is known to have started successfully.
1212 At this point, the child process PID is returned. If the child
1282 At this point, the child process PID is returned. If the child
1213 process fails to start or finishes before condfn() evaluates to
1283 process fails to start or finishes before condfn() evaluates to
1214 True, return -1.
1284 True, return -1.
1215 """
1285 """
1216 # Windows case is easier because the child process is either
1286 # Windows case is easier because the child process is either
1217 # successfully starting and validating the condition or exiting
1287 # successfully starting and validating the condition or exiting
1218 # on failure. We just poll on its PID. On Unix, if the child
1288 # on failure. We just poll on its PID. On Unix, if the child
1219 # process fails to start, it will be left in a zombie state until
1289 # process fails to start, it will be left in a zombie state until
1220 # the parent wait on it, which we cannot do since we expect a long
1290 # the parent wait on it, which we cannot do since we expect a long
1221 # running process on success. Instead we listen for SIGCHLD telling
1291 # running process on success. Instead we listen for SIGCHLD telling
1222 # us our child process terminated.
1292 # us our child process terminated.
1223 terminated = set()
1293 terminated = set()
1224 def handler(signum, frame):
1294 def handler(signum, frame):
1225 terminated.add(os.wait())
1295 terminated.add(os.wait())
1226 prevhandler = None
1296 prevhandler = None
1227 if hasattr(signal, 'SIGCHLD'):
1297 if hasattr(signal, 'SIGCHLD'):
1228 prevhandler = signal.signal(signal.SIGCHLD, handler)
1298 prevhandler = signal.signal(signal.SIGCHLD, handler)
1229 try:
1299 try:
1230 pid = spawndetached(args)
1300 pid = spawndetached(args)
1231 while not condfn():
1301 while not condfn():
1232 if ((pid in terminated or not testpid(pid))
1302 if ((pid in terminated or not testpid(pid))
1233 and not condfn()):
1303 and not condfn()):
1234 return -1
1304 return -1
1235 time.sleep(0.1)
1305 time.sleep(0.1)
1236 return pid
1306 return pid
1237 finally:
1307 finally:
1238 if prevhandler is not None:
1308 if prevhandler is not None:
1239 signal.signal(signal.SIGCHLD, prevhandler)
1309 signal.signal(signal.SIGCHLD, prevhandler)
1240
1310
1241 try:
1311 try:
1242 any, all = any, all
1312 any, all = any, all
1243 except NameError:
1313 except NameError:
1244 def any(iterable):
1314 def any(iterable):
1245 for i in iterable:
1315 for i in iterable:
1246 if i:
1316 if i:
1247 return True
1317 return True
1248 return False
1318 return False
1249
1319
1250 def all(iterable):
1320 def all(iterable):
1251 for i in iterable:
1321 for i in iterable:
1252 if not i:
1322 if not i:
1253 return False
1323 return False
1254 return True
1324 return True
1255
1325
1256 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1326 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1257 """Return the result of interpolating items in the mapping into string s.
1327 """Return the result of interpolating items in the mapping into string s.
1258
1328
1259 prefix is a single character string, or a two character string with
1329 prefix is a single character string, or a two character string with
1260 a backslash as the first character if the prefix needs to be escaped in
1330 a backslash as the first character if the prefix needs to be escaped in
1261 a regular expression.
1331 a regular expression.
1262
1332
1263 fn is an optional function that will be applied to the replacement text
1333 fn is an optional function that will be applied to the replacement text
1264 just before replacement.
1334 just before replacement.
1265
1335
1266 escape_prefix is an optional flag that allows using doubled prefix for
1336 escape_prefix is an optional flag that allows using doubled prefix for
1267 its escaping.
1337 its escaping.
1268 """
1338 """
1269 fn = fn or (lambda s: s)
1339 fn = fn or (lambda s: s)
1270 patterns = '|'.join(mapping.keys())
1340 patterns = '|'.join(mapping.keys())
1271 if escape_prefix:
1341 if escape_prefix:
1272 patterns += '|' + prefix
1342 patterns += '|' + prefix
1273 if len(prefix) > 1:
1343 if len(prefix) > 1:
1274 prefix_char = prefix[1:]
1344 prefix_char = prefix[1:]
1275 else:
1345 else:
1276 prefix_char = prefix
1346 prefix_char = prefix
1277 mapping[prefix_char] = prefix_char
1347 mapping[prefix_char] = prefix_char
1278 r = re.compile(r'%s(%s)' % (prefix, patterns))
1348 r = re.compile(r'%s(%s)' % (prefix, patterns))
1279 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1349 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1280
1350
1281 def getport(port):
1351 def getport(port):
1282 """Return the port for a given network service.
1352 """Return the port for a given network service.
1283
1353
1284 If port is an integer, it's returned as is. If it's a string, it's
1354 If port is an integer, it's returned as is. If it's a string, it's
1285 looked up using socket.getservbyname(). If there's no matching
1355 looked up using socket.getservbyname(). If there's no matching
1286 service, util.Abort is raised.
1356 service, util.Abort is raised.
1287 """
1357 """
1288 try:
1358 try:
1289 return int(port)
1359 return int(port)
1290 except ValueError:
1360 except ValueError:
1291 pass
1361 pass
1292
1362
1293 try:
1363 try:
1294 return socket.getservbyname(port)
1364 return socket.getservbyname(port)
1295 except socket.error:
1365 except socket.error:
1296 raise Abort(_("no port number associated with service '%s'") % port)
1366 raise Abort(_("no port number associated with service '%s'") % port)
1297
1367
1298 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1368 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1299 '0': False, 'no': False, 'false': False, 'off': False,
1369 '0': False, 'no': False, 'false': False, 'off': False,
1300 'never': False}
1370 'never': False}
1301
1371
1302 def parsebool(s):
1372 def parsebool(s):
1303 """Parse s into a boolean.
1373 """Parse s into a boolean.
1304
1374
1305 If s is not a valid boolean, returns None.
1375 If s is not a valid boolean, returns None.
1306 """
1376 """
1307 return _booleans.get(s.lower(), None)
1377 return _booleans.get(s.lower(), None)
1308
1378
1309 _hexdig = '0123456789ABCDEFabcdef'
1379 _hexdig = '0123456789ABCDEFabcdef'
1310 _hextochr = dict((a + b, chr(int(a + b, 16)))
1380 _hextochr = dict((a + b, chr(int(a + b, 16)))
1311 for a in _hexdig for b in _hexdig)
1381 for a in _hexdig for b in _hexdig)
1312
1382
1313 def _urlunquote(s):
1383 def _urlunquote(s):
1314 """unquote('abc%20def') -> 'abc def'."""
1384 """unquote('abc%20def') -> 'abc def'."""
1315 res = s.split('%')
1385 res = s.split('%')
1316 # fastpath
1386 # fastpath
1317 if len(res) == 1:
1387 if len(res) == 1:
1318 return s
1388 return s
1319 s = res[0]
1389 s = res[0]
1320 for item in res[1:]:
1390 for item in res[1:]:
1321 try:
1391 try:
1322 s += _hextochr[item[:2]] + item[2:]
1392 s += _hextochr[item[:2]] + item[2:]
1323 except KeyError:
1393 except KeyError:
1324 s += '%' + item
1394 s += '%' + item
1325 except UnicodeDecodeError:
1395 except UnicodeDecodeError:
1326 s += unichr(int(item[:2], 16)) + item[2:]
1396 s += unichr(int(item[:2], 16)) + item[2:]
1327 return s
1397 return s
1328
1398
1329 class url(object):
1399 class url(object):
1330 r"""Reliable URL parser.
1400 r"""Reliable URL parser.
1331
1401
1332 This parses URLs and provides attributes for the following
1402 This parses URLs and provides attributes for the following
1333 components:
1403 components:
1334
1404
1335 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1405 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1336
1406
1337 Missing components are set to None. The only exception is
1407 Missing components are set to None. The only exception is
1338 fragment, which is set to '' if present but empty.
1408 fragment, which is set to '' if present but empty.
1339
1409
1340 If parsefragment is False, fragment is included in query. If
1410 If parsefragment is False, fragment is included in query. If
1341 parsequery is False, query is included in path. If both are
1411 parsequery is False, query is included in path. If both are
1342 False, both fragment and query are included in path.
1412 False, both fragment and query are included in path.
1343
1413
1344 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1414 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1345
1415
1346 Note that for backward compatibility reasons, bundle URLs do not
1416 Note that for backward compatibility reasons, bundle URLs do not
1347 take host names. That means 'bundle://../' has a path of '../'.
1417 take host names. That means 'bundle://../' has a path of '../'.
1348
1418
1349 Examples:
1419 Examples:
1350
1420
1351 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1421 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1352 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1422 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1353 >>> url('ssh://[::1]:2200//home/joe/repo')
1423 >>> url('ssh://[::1]:2200//home/joe/repo')
1354 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1424 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1355 >>> url('file:///home/joe/repo')
1425 >>> url('file:///home/joe/repo')
1356 <url scheme: 'file', path: '/home/joe/repo'>
1426 <url scheme: 'file', path: '/home/joe/repo'>
1357 >>> url('file:///c:/temp/foo/')
1427 >>> url('file:///c:/temp/foo/')
1358 <url scheme: 'file', path: 'c:/temp/foo/'>
1428 <url scheme: 'file', path: 'c:/temp/foo/'>
1359 >>> url('bundle:foo')
1429 >>> url('bundle:foo')
1360 <url scheme: 'bundle', path: 'foo'>
1430 <url scheme: 'bundle', path: 'foo'>
1361 >>> url('bundle://../foo')
1431 >>> url('bundle://../foo')
1362 <url scheme: 'bundle', path: '../foo'>
1432 <url scheme: 'bundle', path: '../foo'>
1363 >>> url(r'c:\foo\bar')
1433 >>> url(r'c:\foo\bar')
1364 <url path: 'c:\\foo\\bar'>
1434 <url path: 'c:\\foo\\bar'>
1365 >>> url(r'\\blah\blah\blah')
1435 >>> url(r'\\blah\blah\blah')
1366 <url path: '\\\\blah\\blah\\blah'>
1436 <url path: '\\\\blah\\blah\\blah'>
1367
1437
1368 Authentication credentials:
1438 Authentication credentials:
1369
1439
1370 >>> url('ssh://joe:xyz@x/repo')
1440 >>> url('ssh://joe:xyz@x/repo')
1371 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1441 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1372 >>> url('ssh://joe@x/repo')
1442 >>> url('ssh://joe@x/repo')
1373 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1443 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1374
1444
1375 Query strings and fragments:
1445 Query strings and fragments:
1376
1446
1377 >>> url('http://host/a?b#c')
1447 >>> url('http://host/a?b#c')
1378 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1448 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1379 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1449 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1380 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1450 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1381 """
1451 """
1382
1452
1383 _safechars = "!~*'()+"
1453 _safechars = "!~*'()+"
1384 _safepchars = "/!~*'()+"
1454 _safepchars = "/!~*'()+"
1385 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1455 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1386
1456
1387 def __init__(self, path, parsequery=True, parsefragment=True):
1457 def __init__(self, path, parsequery=True, parsefragment=True):
1388 # We slowly chomp away at path until we have only the path left
1458 # We slowly chomp away at path until we have only the path left
1389 self.scheme = self.user = self.passwd = self.host = None
1459 self.scheme = self.user = self.passwd = self.host = None
1390 self.port = self.path = self.query = self.fragment = None
1460 self.port = self.path = self.query = self.fragment = None
1391 self._localpath = True
1461 self._localpath = True
1392 self._hostport = ''
1462 self._hostport = ''
1393 self._origpath = path
1463 self._origpath = path
1394
1464
1395 # special case for Windows drive letters and UNC paths
1465 # special case for Windows drive letters and UNC paths
1396 if hasdriveletter(path) or path.startswith(r'\\'):
1466 if hasdriveletter(path) or path.startswith(r'\\'):
1397 self.path = path
1467 self.path = path
1398 return
1468 return
1399
1469
1400 # For compatibility reasons, we can't handle bundle paths as
1470 # For compatibility reasons, we can't handle bundle paths as
1401 # normal URLS
1471 # normal URLS
1402 if path.startswith('bundle:'):
1472 if path.startswith('bundle:'):
1403 self.scheme = 'bundle'
1473 self.scheme = 'bundle'
1404 path = path[7:]
1474 path = path[7:]
1405 if path.startswith('//'):
1475 if path.startswith('//'):
1406 path = path[2:]
1476 path = path[2:]
1407 self.path = path
1477 self.path = path
1408 return
1478 return
1409
1479
1410 if self._matchscheme(path):
1480 if self._matchscheme(path):
1411 parts = path.split(':', 1)
1481 parts = path.split(':', 1)
1412 if parts[0]:
1482 if parts[0]:
1413 self.scheme, path = parts
1483 self.scheme, path = parts
1414 self._localpath = False
1484 self._localpath = False
1415
1485
1416 if not path:
1486 if not path:
1417 path = None
1487 path = None
1418 if self._localpath:
1488 if self._localpath:
1419 self.path = ''
1489 self.path = ''
1420 return
1490 return
1421 else:
1491 else:
1422 if parsefragment and '#' in path:
1492 if parsefragment and '#' in path:
1423 path, self.fragment = path.split('#', 1)
1493 path, self.fragment = path.split('#', 1)
1424 if not path:
1494 if not path:
1425 path = None
1495 path = None
1426 if self._localpath:
1496 if self._localpath:
1427 self.path = path
1497 self.path = path
1428 return
1498 return
1429
1499
1430 if parsequery and '?' in path:
1500 if parsequery and '?' in path:
1431 path, self.query = path.split('?', 1)
1501 path, self.query = path.split('?', 1)
1432 if not path:
1502 if not path:
1433 path = None
1503 path = None
1434 if not self.query:
1504 if not self.query:
1435 self.query = None
1505 self.query = None
1436
1506
1437 # // is required to specify a host/authority
1507 # // is required to specify a host/authority
1438 if path and path.startswith('//'):
1508 if path and path.startswith('//'):
1439 parts = path[2:].split('/', 1)
1509 parts = path[2:].split('/', 1)
1440 if len(parts) > 1:
1510 if len(parts) > 1:
1441 self.host, path = parts
1511 self.host, path = parts
1442 path = path
1512 path = path
1443 else:
1513 else:
1444 self.host = parts[0]
1514 self.host = parts[0]
1445 path = None
1515 path = None
1446 if not self.host:
1516 if not self.host:
1447 self.host = None
1517 self.host = None
1448 # path of file:///d is /d
1518 # path of file:///d is /d
1449 # path of file:///d:/ is d:/, not /d:/
1519 # path of file:///d:/ is d:/, not /d:/
1450 if path and not hasdriveletter(path):
1520 if path and not hasdriveletter(path):
1451 path = '/' + path
1521 path = '/' + path
1452
1522
1453 if self.host and '@' in self.host:
1523 if self.host and '@' in self.host:
1454 self.user, self.host = self.host.rsplit('@', 1)
1524 self.user, self.host = self.host.rsplit('@', 1)
1455 if ':' in self.user:
1525 if ':' in self.user:
1456 self.user, self.passwd = self.user.split(':', 1)
1526 self.user, self.passwd = self.user.split(':', 1)
1457 if not self.host:
1527 if not self.host:
1458 self.host = None
1528 self.host = None
1459
1529
1460 # Don't split on colons in IPv6 addresses without ports
1530 # Don't split on colons in IPv6 addresses without ports
1461 if (self.host and ':' in self.host and
1531 if (self.host and ':' in self.host and
1462 not (self.host.startswith('[') and self.host.endswith(']'))):
1532 not (self.host.startswith('[') and self.host.endswith(']'))):
1463 self._hostport = self.host
1533 self._hostport = self.host
1464 self.host, self.port = self.host.rsplit(':', 1)
1534 self.host, self.port = self.host.rsplit(':', 1)
1465 if not self.host:
1535 if not self.host:
1466 self.host = None
1536 self.host = None
1467
1537
1468 if (self.host and self.scheme == 'file' and
1538 if (self.host and self.scheme == 'file' and
1469 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1539 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1470 raise Abort(_('file:// URLs can only refer to localhost'))
1540 raise Abort(_('file:// URLs can only refer to localhost'))
1471
1541
1472 self.path = path
1542 self.path = path
1473
1543
1474 # leave the query string escaped
1544 # leave the query string escaped
1475 for a in ('user', 'passwd', 'host', 'port',
1545 for a in ('user', 'passwd', 'host', 'port',
1476 'path', 'fragment'):
1546 'path', 'fragment'):
1477 v = getattr(self, a)
1547 v = getattr(self, a)
1478 if v is not None:
1548 if v is not None:
1479 setattr(self, a, _urlunquote(v))
1549 setattr(self, a, _urlunquote(v))
1480
1550
1481 def __repr__(self):
1551 def __repr__(self):
1482 attrs = []
1552 attrs = []
1483 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1553 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1484 'query', 'fragment'):
1554 'query', 'fragment'):
1485 v = getattr(self, a)
1555 v = getattr(self, a)
1486 if v is not None:
1556 if v is not None:
1487 attrs.append('%s: %r' % (a, v))
1557 attrs.append('%s: %r' % (a, v))
1488 return '<url %s>' % ', '.join(attrs)
1558 return '<url %s>' % ', '.join(attrs)
1489
1559
1490 def __str__(self):
1560 def __str__(self):
1491 r"""Join the URL's components back into a URL string.
1561 r"""Join the URL's components back into a URL string.
1492
1562
1493 Examples:
1563 Examples:
1494
1564
1495 >>> str(url('http://user:pw@host:80/?foo#bar'))
1565 >>> str(url('http://user:pw@host:80/?foo#bar'))
1496 'http://user:pw@host:80/?foo#bar'
1566 'http://user:pw@host:80/?foo#bar'
1497 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1567 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1498 'http://user:pw@host:80/?foo=bar&baz=42'
1568 'http://user:pw@host:80/?foo=bar&baz=42'
1499 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1569 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1500 'http://user:pw@host:80/?foo=bar%3dbaz'
1570 'http://user:pw@host:80/?foo=bar%3dbaz'
1501 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1571 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1502 'ssh://user:pw@[::1]:2200//home/joe#'
1572 'ssh://user:pw@[::1]:2200//home/joe#'
1503 >>> str(url('http://localhost:80//'))
1573 >>> str(url('http://localhost:80//'))
1504 'http://localhost:80//'
1574 'http://localhost:80//'
1505 >>> str(url('http://localhost:80/'))
1575 >>> str(url('http://localhost:80/'))
1506 'http://localhost:80/'
1576 'http://localhost:80/'
1507 >>> str(url('http://localhost:80'))
1577 >>> str(url('http://localhost:80'))
1508 'http://localhost:80/'
1578 'http://localhost:80/'
1509 >>> str(url('bundle:foo'))
1579 >>> str(url('bundle:foo'))
1510 'bundle:foo'
1580 'bundle:foo'
1511 >>> str(url('bundle://../foo'))
1581 >>> str(url('bundle://../foo'))
1512 'bundle:../foo'
1582 'bundle:../foo'
1513 >>> str(url('path'))
1583 >>> str(url('path'))
1514 'path'
1584 'path'
1515 >>> str(url('file:///tmp/foo/bar'))
1585 >>> str(url('file:///tmp/foo/bar'))
1516 'file:///tmp/foo/bar'
1586 'file:///tmp/foo/bar'
1517 >>> print url(r'bundle:foo\bar')
1587 >>> print url(r'bundle:foo\bar')
1518 bundle:foo\bar
1588 bundle:foo\bar
1519 """
1589 """
1520 if self._localpath:
1590 if self._localpath:
1521 s = self.path
1591 s = self.path
1522 if self.scheme == 'bundle':
1592 if self.scheme == 'bundle':
1523 s = 'bundle:' + s
1593 s = 'bundle:' + s
1524 if self.fragment:
1594 if self.fragment:
1525 s += '#' + self.fragment
1595 s += '#' + self.fragment
1526 return s
1596 return s
1527
1597
1528 s = self.scheme + ':'
1598 s = self.scheme + ':'
1529 if self.user or self.passwd or self.host:
1599 if self.user or self.passwd or self.host:
1530 s += '//'
1600 s += '//'
1531 elif self.scheme and (not self.path or self.path.startswith('/')):
1601 elif self.scheme and (not self.path or self.path.startswith('/')):
1532 s += '//'
1602 s += '//'
1533 if self.user:
1603 if self.user:
1534 s += urllib.quote(self.user, safe=self._safechars)
1604 s += urllib.quote(self.user, safe=self._safechars)
1535 if self.passwd:
1605 if self.passwd:
1536 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1606 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1537 if self.user or self.passwd:
1607 if self.user or self.passwd:
1538 s += '@'
1608 s += '@'
1539 if self.host:
1609 if self.host:
1540 if not (self.host.startswith('[') and self.host.endswith(']')):
1610 if not (self.host.startswith('[') and self.host.endswith(']')):
1541 s += urllib.quote(self.host)
1611 s += urllib.quote(self.host)
1542 else:
1612 else:
1543 s += self.host
1613 s += self.host
1544 if self.port:
1614 if self.port:
1545 s += ':' + urllib.quote(self.port)
1615 s += ':' + urllib.quote(self.port)
1546 if self.host:
1616 if self.host:
1547 s += '/'
1617 s += '/'
1548 if self.path:
1618 if self.path:
1549 # TODO: similar to the query string, we should not unescape the
1619 # TODO: similar to the query string, we should not unescape the
1550 # path when we store it, the path might contain '%2f' = '/',
1620 # path when we store it, the path might contain '%2f' = '/',
1551 # which we should *not* escape.
1621 # which we should *not* escape.
1552 s += urllib.quote(self.path, safe=self._safepchars)
1622 s += urllib.quote(self.path, safe=self._safepchars)
1553 if self.query:
1623 if self.query:
1554 # we store the query in escaped form.
1624 # we store the query in escaped form.
1555 s += '?' + self.query
1625 s += '?' + self.query
1556 if self.fragment is not None:
1626 if self.fragment is not None:
1557 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1627 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1558 return s
1628 return s
1559
1629
1560 def authinfo(self):
1630 def authinfo(self):
1561 user, passwd = self.user, self.passwd
1631 user, passwd = self.user, self.passwd
1562 try:
1632 try:
1563 self.user, self.passwd = None, None
1633 self.user, self.passwd = None, None
1564 s = str(self)
1634 s = str(self)
1565 finally:
1635 finally:
1566 self.user, self.passwd = user, passwd
1636 self.user, self.passwd = user, passwd
1567 if not self.user:
1637 if not self.user:
1568 return (s, None)
1638 return (s, None)
1569 # authinfo[1] is passed to urllib2 password manager, and its URIs
1639 # authinfo[1] is passed to urllib2 password manager, and its URIs
1570 # must not contain credentials.
1640 # must not contain credentials.
1571 return (s, (None, (s, self.host),
1641 return (s, (None, (s, self.host),
1572 self.user, self.passwd or ''))
1642 self.user, self.passwd or ''))
1573
1643
1574 def isabs(self):
1644 def isabs(self):
1575 if self.scheme and self.scheme != 'file':
1645 if self.scheme and self.scheme != 'file':
1576 return True # remote URL
1646 return True # remote URL
1577 if hasdriveletter(self.path):
1647 if hasdriveletter(self.path):
1578 return True # absolute for our purposes - can't be joined()
1648 return True # absolute for our purposes - can't be joined()
1579 if self.path.startswith(r'\\'):
1649 if self.path.startswith(r'\\'):
1580 return True # Windows UNC path
1650 return True # Windows UNC path
1581 if self.path.startswith('/'):
1651 if self.path.startswith('/'):
1582 return True # POSIX-style
1652 return True # POSIX-style
1583 return False
1653 return False
1584
1654
1585 def localpath(self):
1655 def localpath(self):
1586 if self.scheme == 'file' or self.scheme == 'bundle':
1656 if self.scheme == 'file' or self.scheme == 'bundle':
1587 path = self.path or '/'
1657 path = self.path or '/'
1588 # For Windows, we need to promote hosts containing drive
1658 # For Windows, we need to promote hosts containing drive
1589 # letters to paths with drive letters.
1659 # letters to paths with drive letters.
1590 if hasdriveletter(self._hostport):
1660 if hasdriveletter(self._hostport):
1591 path = self._hostport + '/' + self.path
1661 path = self._hostport + '/' + self.path
1592 elif self.host is not None and self.path:
1662 elif self.host is not None and self.path:
1593 path = '/' + path
1663 path = '/' + path
1594 return path
1664 return path
1595 return self._origpath
1665 return self._origpath
1596
1666
1597 def hasscheme(path):
1667 def hasscheme(path):
1598 return bool(url(path).scheme)
1668 return bool(url(path).scheme)
1599
1669
1600 def hasdriveletter(path):
1670 def hasdriveletter(path):
1601 return path[1:2] == ':' and path[0:1].isalpha()
1671 return path[1:2] == ':' and path[0:1].isalpha()
1602
1672
1603 def urllocalpath(path):
1673 def urllocalpath(path):
1604 return url(path, parsequery=False, parsefragment=False).localpath()
1674 return url(path, parsequery=False, parsefragment=False).localpath()
1605
1675
1606 def hidepassword(u):
1676 def hidepassword(u):
1607 '''hide user credential in a url string'''
1677 '''hide user credential in a url string'''
1608 u = url(u)
1678 u = url(u)
1609 if u.passwd:
1679 if u.passwd:
1610 u.passwd = '***'
1680 u.passwd = '***'
1611 return str(u)
1681 return str(u)
1612
1682
1613 def removeauth(u):
1683 def removeauth(u):
1614 '''remove all authentication information from a url string'''
1684 '''remove all authentication information from a url string'''
1615 u = url(u)
1685 u = url(u)
1616 u.user = u.passwd = None
1686 u.user = u.passwd = None
1617 return str(u)
1687 return str(u)
1618
1688
1619 def isatty(fd):
1689 def isatty(fd):
1620 try:
1690 try:
1621 return fd.isatty()
1691 return fd.isatty()
1622 except AttributeError:
1692 except AttributeError:
1623 return False
1693 return False
@@ -1,139 +1,140 b''
1 Test alignment of multibyte characters
1 Test alignment of multibyte characters
2
2
3 $ HGENCODING=utf-8
3 $ HGENCODING=utf-8
4 $ export HGENCODING
4 $ export HGENCODING
5 $ hg init t
5 $ hg init t
6 $ cd t
6 $ cd t
7 $ python << EOF
7 $ python << EOF
8 > # (byte, width) = (6, 4)
8 > # (byte, width) = (6, 4)
9 > s = "\xe7\x9f\xad\xe5\x90\x8d"
9 > s = "\xe7\x9f\xad\xe5\x90\x8d"
10 > # (byte, width) = (7, 7): odd width is good for alignment test
10 > # (byte, width) = (7, 7): odd width is good for alignment test
11 > m = "MIDDLE_"
11 > m = "MIDDLE_"
12 > # (byte, width) = (18, 12)
12 > # (byte, width) = (18, 12)
13 > l = "\xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d"
13 > l = "\xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d"
14 > f = file('s', 'w'); f.write(s); f.close()
14 > f = file('s', 'w'); f.write(s); f.close()
15 > f = file('m', 'w'); f.write(m); f.close()
15 > f = file('m', 'w'); f.write(m); f.close()
16 > f = file('l', 'w'); f.write(l); f.close()
16 > f = file('l', 'w'); f.write(l); f.close()
17 > # instant extension to show list of options
17 > # instant extension to show list of options
18 > f = file('showoptlist.py', 'w'); f.write("""# encoding: utf-8
18 > f = file('showoptlist.py', 'w'); f.write("""# encoding: utf-8
19 > def showoptlist(ui, repo, *pats, **opts):
19 > def showoptlist(ui, repo, *pats, **opts):
20 > '''dummy command to show option descriptions'''
20 > '''dummy command to show option descriptions'''
21 > return 0
21 > return 0
22 > cmdtable = {
22 > cmdtable = {
23 > 'showoptlist':
23 > 'showoptlist':
24 > (showoptlist,
24 > (showoptlist,
25 > [('s', 'opt1', '', 'short width' + ' %(s)s' * 8, '%(s)s'),
25 > [('s', 'opt1', '', 'short width' + ' %(s)s' * 8, '%(s)s'),
26 > ('m', 'opt2', '', 'middle width' + ' %(m)s' * 8, '%(m)s'),
26 > ('m', 'opt2', '', 'middle width' + ' %(m)s' * 8, '%(m)s'),
27 > ('l', 'opt3', '', 'long width' + ' %(l)s' * 8, '%(l)s')
27 > ('l', 'opt3', '', 'long width' + ' %(l)s' * 8, '%(l)s')
28 > ],
28 > ],
29 > ""
29 > ""
30 > )
30 > )
31 > }
31 > }
32 > """ % globals())
32 > """ % globals())
33 > f.close()
33 > f.close()
34 > EOF
34 > EOF
35 $ S=`cat s`
35 $ S=`cat s`
36 $ M=`cat m`
36 $ M=`cat m`
37 $ L=`cat l`
37 $ L=`cat l`
38
38
39 alignment of option descriptions in help
39 alignment of option descriptions in help
40
40
41 $ cat <<EOF > .hg/hgrc
41 $ cat <<EOF > .hg/hgrc
42 > [extensions]
42 > [extensions]
43 > ja_ext = `pwd`/showoptlist.py
43 > ja_ext = `pwd`/showoptlist.py
44 > EOF
44 > EOF
45
45
46 check alignment of option descriptions in help
46 check alignment of option descriptions in help
47
47
48 $ hg help showoptlist
48 $ hg help showoptlist
49 hg showoptlist
49 hg showoptlist
50
50
51 dummy command to show option descriptions
51 dummy command to show option descriptions
52
52
53 options:
53 options:
54
54
55 -s --opt1 \xe7\x9f\xad\xe5\x90\x8d short width \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d (esc)
55 -s --opt1 \xe7\x9f\xad\xe5\x90\x8d short width \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d \xe7\x9f\xad\xe5\x90\x8d (esc)
56 -m --opt2 MIDDLE_ middle width MIDDLE_ MIDDLE_ MIDDLE_ MIDDLE_ MIDDLE_
56 -m --opt2 MIDDLE_ middle width MIDDLE_ MIDDLE_ MIDDLE_ MIDDLE_ MIDDLE_
57 MIDDLE_ MIDDLE_ MIDDLE_
57 MIDDLE_ MIDDLE_ MIDDLE_
58 -l --opt3 \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d long width \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc)
58 -l --opt3 \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d long width \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc)
59 \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc)
59 \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc)
60 \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc)
60
61
61 use "hg -v help showoptlist" to show global options
62 use "hg -v help showoptlist" to show global options
62
63
63
64
64 $ rm -f s; touch s
65 $ rm -f s; touch s
65 $ rm -f m; touch m
66 $ rm -f m; touch m
66 $ rm -f l; touch l
67 $ rm -f l; touch l
67
68
68 add files
69 add files
69
70
70 $ cp s $S
71 $ cp s $S
71 $ hg add $S
72 $ hg add $S
72 $ cp m $M
73 $ cp m $M
73 $ hg add $M
74 $ hg add $M
74 $ cp l $L
75 $ cp l $L
75 $ hg add $L
76 $ hg add $L
76
77
77 commit(1)
78 commit(1)
78
79
79 $ echo 'first line(1)' >> s; cp s $S
80 $ echo 'first line(1)' >> s; cp s $S
80 $ echo 'first line(2)' >> m; cp m $M
81 $ echo 'first line(2)' >> m; cp m $M
81 $ echo 'first line(3)' >> l; cp l $L
82 $ echo 'first line(3)' >> l; cp l $L
82 $ hg commit -m 'first commit' -u $S
83 $ hg commit -m 'first commit' -u $S
83
84
84 commit(2)
85 commit(2)
85
86
86 $ echo 'second line(1)' >> s; cp s $S
87 $ echo 'second line(1)' >> s; cp s $S
87 $ echo 'second line(2)' >> m; cp m $M
88 $ echo 'second line(2)' >> m; cp m $M
88 $ echo 'second line(3)' >> l; cp l $L
89 $ echo 'second line(3)' >> l; cp l $L
89 $ hg commit -m 'second commit' -u $M
90 $ hg commit -m 'second commit' -u $M
90
91
91 commit(3)
92 commit(3)
92
93
93 $ echo 'third line(1)' >> s; cp s $S
94 $ echo 'third line(1)' >> s; cp s $S
94 $ echo 'third line(2)' >> m; cp m $M
95 $ echo 'third line(2)' >> m; cp m $M
95 $ echo 'third line(3)' >> l; cp l $L
96 $ echo 'third line(3)' >> l; cp l $L
96 $ hg commit -m 'third commit' -u $L
97 $ hg commit -m 'third commit' -u $L
97
98
98 check alignment of user names in annotate
99 check alignment of user names in annotate
99
100
100 $ hg annotate -u $M
101 $ hg annotate -u $M
101 \xe7\x9f\xad\xe5\x90\x8d: first line(2) (esc)
102 \xe7\x9f\xad\xe5\x90\x8d: first line(2) (esc)
102 MIDDLE_: second line(2)
103 MIDDLE_: second line(2)
103 \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d: third line(2) (esc)
104 \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d: third line(2) (esc)
104
105
105 check alignment of filenames in diffstat
106 check alignment of filenames in diffstat
106
107
107 $ hg diff -c tip --stat
108 $ hg diff -c tip --stat
108 MIDDLE_ | 1 +
109 MIDDLE_ | 1 +
109 \xe7\x9f\xad\xe5\x90\x8d | 1 + (esc)
110 \xe7\x9f\xad\xe5\x90\x8d | 1 + (esc)
110 \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d | 1 + (esc)
111 \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d | 1 + (esc)
111 3 files changed, 3 insertions(+), 0 deletions(-)
112 3 files changed, 3 insertions(+), 0 deletions(-)
112
113
113 add branches/tags
114 add branches/tags
114
115
115 $ hg branch $S
116 $ hg branch $S
116 marked working directory as branch \xe7\x9f\xad\xe5\x90\x8d (esc)
117 marked working directory as branch \xe7\x9f\xad\xe5\x90\x8d (esc)
117 $ hg tag $S
118 $ hg tag $S
118 $ hg branch $M
119 $ hg branch $M
119 marked working directory as branch MIDDLE_
120 marked working directory as branch MIDDLE_
120 $ hg tag $M
121 $ hg tag $M
121 $ hg branch $L
122 $ hg branch $L
122 marked working directory as branch \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc)
123 marked working directory as branch \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc)
123 $ hg tag $L
124 $ hg tag $L
124
125
125 check alignment of branches
126 check alignment of branches
126
127
127 $ hg tags
128 $ hg tags
128 tip 5:d745ff46155b
129 tip 5:d745ff46155b
129 \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d 4:9259be597f19 (esc)
130 \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d 4:9259be597f19 (esc)
130 MIDDLE_ 3:b06c5b6def9e
131 MIDDLE_ 3:b06c5b6def9e
131 \xe7\x9f\xad\xe5\x90\x8d 2:64a70663cee8 (esc)
132 \xe7\x9f\xad\xe5\x90\x8d 2:64a70663cee8 (esc)
132
133
133 check alignment of tags
134 check alignment of tags
134
135
135 $ hg tags
136 $ hg tags
136 tip 5:d745ff46155b
137 tip 5:d745ff46155b
137 \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d 4:9259be597f19 (esc)
138 \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d 4:9259be597f19 (esc)
138 MIDDLE_ 3:b06c5b6def9e
139 MIDDLE_ 3:b06c5b6def9e
139 \xe7\x9f\xad\xe5\x90\x8d 2:64a70663cee8 (esc)
140 \xe7\x9f\xad\xe5\x90\x8d 2:64a70663cee8 (esc)
General Comments 0
You need to be logged in to leave comments. Login now