##// END OF EJS Templates
formatting: fix some recent formatting regressions...
Martin von Zweigbergk -
r44307:612951e0 default
parent child Browse files
Show More
@@ -1,363 +1,363 b''
1 1 from __future__ import absolute_import, print_function
2 2
3 3 import argparse
4 4 import struct
5 5 import zipfile
6 6
7 7 from mercurial import (
8 8 hg,
9 9 ui as uimod,
10 10 )
11 11
12 12 ap = argparse.ArgumentParser()
13 13 ap.add_argument("out", metavar="some.zip", type=str, nargs=1)
14 14 args = ap.parse_args()
15 15
16 16
17 17 class deltafrag(object):
18 18 def __init__(self, start, end, data):
19 19 self.start = start
20 20 self.end = end
21 21 self.data = data
22 22
23 23 def __repr__(self):
24 24 # py2 calls __repr__ when you do `bytes(foo)`
25 25 return self.__bytes__()
26 26
27 27 def __bytes__(self):
28 28 return (
29 29 struct.pack(">lll", self.start, self.end, len(self.data))
30 30 + self.data
31 31 )
32 32
33 33
34 34 class delta(object):
35 35 def __init__(self, frags):
36 36 self.frags = frags
37 37
38 38 def __repr__(self):
39 39 # py2 calls __repr__ when you do `bytes(foo)`
40 40 return self.__bytes__()
41 41
42 42 def __bytes__(self):
43 43 return b''.join(bytes(f) for f in self.frags)
44 44
45 45
46 46 class corpus(object):
47 47 def __init__(self, base, deltas):
48 48 self.base = base
49 49 self.deltas = deltas
50 50
51 51 def __repr__(self):
52 52 # py2 calls __repr__ when you do `bytes(foo)`
53 53 return self.__bytes__()
54 54
55 55 def __bytes__(self):
56 56 deltas = [bytes(d) for d in self.deltas]
57 57 parts = (
58 58 [
59 59 struct.pack(">B", len(deltas) + 1),
60 60 struct.pack(">H", len(self.base)),
61 61 ]
62 62 + [struct.pack(">H", len(d)) for d in deltas]
63 63 + [self.base]
64 64 + deltas
65 65 )
66 66 return b''.join(parts)
67 67
68 68
69 69 with zipfile.ZipFile(args.out[0], "w", zipfile.ZIP_STORED) as zf:
70 70 # Manually constructed entries
71 71 zf.writestr(
72 72 "one_delta_applies",
73 bytes(corpus(b'a', [delta([deltafrag(0, 1, b'b')])]))
73 bytes(corpus(b'a', [delta([deltafrag(0, 1, b'b')])])),
74 74 )
75 75 zf.writestr(
76 76 "one_delta_starts_late",
77 bytes(corpus(b'a', [delta([deltafrag(3, 1, b'b')])]))
77 bytes(corpus(b'a', [delta([deltafrag(3, 1, b'b')])])),
78 78 )
79 79 zf.writestr(
80 80 "one_delta_ends_late",
81 bytes(corpus(b'a', [delta([deltafrag(0, 20, b'b')])]))
81 bytes(corpus(b'a', [delta([deltafrag(0, 20, b'b')])])),
82 82 )
83 83
84 84 try:
85 85 # Generated from repo data
86 86 r = hg.repository(uimod.ui(), b'../..')
87 87 fl = r.file(b'mercurial/manifest.py')
88 88 rl = getattr(fl, '_revlog', fl)
89 89 bins = rl._chunks(rl._deltachain(10)[0])
90 90 zf.writestr('manifest_py_rev_10', bytes(corpus(bins[0], bins[1:])))
91 91 except: # skip this, so no re-raises
92 92 print('skipping seed file from repo data')
93 93 # Automatically discovered by running the fuzzer
94 94 zf.writestr(
95 95 "mpatch_decode_old_overread", b"\x02\x00\x00\x00\x02\x00\x00\x00"
96 96 )
97 97 # https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=8876
98 98 zf.writestr(
99 99 "mpatch_ossfuzz_getbe32_ubsan",
100 100 b"\x02\x00\x00\x00\x0c \xff\xff\xff\xff ",
101 101 )
102 102 zf.writestr(
103 103 "mpatch_apply_over_memcpy",
104 104 b'\x13\x01\x00\x05\xd0\x00\x00\x00\x00\x00\x00\x00\x00\n \x00\x00\x00'
105 105 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
106 106 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00'
107 107 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
108 108 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
109 109 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
110 110 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
111 111 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
112 112 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
113 113 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
114 114 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
115 115 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
116 116 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
117 117 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
118 118 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
119 119 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
120 120 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
121 121 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
122 122 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
123 123 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
124 124 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
125 125 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
126 126 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
127 127 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
128 128 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
129 129 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
130 130 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
131 131 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
132 132 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
133 133 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
134 134 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8c\x00\x00\x00\x00'
135 135 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
136 136 b'\x00\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00\x00\x00\x00\x00\x00'
137 137 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
138 138 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
139 139 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
140 140 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
141 141 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
142 142 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
143 143 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
144 144 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
145 145 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
146 146 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
147 147 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
148 148 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
149 149 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
150 150 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
151 151 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
152 152 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
153 153 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
154 154 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
155 155 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
156 156 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
157 157 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
158 158 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
159 159 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
160 160 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
161 161 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
162 162 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
163 163 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
164 164 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
165 165 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
166 166 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
167 167 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
168 168 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
169 169 b'\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00A\x00\x00\x00\x00'
170 170 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
171 171 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
172 172 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
173 173 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
174 174 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
175 175 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
176 176 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
177 177 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
178 178 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
179 179 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
180 180 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
181 181 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
182 182 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
183 183 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
184 184 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
185 185 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
186 186 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
187 187 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
188 188 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
189 189 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
190 190 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
191 191 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
192 192 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
193 193 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
194 194 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x94\x18'
195 195 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
196 196 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
197 197 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
198 198 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
199 199 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
200 200 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
201 201 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
202 202 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
203 203 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
204 204 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
205 205 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
206 206 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
207 207 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
208 208 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
209 209 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
210 210 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
211 211 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
212 212 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
213 213 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
214 214 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
215 215 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
216 216 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
217 217 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
218 218 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
219 219 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
220 220 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
221 221 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
222 222 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
223 223 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
224 224 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
225 225 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
226 226 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
227 227 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
228 228 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
229 229 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
230 230 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
231 231 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
232 232 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
233 233 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
234 234 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
235 235 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
236 236 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
237 237 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
238 238 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
239 239 b'\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
240 240 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
241 241 b'\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfa\x00\x00\x00\x00\x00\x00\x00'
242 242 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
243 243 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
244 244 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
245 245 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
246 246 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
247 247 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
248 248 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
249 249 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
250 250 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
251 251 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
252 252 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
253 253 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
254 254 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
255 255 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
256 256 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
257 257 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
258 258 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
259 259 b'\x00\x00\x94\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
260 260 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
261 261 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
262 262 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
263 263 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
264 264 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
265 265 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
266 266 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
267 267 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
268 268 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
269 269 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
270 270 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
271 271 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
272 272 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
273 273 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
274 274 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
275 275 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
276 276 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
277 277 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
278 278 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
279 279 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
280 280 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
281 281 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
282 282 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
283 283 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
284 284 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
285 285 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
286 286 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
287 287 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
288 288 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
289 289 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
290 290 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
291 291 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
292 292 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
293 293 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
294 294 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
295 295 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
296 296 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
297 297 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
298 298 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
299 299 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
300 300 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
301 301 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
302 302 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
303 303 b'\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
304 304 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
305 305 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfa\x00\x00\x00'
306 306 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
307 307 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
308 308 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
309 309 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
310 310 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
311 311 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
312 312 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
313 313 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
314 314 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
315 315 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
316 316 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
317 317 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
318 318 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
319 319 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
320 320 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
321 321 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
322 322 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
323 323 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
324 324 b'\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00'
325 325 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
326 326 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
327 327 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
328 328 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
329 329 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
330 330 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
331 331 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
332 332 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
333 333 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
334 334 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
335 335 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
336 336 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
337 337 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
338 338 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
339 339 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13\x00\x00\x00\x00\x00\x00'
340 340 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
341 341 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
342 342 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
343 343 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
344 344 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00]\x00\x00'
345 345 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
346 346 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
347 347 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
348 348 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
349 349 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
350 350 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
351 351 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
352 352 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
353 353 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
354 354 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
355 355 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
356 356 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
357 357 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
358 358 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
359 359 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
360 360 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00se\x00\x00'
361 361 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
362 362 b'\x00\x00\x00\x00',
363 363 )
@@ -1,4009 +1,4017 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import copy as copymod
11 11 import errno
12 12 import os
13 13 import re
14 14
15 15 from .i18n import _
16 16 from .node import (
17 17 hex,
18 18 nullid,
19 19 nullrev,
20 20 short,
21 21 )
22 22 from .pycompat import (
23 23 getattr,
24 24 open,
25 25 setattr,
26 26 )
27 27 from .thirdparty import attr
28 28
29 29 from . import (
30 30 bookmarks,
31 31 changelog,
32 32 copies,
33 33 crecord as crecordmod,
34 34 dirstateguard,
35 35 encoding,
36 36 error,
37 37 formatter,
38 38 logcmdutil,
39 39 match as matchmod,
40 40 merge as mergemod,
41 41 mergeutil,
42 42 obsolete,
43 43 patch,
44 44 pathutil,
45 45 phases,
46 46 pycompat,
47 47 repair,
48 48 revlog,
49 49 rewriteutil,
50 50 scmutil,
51 51 smartset,
52 52 state as statemod,
53 53 subrepoutil,
54 54 templatekw,
55 55 templater,
56 56 util,
57 57 vfs as vfsmod,
58 58 )
59 59
60 60 from .utils import (
61 61 dateutil,
62 62 stringutil,
63 63 )
64 64
65 65 if pycompat.TYPE_CHECKING:
66 66 from typing import (
67 67 Any,
68 68 Dict,
69 69 )
70 70
71 71 for t in (Any, Dict):
72 72 assert t
73 73
74 74 stringio = util.stringio
75 75
76 76 # templates of common command options
77 77
78 78 dryrunopts = [
79 79 (b'n', b'dry-run', None, _(b'do not perform actions, just print output')),
80 80 ]
81 81
82 82 confirmopts = [
83 83 (b'', b'confirm', None, _(b'ask before applying actions')),
84 84 ]
85 85
86 86 remoteopts = [
87 87 (b'e', b'ssh', b'', _(b'specify ssh command to use'), _(b'CMD')),
88 88 (
89 89 b'',
90 90 b'remotecmd',
91 91 b'',
92 92 _(b'specify hg command to run on the remote side'),
93 93 _(b'CMD'),
94 94 ),
95 95 (
96 96 b'',
97 97 b'insecure',
98 98 None,
99 99 _(b'do not verify server certificate (ignoring web.cacerts config)'),
100 100 ),
101 101 ]
102 102
103 103 walkopts = [
104 104 (
105 105 b'I',
106 106 b'include',
107 107 [],
108 108 _(b'include names matching the given patterns'),
109 109 _(b'PATTERN'),
110 110 ),
111 111 (
112 112 b'X',
113 113 b'exclude',
114 114 [],
115 115 _(b'exclude names matching the given patterns'),
116 116 _(b'PATTERN'),
117 117 ),
118 118 ]
119 119
120 120 commitopts = [
121 121 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
122 122 (b'l', b'logfile', b'', _(b'read commit message from file'), _(b'FILE')),
123 123 ]
124 124
125 125 commitopts2 = [
126 126 (
127 127 b'd',
128 128 b'date',
129 129 b'',
130 130 _(b'record the specified date as commit date'),
131 131 _(b'DATE'),
132 132 ),
133 133 (
134 134 b'u',
135 135 b'user',
136 136 b'',
137 137 _(b'record the specified user as committer'),
138 138 _(b'USER'),
139 139 ),
140 140 ]
141 141
142 142 commitopts3 = [
143 143 (b'D', b'currentdate', None, _(b'record the current date as commit date')),
144 144 (b'U', b'currentuser', None, _(b'record the current user as committer')),
145 145 ]
146 146
147 147 formatteropts = [
148 148 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
149 149 ]
150 150
151 151 templateopts = [
152 152 (
153 153 b'',
154 154 b'style',
155 155 b'',
156 156 _(b'display using template map file (DEPRECATED)'),
157 157 _(b'STYLE'),
158 158 ),
159 159 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
160 160 ]
161 161
162 162 logopts = [
163 163 (b'p', b'patch', None, _(b'show patch')),
164 164 (b'g', b'git', None, _(b'use git extended diff format')),
165 165 (b'l', b'limit', b'', _(b'limit number of changes displayed'), _(b'NUM')),
166 166 (b'M', b'no-merges', None, _(b'do not show merges')),
167 167 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
168 168 (b'G', b'graph', None, _(b"show the revision DAG")),
169 169 ] + templateopts
170 170
171 171 diffopts = [
172 172 (b'a', b'text', None, _(b'treat all files as text')),
173 173 (b'g', b'git', None, _(b'use git extended diff format')),
174 174 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
175 175 (b'', b'nodates', None, _(b'omit dates from diff headers')),
176 176 ]
177 177
178 178 diffwsopts = [
179 179 (
180 180 b'w',
181 181 b'ignore-all-space',
182 182 None,
183 183 _(b'ignore white space when comparing lines'),
184 184 ),
185 185 (
186 186 b'b',
187 187 b'ignore-space-change',
188 188 None,
189 189 _(b'ignore changes in the amount of white space'),
190 190 ),
191 191 (
192 192 b'B',
193 193 b'ignore-blank-lines',
194 194 None,
195 195 _(b'ignore changes whose lines are all blank'),
196 196 ),
197 197 (
198 198 b'Z',
199 199 b'ignore-space-at-eol',
200 200 None,
201 201 _(b'ignore changes in whitespace at EOL'),
202 202 ),
203 203 ]
204 204
205 205 diffopts2 = (
206 206 [
207 207 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
208 208 (
209 209 b'p',
210 210 b'show-function',
211 211 None,
212 212 _(b'show which function each change is in'),
213 213 ),
214 214 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
215 215 ]
216 216 + diffwsopts
217 217 + [
218 218 (
219 219 b'U',
220 220 b'unified',
221 221 b'',
222 222 _(b'number of lines of context to show'),
223 223 _(b'NUM'),
224 224 ),
225 225 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
226 226 (
227 227 b'',
228 228 b'root',
229 229 b'',
230 230 _(b'produce diffs relative to subdirectory'),
231 231 _(b'DIR'),
232 232 ),
233 233 ]
234 234 )
235 235
236 236 mergetoolopts = [
237 237 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
238 238 ]
239 239
240 240 similarityopts = [
241 241 (
242 242 b's',
243 243 b'similarity',
244 244 b'',
245 245 _(b'guess renamed files by similarity (0<=s<=100)'),
246 246 _(b'SIMILARITY'),
247 247 )
248 248 ]
249 249
250 250 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
251 251
252 252 debugrevlogopts = [
253 253 (b'c', b'changelog', False, _(b'open changelog')),
254 254 (b'm', b'manifest', False, _(b'open manifest')),
255 255 (b'', b'dir', b'', _(b'open directory manifest')),
256 256 ]
257 257
258 258 # special string such that everything below this line will be ingored in the
259 259 # editor text
260 260 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
261 261
262 262
263 263 def resolvecommitoptions(ui, opts):
264 264 """modify commit options dict to handle related options
265 265
266 266 The return value indicates that ``rewrite.update-timestamp`` is the reason
267 267 the ``date`` option is set.
268 268 """
269 269 if opts.get(b'date') and opts.get(b'currentdate'):
270 270 raise error.Abort(_(b'--date and --currentdate are mutually exclusive'))
271 271 if opts.get(b'user') and opts.get(b'currentuser'):
272 272 raise error.Abort(_(b'--user and --currentuser are mutually exclusive'))
273 273
274 274 datemaydiffer = False # date-only change should be ignored?
275 275
276 276 if opts.get(b'currentdate'):
277 277 opts[b'date'] = b'%d %d' % dateutil.makedate()
278 278 elif (
279 279 not opts.get(b'date')
280 280 and ui.configbool(b'rewrite', b'update-timestamp')
281 281 and opts.get(b'currentdate') is None
282 282 ):
283 283 opts[b'date'] = b'%d %d' % dateutil.makedate()
284 284 datemaydiffer = True
285 285
286 286 if opts.get(b'currentuser'):
287 287 opts[b'user'] = ui.username()
288 288
289 289 return datemaydiffer
290 290
291 291
292 292 def checknotesize(ui, opts):
293 293 """ make sure note is of valid format """
294 294
295 295 note = opts.get(b'note')
296 296 if not note:
297 297 return
298 298
299 299 if len(note) > 255:
300 300 raise error.Abort(_(b"cannot store a note of more than 255 bytes"))
301 301 if b'\n' in note:
302 302 raise error.Abort(_(b"note cannot contain a newline"))
303 303
304 304
305 305 def ishunk(x):
306 306 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
307 307 return isinstance(x, hunkclasses)
308 308
309 309
310 310 def newandmodified(chunks, originalchunks):
311 311 newlyaddedandmodifiedfiles = set()
312 312 alsorestore = set()
313 313 for chunk in chunks:
314 314 if (
315 315 ishunk(chunk)
316 316 and chunk.header.isnewfile()
317 317 and chunk not in originalchunks
318 318 ):
319 319 newlyaddedandmodifiedfiles.add(chunk.header.filename())
320 320 alsorestore.update(
321 321 set(chunk.header.files()) - {chunk.header.filename()}
322 322 )
323 323 return newlyaddedandmodifiedfiles, alsorestore
324 324
325 325
326 326 def parsealiases(cmd):
327 327 return cmd.split(b"|")
328 328
329 329
330 330 def setupwrapcolorwrite(ui):
331 331 # wrap ui.write so diff output can be labeled/colorized
332 332 def wrapwrite(orig, *args, **kw):
333 333 label = kw.pop('label', b'')
334 334 for chunk, l in patch.difflabel(lambda: args):
335 335 orig(chunk, label=label + l)
336 336
337 337 oldwrite = ui.write
338 338
339 339 def wrap(*args, **kwargs):
340 340 return wrapwrite(oldwrite, *args, **kwargs)
341 341
342 342 setattr(ui, 'write', wrap)
343 343 return oldwrite
344 344
345 345
346 346 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
347 347 try:
348 348 if usecurses:
349 349 if testfile:
350 350 recordfn = crecordmod.testdecorator(
351 351 testfile, crecordmod.testchunkselector
352 352 )
353 353 else:
354 354 recordfn = crecordmod.chunkselector
355 355
356 356 return crecordmod.filterpatch(
357 357 ui, originalhunks, recordfn, operation
358 358 )
359 359 except crecordmod.fallbackerror as e:
360 360 ui.warn(b'%s\n' % e.message) # pytype: disable=attribute-error
361 361 ui.warn(_(b'falling back to text mode\n'))
362 362
363 363 return patch.filterpatch(ui, originalhunks, match, operation)
364 364
365 365
366 366 def recordfilter(ui, originalhunks, match, operation=None):
367 367 """ Prompts the user to filter the originalhunks and return a list of
368 368 selected hunks.
369 369 *operation* is used for to build ui messages to indicate the user what
370 370 kind of filtering they are doing: reverting, committing, shelving, etc.
371 371 (see patch.filterpatch).
372 372 """
373 373 usecurses = crecordmod.checkcurses(ui)
374 374 testfile = ui.config(b'experimental', b'crecordtest')
375 375 oldwrite = setupwrapcolorwrite(ui)
376 376 try:
377 377 newchunks, newopts = filterchunks(
378 378 ui, originalhunks, usecurses, testfile, match, operation
379 379 )
380 380 finally:
381 381 ui.write = oldwrite
382 382 return newchunks, newopts
383 383
384 384
385 385 def dorecord(
386 386 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
387 387 ):
388 388 opts = pycompat.byteskwargs(opts)
389 389 if not ui.interactive():
390 390 if cmdsuggest:
391 391 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
392 392 else:
393 393 msg = _(b'running non-interactively')
394 394 raise error.Abort(msg)
395 395
396 396 # make sure username is set before going interactive
397 397 if not opts.get(b'user'):
398 398 ui.username() # raise exception, username not provided
399 399
400 400 def recordfunc(ui, repo, message, match, opts):
401 401 """This is generic record driver.
402 402
403 403 Its job is to interactively filter local changes, and
404 404 accordingly prepare working directory into a state in which the
405 405 job can be delegated to a non-interactive commit command such as
406 406 'commit' or 'qrefresh'.
407 407
408 408 After the actual job is done by non-interactive command, the
409 409 working directory is restored to its original state.
410 410
411 411 In the end we'll record interesting changes, and everything else
412 412 will be left in place, so the user can continue working.
413 413 """
414 414 if not opts.get(b'interactive-unshelve'):
415 415 checkunfinished(repo, commit=True)
416 416 wctx = repo[None]
417 417 merge = len(wctx.parents()) > 1
418 418 if merge:
419 419 raise error.Abort(
420 420 _(
421 421 b'cannot partially commit a merge '
422 422 b'(use "hg commit" instead)'
423 423 )
424 424 )
425 425
426 426 def fail(f, msg):
427 427 raise error.Abort(b'%s: %s' % (f, msg))
428 428
429 429 force = opts.get(b'force')
430 430 if not force:
431 431 match = matchmod.badmatch(match, fail)
432 432
433 433 status = repo.status(match=match)
434 434
435 435 overrides = {(b'ui', b'commitsubrepos'): True}
436 436
437 437 with repo.ui.configoverride(overrides, b'record'):
438 438 # subrepoutil.precommit() modifies the status
439 439 tmpstatus = scmutil.status(
440 440 copymod.copy(status.modified),
441 441 copymod.copy(status.added),
442 442 copymod.copy(status.removed),
443 443 copymod.copy(status.deleted),
444 444 copymod.copy(status.unknown),
445 445 copymod.copy(status.ignored),
446 446 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
447 447 )
448 448
449 449 # Force allows -X subrepo to skip the subrepo.
450 450 subs, commitsubs, newstate = subrepoutil.precommit(
451 451 repo.ui, wctx, tmpstatus, match, force=True
452 452 )
453 453 for s in subs:
454 454 if s in commitsubs:
455 455 dirtyreason = wctx.sub(s).dirtyreason(True)
456 456 raise error.Abort(dirtyreason)
457 457
458 458 if not force:
459 459 repo.checkcommitpatterns(wctx, match, status, fail)
460 460 diffopts = patch.difffeatureopts(
461 461 ui,
462 462 opts=opts,
463 463 whitespace=True,
464 464 section=b'commands',
465 465 configprefix=b'commit.interactive.',
466 466 )
467 467 diffopts.nodates = True
468 468 diffopts.git = True
469 469 diffopts.showfunc = True
470 470 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
471 471 originalchunks = patch.parsepatch(originaldiff)
472 472 match = scmutil.match(repo[None], pats)
473 473
474 474 # 1. filter patch, since we are intending to apply subset of it
475 475 try:
476 476 chunks, newopts = filterfn(ui, originalchunks, match)
477 477 except error.PatchError as err:
478 478 raise error.Abort(_(b'error parsing patch: %s') % err)
479 479 opts.update(newopts)
480 480
481 481 # We need to keep a backup of files that have been newly added and
482 482 # modified during the recording process because there is a previous
483 483 # version without the edit in the workdir. We also will need to restore
484 484 # files that were the sources of renames so that the patch application
485 485 # works.
486 486 newlyaddedandmodifiedfiles, alsorestore = newandmodified(
487 487 chunks, originalchunks
488 488 )
489 489 contenders = set()
490 490 for h in chunks:
491 491 try:
492 492 contenders.update(set(h.files()))
493 493 except AttributeError:
494 494 pass
495 495
496 496 changed = status.modified + status.added + status.removed
497 497 newfiles = [f for f in changed if f in contenders]
498 498 if not newfiles:
499 499 ui.status(_(b'no changes to record\n'))
500 500 return 0
501 501
502 502 modified = set(status.modified)
503 503
504 504 # 2. backup changed files, so we can restore them in the end
505 505
506 506 if backupall:
507 507 tobackup = changed
508 508 else:
509 509 tobackup = [
510 510 f
511 511 for f in newfiles
512 512 if f in modified or f in newlyaddedandmodifiedfiles
513 513 ]
514 514 backups = {}
515 515 if tobackup:
516 516 backupdir = repo.vfs.join(b'record-backups')
517 517 try:
518 518 os.mkdir(backupdir)
519 519 except OSError as err:
520 520 if err.errno != errno.EEXIST:
521 521 raise
522 522 try:
523 523 # backup continues
524 524 for f in tobackup:
525 525 fd, tmpname = pycompat.mkstemp(
526 526 prefix=f.replace(b'/', b'_') + b'.', dir=backupdir
527 527 )
528 528 os.close(fd)
529 529 ui.debug(b'backup %r as %r\n' % (f, tmpname))
530 530 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
531 531 backups[f] = tmpname
532 532
533 533 fp = stringio()
534 534 for c in chunks:
535 535 fname = c.filename()
536 536 if fname in backups:
537 537 c.write(fp)
538 538 dopatch = fp.tell()
539 539 fp.seek(0)
540 540
541 541 # 2.5 optionally review / modify patch in text editor
542 542 if opts.get(b'review', False):
543 543 patchtext = (
544 544 crecordmod.diffhelptext
545 545 + crecordmod.patchhelptext
546 546 + fp.read()
547 547 )
548 548 reviewedpatch = ui.edit(
549 549 patchtext, b"", action=b"diff", repopath=repo.path
550 550 )
551 551 fp.truncate(0)
552 552 fp.write(reviewedpatch)
553 553 fp.seek(0)
554 554
555 555 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
556 556 # 3a. apply filtered patch to clean repo (clean)
557 557 if backups:
558 558 # Equivalent to hg.revert
559 559 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
560 560 mergemod.update(
561 561 repo,
562 562 repo.dirstate.p1(),
563 563 branchmerge=False,
564 564 force=True,
565 565 matcher=m,
566 566 )
567 567
568 568 # 3b. (apply)
569 569 if dopatch:
570 570 try:
571 571 ui.debug(b'applying patch\n')
572 572 ui.debug(fp.getvalue())
573 573 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
574 574 except error.PatchError as err:
575 575 raise error.Abort(pycompat.bytestr(err))
576 576 del fp
577 577
578 578 # 4. We prepared working directory according to filtered
579 579 # patch. Now is the time to delegate the job to
580 580 # commit/qrefresh or the like!
581 581
582 582 # Make all of the pathnames absolute.
583 583 newfiles = [repo.wjoin(nf) for nf in newfiles]
584 584 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
585 585 finally:
586 586 # 5. finally restore backed-up files
587 587 try:
588 588 dirstate = repo.dirstate
589 589 for realname, tmpname in pycompat.iteritems(backups):
590 590 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
591 591
592 592 if dirstate[realname] == b'n':
593 593 # without normallookup, restoring timestamp
594 594 # may cause partially committed files
595 595 # to be treated as unmodified
596 596 dirstate.normallookup(realname)
597 597
598 598 # copystat=True here and above are a hack to trick any
599 599 # editors that have f open that we haven't modified them.
600 600 #
601 601 # Also note that this racy as an editor could notice the
602 602 # file's mtime before we've finished writing it.
603 603 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
604 604 os.unlink(tmpname)
605 605 if tobackup:
606 606 os.rmdir(backupdir)
607 607 except OSError:
608 608 pass
609 609
610 610 def recordinwlock(ui, repo, message, match, opts):
611 611 with repo.wlock():
612 612 return recordfunc(ui, repo, message, match, opts)
613 613
614 614 return commit(ui, repo, recordinwlock, pats, opts)
615 615
616 616
617 617 class dirnode(object):
618 618 """
619 619 Represent a directory in user working copy with information required for
620 620 the purpose of tersing its status.
621 621
622 622 path is the path to the directory, without a trailing '/'
623 623
624 624 statuses is a set of statuses of all files in this directory (this includes
625 625 all the files in all the subdirectories too)
626 626
627 627 files is a list of files which are direct child of this directory
628 628
629 629 subdirs is a dictionary of sub-directory name as the key and it's own
630 630 dirnode object as the value
631 631 """
632 632
633 633 def __init__(self, dirpath):
634 634 self.path = dirpath
635 635 self.statuses = set()
636 636 self.files = []
637 637 self.subdirs = {}
638 638
639 639 def _addfileindir(self, filename, status):
640 640 """Add a file in this directory as a direct child."""
641 641 self.files.append((filename, status))
642 642
643 643 def addfile(self, filename, status):
644 644 """
645 645 Add a file to this directory or to its direct parent directory.
646 646
647 647 If the file is not direct child of this directory, we traverse to the
648 648 directory of which this file is a direct child of and add the file
649 649 there.
650 650 """
651 651
652 652 # the filename contains a path separator, it means it's not the direct
653 653 # child of this directory
654 654 if b'/' in filename:
655 655 subdir, filep = filename.split(b'/', 1)
656 656
657 657 # does the dirnode object for subdir exists
658 658 if subdir not in self.subdirs:
659 659 subdirpath = pathutil.join(self.path, subdir)
660 660 self.subdirs[subdir] = dirnode(subdirpath)
661 661
662 662 # try adding the file in subdir
663 663 self.subdirs[subdir].addfile(filep, status)
664 664
665 665 else:
666 666 self._addfileindir(filename, status)
667 667
668 668 if status not in self.statuses:
669 669 self.statuses.add(status)
670 670
671 671 def iterfilepaths(self):
672 672 """Yield (status, path) for files directly under this directory."""
673 673 for f, st in self.files:
674 674 yield st, pathutil.join(self.path, f)
675 675
676 676 def tersewalk(self, terseargs):
677 677 """
678 678 Yield (status, path) obtained by processing the status of this
679 679 dirnode.
680 680
681 681 terseargs is the string of arguments passed by the user with `--terse`
682 682 flag.
683 683
684 684 Following are the cases which can happen:
685 685
686 686 1) All the files in the directory (including all the files in its
687 687 subdirectories) share the same status and the user has asked us to terse
688 688 that status. -> yield (status, dirpath). dirpath will end in '/'.
689 689
690 690 2) Otherwise, we do following:
691 691
692 692 a) Yield (status, filepath) for all the files which are in this
693 693 directory (only the ones in this directory, not the subdirs)
694 694
695 695 b) Recurse the function on all the subdirectories of this
696 696 directory
697 697 """
698 698
699 699 if len(self.statuses) == 1:
700 700 onlyst = self.statuses.pop()
701 701
702 702 # Making sure we terse only when the status abbreviation is
703 703 # passed as terse argument
704 704 if onlyst in terseargs:
705 705 yield onlyst, self.path + b'/'
706 706 return
707 707
708 708 # add the files to status list
709 709 for st, fpath in self.iterfilepaths():
710 710 yield st, fpath
711 711
712 712 # recurse on the subdirs
713 713 for dirobj in self.subdirs.values():
714 714 for st, fpath in dirobj.tersewalk(terseargs):
715 715 yield st, fpath
716 716
717 717
718 718 def tersedir(statuslist, terseargs):
719 719 """
720 720 Terse the status if all the files in a directory shares the same status.
721 721
722 722 statuslist is scmutil.status() object which contains a list of files for
723 723 each status.
724 724 terseargs is string which is passed by the user as the argument to `--terse`
725 725 flag.
726 726
727 727 The function makes a tree of objects of dirnode class, and at each node it
728 728 stores the information required to know whether we can terse a certain
729 729 directory or not.
730 730 """
731 731 # the order matters here as that is used to produce final list
732 732 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
733 733
734 734 # checking the argument validity
735 735 for s in pycompat.bytestr(terseargs):
736 736 if s not in allst:
737 737 raise error.Abort(_(b"'%s' not recognized") % s)
738 738
739 739 # creating a dirnode object for the root of the repo
740 740 rootobj = dirnode(b'')
741 741 pstatus = (
742 742 b'modified',
743 743 b'added',
744 744 b'deleted',
745 745 b'clean',
746 746 b'unknown',
747 747 b'ignored',
748 748 b'removed',
749 749 )
750 750
751 751 tersedict = {}
752 752 for attrname in pstatus:
753 753 statuschar = attrname[0:1]
754 754 for f in getattr(statuslist, attrname):
755 755 rootobj.addfile(f, statuschar)
756 756 tersedict[statuschar] = []
757 757
758 758 # we won't be tersing the root dir, so add files in it
759 759 for st, fpath in rootobj.iterfilepaths():
760 760 tersedict[st].append(fpath)
761 761
762 762 # process each sub-directory and build tersedict
763 763 for subdir in rootobj.subdirs.values():
764 764 for st, f in subdir.tersewalk(terseargs):
765 765 tersedict[st].append(f)
766 766
767 767 tersedlist = []
768 768 for st in allst:
769 769 tersedict[st].sort()
770 770 tersedlist.append(tersedict[st])
771 771
772 772 return scmutil.status(*tersedlist)
773 773
774 774
775 775 def _commentlines(raw):
776 776 '''Surround lineswith a comment char and a new line'''
777 777 lines = raw.splitlines()
778 778 commentedlines = [b'# %s' % line for line in lines]
779 779 return b'\n'.join(commentedlines) + b'\n'
780 780
781 781
782 782 @attr.s(frozen=True)
783 783 class morestatus(object):
784 784 reporoot = attr.ib()
785 785 unfinishedop = attr.ib()
786 786 unfinishedmsg = attr.ib()
787 787 inmergestate = attr.ib()
788 788 unresolvedpaths = attr.ib()
789 789 _label = b'status.morestatus'
790 790
791 791 def formatfile(self, path, fm):
792 792 if self.inmergestate and path in self.unresolvedpaths:
793 793 fm.data(unresolved=True)
794 794
795 795 def formatfooter(self, fm):
796 statemsg = _(b'The repository is in an unfinished *%s* state.'
797 ) % self.unfinishedop
796 statemsg = (
797 _(b'The repository is in an unfinished *%s* state.')
798 % self.unfinishedop
799 )
798 800 fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label)
799 801
800 802 self._formatconflicts(fm)
801 803 if self.unfinishedmsg:
802 fm.plain(b'%s\n' % _commentlines(self.unfinishedmsg),
803 label=self._label)
804 fm.plain(
805 b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label
806 )
804 807
805 808 def _formatconflicts(self, fm):
806 809 if not self.inmergestate:
807 810 return
808 811
809 812 if self.unresolvedpaths:
810 813 mergeliststr = b'\n'.join(
811 814 [
812 b' %s' % util.pathto(self.reporoot, encoding.getcwd(),
813 path)
815 b' %s'
816 % util.pathto(self.reporoot, encoding.getcwd(), path)
814 817 for path in self.unresolvedpaths
815 818 ]
816 819 )
817 820 msg = (
818 821 _(
819 822 '''Unresolved merge conflicts:
820 823
821 824 %s
822 825
823 826 To mark files as resolved: hg resolve --mark FILE'''
824 827 )
825 828 % mergeliststr
826 829 )
827 830 else:
828 831 msg = _(b'No unresolved merge conflicts.')
829 832
830 833 fm.plain(b'%s\n' % _commentlines(msg), label=self._label)
831 834
832 835
833 836 def readmorestatus(repo):
834 837 """Returns a morestatus object if the repo has unfinished state."""
835 838 statetuple = statemod.getrepostate(repo)
836 839 if not statetuple:
837 840 return None
838 841
839 842 unfinishedop, unfinishedmsg = statetuple
840 843 mergestate = mergemod.mergestate.read(repo)
841 844 unresolved = None
842 845 if mergestate.active():
843 846 unresolved = sorted(mergestate.unresolved())
844 return morestatus(repo.root, unfinishedop, unfinishedmsg,
845 unresolved is not None, unresolved)
847 return morestatus(
848 repo.root,
849 unfinishedop,
850 unfinishedmsg,
851 unresolved is not None,
852 unresolved,
853 )
846 854
847 855
848 856 def findpossible(cmd, table, strict=False):
849 857 """
850 858 Return cmd -> (aliases, command table entry)
851 859 for each matching command.
852 860 Return debug commands (or their aliases) only if no normal command matches.
853 861 """
854 862 choice = {}
855 863 debugchoice = {}
856 864
857 865 if cmd in table:
858 866 # short-circuit exact matches, "log" alias beats "log|history"
859 867 keys = [cmd]
860 868 else:
861 869 keys = table.keys()
862 870
863 871 allcmds = []
864 872 for e in keys:
865 873 aliases = parsealiases(e)
866 874 allcmds.extend(aliases)
867 875 found = None
868 876 if cmd in aliases:
869 877 found = cmd
870 878 elif not strict:
871 879 for a in aliases:
872 880 if a.startswith(cmd):
873 881 found = a
874 882 break
875 883 if found is not None:
876 884 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
877 885 debugchoice[found] = (aliases, table[e])
878 886 else:
879 887 choice[found] = (aliases, table[e])
880 888
881 889 if not choice and debugchoice:
882 890 choice = debugchoice
883 891
884 892 return choice, allcmds
885 893
886 894
887 895 def findcmd(cmd, table, strict=True):
888 896 """Return (aliases, command table entry) for command string."""
889 897 choice, allcmds = findpossible(cmd, table, strict)
890 898
891 899 if cmd in choice:
892 900 return choice[cmd]
893 901
894 902 if len(choice) > 1:
895 903 clist = sorted(choice)
896 904 raise error.AmbiguousCommand(cmd, clist)
897 905
898 906 if choice:
899 907 return list(choice.values())[0]
900 908
901 909 raise error.UnknownCommand(cmd, allcmds)
902 910
903 911
904 912 def changebranch(ui, repo, revs, label):
905 913 """ Change the branch name of given revs to label """
906 914
907 915 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
908 916 # abort in case of uncommitted merge or dirty wdir
909 917 bailifchanged(repo)
910 918 revs = scmutil.revrange(repo, revs)
911 919 if not revs:
912 920 raise error.Abort(b"empty revision set")
913 921 roots = repo.revs(b'roots(%ld)', revs)
914 922 if len(roots) > 1:
915 923 raise error.Abort(
916 924 _(b"cannot change branch of non-linear revisions")
917 925 )
918 926 rewriteutil.precheck(repo, revs, b'change branch of')
919 927
920 928 root = repo[roots.first()]
921 929 rpb = {parent.branch() for parent in root.parents()}
922 930 if label not in rpb and label in repo.branchmap():
923 931 raise error.Abort(_(b"a branch of the same name already exists"))
924 932
925 933 if repo.revs(b'obsolete() and %ld', revs):
926 934 raise error.Abort(
927 935 _(b"cannot change branch of a obsolete changeset")
928 936 )
929 937
930 938 # make sure only topological heads
931 939 if repo.revs(b'heads(%ld) - head()', revs):
932 940 raise error.Abort(_(b"cannot change branch in middle of a stack"))
933 941
934 942 replacements = {}
935 943 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
936 944 # mercurial.subrepo -> mercurial.cmdutil
937 945 from . import context
938 946
939 947 for rev in revs:
940 948 ctx = repo[rev]
941 949 oldbranch = ctx.branch()
942 950 # check if ctx has same branch
943 951 if oldbranch == label:
944 952 continue
945 953
946 954 def filectxfn(repo, newctx, path):
947 955 try:
948 956 return ctx[path]
949 957 except error.ManifestLookupError:
950 958 return None
951 959
952 960 ui.debug(
953 961 b"changing branch of '%s' from '%s' to '%s'\n"
954 962 % (hex(ctx.node()), oldbranch, label)
955 963 )
956 964 extra = ctx.extra()
957 965 extra[b'branch_change'] = hex(ctx.node())
958 966 # While changing branch of set of linear commits, make sure that
959 967 # we base our commits on new parent rather than old parent which
960 968 # was obsoleted while changing the branch
961 969 p1 = ctx.p1().node()
962 970 p2 = ctx.p2().node()
963 971 if p1 in replacements:
964 972 p1 = replacements[p1][0]
965 973 if p2 in replacements:
966 974 p2 = replacements[p2][0]
967 975
968 976 mc = context.memctx(
969 977 repo,
970 978 (p1, p2),
971 979 ctx.description(),
972 980 ctx.files(),
973 981 filectxfn,
974 982 user=ctx.user(),
975 983 date=ctx.date(),
976 984 extra=extra,
977 985 branch=label,
978 986 )
979 987
980 988 newnode = repo.commitctx(mc)
981 989 replacements[ctx.node()] = (newnode,)
982 990 ui.debug(b'new node id is %s\n' % hex(newnode))
983 991
984 992 # create obsmarkers and move bookmarks
985 993 scmutil.cleanupnodes(
986 994 repo, replacements, b'branch-change', fixphase=True
987 995 )
988 996
989 997 # move the working copy too
990 998 wctx = repo[None]
991 999 # in-progress merge is a bit too complex for now.
992 1000 if len(wctx.parents()) == 1:
993 1001 newid = replacements.get(wctx.p1().node())
994 1002 if newid is not None:
995 1003 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
996 1004 # mercurial.cmdutil
997 1005 from . import hg
998 1006
999 1007 hg.update(repo, newid[0], quietempty=True)
1000 1008
1001 1009 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
1002 1010
1003 1011
1004 1012 def findrepo(p):
1005 1013 while not os.path.isdir(os.path.join(p, b".hg")):
1006 1014 oldp, p = p, os.path.dirname(p)
1007 1015 if p == oldp:
1008 1016 return None
1009 1017
1010 1018 return p
1011 1019
1012 1020
1013 1021 def bailifchanged(repo, merge=True, hint=None):
1014 1022 """ enforce the precondition that working directory must be clean.
1015 1023
1016 1024 'merge' can be set to false if a pending uncommitted merge should be
1017 1025 ignored (such as when 'update --check' runs).
1018 1026
1019 1027 'hint' is the usual hint given to Abort exception.
1020 1028 """
1021 1029
1022 1030 if merge and repo.dirstate.p2() != nullid:
1023 1031 raise error.Abort(_(b'outstanding uncommitted merge'), hint=hint)
1024 1032 st = repo.status()
1025 1033 if st.modified or st.added or st.removed or st.deleted:
1026 1034 raise error.Abort(_(b'uncommitted changes'), hint=hint)
1027 1035 ctx = repo[None]
1028 1036 for s in sorted(ctx.substate):
1029 1037 ctx.sub(s).bailifchanged(hint=hint)
1030 1038
1031 1039
1032 1040 def logmessage(ui, opts):
1033 1041 """ get the log message according to -m and -l option """
1034 1042 message = opts.get(b'message')
1035 1043 logfile = opts.get(b'logfile')
1036 1044
1037 1045 if message and logfile:
1038 1046 raise error.Abort(
1039 1047 _(b'options --message and --logfile are mutually exclusive')
1040 1048 )
1041 1049 if not message and logfile:
1042 1050 try:
1043 1051 if isstdiofilename(logfile):
1044 1052 message = ui.fin.read()
1045 1053 else:
1046 1054 message = b'\n'.join(util.readfile(logfile).splitlines())
1047 1055 except IOError as inst:
1048 1056 raise error.Abort(
1049 1057 _(b"can't read commit message '%s': %s")
1050 1058 % (logfile, encoding.strtolocal(inst.strerror))
1051 1059 )
1052 1060 return message
1053 1061
1054 1062
1055 1063 def mergeeditform(ctxorbool, baseformname):
1056 1064 """return appropriate editform name (referencing a committemplate)
1057 1065
1058 1066 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1059 1067 merging is committed.
1060 1068
1061 1069 This returns baseformname with '.merge' appended if it is a merge,
1062 1070 otherwise '.normal' is appended.
1063 1071 """
1064 1072 if isinstance(ctxorbool, bool):
1065 1073 if ctxorbool:
1066 1074 return baseformname + b".merge"
1067 1075 elif len(ctxorbool.parents()) > 1:
1068 1076 return baseformname + b".merge"
1069 1077
1070 1078 return baseformname + b".normal"
1071 1079
1072 1080
1073 1081 def getcommiteditor(
1074 1082 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1075 1083 ):
1076 1084 """get appropriate commit message editor according to '--edit' option
1077 1085
1078 1086 'finishdesc' is a function to be called with edited commit message
1079 1087 (= 'description' of the new changeset) just after editing, but
1080 1088 before checking empty-ness. It should return actual text to be
1081 1089 stored into history. This allows to change description before
1082 1090 storing.
1083 1091
1084 1092 'extramsg' is a extra message to be shown in the editor instead of
1085 1093 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1086 1094 is automatically added.
1087 1095
1088 1096 'editform' is a dot-separated list of names, to distinguish
1089 1097 the purpose of commit text editing.
1090 1098
1091 1099 'getcommiteditor' returns 'commitforceeditor' regardless of
1092 1100 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1093 1101 they are specific for usage in MQ.
1094 1102 """
1095 1103 if edit or finishdesc or extramsg:
1096 1104 return lambda r, c, s: commitforceeditor(
1097 1105 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1098 1106 )
1099 1107 elif editform:
1100 1108 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1101 1109 else:
1102 1110 return commiteditor
1103 1111
1104 1112
1105 1113 def _escapecommandtemplate(tmpl):
1106 1114 parts = []
1107 1115 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1108 1116 if typ == b'string':
1109 1117 parts.append(stringutil.escapestr(tmpl[start:end]))
1110 1118 else:
1111 1119 parts.append(tmpl[start:end])
1112 1120 return b''.join(parts)
1113 1121
1114 1122
1115 1123 def rendercommandtemplate(ui, tmpl, props):
1116 1124 r"""Expand a literal template 'tmpl' in a way suitable for command line
1117 1125
1118 1126 '\' in outermost string is not taken as an escape character because it
1119 1127 is a directory separator on Windows.
1120 1128
1121 1129 >>> from . import ui as uimod
1122 1130 >>> ui = uimod.ui()
1123 1131 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1124 1132 'c:\\foo'
1125 1133 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1126 1134 'c:{path}'
1127 1135 """
1128 1136 if not tmpl:
1129 1137 return tmpl
1130 1138 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1131 1139 return t.renderdefault(props)
1132 1140
1133 1141
1134 1142 def rendertemplate(ctx, tmpl, props=None):
1135 1143 """Expand a literal template 'tmpl' byte-string against one changeset
1136 1144
1137 1145 Each props item must be a stringify-able value or a callable returning
1138 1146 such value, i.e. no bare list nor dict should be passed.
1139 1147 """
1140 1148 repo = ctx.repo()
1141 1149 tres = formatter.templateresources(repo.ui, repo)
1142 1150 t = formatter.maketemplater(
1143 1151 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1144 1152 )
1145 1153 mapping = {b'ctx': ctx}
1146 1154 if props:
1147 1155 mapping.update(props)
1148 1156 return t.renderdefault(mapping)
1149 1157
1150 1158
1151 1159 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1152 1160 r"""Convert old-style filename format string to template string
1153 1161
1154 1162 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1155 1163 'foo-{reporoot|basename}-{seqno}.patch'
1156 1164 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1157 1165 '{rev}{tags % "{tag}"}{node}'
1158 1166
1159 1167 '\' in outermost strings has to be escaped because it is a directory
1160 1168 separator on Windows:
1161 1169
1162 1170 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1163 1171 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1164 1172 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1165 1173 '\\\\\\\\foo\\\\bar.patch'
1166 1174 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1167 1175 '\\\\{tags % "{tag}"}'
1168 1176
1169 1177 but inner strings follow the template rules (i.e. '\' is taken as an
1170 1178 escape character):
1171 1179
1172 1180 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1173 1181 '{"c:\\tmp"}'
1174 1182 """
1175 1183 expander = {
1176 1184 b'H': b'{node}',
1177 1185 b'R': b'{rev}',
1178 1186 b'h': b'{node|short}',
1179 1187 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1180 1188 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1181 1189 b'%': b'%',
1182 1190 b'b': b'{reporoot|basename}',
1183 1191 }
1184 1192 if total is not None:
1185 1193 expander[b'N'] = b'{total}'
1186 1194 if seqno is not None:
1187 1195 expander[b'n'] = b'{seqno}'
1188 1196 if total is not None and seqno is not None:
1189 1197 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1190 1198 if pathname is not None:
1191 1199 expander[b's'] = b'{pathname|basename}'
1192 1200 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1193 1201 expander[b'p'] = b'{pathname}'
1194 1202
1195 1203 newname = []
1196 1204 for typ, start, end in templater.scantemplate(pat, raw=True):
1197 1205 if typ != b'string':
1198 1206 newname.append(pat[start:end])
1199 1207 continue
1200 1208 i = start
1201 1209 while i < end:
1202 1210 n = pat.find(b'%', i, end)
1203 1211 if n < 0:
1204 1212 newname.append(stringutil.escapestr(pat[i:end]))
1205 1213 break
1206 1214 newname.append(stringutil.escapestr(pat[i:n]))
1207 1215 if n + 2 > end:
1208 1216 raise error.Abort(
1209 1217 _(b"incomplete format spec in output filename")
1210 1218 )
1211 1219 c = pat[n + 1 : n + 2]
1212 1220 i = n + 2
1213 1221 try:
1214 1222 newname.append(expander[c])
1215 1223 except KeyError:
1216 1224 raise error.Abort(
1217 1225 _(b"invalid format spec '%%%s' in output filename") % c
1218 1226 )
1219 1227 return b''.join(newname)
1220 1228
1221 1229
1222 1230 def makefilename(ctx, pat, **props):
1223 1231 if not pat:
1224 1232 return pat
1225 1233 tmpl = _buildfntemplate(pat, **props)
1226 1234 # BUG: alias expansion shouldn't be made against template fragments
1227 1235 # rewritten from %-format strings, but we have no easy way to partially
1228 1236 # disable the expansion.
1229 1237 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1230 1238
1231 1239
1232 1240 def isstdiofilename(pat):
1233 1241 """True if the given pat looks like a filename denoting stdin/stdout"""
1234 1242 return not pat or pat == b'-'
1235 1243
1236 1244
1237 1245 class _unclosablefile(object):
1238 1246 def __init__(self, fp):
1239 1247 self._fp = fp
1240 1248
1241 1249 def close(self):
1242 1250 pass
1243 1251
1244 1252 def __iter__(self):
1245 1253 return iter(self._fp)
1246 1254
1247 1255 def __getattr__(self, attr):
1248 1256 return getattr(self._fp, attr)
1249 1257
1250 1258 def __enter__(self):
1251 1259 return self
1252 1260
1253 1261 def __exit__(self, exc_type, exc_value, exc_tb):
1254 1262 pass
1255 1263
1256 1264
1257 1265 def makefileobj(ctx, pat, mode=b'wb', **props):
1258 1266 writable = mode not in (b'r', b'rb')
1259 1267
1260 1268 if isstdiofilename(pat):
1261 1269 repo = ctx.repo()
1262 1270 if writable:
1263 1271 fp = repo.ui.fout
1264 1272 else:
1265 1273 fp = repo.ui.fin
1266 1274 return _unclosablefile(fp)
1267 1275 fn = makefilename(ctx, pat, **props)
1268 1276 return open(fn, mode)
1269 1277
1270 1278
1271 1279 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1272 1280 """opens the changelog, manifest, a filelog or a given revlog"""
1273 1281 cl = opts[b'changelog']
1274 1282 mf = opts[b'manifest']
1275 1283 dir = opts[b'dir']
1276 1284 msg = None
1277 1285 if cl and mf:
1278 1286 msg = _(b'cannot specify --changelog and --manifest at the same time')
1279 1287 elif cl and dir:
1280 1288 msg = _(b'cannot specify --changelog and --dir at the same time')
1281 1289 elif cl or mf or dir:
1282 1290 if file_:
1283 1291 msg = _(b'cannot specify filename with --changelog or --manifest')
1284 1292 elif not repo:
1285 1293 msg = _(
1286 1294 b'cannot specify --changelog or --manifest or --dir '
1287 1295 b'without a repository'
1288 1296 )
1289 1297 if msg:
1290 1298 raise error.Abort(msg)
1291 1299
1292 1300 r = None
1293 1301 if repo:
1294 1302 if cl:
1295 1303 r = repo.unfiltered().changelog
1296 1304 elif dir:
1297 1305 if b'treemanifest' not in repo.requirements:
1298 1306 raise error.Abort(
1299 1307 _(
1300 1308 b"--dir can only be used on repos with "
1301 1309 b"treemanifest enabled"
1302 1310 )
1303 1311 )
1304 1312 if not dir.endswith(b'/'):
1305 1313 dir = dir + b'/'
1306 1314 dirlog = repo.manifestlog.getstorage(dir)
1307 1315 if len(dirlog):
1308 1316 r = dirlog
1309 1317 elif mf:
1310 1318 r = repo.manifestlog.getstorage(b'')
1311 1319 elif file_:
1312 1320 filelog = repo.file(file_)
1313 1321 if len(filelog):
1314 1322 r = filelog
1315 1323
1316 1324 # Not all storage may be revlogs. If requested, try to return an actual
1317 1325 # revlog instance.
1318 1326 if returnrevlog:
1319 1327 if isinstance(r, revlog.revlog):
1320 1328 pass
1321 1329 elif util.safehasattr(r, b'_revlog'):
1322 1330 r = r._revlog # pytype: disable=attribute-error
1323 1331 elif r is not None:
1324 1332 raise error.Abort(_(b'%r does not appear to be a revlog') % r)
1325 1333
1326 1334 if not r:
1327 1335 if not returnrevlog:
1328 1336 raise error.Abort(_(b'cannot give path to non-revlog'))
1329 1337
1330 1338 if not file_:
1331 1339 raise error.CommandError(cmd, _(b'invalid arguments'))
1332 1340 if not os.path.isfile(file_):
1333 1341 raise error.Abort(_(b"revlog '%s' not found") % file_)
1334 1342 r = revlog.revlog(
1335 1343 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i"
1336 1344 )
1337 1345 return r
1338 1346
1339 1347
1340 1348 def openrevlog(repo, cmd, file_, opts):
1341 1349 """Obtain a revlog backing storage of an item.
1342 1350
1343 1351 This is similar to ``openstorage()`` except it always returns a revlog.
1344 1352
1345 1353 In most cases, a caller cares about the main storage object - not the
1346 1354 revlog backing it. Therefore, this function should only be used by code
1347 1355 that needs to examine low-level revlog implementation details. e.g. debug
1348 1356 commands.
1349 1357 """
1350 1358 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1351 1359
1352 1360
1353 1361 def copy(ui, repo, pats, opts, rename=False):
1354 1362 # called with the repo lock held
1355 1363 #
1356 1364 # hgsep => pathname that uses "/" to separate directories
1357 1365 # ossep => pathname that uses os.sep to separate directories
1358 1366 cwd = repo.getcwd()
1359 1367 targets = {}
1360 1368 after = opts.get(b"after")
1361 1369 dryrun = opts.get(b"dry_run")
1362 1370 wctx = repo[None]
1363 1371
1364 1372 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1365 1373
1366 1374 def walkpat(pat):
1367 1375 srcs = []
1368 1376 if after:
1369 1377 badstates = b'?'
1370 1378 else:
1371 1379 badstates = b'?r'
1372 1380 m = scmutil.match(wctx, [pat], opts, globbed=True)
1373 1381 for abs in wctx.walk(m):
1374 1382 state = repo.dirstate[abs]
1375 1383 rel = uipathfn(abs)
1376 1384 exact = m.exact(abs)
1377 1385 if state in badstates:
1378 1386 if exact and state == b'?':
1379 1387 ui.warn(_(b'%s: not copying - file is not managed\n') % rel)
1380 1388 if exact and state == b'r':
1381 1389 ui.warn(
1382 1390 _(
1383 1391 b'%s: not copying - file has been marked for'
1384 1392 b' remove\n'
1385 1393 )
1386 1394 % rel
1387 1395 )
1388 1396 continue
1389 1397 # abs: hgsep
1390 1398 # rel: ossep
1391 1399 srcs.append((abs, rel, exact))
1392 1400 return srcs
1393 1401
1394 1402 # abssrc: hgsep
1395 1403 # relsrc: ossep
1396 1404 # otarget: ossep
1397 1405 def copyfile(abssrc, relsrc, otarget, exact):
1398 1406 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1399 1407 if b'/' in abstarget:
1400 1408 # We cannot normalize abstarget itself, this would prevent
1401 1409 # case only renames, like a => A.
1402 1410 abspath, absname = abstarget.rsplit(b'/', 1)
1403 1411 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1404 1412 reltarget = repo.pathto(abstarget, cwd)
1405 1413 target = repo.wjoin(abstarget)
1406 1414 src = repo.wjoin(abssrc)
1407 1415 state = repo.dirstate[abstarget]
1408 1416
1409 1417 scmutil.checkportable(ui, abstarget)
1410 1418
1411 1419 # check for collisions
1412 1420 prevsrc = targets.get(abstarget)
1413 1421 if prevsrc is not None:
1414 1422 ui.warn(
1415 1423 _(b'%s: not overwriting - %s collides with %s\n')
1416 1424 % (
1417 1425 reltarget,
1418 1426 repo.pathto(abssrc, cwd),
1419 1427 repo.pathto(prevsrc, cwd),
1420 1428 )
1421 1429 )
1422 1430 return True # report a failure
1423 1431
1424 1432 # check for overwrites
1425 1433 exists = os.path.lexists(target)
1426 1434 samefile = False
1427 1435 if exists and abssrc != abstarget:
1428 1436 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1429 1437 abstarget
1430 1438 ):
1431 1439 if not rename:
1432 1440 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1433 1441 return True # report a failure
1434 1442 exists = False
1435 1443 samefile = True
1436 1444
1437 1445 if not after and exists or after and state in b'mn':
1438 1446 if not opts[b'force']:
1439 1447 if state in b'mn':
1440 1448 msg = _(b'%s: not overwriting - file already committed\n')
1441 1449 if after:
1442 1450 flags = b'--after --force'
1443 1451 else:
1444 1452 flags = b'--force'
1445 1453 if rename:
1446 1454 hint = (
1447 1455 _(
1448 1456 b"('hg rename %s' to replace the file by "
1449 1457 b'recording a rename)\n'
1450 1458 )
1451 1459 % flags
1452 1460 )
1453 1461 else:
1454 1462 hint = (
1455 1463 _(
1456 1464 b"('hg copy %s' to replace the file by "
1457 1465 b'recording a copy)\n'
1458 1466 )
1459 1467 % flags
1460 1468 )
1461 1469 else:
1462 1470 msg = _(b'%s: not overwriting - file exists\n')
1463 1471 if rename:
1464 1472 hint = _(
1465 1473 b"('hg rename --after' to record the rename)\n"
1466 1474 )
1467 1475 else:
1468 1476 hint = _(b"('hg copy --after' to record the copy)\n")
1469 1477 ui.warn(msg % reltarget)
1470 1478 ui.warn(hint)
1471 1479 return True # report a failure
1472 1480
1473 1481 if after:
1474 1482 if not exists:
1475 1483 if rename:
1476 1484 ui.warn(
1477 1485 _(b'%s: not recording move - %s does not exist\n')
1478 1486 % (relsrc, reltarget)
1479 1487 )
1480 1488 else:
1481 1489 ui.warn(
1482 1490 _(b'%s: not recording copy - %s does not exist\n')
1483 1491 % (relsrc, reltarget)
1484 1492 )
1485 1493 return True # report a failure
1486 1494 elif not dryrun:
1487 1495 try:
1488 1496 if exists:
1489 1497 os.unlink(target)
1490 1498 targetdir = os.path.dirname(target) or b'.'
1491 1499 if not os.path.isdir(targetdir):
1492 1500 os.makedirs(targetdir)
1493 1501 if samefile:
1494 1502 tmp = target + b"~hgrename"
1495 1503 os.rename(src, tmp)
1496 1504 os.rename(tmp, target)
1497 1505 else:
1498 1506 # Preserve stat info on renames, not on copies; this matches
1499 1507 # Linux CLI behavior.
1500 1508 util.copyfile(src, target, copystat=rename)
1501 1509 srcexists = True
1502 1510 except IOError as inst:
1503 1511 if inst.errno == errno.ENOENT:
1504 1512 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1505 1513 srcexists = False
1506 1514 else:
1507 1515 ui.warn(
1508 1516 _(b'%s: cannot copy - %s\n')
1509 1517 % (relsrc, encoding.strtolocal(inst.strerror))
1510 1518 )
1511 1519 return True # report a failure
1512 1520
1513 1521 if ui.verbose or not exact:
1514 1522 if rename:
1515 1523 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1516 1524 else:
1517 1525 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1518 1526
1519 1527 targets[abstarget] = abssrc
1520 1528
1521 1529 # fix up dirstate
1522 1530 scmutil.dirstatecopy(
1523 1531 ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1524 1532 )
1525 1533 if rename and not dryrun:
1526 1534 if not after and srcexists and not samefile:
1527 1535 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1528 1536 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1529 1537 wctx.forget([abssrc])
1530 1538
1531 1539 # pat: ossep
1532 1540 # dest ossep
1533 1541 # srcs: list of (hgsep, hgsep, ossep, bool)
1534 1542 # return: function that takes hgsep and returns ossep
1535 1543 def targetpathfn(pat, dest, srcs):
1536 1544 if os.path.isdir(pat):
1537 1545 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1538 1546 abspfx = util.localpath(abspfx)
1539 1547 if destdirexists:
1540 1548 striplen = len(os.path.split(abspfx)[0])
1541 1549 else:
1542 1550 striplen = len(abspfx)
1543 1551 if striplen:
1544 1552 striplen += len(pycompat.ossep)
1545 1553 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1546 1554 elif destdirexists:
1547 1555 res = lambda p: os.path.join(
1548 1556 dest, os.path.basename(util.localpath(p))
1549 1557 )
1550 1558 else:
1551 1559 res = lambda p: dest
1552 1560 return res
1553 1561
1554 1562 # pat: ossep
1555 1563 # dest ossep
1556 1564 # srcs: list of (hgsep, hgsep, ossep, bool)
1557 1565 # return: function that takes hgsep and returns ossep
1558 1566 def targetpathafterfn(pat, dest, srcs):
1559 1567 if matchmod.patkind(pat):
1560 1568 # a mercurial pattern
1561 1569 res = lambda p: os.path.join(
1562 1570 dest, os.path.basename(util.localpath(p))
1563 1571 )
1564 1572 else:
1565 1573 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1566 1574 if len(abspfx) < len(srcs[0][0]):
1567 1575 # A directory. Either the target path contains the last
1568 1576 # component of the source path or it does not.
1569 1577 def evalpath(striplen):
1570 1578 score = 0
1571 1579 for s in srcs:
1572 1580 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1573 1581 if os.path.lexists(t):
1574 1582 score += 1
1575 1583 return score
1576 1584
1577 1585 abspfx = util.localpath(abspfx)
1578 1586 striplen = len(abspfx)
1579 1587 if striplen:
1580 1588 striplen += len(pycompat.ossep)
1581 1589 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1582 1590 score = evalpath(striplen)
1583 1591 striplen1 = len(os.path.split(abspfx)[0])
1584 1592 if striplen1:
1585 1593 striplen1 += len(pycompat.ossep)
1586 1594 if evalpath(striplen1) > score:
1587 1595 striplen = striplen1
1588 1596 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1589 1597 else:
1590 1598 # a file
1591 1599 if destdirexists:
1592 1600 res = lambda p: os.path.join(
1593 1601 dest, os.path.basename(util.localpath(p))
1594 1602 )
1595 1603 else:
1596 1604 res = lambda p: dest
1597 1605 return res
1598 1606
1599 1607 pats = scmutil.expandpats(pats)
1600 1608 if not pats:
1601 1609 raise error.Abort(_(b'no source or destination specified'))
1602 1610 if len(pats) == 1:
1603 1611 raise error.Abort(_(b'no destination specified'))
1604 1612 dest = pats.pop()
1605 1613 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1606 1614 if not destdirexists:
1607 1615 if len(pats) > 1 or matchmod.patkind(pats[0]):
1608 1616 raise error.Abort(
1609 1617 _(
1610 1618 b'with multiple sources, destination must be an '
1611 1619 b'existing directory'
1612 1620 )
1613 1621 )
1614 1622 if util.endswithsep(dest):
1615 1623 raise error.Abort(_(b'destination %s is not a directory') % dest)
1616 1624
1617 1625 tfn = targetpathfn
1618 1626 if after:
1619 1627 tfn = targetpathafterfn
1620 1628 copylist = []
1621 1629 for pat in pats:
1622 1630 srcs = walkpat(pat)
1623 1631 if not srcs:
1624 1632 continue
1625 1633 copylist.append((tfn(pat, dest, srcs), srcs))
1626 1634 if not copylist:
1627 1635 raise error.Abort(_(b'no files to copy'))
1628 1636
1629 1637 errors = 0
1630 1638 for targetpath, srcs in copylist:
1631 1639 for abssrc, relsrc, exact in srcs:
1632 1640 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1633 1641 errors += 1
1634 1642
1635 1643 return errors != 0
1636 1644
1637 1645
1638 1646 ## facility to let extension process additional data into an import patch
1639 1647 # list of identifier to be executed in order
1640 1648 extrapreimport = [] # run before commit
1641 1649 extrapostimport = [] # run after commit
1642 1650 # mapping from identifier to actual import function
1643 1651 #
1644 1652 # 'preimport' are run before the commit is made and are provided the following
1645 1653 # arguments:
1646 1654 # - repo: the localrepository instance,
1647 1655 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1648 1656 # - extra: the future extra dictionary of the changeset, please mutate it,
1649 1657 # - opts: the import options.
1650 1658 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1651 1659 # mutation of in memory commit and more. Feel free to rework the code to get
1652 1660 # there.
1653 1661 extrapreimportmap = {}
1654 1662 # 'postimport' are run after the commit is made and are provided the following
1655 1663 # argument:
1656 1664 # - ctx: the changectx created by import.
1657 1665 extrapostimportmap = {}
1658 1666
1659 1667
1660 1668 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1661 1669 """Utility function used by commands.import to import a single patch
1662 1670
1663 1671 This function is explicitly defined here to help the evolve extension to
1664 1672 wrap this part of the import logic.
1665 1673
1666 1674 The API is currently a bit ugly because it a simple code translation from
1667 1675 the import command. Feel free to make it better.
1668 1676
1669 1677 :patchdata: a dictionary containing parsed patch data (such as from
1670 1678 ``patch.extract()``)
1671 1679 :parents: nodes that will be parent of the created commit
1672 1680 :opts: the full dict of option passed to the import command
1673 1681 :msgs: list to save commit message to.
1674 1682 (used in case we need to save it when failing)
1675 1683 :updatefunc: a function that update a repo to a given node
1676 1684 updatefunc(<repo>, <node>)
1677 1685 """
1678 1686 # avoid cycle context -> subrepo -> cmdutil
1679 1687 from . import context
1680 1688
1681 1689 tmpname = patchdata.get(b'filename')
1682 1690 message = patchdata.get(b'message')
1683 1691 user = opts.get(b'user') or patchdata.get(b'user')
1684 1692 date = opts.get(b'date') or patchdata.get(b'date')
1685 1693 branch = patchdata.get(b'branch')
1686 1694 nodeid = patchdata.get(b'nodeid')
1687 1695 p1 = patchdata.get(b'p1')
1688 1696 p2 = patchdata.get(b'p2')
1689 1697
1690 1698 nocommit = opts.get(b'no_commit')
1691 1699 importbranch = opts.get(b'import_branch')
1692 1700 update = not opts.get(b'bypass')
1693 1701 strip = opts[b"strip"]
1694 1702 prefix = opts[b"prefix"]
1695 1703 sim = float(opts.get(b'similarity') or 0)
1696 1704
1697 1705 if not tmpname:
1698 1706 return None, None, False
1699 1707
1700 1708 rejects = False
1701 1709
1702 1710 cmdline_message = logmessage(ui, opts)
1703 1711 if cmdline_message:
1704 1712 # pickup the cmdline msg
1705 1713 message = cmdline_message
1706 1714 elif message:
1707 1715 # pickup the patch msg
1708 1716 message = message.strip()
1709 1717 else:
1710 1718 # launch the editor
1711 1719 message = None
1712 1720 ui.debug(b'message:\n%s\n' % (message or b''))
1713 1721
1714 1722 if len(parents) == 1:
1715 1723 parents.append(repo[nullid])
1716 1724 if opts.get(b'exact'):
1717 1725 if not nodeid or not p1:
1718 1726 raise error.Abort(_(b'not a Mercurial patch'))
1719 1727 p1 = repo[p1]
1720 1728 p2 = repo[p2 or nullid]
1721 1729 elif p2:
1722 1730 try:
1723 1731 p1 = repo[p1]
1724 1732 p2 = repo[p2]
1725 1733 # Without any options, consider p2 only if the
1726 1734 # patch is being applied on top of the recorded
1727 1735 # first parent.
1728 1736 if p1 != parents[0]:
1729 1737 p1 = parents[0]
1730 1738 p2 = repo[nullid]
1731 1739 except error.RepoError:
1732 1740 p1, p2 = parents
1733 1741 if p2.node() == nullid:
1734 1742 ui.warn(
1735 1743 _(
1736 1744 b"warning: import the patch as a normal revision\n"
1737 1745 b"(use --exact to import the patch as a merge)\n"
1738 1746 )
1739 1747 )
1740 1748 else:
1741 1749 p1, p2 = parents
1742 1750
1743 1751 n = None
1744 1752 if update:
1745 1753 if p1 != parents[0]:
1746 1754 updatefunc(repo, p1.node())
1747 1755 if p2 != parents[1]:
1748 1756 repo.setparents(p1.node(), p2.node())
1749 1757
1750 1758 if opts.get(b'exact') or importbranch:
1751 1759 repo.dirstate.setbranch(branch or b'default')
1752 1760
1753 1761 partial = opts.get(b'partial', False)
1754 1762 files = set()
1755 1763 try:
1756 1764 patch.patch(
1757 1765 ui,
1758 1766 repo,
1759 1767 tmpname,
1760 1768 strip=strip,
1761 1769 prefix=prefix,
1762 1770 files=files,
1763 1771 eolmode=None,
1764 1772 similarity=sim / 100.0,
1765 1773 )
1766 1774 except error.PatchError as e:
1767 1775 if not partial:
1768 1776 raise error.Abort(pycompat.bytestr(e))
1769 1777 if partial:
1770 1778 rejects = True
1771 1779
1772 1780 files = list(files)
1773 1781 if nocommit:
1774 1782 if message:
1775 1783 msgs.append(message)
1776 1784 else:
1777 1785 if opts.get(b'exact') or p2:
1778 1786 # If you got here, you either use --force and know what
1779 1787 # you are doing or used --exact or a merge patch while
1780 1788 # being updated to its first parent.
1781 1789 m = None
1782 1790 else:
1783 1791 m = scmutil.matchfiles(repo, files or [])
1784 1792 editform = mergeeditform(repo[None], b'import.normal')
1785 1793 if opts.get(b'exact'):
1786 1794 editor = None
1787 1795 else:
1788 1796 editor = getcommiteditor(
1789 1797 editform=editform, **pycompat.strkwargs(opts)
1790 1798 )
1791 1799 extra = {}
1792 1800 for idfunc in extrapreimport:
1793 1801 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1794 1802 overrides = {}
1795 1803 if partial:
1796 1804 overrides[(b'ui', b'allowemptycommit')] = True
1797 1805 if opts.get(b'secret'):
1798 1806 overrides[(b'phases', b'new-commit')] = b'secret'
1799 1807 with repo.ui.configoverride(overrides, b'import'):
1800 1808 n = repo.commit(
1801 1809 message, user, date, match=m, editor=editor, extra=extra
1802 1810 )
1803 1811 for idfunc in extrapostimport:
1804 1812 extrapostimportmap[idfunc](repo[n])
1805 1813 else:
1806 1814 if opts.get(b'exact') or importbranch:
1807 1815 branch = branch or b'default'
1808 1816 else:
1809 1817 branch = p1.branch()
1810 1818 store = patch.filestore()
1811 1819 try:
1812 1820 files = set()
1813 1821 try:
1814 1822 patch.patchrepo(
1815 1823 ui,
1816 1824 repo,
1817 1825 p1,
1818 1826 store,
1819 1827 tmpname,
1820 1828 strip,
1821 1829 prefix,
1822 1830 files,
1823 1831 eolmode=None,
1824 1832 )
1825 1833 except error.PatchError as e:
1826 1834 raise error.Abort(stringutil.forcebytestr(e))
1827 1835 if opts.get(b'exact'):
1828 1836 editor = None
1829 1837 else:
1830 1838 editor = getcommiteditor(editform=b'import.bypass')
1831 1839 memctx = context.memctx(
1832 1840 repo,
1833 1841 (p1.node(), p2.node()),
1834 1842 message,
1835 1843 files=files,
1836 1844 filectxfn=store,
1837 1845 user=user,
1838 1846 date=date,
1839 1847 branch=branch,
1840 1848 editor=editor,
1841 1849 )
1842 1850 n = memctx.commit()
1843 1851 finally:
1844 1852 store.close()
1845 1853 if opts.get(b'exact') and nocommit:
1846 1854 # --exact with --no-commit is still useful in that it does merge
1847 1855 # and branch bits
1848 1856 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
1849 1857 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
1850 1858 raise error.Abort(_(b'patch is damaged or loses information'))
1851 1859 msg = _(b'applied to working directory')
1852 1860 if n:
1853 1861 # i18n: refers to a short changeset id
1854 1862 msg = _(b'created %s') % short(n)
1855 1863 return msg, n, rejects
1856 1864
1857 1865
1858 1866 # facility to let extensions include additional data in an exported patch
1859 1867 # list of identifiers to be executed in order
1860 1868 extraexport = []
1861 1869 # mapping from identifier to actual export function
1862 1870 # function as to return a string to be added to the header or None
1863 1871 # it is given two arguments (sequencenumber, changectx)
1864 1872 extraexportmap = {}
1865 1873
1866 1874
1867 1875 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
1868 1876 node = scmutil.binnode(ctx)
1869 1877 parents = [p.node() for p in ctx.parents() if p]
1870 1878 branch = ctx.branch()
1871 1879 if switch_parent:
1872 1880 parents.reverse()
1873 1881
1874 1882 if parents:
1875 1883 prev = parents[0]
1876 1884 else:
1877 1885 prev = nullid
1878 1886
1879 1887 fm.context(ctx=ctx)
1880 1888 fm.plain(b'# HG changeset patch\n')
1881 1889 fm.write(b'user', b'# User %s\n', ctx.user())
1882 1890 fm.plain(b'# Date %d %d\n' % ctx.date())
1883 1891 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
1884 1892 fm.condwrite(
1885 1893 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
1886 1894 )
1887 1895 fm.write(b'node', b'# Node ID %s\n', hex(node))
1888 1896 fm.plain(b'# Parent %s\n' % hex(prev))
1889 1897 if len(parents) > 1:
1890 1898 fm.plain(b'# Parent %s\n' % hex(parents[1]))
1891 1899 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
1892 1900
1893 1901 # TODO: redesign extraexportmap function to support formatter
1894 1902 for headerid in extraexport:
1895 1903 header = extraexportmap[headerid](seqno, ctx)
1896 1904 if header is not None:
1897 1905 fm.plain(b'# %s\n' % header)
1898 1906
1899 1907 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
1900 1908 fm.plain(b'\n')
1901 1909
1902 1910 if fm.isplain():
1903 1911 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
1904 1912 for chunk, label in chunkiter:
1905 1913 fm.plain(chunk, label=label)
1906 1914 else:
1907 1915 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
1908 1916 # TODO: make it structured?
1909 1917 fm.data(diff=b''.join(chunkiter))
1910 1918
1911 1919
1912 1920 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
1913 1921 """Export changesets to stdout or a single file"""
1914 1922 for seqno, rev in enumerate(revs, 1):
1915 1923 ctx = repo[rev]
1916 1924 if not dest.startswith(b'<'):
1917 1925 repo.ui.note(b"%s\n" % dest)
1918 1926 fm.startitem()
1919 1927 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
1920 1928
1921 1929
1922 1930 def _exportfntemplate(
1923 1931 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
1924 1932 ):
1925 1933 """Export changesets to possibly multiple files"""
1926 1934 total = len(revs)
1927 1935 revwidth = max(len(str(rev)) for rev in revs)
1928 1936 filemap = util.sortdict() # filename: [(seqno, rev), ...]
1929 1937
1930 1938 for seqno, rev in enumerate(revs, 1):
1931 1939 ctx = repo[rev]
1932 1940 dest = makefilename(
1933 1941 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
1934 1942 )
1935 1943 filemap.setdefault(dest, []).append((seqno, rev))
1936 1944
1937 1945 for dest in filemap:
1938 1946 with formatter.maybereopen(basefm, dest) as fm:
1939 1947 repo.ui.note(b"%s\n" % dest)
1940 1948 for seqno, rev in filemap[dest]:
1941 1949 fm.startitem()
1942 1950 ctx = repo[rev]
1943 1951 _exportsingle(
1944 1952 repo, ctx, fm, match, switch_parent, seqno, diffopts
1945 1953 )
1946 1954
1947 1955
1948 1956 def _prefetchchangedfiles(repo, revs, match):
1949 1957 allfiles = set()
1950 1958 for rev in revs:
1951 1959 for file in repo[rev].files():
1952 1960 if not match or match(file):
1953 1961 allfiles.add(file)
1954 1962 scmutil.prefetchfiles(repo, revs, scmutil.matchfiles(repo, allfiles))
1955 1963
1956 1964
1957 1965 def export(
1958 1966 repo,
1959 1967 revs,
1960 1968 basefm,
1961 1969 fntemplate=b'hg-%h.patch',
1962 1970 switch_parent=False,
1963 1971 opts=None,
1964 1972 match=None,
1965 1973 ):
1966 1974 '''export changesets as hg patches
1967 1975
1968 1976 Args:
1969 1977 repo: The repository from which we're exporting revisions.
1970 1978 revs: A list of revisions to export as revision numbers.
1971 1979 basefm: A formatter to which patches should be written.
1972 1980 fntemplate: An optional string to use for generating patch file names.
1973 1981 switch_parent: If True, show diffs against second parent when not nullid.
1974 1982 Default is false, which always shows diff against p1.
1975 1983 opts: diff options to use for generating the patch.
1976 1984 match: If specified, only export changes to files matching this matcher.
1977 1985
1978 1986 Returns:
1979 1987 Nothing.
1980 1988
1981 1989 Side Effect:
1982 1990 "HG Changeset Patch" data is emitted to one of the following
1983 1991 destinations:
1984 1992 fntemplate specified: Each rev is written to a unique file named using
1985 1993 the given template.
1986 1994 Otherwise: All revs will be written to basefm.
1987 1995 '''
1988 1996 _prefetchchangedfiles(repo, revs, match)
1989 1997
1990 1998 if not fntemplate:
1991 1999 _exportfile(
1992 2000 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
1993 2001 )
1994 2002 else:
1995 2003 _exportfntemplate(
1996 2004 repo, revs, basefm, fntemplate, switch_parent, opts, match
1997 2005 )
1998 2006
1999 2007
2000 2008 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
2001 2009 """Export changesets to the given file stream"""
2002 2010 _prefetchchangedfiles(repo, revs, match)
2003 2011
2004 2012 dest = getattr(fp, 'name', b'<unnamed>')
2005 2013 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
2006 2014 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
2007 2015
2008 2016
2009 2017 def showmarker(fm, marker, index=None):
2010 2018 """utility function to display obsolescence marker in a readable way
2011 2019
2012 2020 To be used by debug function."""
2013 2021 if index is not None:
2014 2022 fm.write(b'index', b'%i ', index)
2015 2023 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
2016 2024 succs = marker.succnodes()
2017 2025 fm.condwrite(
2018 2026 succs,
2019 2027 b'succnodes',
2020 2028 b'%s ',
2021 2029 fm.formatlist(map(hex, succs), name=b'node'),
2022 2030 )
2023 2031 fm.write(b'flag', b'%X ', marker.flags())
2024 2032 parents = marker.parentnodes()
2025 2033 if parents is not None:
2026 2034 fm.write(
2027 2035 b'parentnodes',
2028 2036 b'{%s} ',
2029 2037 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2030 2038 )
2031 2039 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2032 2040 meta = marker.metadata().copy()
2033 2041 meta.pop(b'date', None)
2034 2042 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2035 2043 fm.write(
2036 2044 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2037 2045 )
2038 2046 fm.plain(b'\n')
2039 2047
2040 2048
2041 2049 def finddate(ui, repo, date):
2042 2050 """Find the tipmost changeset that matches the given date spec"""
2043 2051
2044 2052 df = dateutil.matchdate(date)
2045 2053 m = scmutil.matchall(repo)
2046 2054 results = {}
2047 2055
2048 2056 def prep(ctx, fns):
2049 2057 d = ctx.date()
2050 2058 if df(d[0]):
2051 2059 results[ctx.rev()] = d
2052 2060
2053 2061 for ctx in walkchangerevs(repo, m, {b'rev': None}, prep):
2054 2062 rev = ctx.rev()
2055 2063 if rev in results:
2056 2064 ui.status(
2057 2065 _(b"found revision %d from %s\n")
2058 2066 % (rev, dateutil.datestr(results[rev]))
2059 2067 )
2060 2068 return b'%d' % rev
2061 2069
2062 2070 raise error.Abort(_(b"revision matching date not found"))
2063 2071
2064 2072
2065 2073 def increasingwindows(windowsize=8, sizelimit=512):
2066 2074 while True:
2067 2075 yield windowsize
2068 2076 if windowsize < sizelimit:
2069 2077 windowsize *= 2
2070 2078
2071 2079
2072 2080 def _walkrevs(repo, opts):
2073 2081 # Default --rev value depends on --follow but --follow behavior
2074 2082 # depends on revisions resolved from --rev...
2075 2083 follow = opts.get(b'follow') or opts.get(b'follow_first')
2076 2084 if opts.get(b'rev'):
2077 2085 revs = scmutil.revrange(repo, opts[b'rev'])
2078 2086 elif follow and repo.dirstate.p1() == nullid:
2079 2087 revs = smartset.baseset()
2080 2088 elif follow:
2081 2089 revs = repo.revs(b'reverse(:.)')
2082 2090 else:
2083 2091 revs = smartset.spanset(repo)
2084 2092 revs.reverse()
2085 2093 return revs
2086 2094
2087 2095
2088 2096 class FileWalkError(Exception):
2089 2097 pass
2090 2098
2091 2099
2092 2100 def walkfilerevs(repo, match, follow, revs, fncache):
2093 2101 '''Walks the file history for the matched files.
2094 2102
2095 2103 Returns the changeset revs that are involved in the file history.
2096 2104
2097 2105 Throws FileWalkError if the file history can't be walked using
2098 2106 filelogs alone.
2099 2107 '''
2100 2108 wanted = set()
2101 2109 copies = []
2102 2110 minrev, maxrev = min(revs), max(revs)
2103 2111
2104 2112 def filerevs(filelog, last):
2105 2113 """
2106 2114 Only files, no patterns. Check the history of each file.
2107 2115
2108 2116 Examines filelog entries within minrev, maxrev linkrev range
2109 2117 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
2110 2118 tuples in backwards order
2111 2119 """
2112 2120 cl_count = len(repo)
2113 2121 revs = []
2114 2122 for j in pycompat.xrange(0, last + 1):
2115 2123 linkrev = filelog.linkrev(j)
2116 2124 if linkrev < minrev:
2117 2125 continue
2118 2126 # only yield rev for which we have the changelog, it can
2119 2127 # happen while doing "hg log" during a pull or commit
2120 2128 if linkrev >= cl_count:
2121 2129 break
2122 2130
2123 2131 parentlinkrevs = []
2124 2132 for p in filelog.parentrevs(j):
2125 2133 if p != nullrev:
2126 2134 parentlinkrevs.append(filelog.linkrev(p))
2127 2135 n = filelog.node(j)
2128 2136 revs.append(
2129 2137 (linkrev, parentlinkrevs, follow and filelog.renamed(n))
2130 2138 )
2131 2139
2132 2140 return reversed(revs)
2133 2141
2134 2142 def iterfiles():
2135 2143 pctx = repo[b'.']
2136 2144 for filename in match.files():
2137 2145 if follow:
2138 2146 if filename not in pctx:
2139 2147 raise error.Abort(
2140 2148 _(
2141 2149 b'cannot follow file not in parent '
2142 2150 b'revision: "%s"'
2143 2151 )
2144 2152 % filename
2145 2153 )
2146 2154 yield filename, pctx[filename].filenode()
2147 2155 else:
2148 2156 yield filename, None
2149 2157 for filename_node in copies:
2150 2158 yield filename_node
2151 2159
2152 2160 for file_, node in iterfiles():
2153 2161 filelog = repo.file(file_)
2154 2162 if not len(filelog):
2155 2163 if node is None:
2156 2164 # A zero count may be a directory or deleted file, so
2157 2165 # try to find matching entries on the slow path.
2158 2166 if follow:
2159 2167 raise error.Abort(
2160 2168 _(b'cannot follow nonexistent file: "%s"') % file_
2161 2169 )
2162 2170 raise FileWalkError(b"Cannot walk via filelog")
2163 2171 else:
2164 2172 continue
2165 2173
2166 2174 if node is None:
2167 2175 last = len(filelog) - 1
2168 2176 else:
2169 2177 last = filelog.rev(node)
2170 2178
2171 2179 # keep track of all ancestors of the file
2172 2180 ancestors = {filelog.linkrev(last)}
2173 2181
2174 2182 # iterate from latest to oldest revision
2175 2183 for rev, flparentlinkrevs, copied in filerevs(filelog, last):
2176 2184 if not follow:
2177 2185 if rev > maxrev:
2178 2186 continue
2179 2187 else:
2180 2188 # Note that last might not be the first interesting
2181 2189 # rev to us:
2182 2190 # if the file has been changed after maxrev, we'll
2183 2191 # have linkrev(last) > maxrev, and we still need
2184 2192 # to explore the file graph
2185 2193 if rev not in ancestors:
2186 2194 continue
2187 2195 # XXX insert 1327 fix here
2188 2196 if flparentlinkrevs:
2189 2197 ancestors.update(flparentlinkrevs)
2190 2198
2191 2199 fncache.setdefault(rev, []).append(file_)
2192 2200 wanted.add(rev)
2193 2201 if copied:
2194 2202 copies.append(copied)
2195 2203
2196 2204 return wanted
2197 2205
2198 2206
2199 2207 class _followfilter(object):
2200 2208 def __init__(self, repo, onlyfirst=False):
2201 2209 self.repo = repo
2202 2210 self.startrev = nullrev
2203 2211 self.roots = set()
2204 2212 self.onlyfirst = onlyfirst
2205 2213
2206 2214 def match(self, rev):
2207 2215 def realparents(rev):
2208 2216 if self.onlyfirst:
2209 2217 return self.repo.changelog.parentrevs(rev)[0:1]
2210 2218 else:
2211 2219 return filter(
2212 2220 lambda x: x != nullrev, self.repo.changelog.parentrevs(rev)
2213 2221 )
2214 2222
2215 2223 if self.startrev == nullrev:
2216 2224 self.startrev = rev
2217 2225 return True
2218 2226
2219 2227 if rev > self.startrev:
2220 2228 # forward: all descendants
2221 2229 if not self.roots:
2222 2230 self.roots.add(self.startrev)
2223 2231 for parent in realparents(rev):
2224 2232 if parent in self.roots:
2225 2233 self.roots.add(rev)
2226 2234 return True
2227 2235 else:
2228 2236 # backwards: all parents
2229 2237 if not self.roots:
2230 2238 self.roots.update(realparents(self.startrev))
2231 2239 if rev in self.roots:
2232 2240 self.roots.remove(rev)
2233 2241 self.roots.update(realparents(rev))
2234 2242 return True
2235 2243
2236 2244 return False
2237 2245
2238 2246
2239 2247 def walkchangerevs(repo, match, opts, prepare):
2240 2248 '''Iterate over files and the revs in which they changed.
2241 2249
2242 2250 Callers most commonly need to iterate backwards over the history
2243 2251 in which they are interested. Doing so has awful (quadratic-looking)
2244 2252 performance, so we use iterators in a "windowed" way.
2245 2253
2246 2254 We walk a window of revisions in the desired order. Within the
2247 2255 window, we first walk forwards to gather data, then in the desired
2248 2256 order (usually backwards) to display it.
2249 2257
2250 2258 This function returns an iterator yielding contexts. Before
2251 2259 yielding each context, the iterator will first call the prepare
2252 2260 function on each context in the window in forward order.'''
2253 2261
2254 2262 allfiles = opts.get(b'all_files')
2255 2263 follow = opts.get(b'follow') or opts.get(b'follow_first')
2256 2264 revs = _walkrevs(repo, opts)
2257 2265 if not revs:
2258 2266 return []
2259 2267 wanted = set()
2260 2268 slowpath = match.anypats() or (not match.always() and opts.get(b'removed'))
2261 2269 fncache = {}
2262 2270 change = repo.__getitem__
2263 2271
2264 2272 # First step is to fill wanted, the set of revisions that we want to yield.
2265 2273 # When it does not induce extra cost, we also fill fncache for revisions in
2266 2274 # wanted: a cache of filenames that were changed (ctx.files()) and that
2267 2275 # match the file filtering conditions.
2268 2276
2269 2277 if match.always() or allfiles:
2270 2278 # No files, no patterns. Display all revs.
2271 2279 wanted = revs
2272 2280 elif not slowpath:
2273 2281 # We only have to read through the filelog to find wanted revisions
2274 2282
2275 2283 try:
2276 2284 wanted = walkfilerevs(repo, match, follow, revs, fncache)
2277 2285 except FileWalkError:
2278 2286 slowpath = True
2279 2287
2280 2288 # We decided to fall back to the slowpath because at least one
2281 2289 # of the paths was not a file. Check to see if at least one of them
2282 2290 # existed in history, otherwise simply return
2283 2291 for path in match.files():
2284 2292 if path == b'.' or path in repo.store:
2285 2293 break
2286 2294 else:
2287 2295 return []
2288 2296
2289 2297 if slowpath:
2290 2298 # We have to read the changelog to match filenames against
2291 2299 # changed files
2292 2300
2293 2301 if follow:
2294 2302 raise error.Abort(
2295 2303 _(b'can only follow copies/renames for explicit filenames')
2296 2304 )
2297 2305
2298 2306 # The slow path checks files modified in every changeset.
2299 2307 # This is really slow on large repos, so compute the set lazily.
2300 2308 class lazywantedset(object):
2301 2309 def __init__(self):
2302 2310 self.set = set()
2303 2311 self.revs = set(revs)
2304 2312
2305 2313 # No need to worry about locality here because it will be accessed
2306 2314 # in the same order as the increasing window below.
2307 2315 def __contains__(self, value):
2308 2316 if value in self.set:
2309 2317 return True
2310 2318 elif not value in self.revs:
2311 2319 return False
2312 2320 else:
2313 2321 self.revs.discard(value)
2314 2322 ctx = change(value)
2315 2323 if allfiles:
2316 2324 matches = list(ctx.manifest().walk(match))
2317 2325 else:
2318 2326 matches = [f for f in ctx.files() if match(f)]
2319 2327 if matches:
2320 2328 fncache[value] = matches
2321 2329 self.set.add(value)
2322 2330 return True
2323 2331 return False
2324 2332
2325 2333 def discard(self, value):
2326 2334 self.revs.discard(value)
2327 2335 self.set.discard(value)
2328 2336
2329 2337 wanted = lazywantedset()
2330 2338
2331 2339 # it might be worthwhile to do this in the iterator if the rev range
2332 2340 # is descending and the prune args are all within that range
2333 2341 for rev in opts.get(b'prune', ()):
2334 2342 rev = repo[rev].rev()
2335 2343 ff = _followfilter(repo)
2336 2344 stop = min(revs[0], revs[-1])
2337 2345 for x in pycompat.xrange(rev, stop - 1, -1):
2338 2346 if ff.match(x):
2339 2347 wanted = wanted - [x]
2340 2348
2341 2349 # Now that wanted is correctly initialized, we can iterate over the
2342 2350 # revision range, yielding only revisions in wanted.
2343 2351 def iterate():
2344 2352 if follow and match.always():
2345 2353 ff = _followfilter(repo, onlyfirst=opts.get(b'follow_first'))
2346 2354
2347 2355 def want(rev):
2348 2356 return ff.match(rev) and rev in wanted
2349 2357
2350 2358 else:
2351 2359
2352 2360 def want(rev):
2353 2361 return rev in wanted
2354 2362
2355 2363 it = iter(revs)
2356 2364 stopiteration = False
2357 2365 for windowsize in increasingwindows():
2358 2366 nrevs = []
2359 2367 for i in pycompat.xrange(windowsize):
2360 2368 rev = next(it, None)
2361 2369 if rev is None:
2362 2370 stopiteration = True
2363 2371 break
2364 2372 elif want(rev):
2365 2373 nrevs.append(rev)
2366 2374 for rev in sorted(nrevs):
2367 2375 fns = fncache.get(rev)
2368 2376 ctx = change(rev)
2369 2377 if not fns:
2370 2378
2371 2379 def fns_generator():
2372 2380 if allfiles:
2373 2381 fiter = iter(ctx)
2374 2382 else:
2375 2383 fiter = ctx.files()
2376 2384 for f in fiter:
2377 2385 if match(f):
2378 2386 yield f
2379 2387
2380 2388 fns = fns_generator()
2381 2389 prepare(ctx, fns)
2382 2390 for rev in nrevs:
2383 2391 yield change(rev)
2384 2392
2385 2393 if stopiteration:
2386 2394 break
2387 2395
2388 2396 return iterate()
2389 2397
2390 2398
2391 2399 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2392 2400 bad = []
2393 2401
2394 2402 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2395 2403 names = []
2396 2404 wctx = repo[None]
2397 2405 cca = None
2398 2406 abort, warn = scmutil.checkportabilityalert(ui)
2399 2407 if abort or warn:
2400 2408 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2401 2409
2402 2410 match = repo.narrowmatch(match, includeexact=True)
2403 2411 badmatch = matchmod.badmatch(match, badfn)
2404 2412 dirstate = repo.dirstate
2405 2413 # We don't want to just call wctx.walk here, since it would return a lot of
2406 2414 # clean files, which we aren't interested in and takes time.
2407 2415 for f in sorted(
2408 2416 dirstate.walk(
2409 2417 badmatch,
2410 2418 subrepos=sorted(wctx.substate),
2411 2419 unknown=True,
2412 2420 ignored=False,
2413 2421 full=False,
2414 2422 )
2415 2423 ):
2416 2424 exact = match.exact(f)
2417 2425 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2418 2426 if cca:
2419 2427 cca(f)
2420 2428 names.append(f)
2421 2429 if ui.verbose or not exact:
2422 2430 ui.status(
2423 2431 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2424 2432 )
2425 2433
2426 2434 for subpath in sorted(wctx.substate):
2427 2435 sub = wctx.sub(subpath)
2428 2436 try:
2429 2437 submatch = matchmod.subdirmatcher(subpath, match)
2430 2438 subprefix = repo.wvfs.reljoin(prefix, subpath)
2431 2439 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2432 2440 if opts.get('subrepos'):
2433 2441 bad.extend(
2434 2442 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2435 2443 )
2436 2444 else:
2437 2445 bad.extend(
2438 2446 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2439 2447 )
2440 2448 except error.LookupError:
2441 2449 ui.status(
2442 2450 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2443 2451 )
2444 2452
2445 2453 if not opts.get('dry_run'):
2446 2454 rejected = wctx.add(names, prefix)
2447 2455 bad.extend(f for f in rejected if f in match.files())
2448 2456 return bad
2449 2457
2450 2458
2451 2459 def addwebdirpath(repo, serverpath, webconf):
2452 2460 webconf[serverpath] = repo.root
2453 2461 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2454 2462
2455 2463 for r in repo.revs(b'filelog("path:.hgsub")'):
2456 2464 ctx = repo[r]
2457 2465 for subpath in ctx.substate:
2458 2466 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2459 2467
2460 2468
2461 2469 def forget(
2462 2470 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2463 2471 ):
2464 2472 if dryrun and interactive:
2465 2473 raise error.Abort(_(b"cannot specify both --dry-run and --interactive"))
2466 2474 bad = []
2467 2475 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2468 2476 wctx = repo[None]
2469 2477 forgot = []
2470 2478
2471 2479 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2472 2480 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2473 2481 if explicitonly:
2474 2482 forget = [f for f in forget if match.exact(f)]
2475 2483
2476 2484 for subpath in sorted(wctx.substate):
2477 2485 sub = wctx.sub(subpath)
2478 2486 submatch = matchmod.subdirmatcher(subpath, match)
2479 2487 subprefix = repo.wvfs.reljoin(prefix, subpath)
2480 2488 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2481 2489 try:
2482 2490 subbad, subforgot = sub.forget(
2483 2491 submatch,
2484 2492 subprefix,
2485 2493 subuipathfn,
2486 2494 dryrun=dryrun,
2487 2495 interactive=interactive,
2488 2496 )
2489 2497 bad.extend([subpath + b'/' + f for f in subbad])
2490 2498 forgot.extend([subpath + b'/' + f for f in subforgot])
2491 2499 except error.LookupError:
2492 2500 ui.status(
2493 2501 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2494 2502 )
2495 2503
2496 2504 if not explicitonly:
2497 2505 for f in match.files():
2498 2506 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2499 2507 if f not in forgot:
2500 2508 if repo.wvfs.exists(f):
2501 2509 # Don't complain if the exact case match wasn't given.
2502 2510 # But don't do this until after checking 'forgot', so
2503 2511 # that subrepo files aren't normalized, and this op is
2504 2512 # purely from data cached by the status walk above.
2505 2513 if repo.dirstate.normalize(f) in repo.dirstate:
2506 2514 continue
2507 2515 ui.warn(
2508 2516 _(
2509 2517 b'not removing %s: '
2510 2518 b'file is already untracked\n'
2511 2519 )
2512 2520 % uipathfn(f)
2513 2521 )
2514 2522 bad.append(f)
2515 2523
2516 2524 if interactive:
2517 2525 responses = _(
2518 2526 b'[Ynsa?]'
2519 2527 b'$$ &Yes, forget this file'
2520 2528 b'$$ &No, skip this file'
2521 2529 b'$$ &Skip remaining files'
2522 2530 b'$$ Include &all remaining files'
2523 2531 b'$$ &? (display help)'
2524 2532 )
2525 2533 for filename in forget[:]:
2526 2534 r = ui.promptchoice(
2527 2535 _(b'forget %s %s') % (uipathfn(filename), responses)
2528 2536 )
2529 2537 if r == 4: # ?
2530 2538 while r == 4:
2531 2539 for c, t in ui.extractchoices(responses)[1]:
2532 2540 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2533 2541 r = ui.promptchoice(
2534 2542 _(b'forget %s %s') % (uipathfn(filename), responses)
2535 2543 )
2536 2544 if r == 0: # yes
2537 2545 continue
2538 2546 elif r == 1: # no
2539 2547 forget.remove(filename)
2540 2548 elif r == 2: # Skip
2541 2549 fnindex = forget.index(filename)
2542 2550 del forget[fnindex:]
2543 2551 break
2544 2552 elif r == 3: # All
2545 2553 break
2546 2554
2547 2555 for f in forget:
2548 2556 if ui.verbose or not match.exact(f) or interactive:
2549 2557 ui.status(
2550 2558 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2551 2559 )
2552 2560
2553 2561 if not dryrun:
2554 2562 rejected = wctx.forget(forget, prefix)
2555 2563 bad.extend(f for f in rejected if f in match.files())
2556 2564 forgot.extend(f for f in forget if f not in rejected)
2557 2565 return bad, forgot
2558 2566
2559 2567
2560 2568 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2561 2569 ret = 1
2562 2570
2563 2571 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2564 2572 for f in ctx.matches(m):
2565 2573 fm.startitem()
2566 2574 fm.context(ctx=ctx)
2567 2575 if needsfctx:
2568 2576 fc = ctx[f]
2569 2577 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2570 2578 fm.data(path=f)
2571 2579 fm.plain(fmt % uipathfn(f))
2572 2580 ret = 0
2573 2581
2574 2582 for subpath in sorted(ctx.substate):
2575 2583 submatch = matchmod.subdirmatcher(subpath, m)
2576 2584 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2577 2585 if subrepos or m.exact(subpath) or any(submatch.files()):
2578 2586 sub = ctx.sub(subpath)
2579 2587 try:
2580 2588 recurse = m.exact(subpath) or subrepos
2581 2589 if (
2582 2590 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2583 2591 == 0
2584 2592 ):
2585 2593 ret = 0
2586 2594 except error.LookupError:
2587 2595 ui.status(
2588 2596 _(b"skipping missing subrepository: %s\n")
2589 2597 % uipathfn(subpath)
2590 2598 )
2591 2599
2592 2600 return ret
2593 2601
2594 2602
2595 2603 def remove(
2596 2604 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2597 2605 ):
2598 2606 ret = 0
2599 2607 s = repo.status(match=m, clean=True)
2600 2608 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2601 2609
2602 2610 wctx = repo[None]
2603 2611
2604 2612 if warnings is None:
2605 2613 warnings = []
2606 2614 warn = True
2607 2615 else:
2608 2616 warn = False
2609 2617
2610 2618 subs = sorted(wctx.substate)
2611 2619 progress = ui.makeprogress(
2612 2620 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2613 2621 )
2614 2622 for subpath in subs:
2615 2623 submatch = matchmod.subdirmatcher(subpath, m)
2616 2624 subprefix = repo.wvfs.reljoin(prefix, subpath)
2617 2625 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2618 2626 if subrepos or m.exact(subpath) or any(submatch.files()):
2619 2627 progress.increment()
2620 2628 sub = wctx.sub(subpath)
2621 2629 try:
2622 2630 if sub.removefiles(
2623 2631 submatch,
2624 2632 subprefix,
2625 2633 subuipathfn,
2626 2634 after,
2627 2635 force,
2628 2636 subrepos,
2629 2637 dryrun,
2630 2638 warnings,
2631 2639 ):
2632 2640 ret = 1
2633 2641 except error.LookupError:
2634 2642 warnings.append(
2635 2643 _(b"skipping missing subrepository: %s\n")
2636 2644 % uipathfn(subpath)
2637 2645 )
2638 2646 progress.complete()
2639 2647
2640 2648 # warn about failure to delete explicit files/dirs
2641 2649 deleteddirs = pathutil.dirs(deleted)
2642 2650 files = m.files()
2643 2651 progress = ui.makeprogress(
2644 2652 _(b'deleting'), total=len(files), unit=_(b'files')
2645 2653 )
2646 2654 for f in files:
2647 2655
2648 2656 def insubrepo():
2649 2657 for subpath in wctx.substate:
2650 2658 if f.startswith(subpath + b'/'):
2651 2659 return True
2652 2660 return False
2653 2661
2654 2662 progress.increment()
2655 2663 isdir = f in deleteddirs or wctx.hasdir(f)
2656 2664 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2657 2665 continue
2658 2666
2659 2667 if repo.wvfs.exists(f):
2660 2668 if repo.wvfs.isdir(f):
2661 2669 warnings.append(
2662 2670 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2663 2671 )
2664 2672 else:
2665 2673 warnings.append(
2666 2674 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2667 2675 )
2668 2676 # missing files will generate a warning elsewhere
2669 2677 ret = 1
2670 2678 progress.complete()
2671 2679
2672 2680 if force:
2673 2681 list = modified + deleted + clean + added
2674 2682 elif after:
2675 2683 list = deleted
2676 2684 remaining = modified + added + clean
2677 2685 progress = ui.makeprogress(
2678 2686 _(b'skipping'), total=len(remaining), unit=_(b'files')
2679 2687 )
2680 2688 for f in remaining:
2681 2689 progress.increment()
2682 2690 if ui.verbose or (f in files):
2683 2691 warnings.append(
2684 2692 _(b'not removing %s: file still exists\n') % uipathfn(f)
2685 2693 )
2686 2694 ret = 1
2687 2695 progress.complete()
2688 2696 else:
2689 2697 list = deleted + clean
2690 2698 progress = ui.makeprogress(
2691 2699 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2692 2700 )
2693 2701 for f in modified:
2694 2702 progress.increment()
2695 2703 warnings.append(
2696 2704 _(
2697 2705 b'not removing %s: file is modified (use -f'
2698 2706 b' to force removal)\n'
2699 2707 )
2700 2708 % uipathfn(f)
2701 2709 )
2702 2710 ret = 1
2703 2711 for f in added:
2704 2712 progress.increment()
2705 2713 warnings.append(
2706 2714 _(
2707 2715 b"not removing %s: file has been marked for add"
2708 2716 b" (use 'hg forget' to undo add)\n"
2709 2717 )
2710 2718 % uipathfn(f)
2711 2719 )
2712 2720 ret = 1
2713 2721 progress.complete()
2714 2722
2715 2723 list = sorted(list)
2716 2724 progress = ui.makeprogress(
2717 2725 _(b'deleting'), total=len(list), unit=_(b'files')
2718 2726 )
2719 2727 for f in list:
2720 2728 if ui.verbose or not m.exact(f):
2721 2729 progress.increment()
2722 2730 ui.status(
2723 2731 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2724 2732 )
2725 2733 progress.complete()
2726 2734
2727 2735 if not dryrun:
2728 2736 with repo.wlock():
2729 2737 if not after:
2730 2738 for f in list:
2731 2739 if f in added:
2732 2740 continue # we never unlink added files on remove
2733 2741 rmdir = repo.ui.configbool(
2734 2742 b'experimental', b'removeemptydirs'
2735 2743 )
2736 2744 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2737 2745 repo[None].forget(list)
2738 2746
2739 2747 if warn:
2740 2748 for warning in warnings:
2741 2749 ui.warn(warning)
2742 2750
2743 2751 return ret
2744 2752
2745 2753
2746 2754 def _catfmtneedsdata(fm):
2747 2755 return not fm.datahint() or b'data' in fm.datahint()
2748 2756
2749 2757
2750 2758 def _updatecatformatter(fm, ctx, matcher, path, decode):
2751 2759 """Hook for adding data to the formatter used by ``hg cat``.
2752 2760
2753 2761 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2754 2762 this method first."""
2755 2763
2756 2764 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2757 2765 # wasn't requested.
2758 2766 data = b''
2759 2767 if _catfmtneedsdata(fm):
2760 2768 data = ctx[path].data()
2761 2769 if decode:
2762 2770 data = ctx.repo().wwritedata(path, data)
2763 2771 fm.startitem()
2764 2772 fm.context(ctx=ctx)
2765 2773 fm.write(b'data', b'%s', data)
2766 2774 fm.data(path=path)
2767 2775
2768 2776
2769 2777 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2770 2778 err = 1
2771 2779 opts = pycompat.byteskwargs(opts)
2772 2780
2773 2781 def write(path):
2774 2782 filename = None
2775 2783 if fntemplate:
2776 2784 filename = makefilename(
2777 2785 ctx, fntemplate, pathname=os.path.join(prefix, path)
2778 2786 )
2779 2787 # attempt to create the directory if it does not already exist
2780 2788 try:
2781 2789 os.makedirs(os.path.dirname(filename))
2782 2790 except OSError:
2783 2791 pass
2784 2792 with formatter.maybereopen(basefm, filename) as fm:
2785 2793 _updatecatformatter(fm, ctx, matcher, path, opts.get(b'decode'))
2786 2794
2787 2795 # Automation often uses hg cat on single files, so special case it
2788 2796 # for performance to avoid the cost of parsing the manifest.
2789 2797 if len(matcher.files()) == 1 and not matcher.anypats():
2790 2798 file = matcher.files()[0]
2791 2799 mfl = repo.manifestlog
2792 2800 mfnode = ctx.manifestnode()
2793 2801 try:
2794 2802 if mfnode and mfl[mfnode].find(file)[0]:
2795 2803 if _catfmtneedsdata(basefm):
2796 2804 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2797 2805 write(file)
2798 2806 return 0
2799 2807 except KeyError:
2800 2808 pass
2801 2809
2802 2810 if _catfmtneedsdata(basefm):
2803 2811 scmutil.prefetchfiles(repo, [ctx.rev()], matcher)
2804 2812
2805 2813 for abs in ctx.walk(matcher):
2806 2814 write(abs)
2807 2815 err = 0
2808 2816
2809 2817 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2810 2818 for subpath in sorted(ctx.substate):
2811 2819 sub = ctx.sub(subpath)
2812 2820 try:
2813 2821 submatch = matchmod.subdirmatcher(subpath, matcher)
2814 2822 subprefix = os.path.join(prefix, subpath)
2815 2823 if not sub.cat(
2816 2824 submatch,
2817 2825 basefm,
2818 2826 fntemplate,
2819 2827 subprefix,
2820 2828 **pycompat.strkwargs(opts)
2821 2829 ):
2822 2830 err = 0
2823 2831 except error.RepoLookupError:
2824 2832 ui.status(
2825 2833 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2826 2834 )
2827 2835
2828 2836 return err
2829 2837
2830 2838
2831 2839 def commit(ui, repo, commitfunc, pats, opts):
2832 2840 '''commit the specified files or all outstanding changes'''
2833 2841 date = opts.get(b'date')
2834 2842 if date:
2835 2843 opts[b'date'] = dateutil.parsedate(date)
2836 2844 message = logmessage(ui, opts)
2837 2845 matcher = scmutil.match(repo[None], pats, opts)
2838 2846
2839 2847 dsguard = None
2840 2848 # extract addremove carefully -- this function can be called from a command
2841 2849 # that doesn't support addremove
2842 2850 if opts.get(b'addremove'):
2843 2851 dsguard = dirstateguard.dirstateguard(repo, b'commit')
2844 2852 with dsguard or util.nullcontextmanager():
2845 2853 if dsguard:
2846 2854 relative = scmutil.anypats(pats, opts)
2847 2855 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2848 2856 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
2849 2857 raise error.Abort(
2850 2858 _(b"failed to mark all new/missing files as added/removed")
2851 2859 )
2852 2860
2853 2861 return commitfunc(ui, repo, message, matcher, opts)
2854 2862
2855 2863
2856 2864 def samefile(f, ctx1, ctx2):
2857 2865 if f in ctx1.manifest():
2858 2866 a = ctx1.filectx(f)
2859 2867 if f in ctx2.manifest():
2860 2868 b = ctx2.filectx(f)
2861 2869 return not a.cmp(b) and a.flags() == b.flags()
2862 2870 else:
2863 2871 return False
2864 2872 else:
2865 2873 return f not in ctx2.manifest()
2866 2874
2867 2875
2868 2876 def amend(ui, repo, old, extra, pats, opts):
2869 2877 # avoid cycle context -> subrepo -> cmdutil
2870 2878 from . import context
2871 2879
2872 2880 # amend will reuse the existing user if not specified, but the obsolete
2873 2881 # marker creation requires that the current user's name is specified.
2874 2882 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2875 2883 ui.username() # raise exception if username not set
2876 2884
2877 2885 ui.note(_(b'amending changeset %s\n') % old)
2878 2886 base = old.p1()
2879 2887
2880 2888 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
2881 2889 # Participating changesets:
2882 2890 #
2883 2891 # wctx o - workingctx that contains changes from working copy
2884 2892 # | to go into amending commit
2885 2893 # |
2886 2894 # old o - changeset to amend
2887 2895 # |
2888 2896 # base o - first parent of the changeset to amend
2889 2897 wctx = repo[None]
2890 2898
2891 2899 # Copy to avoid mutating input
2892 2900 extra = extra.copy()
2893 2901 # Update extra dict from amended commit (e.g. to preserve graft
2894 2902 # source)
2895 2903 extra.update(old.extra())
2896 2904
2897 2905 # Also update it from the from the wctx
2898 2906 extra.update(wctx.extra())
2899 2907
2900 2908 # date-only change should be ignored?
2901 2909 datemaydiffer = resolvecommitoptions(ui, opts)
2902 2910
2903 2911 date = old.date()
2904 2912 if opts.get(b'date'):
2905 2913 date = dateutil.parsedate(opts.get(b'date'))
2906 2914 user = opts.get(b'user') or old.user()
2907 2915
2908 2916 if len(old.parents()) > 1:
2909 2917 # ctx.files() isn't reliable for merges, so fall back to the
2910 2918 # slower repo.status() method
2911 2919 st = base.status(old)
2912 2920 files = set(st.modified) | set(st.added) | set(st.removed)
2913 2921 else:
2914 2922 files = set(old.files())
2915 2923
2916 2924 # add/remove the files to the working copy if the "addremove" option
2917 2925 # was specified.
2918 2926 matcher = scmutil.match(wctx, pats, opts)
2919 2927 relative = scmutil.anypats(pats, opts)
2920 2928 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2921 2929 if opts.get(b'addremove') and scmutil.addremove(
2922 2930 repo, matcher, b"", uipathfn, opts
2923 2931 ):
2924 2932 raise error.Abort(
2925 2933 _(b"failed to mark all new/missing files as added/removed")
2926 2934 )
2927 2935
2928 2936 # Check subrepos. This depends on in-place wctx._status update in
2929 2937 # subrepo.precommit(). To minimize the risk of this hack, we do
2930 2938 # nothing if .hgsub does not exist.
2931 2939 if b'.hgsub' in wctx or b'.hgsub' in old:
2932 2940 subs, commitsubs, newsubstate = subrepoutil.precommit(
2933 2941 ui, wctx, wctx._status, matcher
2934 2942 )
2935 2943 # amend should abort if commitsubrepos is enabled
2936 2944 assert not commitsubs
2937 2945 if subs:
2938 2946 subrepoutil.writestate(repo, newsubstate)
2939 2947
2940 2948 ms = mergemod.mergestate.read(repo)
2941 2949 mergeutil.checkunresolved(ms)
2942 2950
2943 2951 filestoamend = set(f for f in wctx.files() if matcher(f))
2944 2952
2945 2953 changes = len(filestoamend) > 0
2946 2954 if changes:
2947 2955 # Recompute copies (avoid recording a -> b -> a)
2948 2956 copied = copies.pathcopies(base, wctx, matcher)
2949 2957 if old.p2:
2950 2958 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2951 2959
2952 2960 # Prune files which were reverted by the updates: if old
2953 2961 # introduced file X and the file was renamed in the working
2954 2962 # copy, then those two files are the same and
2955 2963 # we can discard X from our list of files. Likewise if X
2956 2964 # was removed, it's no longer relevant. If X is missing (aka
2957 2965 # deleted), old X must be preserved.
2958 2966 files.update(filestoamend)
2959 2967 files = [
2960 2968 f
2961 2969 for f in files
2962 2970 if (f not in filestoamend or not samefile(f, wctx, base))
2963 2971 ]
2964 2972
2965 2973 def filectxfn(repo, ctx_, path):
2966 2974 try:
2967 2975 # If the file being considered is not amongst the files
2968 2976 # to be amended, we should return the file context from the
2969 2977 # old changeset. This avoids issues when only some files in
2970 2978 # the working copy are being amended but there are also
2971 2979 # changes to other files from the old changeset.
2972 2980 if path not in filestoamend:
2973 2981 return old.filectx(path)
2974 2982
2975 2983 # Return None for removed files.
2976 2984 if path in wctx.removed():
2977 2985 return None
2978 2986
2979 2987 fctx = wctx[path]
2980 2988 flags = fctx.flags()
2981 2989 mctx = context.memfilectx(
2982 2990 repo,
2983 2991 ctx_,
2984 2992 fctx.path(),
2985 2993 fctx.data(),
2986 2994 islink=b'l' in flags,
2987 2995 isexec=b'x' in flags,
2988 2996 copysource=copied.get(path),
2989 2997 )
2990 2998 return mctx
2991 2999 except KeyError:
2992 3000 return None
2993 3001
2994 3002 else:
2995 3003 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
2996 3004
2997 3005 # Use version of files as in the old cset
2998 3006 def filectxfn(repo, ctx_, path):
2999 3007 try:
3000 3008 return old.filectx(path)
3001 3009 except KeyError:
3002 3010 return None
3003 3011
3004 3012 # See if we got a message from -m or -l, if not, open the editor with
3005 3013 # the message of the changeset to amend.
3006 3014 message = logmessage(ui, opts)
3007 3015
3008 3016 editform = mergeeditform(old, b'commit.amend')
3009 3017
3010 3018 if not message:
3011 3019 message = old.description()
3012 3020 # Default if message isn't provided and --edit is not passed is to
3013 3021 # invoke editor, but allow --no-edit. If somehow we don't have any
3014 3022 # description, let's always start the editor.
3015 3023 doedit = not message or opts.get(b'edit') in [True, None]
3016 3024 else:
3017 3025 # Default if message is provided is to not invoke editor, but allow
3018 3026 # --edit.
3019 3027 doedit = opts.get(b'edit') is True
3020 3028 editor = getcommiteditor(edit=doedit, editform=editform)
3021 3029
3022 3030 pureextra = extra.copy()
3023 3031 extra[b'amend_source'] = old.hex()
3024 3032
3025 3033 new = context.memctx(
3026 3034 repo,
3027 3035 parents=[base.node(), old.p2().node()],
3028 3036 text=message,
3029 3037 files=files,
3030 3038 filectxfn=filectxfn,
3031 3039 user=user,
3032 3040 date=date,
3033 3041 extra=extra,
3034 3042 editor=editor,
3035 3043 )
3036 3044
3037 3045 newdesc = changelog.stripdesc(new.description())
3038 3046 if (
3039 3047 (not changes)
3040 3048 and newdesc == old.description()
3041 3049 and user == old.user()
3042 3050 and (date == old.date() or datemaydiffer)
3043 3051 and pureextra == old.extra()
3044 3052 ):
3045 3053 # nothing changed. continuing here would create a new node
3046 3054 # anyway because of the amend_source noise.
3047 3055 #
3048 3056 # This not what we expect from amend.
3049 3057 return old.node()
3050 3058
3051 3059 commitphase = None
3052 3060 if opts.get(b'secret'):
3053 3061 commitphase = phases.secret
3054 3062 newid = repo.commitctx(new)
3055 3063
3056 3064 # Reroute the working copy parent to the new changeset
3057 3065 repo.setparents(newid, nullid)
3058 3066 mapping = {old.node(): (newid,)}
3059 3067 obsmetadata = None
3060 3068 if opts.get(b'note'):
3061 3069 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
3062 3070 backup = ui.configbool(b'rewrite', b'backup-bundle')
3063 3071 scmutil.cleanupnodes(
3064 3072 repo,
3065 3073 mapping,
3066 3074 b'amend',
3067 3075 metadata=obsmetadata,
3068 3076 fixphase=True,
3069 3077 targetphase=commitphase,
3070 3078 backup=backup,
3071 3079 )
3072 3080
3073 3081 # Fixing the dirstate because localrepo.commitctx does not update
3074 3082 # it. This is rather convenient because we did not need to update
3075 3083 # the dirstate for all the files in the new commit which commitctx
3076 3084 # could have done if it updated the dirstate. Now, we can
3077 3085 # selectively update the dirstate only for the amended files.
3078 3086 dirstate = repo.dirstate
3079 3087
3080 3088 # Update the state of the files which were added and modified in the
3081 3089 # amend to "normal" in the dirstate. We need to use "normallookup" since
3082 3090 # the files may have changed since the command started; using "normal"
3083 3091 # would mark them as clean but with uncommitted contents.
3084 3092 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
3085 3093 for f in normalfiles:
3086 3094 dirstate.normallookup(f)
3087 3095
3088 3096 # Update the state of files which were removed in the amend
3089 3097 # to "removed" in the dirstate.
3090 3098 removedfiles = set(wctx.removed()) & filestoamend
3091 3099 for f in removedfiles:
3092 3100 dirstate.drop(f)
3093 3101
3094 3102 return newid
3095 3103
3096 3104
3097 3105 def commiteditor(repo, ctx, subs, editform=b''):
3098 3106 if ctx.description():
3099 3107 return ctx.description()
3100 3108 return commitforceeditor(
3101 3109 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
3102 3110 )
3103 3111
3104 3112
3105 3113 def commitforceeditor(
3106 3114 repo,
3107 3115 ctx,
3108 3116 subs,
3109 3117 finishdesc=None,
3110 3118 extramsg=None,
3111 3119 editform=b'',
3112 3120 unchangedmessagedetection=False,
3113 3121 ):
3114 3122 if not extramsg:
3115 3123 extramsg = _(b"Leave message empty to abort commit.")
3116 3124
3117 3125 forms = [e for e in editform.split(b'.') if e]
3118 3126 forms.insert(0, b'changeset')
3119 3127 templatetext = None
3120 3128 while forms:
3121 3129 ref = b'.'.join(forms)
3122 3130 if repo.ui.config(b'committemplate', ref):
3123 3131 templatetext = committext = buildcommittemplate(
3124 3132 repo, ctx, subs, extramsg, ref
3125 3133 )
3126 3134 break
3127 3135 forms.pop()
3128 3136 else:
3129 3137 committext = buildcommittext(repo, ctx, subs, extramsg)
3130 3138
3131 3139 # run editor in the repository root
3132 3140 olddir = encoding.getcwd()
3133 3141 os.chdir(repo.root)
3134 3142
3135 3143 # make in-memory changes visible to external process
3136 3144 tr = repo.currenttransaction()
3137 3145 repo.dirstate.write(tr)
3138 3146 pending = tr and tr.writepending() and repo.root
3139 3147
3140 3148 editortext = repo.ui.edit(
3141 3149 committext,
3142 3150 ctx.user(),
3143 3151 ctx.extra(),
3144 3152 editform=editform,
3145 3153 pending=pending,
3146 3154 repopath=repo.path,
3147 3155 action=b'commit',
3148 3156 )
3149 3157 text = editortext
3150 3158
3151 3159 # strip away anything below this special string (used for editors that want
3152 3160 # to display the diff)
3153 3161 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3154 3162 if stripbelow:
3155 3163 text = text[: stripbelow.start()]
3156 3164
3157 3165 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3158 3166 os.chdir(olddir)
3159 3167
3160 3168 if finishdesc:
3161 3169 text = finishdesc(text)
3162 3170 if not text.strip():
3163 3171 raise error.Abort(_(b"empty commit message"))
3164 3172 if unchangedmessagedetection and editortext == templatetext:
3165 3173 raise error.Abort(_(b"commit message unchanged"))
3166 3174
3167 3175 return text
3168 3176
3169 3177
3170 3178 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3171 3179 ui = repo.ui
3172 3180 spec = formatter.templatespec(ref, None, None)
3173 3181 t = logcmdutil.changesettemplater(ui, repo, spec)
3174 3182 t.t.cache.update(
3175 3183 (k, templater.unquotestring(v))
3176 3184 for k, v in repo.ui.configitems(b'committemplate')
3177 3185 )
3178 3186
3179 3187 if not extramsg:
3180 3188 extramsg = b'' # ensure that extramsg is string
3181 3189
3182 3190 ui.pushbuffer()
3183 3191 t.show(ctx, extramsg=extramsg)
3184 3192 return ui.popbuffer()
3185 3193
3186 3194
3187 3195 def hgprefix(msg):
3188 3196 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3189 3197
3190 3198
3191 3199 def buildcommittext(repo, ctx, subs, extramsg):
3192 3200 edittext = []
3193 3201 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3194 3202 if ctx.description():
3195 3203 edittext.append(ctx.description())
3196 3204 edittext.append(b"")
3197 3205 edittext.append(b"") # Empty line between message and comments.
3198 3206 edittext.append(
3199 3207 hgprefix(
3200 3208 _(
3201 3209 b"Enter commit message."
3202 3210 b" Lines beginning with 'HG:' are removed."
3203 3211 )
3204 3212 )
3205 3213 )
3206 3214 edittext.append(hgprefix(extramsg))
3207 3215 edittext.append(b"HG: --")
3208 3216 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3209 3217 if ctx.p2():
3210 3218 edittext.append(hgprefix(_(b"branch merge")))
3211 3219 if ctx.branch():
3212 3220 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3213 3221 if bookmarks.isactivewdirparent(repo):
3214 3222 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3215 3223 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3216 3224 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3217 3225 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3218 3226 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3219 3227 if not added and not modified and not removed:
3220 3228 edittext.append(hgprefix(_(b"no files changed")))
3221 3229 edittext.append(b"")
3222 3230
3223 3231 return b"\n".join(edittext)
3224 3232
3225 3233
3226 3234 def commitstatus(repo, node, branch, bheads=None, opts=None):
3227 3235 if opts is None:
3228 3236 opts = {}
3229 3237 ctx = repo[node]
3230 3238 parents = ctx.parents()
3231 3239
3232 3240 if (
3233 3241 not opts.get(b'amend')
3234 3242 and bheads
3235 3243 and node not in bheads
3236 3244 and not [
3237 3245 x for x in parents if x.node() in bheads and x.branch() == branch
3238 3246 ]
3239 3247 ):
3240 3248 repo.ui.status(_(b'created new head\n'))
3241 3249 # The message is not printed for initial roots. For the other
3242 3250 # changesets, it is printed in the following situations:
3243 3251 #
3244 3252 # Par column: for the 2 parents with ...
3245 3253 # N: null or no parent
3246 3254 # B: parent is on another named branch
3247 3255 # C: parent is a regular non head changeset
3248 3256 # H: parent was a branch head of the current branch
3249 3257 # Msg column: whether we print "created new head" message
3250 3258 # In the following, it is assumed that there already exists some
3251 3259 # initial branch heads of the current branch, otherwise nothing is
3252 3260 # printed anyway.
3253 3261 #
3254 3262 # Par Msg Comment
3255 3263 # N N y additional topo root
3256 3264 #
3257 3265 # B N y additional branch root
3258 3266 # C N y additional topo head
3259 3267 # H N n usual case
3260 3268 #
3261 3269 # B B y weird additional branch root
3262 3270 # C B y branch merge
3263 3271 # H B n merge with named branch
3264 3272 #
3265 3273 # C C y additional head from merge
3266 3274 # C H n merge with a head
3267 3275 #
3268 3276 # H H n head merge: head count decreases
3269 3277
3270 3278 if not opts.get(b'close_branch'):
3271 3279 for r in parents:
3272 3280 if r.closesbranch() and r.branch() == branch:
3273 3281 repo.ui.status(
3274 3282 _(b'reopening closed branch head %d\n') % r.rev()
3275 3283 )
3276 3284
3277 3285 if repo.ui.debugflag:
3278 3286 repo.ui.write(
3279 3287 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3280 3288 )
3281 3289 elif repo.ui.verbose:
3282 3290 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3283 3291
3284 3292
3285 3293 def postcommitstatus(repo, pats, opts):
3286 3294 return repo.status(match=scmutil.match(repo[None], pats, opts))
3287 3295
3288 3296
3289 3297 def revert(ui, repo, ctx, parents, *pats, **opts):
3290 3298 opts = pycompat.byteskwargs(opts)
3291 3299 parent, p2 = parents
3292 3300 node = ctx.node()
3293 3301
3294 3302 mf = ctx.manifest()
3295 3303 if node == p2:
3296 3304 parent = p2
3297 3305
3298 3306 # need all matching names in dirstate and manifest of target rev,
3299 3307 # so have to walk both. do not print errors if files exist in one
3300 3308 # but not other. in both cases, filesets should be evaluated against
3301 3309 # workingctx to get consistent result (issue4497). this means 'set:**'
3302 3310 # cannot be used to select missing files from target rev.
3303 3311
3304 3312 # `names` is a mapping for all elements in working copy and target revision
3305 3313 # The mapping is in the form:
3306 3314 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3307 3315 names = {}
3308 3316 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3309 3317
3310 3318 with repo.wlock():
3311 3319 ## filling of the `names` mapping
3312 3320 # walk dirstate to fill `names`
3313 3321
3314 3322 interactive = opts.get(b'interactive', False)
3315 3323 wctx = repo[None]
3316 3324 m = scmutil.match(wctx, pats, opts)
3317 3325
3318 3326 # we'll need this later
3319 3327 targetsubs = sorted(s for s in wctx.substate if m(s))
3320 3328
3321 3329 if not m.always():
3322 3330 matcher = matchmod.badmatch(m, lambda x, y: False)
3323 3331 for abs in wctx.walk(matcher):
3324 3332 names[abs] = m.exact(abs)
3325 3333
3326 3334 # walk target manifest to fill `names`
3327 3335
3328 3336 def badfn(path, msg):
3329 3337 if path in names:
3330 3338 return
3331 3339 if path in ctx.substate:
3332 3340 return
3333 3341 path_ = path + b'/'
3334 3342 for f in names:
3335 3343 if f.startswith(path_):
3336 3344 return
3337 3345 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3338 3346
3339 3347 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3340 3348 if abs not in names:
3341 3349 names[abs] = m.exact(abs)
3342 3350
3343 3351 # Find status of all file in `names`.
3344 3352 m = scmutil.matchfiles(repo, names)
3345 3353
3346 3354 changes = repo.status(
3347 3355 node1=node, match=m, unknown=True, ignored=True, clean=True
3348 3356 )
3349 3357 else:
3350 3358 changes = repo.status(node1=node, match=m)
3351 3359 for kind in changes:
3352 3360 for abs in kind:
3353 3361 names[abs] = m.exact(abs)
3354 3362
3355 3363 m = scmutil.matchfiles(repo, names)
3356 3364
3357 3365 modified = set(changes.modified)
3358 3366 added = set(changes.added)
3359 3367 removed = set(changes.removed)
3360 3368 _deleted = set(changes.deleted)
3361 3369 unknown = set(changes.unknown)
3362 3370 unknown.update(changes.ignored)
3363 3371 clean = set(changes.clean)
3364 3372 modadded = set()
3365 3373
3366 3374 # We need to account for the state of the file in the dirstate,
3367 3375 # even when we revert against something else than parent. This will
3368 3376 # slightly alter the behavior of revert (doing back up or not, delete
3369 3377 # or just forget etc).
3370 3378 if parent == node:
3371 3379 dsmodified = modified
3372 3380 dsadded = added
3373 3381 dsremoved = removed
3374 3382 # store all local modifications, useful later for rename detection
3375 3383 localchanges = dsmodified | dsadded
3376 3384 modified, added, removed = set(), set(), set()
3377 3385 else:
3378 3386 changes = repo.status(node1=parent, match=m)
3379 3387 dsmodified = set(changes.modified)
3380 3388 dsadded = set(changes.added)
3381 3389 dsremoved = set(changes.removed)
3382 3390 # store all local modifications, useful later for rename detection
3383 3391 localchanges = dsmodified | dsadded
3384 3392
3385 3393 # only take into account for removes between wc and target
3386 3394 clean |= dsremoved - removed
3387 3395 dsremoved &= removed
3388 3396 # distinct between dirstate remove and other
3389 3397 removed -= dsremoved
3390 3398
3391 3399 modadded = added & dsmodified
3392 3400 added -= modadded
3393 3401
3394 3402 # tell newly modified apart.
3395 3403 dsmodified &= modified
3396 3404 dsmodified |= modified & dsadded # dirstate added may need backup
3397 3405 modified -= dsmodified
3398 3406
3399 3407 # We need to wait for some post-processing to update this set
3400 3408 # before making the distinction. The dirstate will be used for
3401 3409 # that purpose.
3402 3410 dsadded = added
3403 3411
3404 3412 # in case of merge, files that are actually added can be reported as
3405 3413 # modified, we need to post process the result
3406 3414 if p2 != nullid:
3407 3415 mergeadd = set(dsmodified)
3408 3416 for path in dsmodified:
3409 3417 if path in mf:
3410 3418 mergeadd.remove(path)
3411 3419 dsadded |= mergeadd
3412 3420 dsmodified -= mergeadd
3413 3421
3414 3422 # if f is a rename, update `names` to also revert the source
3415 3423 for f in localchanges:
3416 3424 src = repo.dirstate.copied(f)
3417 3425 # XXX should we check for rename down to target node?
3418 3426 if src and src not in names and repo.dirstate[src] == b'r':
3419 3427 dsremoved.add(src)
3420 3428 names[src] = True
3421 3429
3422 3430 # determine the exact nature of the deleted changesets
3423 3431 deladded = set(_deleted)
3424 3432 for path in _deleted:
3425 3433 if path in mf:
3426 3434 deladded.remove(path)
3427 3435 deleted = _deleted - deladded
3428 3436
3429 3437 # distinguish between file to forget and the other
3430 3438 added = set()
3431 3439 for abs in dsadded:
3432 3440 if repo.dirstate[abs] != b'a':
3433 3441 added.add(abs)
3434 3442 dsadded -= added
3435 3443
3436 3444 for abs in deladded:
3437 3445 if repo.dirstate[abs] == b'a':
3438 3446 dsadded.add(abs)
3439 3447 deladded -= dsadded
3440 3448
3441 3449 # For files marked as removed, we check if an unknown file is present at
3442 3450 # the same path. If a such file exists it may need to be backed up.
3443 3451 # Making the distinction at this stage helps have simpler backup
3444 3452 # logic.
3445 3453 removunk = set()
3446 3454 for abs in removed:
3447 3455 target = repo.wjoin(abs)
3448 3456 if os.path.lexists(target):
3449 3457 removunk.add(abs)
3450 3458 removed -= removunk
3451 3459
3452 3460 dsremovunk = set()
3453 3461 for abs in dsremoved:
3454 3462 target = repo.wjoin(abs)
3455 3463 if os.path.lexists(target):
3456 3464 dsremovunk.add(abs)
3457 3465 dsremoved -= dsremovunk
3458 3466
3459 3467 # action to be actually performed by revert
3460 3468 # (<list of file>, message>) tuple
3461 3469 actions = {
3462 3470 b'revert': ([], _(b'reverting %s\n')),
3463 3471 b'add': ([], _(b'adding %s\n')),
3464 3472 b'remove': ([], _(b'removing %s\n')),
3465 3473 b'drop': ([], _(b'removing %s\n')),
3466 3474 b'forget': ([], _(b'forgetting %s\n')),
3467 3475 b'undelete': ([], _(b'undeleting %s\n')),
3468 3476 b'noop': (None, _(b'no changes needed to %s\n')),
3469 3477 b'unknown': (None, _(b'file not managed: %s\n')),
3470 3478 }
3471 3479
3472 3480 # "constant" that convey the backup strategy.
3473 3481 # All set to `discard` if `no-backup` is set do avoid checking
3474 3482 # no_backup lower in the code.
3475 3483 # These values are ordered for comparison purposes
3476 3484 backupinteractive = 3 # do backup if interactively modified
3477 3485 backup = 2 # unconditionally do backup
3478 3486 check = 1 # check if the existing file differs from target
3479 3487 discard = 0 # never do backup
3480 3488 if opts.get(b'no_backup'):
3481 3489 backupinteractive = backup = check = discard
3482 3490 if interactive:
3483 3491 dsmodifiedbackup = backupinteractive
3484 3492 else:
3485 3493 dsmodifiedbackup = backup
3486 3494 tobackup = set()
3487 3495
3488 3496 backupanddel = actions[b'remove']
3489 3497 if not opts.get(b'no_backup'):
3490 3498 backupanddel = actions[b'drop']
3491 3499
3492 3500 disptable = (
3493 3501 # dispatch table:
3494 3502 # file state
3495 3503 # action
3496 3504 # make backup
3497 3505 ## Sets that results that will change file on disk
3498 3506 # Modified compared to target, no local change
3499 3507 (modified, actions[b'revert'], discard),
3500 3508 # Modified compared to target, but local file is deleted
3501 3509 (deleted, actions[b'revert'], discard),
3502 3510 # Modified compared to target, local change
3503 3511 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3504 3512 # Added since target
3505 3513 (added, actions[b'remove'], discard),
3506 3514 # Added in working directory
3507 3515 (dsadded, actions[b'forget'], discard),
3508 3516 # Added since target, have local modification
3509 3517 (modadded, backupanddel, backup),
3510 3518 # Added since target but file is missing in working directory
3511 3519 (deladded, actions[b'drop'], discard),
3512 3520 # Removed since target, before working copy parent
3513 3521 (removed, actions[b'add'], discard),
3514 3522 # Same as `removed` but an unknown file exists at the same path
3515 3523 (removunk, actions[b'add'], check),
3516 3524 # Removed since targe, marked as such in working copy parent
3517 3525 (dsremoved, actions[b'undelete'], discard),
3518 3526 # Same as `dsremoved` but an unknown file exists at the same path
3519 3527 (dsremovunk, actions[b'undelete'], check),
3520 3528 ## the following sets does not result in any file changes
3521 3529 # File with no modification
3522 3530 (clean, actions[b'noop'], discard),
3523 3531 # Existing file, not tracked anywhere
3524 3532 (unknown, actions[b'unknown'], discard),
3525 3533 )
3526 3534
3527 3535 for abs, exact in sorted(names.items()):
3528 3536 # target file to be touch on disk (relative to cwd)
3529 3537 target = repo.wjoin(abs)
3530 3538 # search the entry in the dispatch table.
3531 3539 # if the file is in any of these sets, it was touched in the working
3532 3540 # directory parent and we are sure it needs to be reverted.
3533 3541 for table, (xlist, msg), dobackup in disptable:
3534 3542 if abs not in table:
3535 3543 continue
3536 3544 if xlist is not None:
3537 3545 xlist.append(abs)
3538 3546 if dobackup:
3539 3547 # If in interactive mode, don't automatically create
3540 3548 # .orig files (issue4793)
3541 3549 if dobackup == backupinteractive:
3542 3550 tobackup.add(abs)
3543 3551 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3544 3552 absbakname = scmutil.backuppath(ui, repo, abs)
3545 3553 bakname = os.path.relpath(
3546 3554 absbakname, start=repo.root
3547 3555 )
3548 3556 ui.note(
3549 3557 _(b'saving current version of %s as %s\n')
3550 3558 % (uipathfn(abs), uipathfn(bakname))
3551 3559 )
3552 3560 if not opts.get(b'dry_run'):
3553 3561 if interactive:
3554 3562 util.copyfile(target, absbakname)
3555 3563 else:
3556 3564 util.rename(target, absbakname)
3557 3565 if opts.get(b'dry_run'):
3558 3566 if ui.verbose or not exact:
3559 3567 ui.status(msg % uipathfn(abs))
3560 3568 elif exact:
3561 3569 ui.warn(msg % uipathfn(abs))
3562 3570 break
3563 3571
3564 3572 if not opts.get(b'dry_run'):
3565 3573 needdata = (b'revert', b'add', b'undelete')
3566 3574 oplist = [actions[name][0] for name in needdata]
3567 3575 prefetch = scmutil.prefetchfiles
3568 3576 matchfiles = scmutil.matchfiles
3569 3577 prefetch(
3570 3578 repo,
3571 3579 [ctx.rev()],
3572 3580 matchfiles(repo, [f for sublist in oplist for f in sublist]),
3573 3581 )
3574 3582 match = scmutil.match(repo[None], pats)
3575 3583 _performrevert(
3576 3584 repo,
3577 3585 parents,
3578 3586 ctx,
3579 3587 names,
3580 3588 uipathfn,
3581 3589 actions,
3582 3590 match,
3583 3591 interactive,
3584 3592 tobackup,
3585 3593 )
3586 3594
3587 3595 if targetsubs:
3588 3596 # Revert the subrepos on the revert list
3589 3597 for sub in targetsubs:
3590 3598 try:
3591 3599 wctx.sub(sub).revert(
3592 3600 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3593 3601 )
3594 3602 except KeyError:
3595 3603 raise error.Abort(
3596 3604 b"subrepository '%s' does not exist in %s!"
3597 3605 % (sub, short(ctx.node()))
3598 3606 )
3599 3607
3600 3608
3601 3609 def _performrevert(
3602 3610 repo,
3603 3611 parents,
3604 3612 ctx,
3605 3613 names,
3606 3614 uipathfn,
3607 3615 actions,
3608 3616 match,
3609 3617 interactive=False,
3610 3618 tobackup=None,
3611 3619 ):
3612 3620 """function that actually perform all the actions computed for revert
3613 3621
3614 3622 This is an independent function to let extension to plug in and react to
3615 3623 the imminent revert.
3616 3624
3617 3625 Make sure you have the working directory locked when calling this function.
3618 3626 """
3619 3627 parent, p2 = parents
3620 3628 node = ctx.node()
3621 3629 excluded_files = []
3622 3630
3623 3631 def checkout(f):
3624 3632 fc = ctx[f]
3625 3633 repo.wwrite(f, fc.data(), fc.flags())
3626 3634
3627 3635 def doremove(f):
3628 3636 try:
3629 3637 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3630 3638 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3631 3639 except OSError:
3632 3640 pass
3633 3641 repo.dirstate.remove(f)
3634 3642
3635 3643 def prntstatusmsg(action, f):
3636 3644 exact = names[f]
3637 3645 if repo.ui.verbose or not exact:
3638 3646 repo.ui.status(actions[action][1] % uipathfn(f))
3639 3647
3640 3648 audit_path = pathutil.pathauditor(repo.root, cached=True)
3641 3649 for f in actions[b'forget'][0]:
3642 3650 if interactive:
3643 3651 choice = repo.ui.promptchoice(
3644 3652 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3645 3653 )
3646 3654 if choice == 0:
3647 3655 prntstatusmsg(b'forget', f)
3648 3656 repo.dirstate.drop(f)
3649 3657 else:
3650 3658 excluded_files.append(f)
3651 3659 else:
3652 3660 prntstatusmsg(b'forget', f)
3653 3661 repo.dirstate.drop(f)
3654 3662 for f in actions[b'remove'][0]:
3655 3663 audit_path(f)
3656 3664 if interactive:
3657 3665 choice = repo.ui.promptchoice(
3658 3666 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3659 3667 )
3660 3668 if choice == 0:
3661 3669 prntstatusmsg(b'remove', f)
3662 3670 doremove(f)
3663 3671 else:
3664 3672 excluded_files.append(f)
3665 3673 else:
3666 3674 prntstatusmsg(b'remove', f)
3667 3675 doremove(f)
3668 3676 for f in actions[b'drop'][0]:
3669 3677 audit_path(f)
3670 3678 prntstatusmsg(b'drop', f)
3671 3679 repo.dirstate.remove(f)
3672 3680
3673 3681 normal = None
3674 3682 if node == parent:
3675 3683 # We're reverting to our parent. If possible, we'd like status
3676 3684 # to report the file as clean. We have to use normallookup for
3677 3685 # merges to avoid losing information about merged/dirty files.
3678 3686 if p2 != nullid:
3679 3687 normal = repo.dirstate.normallookup
3680 3688 else:
3681 3689 normal = repo.dirstate.normal
3682 3690
3683 3691 newlyaddedandmodifiedfiles = set()
3684 3692 if interactive:
3685 3693 # Prompt the user for changes to revert
3686 3694 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3687 3695 m = scmutil.matchfiles(repo, torevert)
3688 3696 diffopts = patch.difffeatureopts(
3689 3697 repo.ui,
3690 3698 whitespace=True,
3691 3699 section=b'commands',
3692 3700 configprefix=b'revert.interactive.',
3693 3701 )
3694 3702 diffopts.nodates = True
3695 3703 diffopts.git = True
3696 3704 operation = b'apply'
3697 3705 if node == parent:
3698 3706 if repo.ui.configbool(
3699 3707 b'experimental', b'revert.interactive.select-to-keep'
3700 3708 ):
3701 3709 operation = b'keep'
3702 3710 else:
3703 3711 operation = b'discard'
3704 3712
3705 3713 if operation == b'apply':
3706 3714 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3707 3715 else:
3708 3716 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3709 3717 originalchunks = patch.parsepatch(diff)
3710 3718
3711 3719 try:
3712 3720
3713 3721 chunks, opts = recordfilter(
3714 3722 repo.ui, originalchunks, match, operation=operation
3715 3723 )
3716 3724 if operation == b'discard':
3717 3725 chunks = patch.reversehunks(chunks)
3718 3726
3719 3727 except error.PatchError as err:
3720 3728 raise error.Abort(_(b'error parsing patch: %s') % err)
3721 3729
3722 3730 # FIXME: when doing an interactive revert of a copy, there's no way of
3723 3731 # performing a partial revert of the added file, the only option is
3724 3732 # "remove added file <name> (Yn)?", so we don't need to worry about the
3725 3733 # alsorestore value. Ideally we'd be able to partially revert
3726 3734 # copied/renamed files.
3727 3735 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3728 3736 chunks, originalchunks
3729 3737 )
3730 3738 if tobackup is None:
3731 3739 tobackup = set()
3732 3740 # Apply changes
3733 3741 fp = stringio()
3734 3742 # chunks are serialized per file, but files aren't sorted
3735 3743 for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
3736 3744 prntstatusmsg(b'revert', f)
3737 3745 files = set()
3738 3746 for c in chunks:
3739 3747 if ishunk(c):
3740 3748 abs = c.header.filename()
3741 3749 # Create a backup file only if this hunk should be backed up
3742 3750 if c.header.filename() in tobackup:
3743 3751 target = repo.wjoin(abs)
3744 3752 bakname = scmutil.backuppath(repo.ui, repo, abs)
3745 3753 util.copyfile(target, bakname)
3746 3754 tobackup.remove(abs)
3747 3755 if abs not in files:
3748 3756 files.add(abs)
3749 3757 if operation == b'keep':
3750 3758 checkout(abs)
3751 3759 c.write(fp)
3752 3760 dopatch = fp.tell()
3753 3761 fp.seek(0)
3754 3762 if dopatch:
3755 3763 try:
3756 3764 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3757 3765 except error.PatchError as err:
3758 3766 raise error.Abort(pycompat.bytestr(err))
3759 3767 del fp
3760 3768 else:
3761 3769 for f in actions[b'revert'][0]:
3762 3770 prntstatusmsg(b'revert', f)
3763 3771 checkout(f)
3764 3772 if normal:
3765 3773 normal(f)
3766 3774
3767 3775 for f in actions[b'add'][0]:
3768 3776 # Don't checkout modified files, they are already created by the diff
3769 3777 if f not in newlyaddedandmodifiedfiles:
3770 3778 prntstatusmsg(b'add', f)
3771 3779 checkout(f)
3772 3780 repo.dirstate.add(f)
3773 3781
3774 3782 normal = repo.dirstate.normallookup
3775 3783 if node == parent and p2 == nullid:
3776 3784 normal = repo.dirstate.normal
3777 3785 for f in actions[b'undelete'][0]:
3778 3786 if interactive:
3779 3787 choice = repo.ui.promptchoice(
3780 3788 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3781 3789 )
3782 3790 if choice == 0:
3783 3791 prntstatusmsg(b'undelete', f)
3784 3792 checkout(f)
3785 3793 normal(f)
3786 3794 else:
3787 3795 excluded_files.append(f)
3788 3796 else:
3789 3797 prntstatusmsg(b'undelete', f)
3790 3798 checkout(f)
3791 3799 normal(f)
3792 3800
3793 3801 copied = copies.pathcopies(repo[parent], ctx)
3794 3802
3795 3803 for f in (
3796 3804 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
3797 3805 ):
3798 3806 if f in copied:
3799 3807 repo.dirstate.copy(copied[f], f)
3800 3808
3801 3809
3802 3810 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3803 3811 # commands.outgoing. "missing" is "missing" of the result of
3804 3812 # "findcommonoutgoing()"
3805 3813 outgoinghooks = util.hooks()
3806 3814
3807 3815 # a list of (ui, repo) functions called by commands.summary
3808 3816 summaryhooks = util.hooks()
3809 3817
3810 3818 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3811 3819 #
3812 3820 # functions should return tuple of booleans below, if 'changes' is None:
3813 3821 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3814 3822 #
3815 3823 # otherwise, 'changes' is a tuple of tuples below:
3816 3824 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3817 3825 # - (desturl, destbranch, destpeer, outgoing)
3818 3826 summaryremotehooks = util.hooks()
3819 3827
3820 3828
3821 3829 def checkunfinished(repo, commit=False, skipmerge=False):
3822 3830 '''Look for an unfinished multistep operation, like graft, and abort
3823 3831 if found. It's probably good to check this right before
3824 3832 bailifchanged().
3825 3833 '''
3826 3834 # Check for non-clearable states first, so things like rebase will take
3827 3835 # precedence over update.
3828 3836 for state in statemod._unfinishedstates:
3829 3837 if (
3830 3838 state._clearable
3831 3839 or (commit and state._allowcommit)
3832 3840 or state._reportonly
3833 3841 ):
3834 3842 continue
3835 3843 if state.isunfinished(repo):
3836 3844 raise error.Abort(state.msg(), hint=state.hint())
3837 3845
3838 3846 for s in statemod._unfinishedstates:
3839 3847 if (
3840 3848 not s._clearable
3841 3849 or (commit and s._allowcommit)
3842 3850 or (s._opname == b'merge' and skipmerge)
3843 3851 or s._reportonly
3844 3852 ):
3845 3853 continue
3846 3854 if s.isunfinished(repo):
3847 3855 raise error.Abort(s.msg(), hint=s.hint())
3848 3856
3849 3857
3850 3858 def clearunfinished(repo):
3851 3859 '''Check for unfinished operations (as above), and clear the ones
3852 3860 that are clearable.
3853 3861 '''
3854 3862 for state in statemod._unfinishedstates:
3855 3863 if state._reportonly:
3856 3864 continue
3857 3865 if not state._clearable and state.isunfinished(repo):
3858 3866 raise error.Abort(state.msg(), hint=state.hint())
3859 3867
3860 3868 for s in statemod._unfinishedstates:
3861 3869 if s._opname == b'merge' or state._reportonly:
3862 3870 continue
3863 3871 if s._clearable and s.isunfinished(repo):
3864 3872 util.unlink(repo.vfs.join(s._fname))
3865 3873
3866 3874
3867 3875 def getunfinishedstate(repo):
3868 3876 ''' Checks for unfinished operations and returns statecheck object
3869 3877 for it'''
3870 3878 for state in statemod._unfinishedstates:
3871 3879 if state.isunfinished(repo):
3872 3880 return state
3873 3881 return None
3874 3882
3875 3883
3876 3884 def howtocontinue(repo):
3877 3885 '''Check for an unfinished operation and return the command to finish
3878 3886 it.
3879 3887
3880 3888 statemod._unfinishedstates list is checked for an unfinished operation
3881 3889 and the corresponding message to finish it is generated if a method to
3882 3890 continue is supported by the operation.
3883 3891
3884 3892 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3885 3893 a boolean.
3886 3894 '''
3887 3895 contmsg = _(b"continue: %s")
3888 3896 for state in statemod._unfinishedstates:
3889 3897 if not state._continueflag:
3890 3898 continue
3891 3899 if state.isunfinished(repo):
3892 3900 return contmsg % state.continuemsg(), True
3893 3901 if repo[None].dirty(missing=True, merge=False, branch=False):
3894 3902 return contmsg % _(b"hg commit"), False
3895 3903 return None, None
3896 3904
3897 3905
3898 3906 def checkafterresolved(repo):
3899 3907 '''Inform the user about the next action after completing hg resolve
3900 3908
3901 3909 If there's a an unfinished operation that supports continue flag,
3902 3910 howtocontinue will yield repo.ui.warn as the reporter.
3903 3911
3904 3912 Otherwise, it will yield repo.ui.note.
3905 3913 '''
3906 3914 msg, warning = howtocontinue(repo)
3907 3915 if msg is not None:
3908 3916 if warning:
3909 3917 repo.ui.warn(b"%s\n" % msg)
3910 3918 else:
3911 3919 repo.ui.note(b"%s\n" % msg)
3912 3920
3913 3921
3914 3922 def wrongtooltocontinue(repo, task):
3915 3923 '''Raise an abort suggesting how to properly continue if there is an
3916 3924 active task.
3917 3925
3918 3926 Uses howtocontinue() to find the active task.
3919 3927
3920 3928 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3921 3929 a hint.
3922 3930 '''
3923 3931 after = howtocontinue(repo)
3924 3932 hint = None
3925 3933 if after[1]:
3926 3934 hint = after[0]
3927 3935 raise error.Abort(_(b'no %s in progress') % task, hint=hint)
3928 3936
3929 3937
3930 3938 def abortgraft(ui, repo, graftstate):
3931 3939 """abort the interrupted graft and rollbacks to the state before interrupted
3932 3940 graft"""
3933 3941 if not graftstate.exists():
3934 3942 raise error.Abort(_(b"no interrupted graft to abort"))
3935 3943 statedata = readgraftstate(repo, graftstate)
3936 3944 newnodes = statedata.get(b'newnodes')
3937 3945 if newnodes is None:
3938 3946 # and old graft state which does not have all the data required to abort
3939 3947 # the graft
3940 3948 raise error.Abort(_(b"cannot abort using an old graftstate"))
3941 3949
3942 3950 # changeset from which graft operation was started
3943 3951 if len(newnodes) > 0:
3944 3952 startctx = repo[newnodes[0]].p1()
3945 3953 else:
3946 3954 startctx = repo[b'.']
3947 3955 # whether to strip or not
3948 3956 cleanup = False
3949 3957 from . import hg
3950 3958
3951 3959 if newnodes:
3952 3960 newnodes = [repo[r].rev() for r in newnodes]
3953 3961 cleanup = True
3954 3962 # checking that none of the newnodes turned public or is public
3955 3963 immutable = [c for c in newnodes if not repo[c].mutable()]
3956 3964 if immutable:
3957 3965 repo.ui.warn(
3958 3966 _(b"cannot clean up public changesets %s\n")
3959 3967 % b', '.join(bytes(repo[r]) for r in immutable),
3960 3968 hint=_(b"see 'hg help phases' for details"),
3961 3969 )
3962 3970 cleanup = False
3963 3971
3964 3972 # checking that no new nodes are created on top of grafted revs
3965 3973 desc = set(repo.changelog.descendants(newnodes))
3966 3974 if desc - set(newnodes):
3967 3975 repo.ui.warn(
3968 3976 _(
3969 3977 b"new changesets detected on destination "
3970 3978 b"branch, can't strip\n"
3971 3979 )
3972 3980 )
3973 3981 cleanup = False
3974 3982
3975 3983 if cleanup:
3976 3984 with repo.wlock(), repo.lock():
3977 3985 hg.updaterepo(repo, startctx.node(), overwrite=True)
3978 3986 # stripping the new nodes created
3979 3987 strippoints = [
3980 3988 c.node() for c in repo.set(b"roots(%ld)", newnodes)
3981 3989 ]
3982 3990 repair.strip(repo.ui, repo, strippoints, backup=False)
3983 3991
3984 3992 if not cleanup:
3985 3993 # we don't update to the startnode if we can't strip
3986 3994 startctx = repo[b'.']
3987 3995 hg.updaterepo(repo, startctx.node(), overwrite=True)
3988 3996
3989 3997 ui.status(_(b"graft aborted\n"))
3990 3998 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
3991 3999 graftstate.delete()
3992 4000 return 0
3993 4001
3994 4002
3995 4003 def readgraftstate(repo, graftstate):
3996 4004 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
3997 4005 """read the graft state file and return a dict of the data stored in it"""
3998 4006 try:
3999 4007 return graftstate.read()
4000 4008 except error.CorruptedState:
4001 4009 nodes = repo.vfs.read(b'graftstate').splitlines()
4002 4010 return {b'nodes': nodes}
4003 4011
4004 4012
4005 4013 def hgabortgraft(ui, repo):
4006 4014 """ abort logic for aborting graft using 'hg abort'"""
4007 4015 with repo.wlock():
4008 4016 graftstate = statemod.cmdstate(repo, b'graftstate')
4009 4017 return abortgraft(ui, repo, graftstate)
General Comments 0
You need to be logged in to leave comments. Login now