Mercurial > public > mercurial-scm > hg
comparison mercurial/cmdutil.py @ 43076:2372284d9457
formatting: blacken the codebase
This is using my patch to black
(https://github.com/psf/black/pull/826) so we don't un-wrap collection
literals.
Done with:
hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S
# skip-blame mass-reformatting only
# no-check-commit reformats foo_bar functions
Differential Revision: https://phab.mercurial-scm.org/D6971
author | Augie Fackler <augie@google.com> |
---|---|
date | Sun, 06 Oct 2019 09:45:02 -0400 |
parents | 7e9997041781 |
children | 687b865b95ad |
comparison
equal
deleted
inserted
replaced
43075:57875cf423c9 | 43076:2372284d9457 |
---|---|
59 stringio = util.stringio | 59 stringio = util.stringio |
60 | 60 |
61 # templates of common command options | 61 # templates of common command options |
62 | 62 |
63 dryrunopts = [ | 63 dryrunopts = [ |
64 ('n', 'dry-run', None, | 64 ('n', 'dry-run', None, _('do not perform actions, just print output')), |
65 _('do not perform actions, just print output')), | |
66 ] | 65 ] |
67 | 66 |
68 confirmopts = [ | 67 confirmopts = [ |
69 ('', 'confirm', None, | 68 ('', 'confirm', None, _('ask before applying actions')), |
70 _('ask before applying actions')), | |
71 ] | 69 ] |
72 | 70 |
73 remoteopts = [ | 71 remoteopts = [ |
74 ('e', 'ssh', '', | 72 ('e', 'ssh', '', _('specify ssh command to use'), _('CMD')), |
75 _('specify ssh command to use'), _('CMD')), | 73 ( |
76 ('', 'remotecmd', '', | 74 '', |
77 _('specify hg command to run on the remote side'), _('CMD')), | 75 'remotecmd', |
78 ('', 'insecure', None, | 76 '', |
79 _('do not verify server certificate (ignoring web.cacerts config)')), | 77 _('specify hg command to run on the remote side'), |
78 _('CMD'), | |
79 ), | |
80 ( | |
81 '', | |
82 'insecure', | |
83 None, | |
84 _('do not verify server certificate (ignoring web.cacerts config)'), | |
85 ), | |
80 ] | 86 ] |
81 | 87 |
82 walkopts = [ | 88 walkopts = [ |
83 ('I', 'include', [], | 89 ( |
84 _('include names matching the given patterns'), _('PATTERN')), | 90 'I', |
85 ('X', 'exclude', [], | 91 'include', |
86 _('exclude names matching the given patterns'), _('PATTERN')), | 92 [], |
93 _('include names matching the given patterns'), | |
94 _('PATTERN'), | |
95 ), | |
96 ( | |
97 'X', | |
98 'exclude', | |
99 [], | |
100 _('exclude names matching the given patterns'), | |
101 _('PATTERN'), | |
102 ), | |
87 ] | 103 ] |
88 | 104 |
89 commitopts = [ | 105 commitopts = [ |
90 ('m', 'message', '', | 106 ('m', 'message', '', _('use text as commit message'), _('TEXT')), |
91 _('use text as commit message'), _('TEXT')), | 107 ('l', 'logfile', '', _('read commit message from file'), _('FILE')), |
92 ('l', 'logfile', '', | |
93 _('read commit message from file'), _('FILE')), | |
94 ] | 108 ] |
95 | 109 |
96 commitopts2 = [ | 110 commitopts2 = [ |
97 ('d', 'date', '', | 111 ('d', 'date', '', _('record the specified date as commit date'), _('DATE')), |
98 _('record the specified date as commit date'), _('DATE')), | 112 ('u', 'user', '', _('record the specified user as committer'), _('USER')), |
99 ('u', 'user', '', | |
100 _('record the specified user as committer'), _('USER')), | |
101 ] | 113 ] |
102 | 114 |
103 commitopts3 = [ | 115 commitopts3 = [ |
104 (b'D', b'currentdate', None, | 116 (b'D', b'currentdate', None, _(b'record the current date as commit date')), |
105 _(b'record the current date as commit date')), | 117 (b'U', b'currentuser', None, _(b'record the current user as committer')), |
106 (b'U', b'currentuser', None, | |
107 _(b'record the current user as committer')), | |
108 ] | 118 ] |
109 | 119 |
110 formatteropts = [ | 120 formatteropts = [ |
111 ('T', 'template', '', | 121 ('T', 'template', '', _('display with template'), _('TEMPLATE')), |
112 _('display with template'), _('TEMPLATE')), | |
113 ] | 122 ] |
114 | 123 |
115 templateopts = [ | 124 templateopts = [ |
116 ('', 'style', '', | 125 ( |
117 _('display using template map file (DEPRECATED)'), _('STYLE')), | 126 '', |
118 ('T', 'template', '', | 127 'style', |
119 _('display with template'), _('TEMPLATE')), | 128 '', |
129 _('display using template map file (DEPRECATED)'), | |
130 _('STYLE'), | |
131 ), | |
132 ('T', 'template', '', _('display with template'), _('TEMPLATE')), | |
120 ] | 133 ] |
121 | 134 |
122 logopts = [ | 135 logopts = [ |
123 ('p', 'patch', None, _('show patch')), | 136 ('p', 'patch', None, _('show patch')), |
124 ('g', 'git', None, _('use git extended diff format')), | 137 ('g', 'git', None, _('use git extended diff format')), |
125 ('l', 'limit', '', | 138 ('l', 'limit', '', _('limit number of changes displayed'), _('NUM')), |
126 _('limit number of changes displayed'), _('NUM')), | |
127 ('M', 'no-merges', None, _('do not show merges')), | 139 ('M', 'no-merges', None, _('do not show merges')), |
128 ('', 'stat', None, _('output diffstat-style summary of changes')), | 140 ('', 'stat', None, _('output diffstat-style summary of changes')), |
129 ('G', 'graph', None, _("show the revision DAG")), | 141 ('G', 'graph', None, _("show the revision DAG")), |
130 ] + templateopts | 142 ] + templateopts |
131 | 143 |
132 diffopts = [ | 144 diffopts = [ |
133 ('a', 'text', None, _('treat all files as text')), | 145 ('a', 'text', None, _('treat all files as text')), |
134 ('g', 'git', None, _('use git extended diff format')), | 146 ('g', 'git', None, _('use git extended diff format')), |
135 ('', 'binary', None, _('generate binary diffs in git mode (default)')), | 147 ('', 'binary', None, _('generate binary diffs in git mode (default)')), |
136 ('', 'nodates', None, _('omit dates from diff headers')) | 148 ('', 'nodates', None, _('omit dates from diff headers')), |
137 ] | 149 ] |
138 | 150 |
139 diffwsopts = [ | 151 diffwsopts = [ |
140 ('w', 'ignore-all-space', None, | 152 ( |
141 _('ignore white space when comparing lines')), | 153 'w', |
142 ('b', 'ignore-space-change', None, | 154 'ignore-all-space', |
143 _('ignore changes in the amount of white space')), | 155 None, |
144 ('B', 'ignore-blank-lines', None, | 156 _('ignore white space when comparing lines'), |
145 _('ignore changes whose lines are all blank')), | 157 ), |
146 ('Z', 'ignore-space-at-eol', None, | 158 ( |
147 _('ignore changes in whitespace at EOL')), | 159 'b', |
160 'ignore-space-change', | |
161 None, | |
162 _('ignore changes in the amount of white space'), | |
163 ), | |
164 ( | |
165 'B', | |
166 'ignore-blank-lines', | |
167 None, | |
168 _('ignore changes whose lines are all blank'), | |
169 ), | |
170 ( | |
171 'Z', | |
172 'ignore-space-at-eol', | |
173 None, | |
174 _('ignore changes in whitespace at EOL'), | |
175 ), | |
148 ] | 176 ] |
149 | 177 |
150 diffopts2 = [ | 178 diffopts2 = ( |
151 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')), | 179 [ |
152 ('p', 'show-function', None, _('show which function each change is in')), | 180 ('', 'noprefix', None, _('omit a/ and b/ prefixes from filenames')), |
153 ('', 'reverse', None, _('produce a diff that undoes the changes')), | 181 ( |
154 ] + diffwsopts + [ | 182 'p', |
155 ('U', 'unified', '', | 183 'show-function', |
156 _('number of lines of context to show'), _('NUM')), | 184 None, |
157 ('', 'stat', None, _('output diffstat-style summary of changes')), | 185 _('show which function each change is in'), |
158 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')), | 186 ), |
159 ] | 187 ('', 'reverse', None, _('produce a diff that undoes the changes')), |
188 ] | |
189 + diffwsopts | |
190 + [ | |
191 ('U', 'unified', '', _('number of lines of context to show'), _('NUM')), | |
192 ('', 'stat', None, _('output diffstat-style summary of changes')), | |
193 ('', 'root', '', _('produce diffs relative to subdirectory'), _('DIR')), | |
194 ] | |
195 ) | |
160 | 196 |
161 mergetoolopts = [ | 197 mergetoolopts = [ |
162 ('t', 'tool', '', _('specify merge tool'), _('TOOL')), | 198 ('t', 'tool', '', _('specify merge tool'), _('TOOL')), |
163 ] | 199 ] |
164 | 200 |
165 similarityopts = [ | 201 similarityopts = [ |
166 ('s', 'similarity', '', | 202 ( |
167 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY')) | 203 's', |
204 'similarity', | |
205 '', | |
206 _('guess renamed files by similarity (0<=s<=100)'), | |
207 _('SIMILARITY'), | |
208 ) | |
168 ] | 209 ] |
169 | 210 |
170 subrepoopts = [ | 211 subrepoopts = [('S', 'subrepos', None, _('recurse into subrepositories'))] |
171 ('S', 'subrepos', None, | |
172 _('recurse into subrepositories')) | |
173 ] | |
174 | 212 |
175 debugrevlogopts = [ | 213 debugrevlogopts = [ |
176 ('c', 'changelog', False, _('open changelog')), | 214 ('c', 'changelog', False, _('open changelog')), |
177 ('m', 'manifest', False, _('open manifest')), | 215 ('m', 'manifest', False, _('open manifest')), |
178 ('', 'dir', '', _('open directory manifest')), | 216 ('', 'dir', '', _('open directory manifest')), |
180 | 218 |
181 # special string such that everything below this line will be ingored in the | 219 # special string such that everything below this line will be ingored in the |
182 # editor text | 220 # editor text |
183 _linebelow = "^HG: ------------------------ >8 ------------------------$" | 221 _linebelow = "^HG: ------------------------ >8 ------------------------$" |
184 | 222 |
223 | |
185 def resolvecommitoptions(ui, opts): | 224 def resolvecommitoptions(ui, opts): |
186 """modify commit options dict to handle related options | 225 """modify commit options dict to handle related options |
187 | 226 |
188 The return value indicates that ``rewrite.update-timestamp`` is the reason | 227 The return value indicates that ``rewrite.update-timestamp`` is the reason |
189 the ``date`` option is set. | 228 the ``date`` option is set. |
190 """ | 229 """ |
191 if opts.get('date') and opts.get('currentdate'): | 230 if opts.get('date') and opts.get('currentdate'): |
192 raise error.Abort(_('--date and --currentdate are mutually ' | 231 raise error.Abort( |
193 'exclusive')) | 232 _('--date and --currentdate are mutually ' 'exclusive') |
233 ) | |
194 if opts.get(b'user') and opts.get(b'currentuser'): | 234 if opts.get(b'user') and opts.get(b'currentuser'): |
195 raise error.Abort(_('--user and --currentuser are mutually ' | 235 raise error.Abort( |
196 'exclusive')) | 236 _('--user and --currentuser are mutually ' 'exclusive') |
237 ) | |
197 | 238 |
198 datemaydiffer = False # date-only change should be ignored? | 239 datemaydiffer = False # date-only change should be ignored? |
199 | 240 |
200 if opts.get(b'currentdate'): | 241 if opts.get(b'currentdate'): |
201 opts[b'date'] = b'%d %d' % dateutil.makedate() | 242 opts[b'date'] = b'%d %d' % dateutil.makedate() |
202 elif (not opts.get('date') | 243 elif ( |
203 and ui.configbool('rewrite', 'update-timestamp') | 244 not opts.get('date') |
204 and opts.get('currentdate') is None): | 245 and ui.configbool('rewrite', 'update-timestamp') |
246 and opts.get('currentdate') is None | |
247 ): | |
205 opts[b'date'] = b'%d %d' % dateutil.makedate() | 248 opts[b'date'] = b'%d %d' % dateutil.makedate() |
206 datemaydiffer = True | 249 datemaydiffer = True |
207 | 250 |
208 if opts.get(b'currentuser'): | 251 if opts.get(b'currentuser'): |
209 opts[b'user'] = ui.username() | 252 opts[b'user'] = ui.username() |
210 | 253 |
211 return datemaydiffer | 254 return datemaydiffer |
255 | |
212 | 256 |
213 def checknotesize(ui, opts): | 257 def checknotesize(ui, opts): |
214 """ make sure note is of valid format """ | 258 """ make sure note is of valid format """ |
215 | 259 |
216 note = opts.get('note') | 260 note = opts.get('note') |
220 if len(note) > 255: | 264 if len(note) > 255: |
221 raise error.Abort(_(b"cannot store a note of more than 255 bytes")) | 265 raise error.Abort(_(b"cannot store a note of more than 255 bytes")) |
222 if b'\n' in note: | 266 if b'\n' in note: |
223 raise error.Abort(_(b"note cannot contain a newline")) | 267 raise error.Abort(_(b"note cannot contain a newline")) |
224 | 268 |
269 | |
225 def ishunk(x): | 270 def ishunk(x): |
226 hunkclasses = (crecordmod.uihunk, patch.recordhunk) | 271 hunkclasses = (crecordmod.uihunk, patch.recordhunk) |
227 return isinstance(x, hunkclasses) | 272 return isinstance(x, hunkclasses) |
273 | |
228 | 274 |
229 def newandmodified(chunks, originalchunks): | 275 def newandmodified(chunks, originalchunks): |
230 newlyaddedandmodifiedfiles = set() | 276 newlyaddedandmodifiedfiles = set() |
231 alsorestore = set() | 277 alsorestore = set() |
232 for chunk in chunks: | 278 for chunk in chunks: |
233 if (ishunk(chunk) and chunk.header.isnewfile() and chunk not in | 279 if ( |
234 originalchunks): | 280 ishunk(chunk) |
281 and chunk.header.isnewfile() | |
282 and chunk not in originalchunks | |
283 ): | |
235 newlyaddedandmodifiedfiles.add(chunk.header.filename()) | 284 newlyaddedandmodifiedfiles.add(chunk.header.filename()) |
236 alsorestore.update(set(chunk.header.files()) - | 285 alsorestore.update( |
237 {chunk.header.filename()}) | 286 set(chunk.header.files()) - {chunk.header.filename()} |
287 ) | |
238 return newlyaddedandmodifiedfiles, alsorestore | 288 return newlyaddedandmodifiedfiles, alsorestore |
289 | |
239 | 290 |
240 def parsealiases(cmd): | 291 def parsealiases(cmd): |
241 return cmd.split("|") | 292 return cmd.split("|") |
293 | |
242 | 294 |
243 def setupwrapcolorwrite(ui): | 295 def setupwrapcolorwrite(ui): |
244 # wrap ui.write so diff output can be labeled/colorized | 296 # wrap ui.write so diff output can be labeled/colorized |
245 def wrapwrite(orig, *args, **kw): | 297 def wrapwrite(orig, *args, **kw): |
246 label = kw.pop(r'label', '') | 298 label = kw.pop(r'label', '') |
247 for chunk, l in patch.difflabel(lambda: args): | 299 for chunk, l in patch.difflabel(lambda: args): |
248 orig(chunk, label=label + l) | 300 orig(chunk, label=label + l) |
249 | 301 |
250 oldwrite = ui.write | 302 oldwrite = ui.write |
303 | |
251 def wrap(*args, **kwargs): | 304 def wrap(*args, **kwargs): |
252 return wrapwrite(oldwrite, *args, **kwargs) | 305 return wrapwrite(oldwrite, *args, **kwargs) |
306 | |
253 setattr(ui, 'write', wrap) | 307 setattr(ui, 'write', wrap) |
254 return oldwrite | 308 return oldwrite |
255 | 309 |
256 def filterchunks(ui, originalhunks, usecurses, testfile, match, | 310 |
257 operation=None): | 311 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None): |
258 try: | 312 try: |
259 if usecurses: | 313 if usecurses: |
260 if testfile: | 314 if testfile: |
261 recordfn = crecordmod.testdecorator( | 315 recordfn = crecordmod.testdecorator( |
262 testfile, crecordmod.testchunkselector) | 316 testfile, crecordmod.testchunkselector |
317 ) | |
263 else: | 318 else: |
264 recordfn = crecordmod.chunkselector | 319 recordfn = crecordmod.chunkselector |
265 | 320 |
266 return crecordmod.filterpatch(ui, originalhunks, recordfn, | 321 return crecordmod.filterpatch( |
267 operation) | 322 ui, originalhunks, recordfn, operation |
323 ) | |
268 except crecordmod.fallbackerror as e: | 324 except crecordmod.fallbackerror as e: |
269 ui.warn('%s\n' % e.message) | 325 ui.warn('%s\n' % e.message) |
270 ui.warn(_('falling back to text mode\n')) | 326 ui.warn(_('falling back to text mode\n')) |
271 | 327 |
272 return patch.filterpatch(ui, originalhunks, match, operation) | 328 return patch.filterpatch(ui, originalhunks, match, operation) |
329 | |
273 | 330 |
274 def recordfilter(ui, originalhunks, match, operation=None): | 331 def recordfilter(ui, originalhunks, match, operation=None): |
275 """ Prompts the user to filter the originalhunks and return a list of | 332 """ Prompts the user to filter the originalhunks and return a list of |
276 selected hunks. | 333 selected hunks. |
277 *operation* is used for to build ui messages to indicate the user what | 334 *operation* is used for to build ui messages to indicate the user what |
280 """ | 337 """ |
281 usecurses = crecordmod.checkcurses(ui) | 338 usecurses = crecordmod.checkcurses(ui) |
282 testfile = ui.config('experimental', 'crecordtest') | 339 testfile = ui.config('experimental', 'crecordtest') |
283 oldwrite = setupwrapcolorwrite(ui) | 340 oldwrite = setupwrapcolorwrite(ui) |
284 try: | 341 try: |
285 newchunks, newopts = filterchunks(ui, originalhunks, usecurses, | 342 newchunks, newopts = filterchunks( |
286 testfile, match, operation) | 343 ui, originalhunks, usecurses, testfile, match, operation |
344 ) | |
287 finally: | 345 finally: |
288 ui.write = oldwrite | 346 ui.write = oldwrite |
289 return newchunks, newopts | 347 return newchunks, newopts |
290 | 348 |
291 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, | 349 |
292 filterfn, *pats, **opts): | 350 def dorecord( |
351 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts | |
352 ): | |
293 opts = pycompat.byteskwargs(opts) | 353 opts = pycompat.byteskwargs(opts) |
294 if not ui.interactive(): | 354 if not ui.interactive(): |
295 if cmdsuggest: | 355 if cmdsuggest: |
296 msg = _('running non-interactively, use %s instead') % cmdsuggest | 356 msg = _('running non-interactively, use %s instead') % cmdsuggest |
297 else: | 357 else: |
298 msg = _('running non-interactively') | 358 msg = _('running non-interactively') |
299 raise error.Abort(msg) | 359 raise error.Abort(msg) |
300 | 360 |
301 # make sure username is set before going interactive | 361 # make sure username is set before going interactive |
302 if not opts.get('user'): | 362 if not opts.get('user'): |
303 ui.username() # raise exception, username not provided | 363 ui.username() # raise exception, username not provided |
304 | 364 |
305 def recordfunc(ui, repo, message, match, opts): | 365 def recordfunc(ui, repo, message, match, opts): |
306 """This is generic record driver. | 366 """This is generic record driver. |
307 | 367 |
308 Its job is to interactively filter local changes, and | 368 Its job is to interactively filter local changes, and |
319 if not opts.get('interactive-unshelve'): | 379 if not opts.get('interactive-unshelve'): |
320 checkunfinished(repo, commit=True) | 380 checkunfinished(repo, commit=True) |
321 wctx = repo[None] | 381 wctx = repo[None] |
322 merge = len(wctx.parents()) > 1 | 382 merge = len(wctx.parents()) > 1 |
323 if merge: | 383 if merge: |
324 raise error.Abort(_('cannot partially commit a merge ' | 384 raise error.Abort( |
325 '(use "hg commit" instead)')) | 385 _( |
386 'cannot partially commit a merge ' | |
387 '(use "hg commit" instead)' | |
388 ) | |
389 ) | |
326 | 390 |
327 def fail(f, msg): | 391 def fail(f, msg): |
328 raise error.Abort('%s: %s' % (f, msg)) | 392 raise error.Abort('%s: %s' % (f, msg)) |
329 | 393 |
330 force = opts.get('force') | 394 force = opts.get('force') |
337 | 401 |
338 overrides = {(b'ui', b'commitsubrepos'): True} | 402 overrides = {(b'ui', b'commitsubrepos'): True} |
339 | 403 |
340 with repo.ui.configoverride(overrides, b'record'): | 404 with repo.ui.configoverride(overrides, b'record'): |
341 # subrepoutil.precommit() modifies the status | 405 # subrepoutil.precommit() modifies the status |
342 tmpstatus = scmutil.status(copymod.copy(status[0]), | 406 tmpstatus = scmutil.status( |
343 copymod.copy(status[1]), | 407 copymod.copy(status[0]), |
344 copymod.copy(status[2]), | 408 copymod.copy(status[1]), |
345 copymod.copy(status[3]), | 409 copymod.copy(status[2]), |
346 copymod.copy(status[4]), | 410 copymod.copy(status[3]), |
347 copymod.copy(status[5]), | 411 copymod.copy(status[4]), |
348 copymod.copy(status[6])) | 412 copymod.copy(status[5]), |
413 copymod.copy(status[6]), | |
414 ) | |
349 | 415 |
350 # Force allows -X subrepo to skip the subrepo. | 416 # Force allows -X subrepo to skip the subrepo. |
351 subs, commitsubs, newstate = subrepoutil.precommit( | 417 subs, commitsubs, newstate = subrepoutil.precommit( |
352 repo.ui, wctx, tmpstatus, match, force=True) | 418 repo.ui, wctx, tmpstatus, match, force=True |
419 ) | |
353 for s in subs: | 420 for s in subs: |
354 if s in commitsubs: | 421 if s in commitsubs: |
355 dirtyreason = wctx.sub(s).dirtyreason(True) | 422 dirtyreason = wctx.sub(s).dirtyreason(True) |
356 raise error.Abort(dirtyreason) | 423 raise error.Abort(dirtyreason) |
357 | 424 |
358 if not force: | 425 if not force: |
359 repo.checkcommitpatterns(wctx, vdirs, match, status, fail) | 426 repo.checkcommitpatterns(wctx, vdirs, match, status, fail) |
360 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True, | 427 diffopts = patch.difffeatureopts( |
361 section='commands', | 428 ui, |
362 configprefix='commit.interactive.') | 429 opts=opts, |
430 whitespace=True, | |
431 section='commands', | |
432 configprefix='commit.interactive.', | |
433 ) | |
363 diffopts.nodates = True | 434 diffopts.nodates = True |
364 diffopts.git = True | 435 diffopts.git = True |
365 diffopts.showfunc = True | 436 diffopts.showfunc = True |
366 originaldiff = patch.diff(repo, changes=status, opts=diffopts) | 437 originaldiff = patch.diff(repo, changes=status, opts=diffopts) |
367 originalchunks = patch.parsepatch(originaldiff) | 438 originalchunks = patch.parsepatch(originaldiff) |
377 # We need to keep a backup of files that have been newly added and | 448 # We need to keep a backup of files that have been newly added and |
378 # modified during the recording process because there is a previous | 449 # modified during the recording process because there is a previous |
379 # version without the edit in the workdir. We also will need to restore | 450 # version without the edit in the workdir. We also will need to restore |
380 # files that were the sources of renames so that the patch application | 451 # files that were the sources of renames so that the patch application |
381 # works. | 452 # works. |
382 newlyaddedandmodifiedfiles, alsorestore = newandmodified(chunks, | 453 newlyaddedandmodifiedfiles, alsorestore = newandmodified( |
383 originalchunks) | 454 chunks, originalchunks |
455 ) | |
384 contenders = set() | 456 contenders = set() |
385 for h in chunks: | 457 for h in chunks: |
386 try: | 458 try: |
387 contenders.update(set(h.files())) | 459 contenders.update(set(h.files())) |
388 except AttributeError: | 460 except AttributeError: |
399 # 2. backup changed files, so we can restore them in the end | 471 # 2. backup changed files, so we can restore them in the end |
400 | 472 |
401 if backupall: | 473 if backupall: |
402 tobackup = changed | 474 tobackup = changed |
403 else: | 475 else: |
404 tobackup = [f for f in newfiles if f in modified or f in | 476 tobackup = [ |
405 newlyaddedandmodifiedfiles] | 477 f |
478 for f in newfiles | |
479 if f in modified or f in newlyaddedandmodifiedfiles | |
480 ] | |
406 backups = {} | 481 backups = {} |
407 if tobackup: | 482 if tobackup: |
408 backupdir = repo.vfs.join('record-backups') | 483 backupdir = repo.vfs.join('record-backups') |
409 try: | 484 try: |
410 os.mkdir(backupdir) | 485 os.mkdir(backupdir) |
412 if err.errno != errno.EEXIST: | 487 if err.errno != errno.EEXIST: |
413 raise | 488 raise |
414 try: | 489 try: |
415 # backup continues | 490 # backup continues |
416 for f in tobackup: | 491 for f in tobackup: |
417 fd, tmpname = pycompat.mkstemp(prefix=f.replace('/', '_') + '.', | 492 fd, tmpname = pycompat.mkstemp( |
418 dir=backupdir) | 493 prefix=f.replace('/', '_') + '.', dir=backupdir |
494 ) | |
419 os.close(fd) | 495 os.close(fd) |
420 ui.debug('backup %r as %r\n' % (f, tmpname)) | 496 ui.debug('backup %r as %r\n' % (f, tmpname)) |
421 util.copyfile(repo.wjoin(f), tmpname, copystat=True) | 497 util.copyfile(repo.wjoin(f), tmpname, copystat=True) |
422 backups[f] = tmpname | 498 backups[f] = tmpname |
423 | 499 |
429 dopatch = fp.tell() | 505 dopatch = fp.tell() |
430 fp.seek(0) | 506 fp.seek(0) |
431 | 507 |
432 # 2.5 optionally review / modify patch in text editor | 508 # 2.5 optionally review / modify patch in text editor |
433 if opts.get('review', False): | 509 if opts.get('review', False): |
434 patchtext = (crecordmod.diffhelptext | 510 patchtext = ( |
435 + crecordmod.patchhelptext | 511 crecordmod.diffhelptext |
436 + fp.read()) | 512 + crecordmod.patchhelptext |
437 reviewedpatch = ui.edit(patchtext, "", | 513 + fp.read() |
438 action="diff", | 514 ) |
439 repopath=repo.path) | 515 reviewedpatch = ui.edit( |
516 patchtext, "", action="diff", repopath=repo.path | |
517 ) | |
440 fp.truncate(0) | 518 fp.truncate(0) |
441 fp.write(reviewedpatch) | 519 fp.write(reviewedpatch) |
442 fp.seek(0) | 520 fp.seek(0) |
443 | 521 |
444 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles] | 522 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles] |
445 # 3a. apply filtered patch to clean repo (clean) | 523 # 3a. apply filtered patch to clean repo (clean) |
446 if backups: | 524 if backups: |
447 # Equivalent to hg.revert | 525 # Equivalent to hg.revert |
448 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore) | 526 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore) |
449 mergemod.update(repo, repo.dirstate.p1(), branchmerge=False, | 527 mergemod.update( |
450 force=True, matcher=m) | 528 repo, |
529 repo.dirstate.p1(), | |
530 branchmerge=False, | |
531 force=True, | |
532 matcher=m, | |
533 ) | |
451 | 534 |
452 # 3b. (apply) | 535 # 3b. (apply) |
453 if dopatch: | 536 if dopatch: |
454 try: | 537 try: |
455 ui.debug('applying patch\n') | 538 ui.debug('applying patch\n') |
495 with repo.wlock(): | 578 with repo.wlock(): |
496 return recordfunc(ui, repo, message, match, opts) | 579 return recordfunc(ui, repo, message, match, opts) |
497 | 580 |
498 return commit(ui, repo, recordinwlock, pats, opts) | 581 return commit(ui, repo, recordinwlock, pats, opts) |
499 | 582 |
583 | |
500 class dirnode(object): | 584 class dirnode(object): |
501 """ | 585 """ |
502 Represent a directory in user working copy with information required for | 586 Represent a directory in user working copy with information required for |
503 the purpose of tersing its status. | 587 the purpose of tersing its status. |
504 | 588 |
590 | 674 |
591 # add the files to status list | 675 # add the files to status list |
592 for st, fpath in self.iterfilepaths(): | 676 for st, fpath in self.iterfilepaths(): |
593 yield st, fpath | 677 yield st, fpath |
594 | 678 |
595 #recurse on the subdirs | 679 # recurse on the subdirs |
596 for dirobj in self.subdirs.values(): | 680 for dirobj in self.subdirs.values(): |
597 for st, fpath in dirobj.tersewalk(terseargs): | 681 for st, fpath in dirobj.tersewalk(terseargs): |
598 yield st, fpath | 682 yield st, fpath |
683 | |
599 | 684 |
600 def tersedir(statuslist, terseargs): | 685 def tersedir(statuslist, terseargs): |
601 """ | 686 """ |
602 Terse the status if all the files in a directory shares the same status. | 687 Terse the status if all the files in a directory shares the same status. |
603 | 688 |
618 if s not in allst: | 703 if s not in allst: |
619 raise error.Abort(_("'%s' not recognized") % s) | 704 raise error.Abort(_("'%s' not recognized") % s) |
620 | 705 |
621 # creating a dirnode object for the root of the repo | 706 # creating a dirnode object for the root of the repo |
622 rootobj = dirnode('') | 707 rootobj = dirnode('') |
623 pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown', | 708 pstatus = ( |
624 'ignored', 'removed') | 709 'modified', |
710 'added', | |
711 'deleted', | |
712 'clean', | |
713 'unknown', | |
714 'ignored', | |
715 'removed', | |
716 ) | |
625 | 717 |
626 tersedict = {} | 718 tersedict = {} |
627 for attrname in pstatus: | 719 for attrname in pstatus: |
628 statuschar = attrname[0:1] | 720 statuschar = attrname[0:1] |
629 for f in getattr(statuslist, attrname): | 721 for f in getattr(statuslist, attrname): |
644 tersedict[st].sort() | 736 tersedict[st].sort() |
645 tersedlist.append(tersedict[st]) | 737 tersedlist.append(tersedict[st]) |
646 | 738 |
647 return tersedlist | 739 return tersedlist |
648 | 740 |
741 | |
649 def _commentlines(raw): | 742 def _commentlines(raw): |
650 '''Surround lineswith a comment char and a new line''' | 743 '''Surround lineswith a comment char and a new line''' |
651 lines = raw.splitlines() | 744 lines = raw.splitlines() |
652 commentedlines = ['# %s' % line for line in lines] | 745 commentedlines = ['# %s' % line for line in lines] |
653 return '\n'.join(commentedlines) + '\n' | 746 return '\n'.join(commentedlines) + '\n' |
654 | 747 |
748 | |
655 def _conflictsmsg(repo): | 749 def _conflictsmsg(repo): |
656 mergestate = mergemod.mergestate.read(repo) | 750 mergestate = mergemod.mergestate.read(repo) |
657 if not mergestate.active(): | 751 if not mergestate.active(): |
658 return | 752 return |
659 | 753 |
660 m = scmutil.match(repo[None]) | 754 m = scmutil.match(repo[None]) |
661 unresolvedlist = [f for f in mergestate.unresolved() if m(f)] | 755 unresolvedlist = [f for f in mergestate.unresolved() if m(f)] |
662 if unresolvedlist: | 756 if unresolvedlist: |
663 mergeliststr = '\n'.join( | 757 mergeliststr = '\n'.join( |
664 [' %s' % util.pathto(repo.root, encoding.getcwd(), path) | 758 [ |
665 for path in sorted(unresolvedlist)]) | 759 ' %s' % util.pathto(repo.root, encoding.getcwd(), path) |
666 msg = _('''Unresolved merge conflicts: | 760 for path in sorted(unresolvedlist) |
761 ] | |
762 ) | |
763 msg = ( | |
764 _( | |
765 '''Unresolved merge conflicts: | |
667 | 766 |
668 %s | 767 %s |
669 | 768 |
670 To mark files as resolved: hg resolve --mark FILE''') % mergeliststr | 769 To mark files as resolved: hg resolve --mark FILE''' |
770 ) | |
771 % mergeliststr | |
772 ) | |
671 else: | 773 else: |
672 msg = _('No unresolved merge conflicts.') | 774 msg = _('No unresolved merge conflicts.') |
673 | 775 |
674 return _commentlines(msg) | 776 return _commentlines(msg) |
777 | |
675 | 778 |
676 def morestatus(repo, fm): | 779 def morestatus(repo, fm): |
677 statetuple = statemod.getrepostate(repo) | 780 statetuple = statemod.getrepostate(repo) |
678 label = 'status.morestatus' | 781 label = 'status.morestatus' |
679 if statetuple: | 782 if statetuple: |
683 conmsg = _conflictsmsg(repo) | 786 conmsg = _conflictsmsg(repo) |
684 if conmsg: | 787 if conmsg: |
685 fm.plain('%s\n' % conmsg, label=label) | 788 fm.plain('%s\n' % conmsg, label=label) |
686 if helpfulmsg: | 789 if helpfulmsg: |
687 fm.plain('%s\n' % _commentlines(helpfulmsg), label=label) | 790 fm.plain('%s\n' % _commentlines(helpfulmsg), label=label) |
791 | |
688 | 792 |
689 def findpossible(cmd, table, strict=False): | 793 def findpossible(cmd, table, strict=False): |
690 """ | 794 """ |
691 Return cmd -> (aliases, command table entry) | 795 Return cmd -> (aliases, command table entry) |
692 for each matching command. | 796 for each matching command. |
722 if not choice and debugchoice: | 826 if not choice and debugchoice: |
723 choice = debugchoice | 827 choice = debugchoice |
724 | 828 |
725 return choice, allcmds | 829 return choice, allcmds |
726 | 830 |
831 | |
727 def findcmd(cmd, table, strict=True): | 832 def findcmd(cmd, table, strict=True): |
728 """Return (aliases, command table entry) for command string.""" | 833 """Return (aliases, command table entry) for command string.""" |
729 choice, allcmds = findpossible(cmd, table, strict) | 834 choice, allcmds = findpossible(cmd, table, strict) |
730 | 835 |
731 if cmd in choice: | 836 if cmd in choice: |
737 | 842 |
738 if choice: | 843 if choice: |
739 return list(choice.values())[0] | 844 return list(choice.values())[0] |
740 | 845 |
741 raise error.UnknownCommand(cmd, allcmds) | 846 raise error.UnknownCommand(cmd, allcmds) |
847 | |
742 | 848 |
743 def changebranch(ui, repo, revs, label): | 849 def changebranch(ui, repo, revs, label): |
744 """ Change the branch name of given revs to label """ | 850 """ Change the branch name of given revs to label """ |
745 | 851 |
746 with repo.wlock(), repo.lock(), repo.transaction('branches'): | 852 with repo.wlock(), repo.lock(), repo.transaction('branches'): |
768 | 874 |
769 replacements = {} | 875 replacements = {} |
770 # avoid import cycle mercurial.cmdutil -> mercurial.context -> | 876 # avoid import cycle mercurial.cmdutil -> mercurial.context -> |
771 # mercurial.subrepo -> mercurial.cmdutil | 877 # mercurial.subrepo -> mercurial.cmdutil |
772 from . import context | 878 from . import context |
879 | |
773 for rev in revs: | 880 for rev in revs: |
774 ctx = repo[rev] | 881 ctx = repo[rev] |
775 oldbranch = ctx.branch() | 882 oldbranch = ctx.branch() |
776 # check if ctx has same branch | 883 # check if ctx has same branch |
777 if oldbranch == label: | 884 if oldbranch == label: |
781 try: | 888 try: |
782 return ctx[path] | 889 return ctx[path] |
783 except error.ManifestLookupError: | 890 except error.ManifestLookupError: |
784 return None | 891 return None |
785 | 892 |
786 ui.debug("changing branch of '%s' from '%s' to '%s'\n" | 893 ui.debug( |
787 % (hex(ctx.node()), oldbranch, label)) | 894 "changing branch of '%s' from '%s' to '%s'\n" |
895 % (hex(ctx.node()), oldbranch, label) | |
896 ) | |
788 extra = ctx.extra() | 897 extra = ctx.extra() |
789 extra['branch_change'] = hex(ctx.node()) | 898 extra['branch_change'] = hex(ctx.node()) |
790 # While changing branch of set of linear commits, make sure that | 899 # While changing branch of set of linear commits, make sure that |
791 # we base our commits on new parent rather than old parent which | 900 # we base our commits on new parent rather than old parent which |
792 # was obsoleted while changing the branch | 901 # was obsoleted while changing the branch |
795 if p1 in replacements: | 904 if p1 in replacements: |
796 p1 = replacements[p1][0] | 905 p1 = replacements[p1][0] |
797 if p2 in replacements: | 906 if p2 in replacements: |
798 p2 = replacements[p2][0] | 907 p2 = replacements[p2][0] |
799 | 908 |
800 mc = context.memctx(repo, (p1, p2), | 909 mc = context.memctx( |
801 ctx.description(), | 910 repo, |
802 ctx.files(), | 911 (p1, p2), |
803 filectxfn, | 912 ctx.description(), |
804 user=ctx.user(), | 913 ctx.files(), |
805 date=ctx.date(), | 914 filectxfn, |
806 extra=extra, | 915 user=ctx.user(), |
807 branch=label) | 916 date=ctx.date(), |
917 extra=extra, | |
918 branch=label, | |
919 ) | |
808 | 920 |
809 newnode = repo.commitctx(mc) | 921 newnode = repo.commitctx(mc) |
810 replacements[ctx.node()] = (newnode,) | 922 replacements[ctx.node()] = (newnode,) |
811 ui.debug('new node id is %s\n' % hex(newnode)) | 923 ui.debug('new node id is %s\n' % hex(newnode)) |
812 | 924 |
820 newid = replacements.get(wctx.p1().node()) | 932 newid = replacements.get(wctx.p1().node()) |
821 if newid is not None: | 933 if newid is not None: |
822 # avoid import cycle mercurial.cmdutil -> mercurial.hg -> | 934 # avoid import cycle mercurial.cmdutil -> mercurial.hg -> |
823 # mercurial.cmdutil | 935 # mercurial.cmdutil |
824 from . import hg | 936 from . import hg |
937 | |
825 hg.update(repo, newid[0], quietempty=True) | 938 hg.update(repo, newid[0], quietempty=True) |
826 | 939 |
827 ui.status(_("changed branch on %d changesets\n") % len(replacements)) | 940 ui.status(_("changed branch on %d changesets\n") % len(replacements)) |
941 | |
828 | 942 |
829 def findrepo(p): | 943 def findrepo(p): |
830 while not os.path.isdir(os.path.join(p, ".hg")): | 944 while not os.path.isdir(os.path.join(p, ".hg")): |
831 oldp, p = p, os.path.dirname(p) | 945 oldp, p = p, os.path.dirname(p) |
832 if p == oldp: | 946 if p == oldp: |
833 return None | 947 return None |
834 | 948 |
835 return p | 949 return p |
950 | |
836 | 951 |
837 def bailifchanged(repo, merge=True, hint=None): | 952 def bailifchanged(repo, merge=True, hint=None): |
838 """ enforce the precondition that working directory must be clean. | 953 """ enforce the precondition that working directory must be clean. |
839 | 954 |
840 'merge' can be set to false if a pending uncommitted merge should be | 955 'merge' can be set to false if a pending uncommitted merge should be |
850 raise error.Abort(_('uncommitted changes'), hint=hint) | 965 raise error.Abort(_('uncommitted changes'), hint=hint) |
851 ctx = repo[None] | 966 ctx = repo[None] |
852 for s in sorted(ctx.substate): | 967 for s in sorted(ctx.substate): |
853 ctx.sub(s).bailifchanged(hint=hint) | 968 ctx.sub(s).bailifchanged(hint=hint) |
854 | 969 |
970 | |
855 def logmessage(ui, opts): | 971 def logmessage(ui, opts): |
856 """ get the log message according to -m and -l option """ | 972 """ get the log message according to -m and -l option """ |
857 message = opts.get('message') | 973 message = opts.get('message') |
858 logfile = opts.get('logfile') | 974 logfile = opts.get('logfile') |
859 | 975 |
860 if message and logfile: | 976 if message and logfile: |
861 raise error.Abort(_('options --message and --logfile are mutually ' | 977 raise error.Abort( |
862 'exclusive')) | 978 _('options --message and --logfile are mutually ' 'exclusive') |
979 ) | |
863 if not message and logfile: | 980 if not message and logfile: |
864 try: | 981 try: |
865 if isstdiofilename(logfile): | 982 if isstdiofilename(logfile): |
866 message = ui.fin.read() | 983 message = ui.fin.read() |
867 else: | 984 else: |
868 message = '\n'.join(util.readfile(logfile).splitlines()) | 985 message = '\n'.join(util.readfile(logfile).splitlines()) |
869 except IOError as inst: | 986 except IOError as inst: |
870 raise error.Abort(_("can't read commit message '%s': %s") % | 987 raise error.Abort( |
871 (logfile, encoding.strtolocal(inst.strerror))) | 988 _("can't read commit message '%s': %s") |
989 % (logfile, encoding.strtolocal(inst.strerror)) | |
990 ) | |
872 return message | 991 return message |
992 | |
873 | 993 |
874 def mergeeditform(ctxorbool, baseformname): | 994 def mergeeditform(ctxorbool, baseformname): |
875 """return appropriate editform name (referencing a committemplate) | 995 """return appropriate editform name (referencing a committemplate) |
876 | 996 |
877 'ctxorbool' is either a ctx to be committed, or a bool indicating whether | 997 'ctxorbool' is either a ctx to be committed, or a bool indicating whether |
886 elif len(ctxorbool.parents()) > 1: | 1006 elif len(ctxorbool.parents()) > 1: |
887 return baseformname + ".merge" | 1007 return baseformname + ".merge" |
888 | 1008 |
889 return baseformname + ".normal" | 1009 return baseformname + ".normal" |
890 | 1010 |
891 def getcommiteditor(edit=False, finishdesc=None, extramsg=None, | 1011 |
892 editform='', **opts): | 1012 def getcommiteditor( |
1013 edit=False, finishdesc=None, extramsg=None, editform='', **opts | |
1014 ): | |
893 """get appropriate commit message editor according to '--edit' option | 1015 """get appropriate commit message editor according to '--edit' option |
894 | 1016 |
895 'finishdesc' is a function to be called with edited commit message | 1017 'finishdesc' is a function to be called with edited commit message |
896 (= 'description' of the new changeset) just after editing, but | 1018 (= 'description' of the new changeset) just after editing, but |
897 before checking empty-ness. It should return actual text to be | 1019 before checking empty-ness. It should return actual text to be |
908 'getcommiteditor' returns 'commitforceeditor' regardless of | 1030 'getcommiteditor' returns 'commitforceeditor' regardless of |
909 'edit', if one of 'finishdesc' or 'extramsg' is specified, because | 1031 'edit', if one of 'finishdesc' or 'extramsg' is specified, because |
910 they are specific for usage in MQ. | 1032 they are specific for usage in MQ. |
911 """ | 1033 """ |
912 if edit or finishdesc or extramsg: | 1034 if edit or finishdesc or extramsg: |
913 return lambda r, c, s: commitforceeditor(r, c, s, | 1035 return lambda r, c, s: commitforceeditor( |
914 finishdesc=finishdesc, | 1036 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform |
915 extramsg=extramsg, | 1037 ) |
916 editform=editform) | |
917 elif editform: | 1038 elif editform: |
918 return lambda r, c, s: commiteditor(r, c, s, editform=editform) | 1039 return lambda r, c, s: commiteditor(r, c, s, editform=editform) |
919 else: | 1040 else: |
920 return commiteditor | 1041 return commiteditor |
1042 | |
921 | 1043 |
922 def _escapecommandtemplate(tmpl): | 1044 def _escapecommandtemplate(tmpl): |
923 parts = [] | 1045 parts = [] |
924 for typ, start, end in templater.scantemplate(tmpl, raw=True): | 1046 for typ, start, end in templater.scantemplate(tmpl, raw=True): |
925 if typ == b'string': | 1047 if typ == b'string': |
926 parts.append(stringutil.escapestr(tmpl[start:end])) | 1048 parts.append(stringutil.escapestr(tmpl[start:end])) |
927 else: | 1049 else: |
928 parts.append(tmpl[start:end]) | 1050 parts.append(tmpl[start:end]) |
929 return b''.join(parts) | 1051 return b''.join(parts) |
1052 | |
930 | 1053 |
931 def rendercommandtemplate(ui, tmpl, props): | 1054 def rendercommandtemplate(ui, tmpl, props): |
932 r"""Expand a literal template 'tmpl' in a way suitable for command line | 1055 r"""Expand a literal template 'tmpl' in a way suitable for command line |
933 | 1056 |
934 '\' in outermost string is not taken as an escape character because it | 1057 '\' in outermost string is not taken as an escape character because it |
944 if not tmpl: | 1067 if not tmpl: |
945 return tmpl | 1068 return tmpl |
946 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl)) | 1069 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl)) |
947 return t.renderdefault(props) | 1070 return t.renderdefault(props) |
948 | 1071 |
1072 | |
949 def rendertemplate(ctx, tmpl, props=None): | 1073 def rendertemplate(ctx, tmpl, props=None): |
950 """Expand a literal template 'tmpl' byte-string against one changeset | 1074 """Expand a literal template 'tmpl' byte-string against one changeset |
951 | 1075 |
952 Each props item must be a stringify-able value or a callable returning | 1076 Each props item must be a stringify-able value or a callable returning |
953 such value, i.e. no bare list nor dict should be passed. | 1077 such value, i.e. no bare list nor dict should be passed. |
954 """ | 1078 """ |
955 repo = ctx.repo() | 1079 repo = ctx.repo() |
956 tres = formatter.templateresources(repo.ui, repo) | 1080 tres = formatter.templateresources(repo.ui, repo) |
957 t = formatter.maketemplater(repo.ui, tmpl, defaults=templatekw.keywords, | 1081 t = formatter.maketemplater( |
958 resources=tres) | 1082 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres |
1083 ) | |
959 mapping = {'ctx': ctx} | 1084 mapping = {'ctx': ctx} |
960 if props: | 1085 if props: |
961 mapping.update(props) | 1086 mapping.update(props) |
962 return t.renderdefault(mapping) | 1087 return t.renderdefault(mapping) |
1088 | |
963 | 1089 |
964 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None): | 1090 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None): |
965 r"""Convert old-style filename format string to template string | 1091 r"""Convert old-style filename format string to template string |
966 | 1092 |
967 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0) | 1093 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0) |
1016 if n < 0: | 1142 if n < 0: |
1017 newname.append(stringutil.escapestr(pat[i:end])) | 1143 newname.append(stringutil.escapestr(pat[i:end])) |
1018 break | 1144 break |
1019 newname.append(stringutil.escapestr(pat[i:n])) | 1145 newname.append(stringutil.escapestr(pat[i:n])) |
1020 if n + 2 > end: | 1146 if n + 2 > end: |
1021 raise error.Abort(_("incomplete format spec in output " | 1147 raise error.Abort( |
1022 "filename")) | 1148 _("incomplete format spec in output " "filename") |
1023 c = pat[n + 1:n + 2] | 1149 ) |
1150 c = pat[n + 1 : n + 2] | |
1024 i = n + 2 | 1151 i = n + 2 |
1025 try: | 1152 try: |
1026 newname.append(expander[c]) | 1153 newname.append(expander[c]) |
1027 except KeyError: | 1154 except KeyError: |
1028 raise error.Abort(_("invalid format spec '%%%s' in output " | 1155 raise error.Abort( |
1029 "filename") % c) | 1156 _("invalid format spec '%%%s' in output " "filename") % c |
1157 ) | |
1030 return ''.join(newname) | 1158 return ''.join(newname) |
1159 | |
1031 | 1160 |
1032 def makefilename(ctx, pat, **props): | 1161 def makefilename(ctx, pat, **props): |
1033 if not pat: | 1162 if not pat: |
1034 return pat | 1163 return pat |
1035 tmpl = _buildfntemplate(pat, **props) | 1164 tmpl = _buildfntemplate(pat, **props) |
1036 # BUG: alias expansion shouldn't be made against template fragments | 1165 # BUG: alias expansion shouldn't be made against template fragments |
1037 # rewritten from %-format strings, but we have no easy way to partially | 1166 # rewritten from %-format strings, but we have no easy way to partially |
1038 # disable the expansion. | 1167 # disable the expansion. |
1039 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props)) | 1168 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props)) |
1040 | 1169 |
1170 | |
1041 def isstdiofilename(pat): | 1171 def isstdiofilename(pat): |
1042 """True if the given pat looks like a filename denoting stdin/stdout""" | 1172 """True if the given pat looks like a filename denoting stdin/stdout""" |
1043 return not pat or pat == '-' | 1173 return not pat or pat == '-' |
1044 | 1174 |
1175 | |
1045 class _unclosablefile(object): | 1176 class _unclosablefile(object): |
1046 def __init__(self, fp): | 1177 def __init__(self, fp): |
1047 self._fp = fp | 1178 self._fp = fp |
1048 | 1179 |
1049 def close(self): | 1180 def close(self): |
1058 def __enter__(self): | 1189 def __enter__(self): |
1059 return self | 1190 return self |
1060 | 1191 |
1061 def __exit__(self, exc_type, exc_value, exc_tb): | 1192 def __exit__(self, exc_type, exc_value, exc_tb): |
1062 pass | 1193 pass |
1194 | |
1063 | 1195 |
1064 def makefileobj(ctx, pat, mode='wb', **props): | 1196 def makefileobj(ctx, pat, mode='wb', **props): |
1065 writable = mode not in ('r', 'rb') | 1197 writable = mode not in ('r', 'rb') |
1066 | 1198 |
1067 if isstdiofilename(pat): | 1199 if isstdiofilename(pat): |
1071 else: | 1203 else: |
1072 fp = repo.ui.fin | 1204 fp = repo.ui.fin |
1073 return _unclosablefile(fp) | 1205 return _unclosablefile(fp) |
1074 fn = makefilename(ctx, pat, **props) | 1206 fn = makefilename(ctx, pat, **props) |
1075 return open(fn, mode) | 1207 return open(fn, mode) |
1208 | |
1076 | 1209 |
1077 def openstorage(repo, cmd, file_, opts, returnrevlog=False): | 1210 def openstorage(repo, cmd, file_, opts, returnrevlog=False): |
1078 """opens the changelog, manifest, a filelog or a given revlog""" | 1211 """opens the changelog, manifest, a filelog or a given revlog""" |
1079 cl = opts['changelog'] | 1212 cl = opts['changelog'] |
1080 mf = opts['manifest'] | 1213 mf = opts['manifest'] |
1086 msg = _('cannot specify --changelog and --dir at the same time') | 1219 msg = _('cannot specify --changelog and --dir at the same time') |
1087 elif cl or mf or dir: | 1220 elif cl or mf or dir: |
1088 if file_: | 1221 if file_: |
1089 msg = _('cannot specify filename with --changelog or --manifest') | 1222 msg = _('cannot specify filename with --changelog or --manifest') |
1090 elif not repo: | 1223 elif not repo: |
1091 msg = _('cannot specify --changelog or --manifest or --dir ' | 1224 msg = _( |
1092 'without a repository') | 1225 'cannot specify --changelog or --manifest or --dir ' |
1226 'without a repository' | |
1227 ) | |
1093 if msg: | 1228 if msg: |
1094 raise error.Abort(msg) | 1229 raise error.Abort(msg) |
1095 | 1230 |
1096 r = None | 1231 r = None |
1097 if repo: | 1232 if repo: |
1098 if cl: | 1233 if cl: |
1099 r = repo.unfiltered().changelog | 1234 r = repo.unfiltered().changelog |
1100 elif dir: | 1235 elif dir: |
1101 if 'treemanifest' not in repo.requirements: | 1236 if 'treemanifest' not in repo.requirements: |
1102 raise error.Abort(_("--dir can only be used on repos with " | 1237 raise error.Abort( |
1103 "treemanifest enabled")) | 1238 _( |
1239 "--dir can only be used on repos with " | |
1240 "treemanifest enabled" | |
1241 ) | |
1242 ) | |
1104 if not dir.endswith('/'): | 1243 if not dir.endswith('/'): |
1105 dir = dir + '/' | 1244 dir = dir + '/' |
1106 dirlog = repo.manifestlog.getstorage(dir) | 1245 dirlog = repo.manifestlog.getstorage(dir) |
1107 if len(dirlog): | 1246 if len(dirlog): |
1108 r = dirlog | 1247 r = dirlog |
1129 | 1268 |
1130 if not file_: | 1269 if not file_: |
1131 raise error.CommandError(cmd, _('invalid arguments')) | 1270 raise error.CommandError(cmd, _('invalid arguments')) |
1132 if not os.path.isfile(file_): | 1271 if not os.path.isfile(file_): |
1133 raise error.Abort(_("revlog '%s' not found") % file_) | 1272 raise error.Abort(_("revlog '%s' not found") % file_) |
1134 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False), | 1273 r = revlog.revlog( |
1135 file_[:-2] + ".i") | 1274 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + ".i" |
1275 ) | |
1136 return r | 1276 return r |
1277 | |
1137 | 1278 |
1138 def openrevlog(repo, cmd, file_, opts): | 1279 def openrevlog(repo, cmd, file_, opts): |
1139 """Obtain a revlog backing storage of an item. | 1280 """Obtain a revlog backing storage of an item. |
1140 | 1281 |
1141 This is similar to ``openstorage()`` except it always returns a revlog. | 1282 This is similar to ``openstorage()`` except it always returns a revlog. |
1144 revlog backing it. Therefore, this function should only be used by code | 1285 revlog backing it. Therefore, this function should only be used by code |
1145 that needs to examine low-level revlog implementation details. e.g. debug | 1286 that needs to examine low-level revlog implementation details. e.g. debug |
1146 commands. | 1287 commands. |
1147 """ | 1288 """ |
1148 return openstorage(repo, cmd, file_, opts, returnrevlog=True) | 1289 return openstorage(repo, cmd, file_, opts, returnrevlog=True) |
1290 | |
1149 | 1291 |
1150 def copy(ui, repo, pats, opts, rename=False): | 1292 def copy(ui, repo, pats, opts, rename=False): |
1151 # called with the repo lock held | 1293 # called with the repo lock held |
1152 # | 1294 # |
1153 # hgsep => pathname that uses "/" to separate directories | 1295 # hgsep => pathname that uses "/" to separate directories |
1157 after = opts.get("after") | 1299 after = opts.get("after") |
1158 dryrun = opts.get("dry_run") | 1300 dryrun = opts.get("dry_run") |
1159 wctx = repo[None] | 1301 wctx = repo[None] |
1160 | 1302 |
1161 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True) | 1303 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True) |
1304 | |
1162 def walkpat(pat): | 1305 def walkpat(pat): |
1163 srcs = [] | 1306 srcs = [] |
1164 if after: | 1307 if after: |
1165 badstates = '?' | 1308 badstates = '?' |
1166 else: | 1309 else: |
1172 exact = m.exact(abs) | 1315 exact = m.exact(abs) |
1173 if state in badstates: | 1316 if state in badstates: |
1174 if exact and state == '?': | 1317 if exact and state == '?': |
1175 ui.warn(_('%s: not copying - file is not managed\n') % rel) | 1318 ui.warn(_('%s: not copying - file is not managed\n') % rel) |
1176 if exact and state == 'r': | 1319 if exact and state == 'r': |
1177 ui.warn(_('%s: not copying - file has been marked for' | 1320 ui.warn( |
1178 ' remove\n') % rel) | 1321 _( |
1322 '%s: not copying - file has been marked for' | |
1323 ' remove\n' | |
1324 ) | |
1325 % rel | |
1326 ) | |
1179 continue | 1327 continue |
1180 # abs: hgsep | 1328 # abs: hgsep |
1181 # rel: ossep | 1329 # rel: ossep |
1182 srcs.append((abs, rel, exact)) | 1330 srcs.append((abs, rel, exact)) |
1183 return srcs | 1331 return srcs |
1200 scmutil.checkportable(ui, abstarget) | 1348 scmutil.checkportable(ui, abstarget) |
1201 | 1349 |
1202 # check for collisions | 1350 # check for collisions |
1203 prevsrc = targets.get(abstarget) | 1351 prevsrc = targets.get(abstarget) |
1204 if prevsrc is not None: | 1352 if prevsrc is not None: |
1205 ui.warn(_('%s: not overwriting - %s collides with %s\n') % | 1353 ui.warn( |
1206 (reltarget, repo.pathto(abssrc, cwd), | 1354 _('%s: not overwriting - %s collides with %s\n') |
1207 repo.pathto(prevsrc, cwd))) | 1355 % ( |
1208 return True # report a failure | 1356 reltarget, |
1357 repo.pathto(abssrc, cwd), | |
1358 repo.pathto(prevsrc, cwd), | |
1359 ) | |
1360 ) | |
1361 return True # report a failure | |
1209 | 1362 |
1210 # check for overwrites | 1363 # check for overwrites |
1211 exists = os.path.lexists(target) | 1364 exists = os.path.lexists(target) |
1212 samefile = False | 1365 samefile = False |
1213 if exists and abssrc != abstarget: | 1366 if exists and abssrc != abstarget: |
1214 if (repo.dirstate.normalize(abssrc) == | 1367 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize( |
1215 repo.dirstate.normalize(abstarget)): | 1368 abstarget |
1369 ): | |
1216 if not rename: | 1370 if not rename: |
1217 ui.warn(_("%s: can't copy - same file\n") % reltarget) | 1371 ui.warn(_("%s: can't copy - same file\n") % reltarget) |
1218 return True # report a failure | 1372 return True # report a failure |
1219 exists = False | 1373 exists = False |
1220 samefile = True | 1374 samefile = True |
1221 | 1375 |
1222 if not after and exists or after and state in 'mn': | 1376 if not after and exists or after and state in 'mn': |
1223 if not opts['force']: | 1377 if not opts['force']: |
1226 if after: | 1380 if after: |
1227 flags = '--after --force' | 1381 flags = '--after --force' |
1228 else: | 1382 else: |
1229 flags = '--force' | 1383 flags = '--force' |
1230 if rename: | 1384 if rename: |
1231 hint = _("('hg rename %s' to replace the file by " | 1385 hint = ( |
1232 'recording a rename)\n') % flags | 1386 _( |
1387 "('hg rename %s' to replace the file by " | |
1388 'recording a rename)\n' | |
1389 ) | |
1390 % flags | |
1391 ) | |
1233 else: | 1392 else: |
1234 hint = _("('hg copy %s' to replace the file by " | 1393 hint = ( |
1235 'recording a copy)\n') % flags | 1394 _( |
1395 "('hg copy %s' to replace the file by " | |
1396 'recording a copy)\n' | |
1397 ) | |
1398 % flags | |
1399 ) | |
1236 else: | 1400 else: |
1237 msg = _('%s: not overwriting - file exists\n') | 1401 msg = _('%s: not overwriting - file exists\n') |
1238 if rename: | 1402 if rename: |
1239 hint = _("('hg rename --after' to record the rename)\n") | 1403 hint = _("('hg rename --after' to record the rename)\n") |
1240 else: | 1404 else: |
1241 hint = _("('hg copy --after' to record the copy)\n") | 1405 hint = _("('hg copy --after' to record the copy)\n") |
1242 ui.warn(msg % reltarget) | 1406 ui.warn(msg % reltarget) |
1243 ui.warn(hint) | 1407 ui.warn(hint) |
1244 return True # report a failure | 1408 return True # report a failure |
1245 | 1409 |
1246 if after: | 1410 if after: |
1247 if not exists: | 1411 if not exists: |
1248 if rename: | 1412 if rename: |
1249 ui.warn(_('%s: not recording move - %s does not exist\n') % | 1413 ui.warn( |
1250 (relsrc, reltarget)) | 1414 _('%s: not recording move - %s does not exist\n') |
1415 % (relsrc, reltarget) | |
1416 ) | |
1251 else: | 1417 else: |
1252 ui.warn(_('%s: not recording copy - %s does not exist\n') % | 1418 ui.warn( |
1253 (relsrc, reltarget)) | 1419 _('%s: not recording copy - %s does not exist\n') |
1254 return True # report a failure | 1420 % (relsrc, reltarget) |
1421 ) | |
1422 return True # report a failure | |
1255 elif not dryrun: | 1423 elif not dryrun: |
1256 try: | 1424 try: |
1257 if exists: | 1425 if exists: |
1258 os.unlink(target) | 1426 os.unlink(target) |
1259 targetdir = os.path.dirname(target) or '.' | 1427 targetdir = os.path.dirname(target) or '.' |
1271 except IOError as inst: | 1439 except IOError as inst: |
1272 if inst.errno == errno.ENOENT: | 1440 if inst.errno == errno.ENOENT: |
1273 ui.warn(_('%s: deleted in working directory\n') % relsrc) | 1441 ui.warn(_('%s: deleted in working directory\n') % relsrc) |
1274 srcexists = False | 1442 srcexists = False |
1275 else: | 1443 else: |
1276 ui.warn(_('%s: cannot copy - %s\n') % | 1444 ui.warn( |
1277 (relsrc, encoding.strtolocal(inst.strerror))) | 1445 _('%s: cannot copy - %s\n') |
1278 return True # report a failure | 1446 % (relsrc, encoding.strtolocal(inst.strerror)) |
1447 ) | |
1448 return True # report a failure | |
1279 | 1449 |
1280 if ui.verbose or not exact: | 1450 if ui.verbose or not exact: |
1281 if rename: | 1451 if rename: |
1282 ui.status(_('moving %s to %s\n') % (relsrc, reltarget)) | 1452 ui.status(_('moving %s to %s\n') % (relsrc, reltarget)) |
1283 else: | 1453 else: |
1284 ui.status(_('copying %s to %s\n') % (relsrc, reltarget)) | 1454 ui.status(_('copying %s to %s\n') % (relsrc, reltarget)) |
1285 | 1455 |
1286 targets[abstarget] = abssrc | 1456 targets[abstarget] = abssrc |
1287 | 1457 |
1288 # fix up dirstate | 1458 # fix up dirstate |
1289 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget, | 1459 scmutil.dirstatecopy( |
1290 dryrun=dryrun, cwd=cwd) | 1460 ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd |
1461 ) | |
1291 if rename and not dryrun: | 1462 if rename and not dryrun: |
1292 if not after and srcexists and not samefile: | 1463 if not after and srcexists and not samefile: |
1293 rmdir = repo.ui.configbool('experimental', 'removeemptydirs') | 1464 rmdir = repo.ui.configbool('experimental', 'removeemptydirs') |
1294 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir) | 1465 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir) |
1295 wctx.forget([abssrc]) | 1466 wctx.forget([abssrc]) |
1308 striplen = len(abspfx) | 1479 striplen = len(abspfx) |
1309 if striplen: | 1480 if striplen: |
1310 striplen += len(pycompat.ossep) | 1481 striplen += len(pycompat.ossep) |
1311 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:]) | 1482 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:]) |
1312 elif destdirexists: | 1483 elif destdirexists: |
1313 res = lambda p: os.path.join(dest, | 1484 res = lambda p: os.path.join( |
1314 os.path.basename(util.localpath(p))) | 1485 dest, os.path.basename(util.localpath(p)) |
1486 ) | |
1315 else: | 1487 else: |
1316 res = lambda p: dest | 1488 res = lambda p: dest |
1317 return res | 1489 return res |
1318 | 1490 |
1319 # pat: ossep | 1491 # pat: ossep |
1321 # srcs: list of (hgsep, hgsep, ossep, bool) | 1493 # srcs: list of (hgsep, hgsep, ossep, bool) |
1322 # return: function that takes hgsep and returns ossep | 1494 # return: function that takes hgsep and returns ossep |
1323 def targetpathafterfn(pat, dest, srcs): | 1495 def targetpathafterfn(pat, dest, srcs): |
1324 if matchmod.patkind(pat): | 1496 if matchmod.patkind(pat): |
1325 # a mercurial pattern | 1497 # a mercurial pattern |
1326 res = lambda p: os.path.join(dest, | 1498 res = lambda p: os.path.join( |
1327 os.path.basename(util.localpath(p))) | 1499 dest, os.path.basename(util.localpath(p)) |
1500 ) | |
1328 else: | 1501 else: |
1329 abspfx = pathutil.canonpath(repo.root, cwd, pat) | 1502 abspfx = pathutil.canonpath(repo.root, cwd, pat) |
1330 if len(abspfx) < len(srcs[0][0]): | 1503 if len(abspfx) < len(srcs[0][0]): |
1331 # A directory. Either the target path contains the last | 1504 # A directory. Either the target path contains the last |
1332 # component of the source path or it does not. | 1505 # component of the source path or it does not. |
1347 striplen1 = len(os.path.split(abspfx)[0]) | 1520 striplen1 = len(os.path.split(abspfx)[0]) |
1348 if striplen1: | 1521 if striplen1: |
1349 striplen1 += len(pycompat.ossep) | 1522 striplen1 += len(pycompat.ossep) |
1350 if evalpath(striplen1) > score: | 1523 if evalpath(striplen1) > score: |
1351 striplen = striplen1 | 1524 striplen = striplen1 |
1352 res = lambda p: os.path.join(dest, | 1525 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:]) |
1353 util.localpath(p)[striplen:]) | |
1354 else: | 1526 else: |
1355 # a file | 1527 # a file |
1356 if destdirexists: | 1528 if destdirexists: |
1357 res = lambda p: os.path.join(dest, | 1529 res = lambda p: os.path.join( |
1358 os.path.basename(util.localpath(p))) | 1530 dest, os.path.basename(util.localpath(p)) |
1531 ) | |
1359 else: | 1532 else: |
1360 res = lambda p: dest | 1533 res = lambda p: dest |
1361 return res | 1534 return res |
1362 | 1535 |
1363 pats = scmutil.expandpats(pats) | 1536 pats = scmutil.expandpats(pats) |
1367 raise error.Abort(_('no destination specified')) | 1540 raise error.Abort(_('no destination specified')) |
1368 dest = pats.pop() | 1541 dest = pats.pop() |
1369 destdirexists = os.path.isdir(dest) and not os.path.islink(dest) | 1542 destdirexists = os.path.isdir(dest) and not os.path.islink(dest) |
1370 if not destdirexists: | 1543 if not destdirexists: |
1371 if len(pats) > 1 or matchmod.patkind(pats[0]): | 1544 if len(pats) > 1 or matchmod.patkind(pats[0]): |
1372 raise error.Abort(_('with multiple sources, destination must be an ' | 1545 raise error.Abort( |
1373 'existing directory')) | 1546 _( |
1547 'with multiple sources, destination must be an ' | |
1548 'existing directory' | |
1549 ) | |
1550 ) | |
1374 if util.endswithsep(dest): | 1551 if util.endswithsep(dest): |
1375 raise error.Abort(_('destination %s is not a directory') % dest) | 1552 raise error.Abort(_('destination %s is not a directory') % dest) |
1376 | 1553 |
1377 tfn = targetpathfn | 1554 tfn = targetpathfn |
1378 if after: | 1555 if after: |
1392 if copyfile(abssrc, relsrc, targetpath(abssrc), exact): | 1569 if copyfile(abssrc, relsrc, targetpath(abssrc), exact): |
1393 errors += 1 | 1570 errors += 1 |
1394 | 1571 |
1395 return errors != 0 | 1572 return errors != 0 |
1396 | 1573 |
1574 | |
1397 ## facility to let extension process additional data into an import patch | 1575 ## facility to let extension process additional data into an import patch |
1398 # list of identifier to be executed in order | 1576 # list of identifier to be executed in order |
1399 extrapreimport = [] # run before commit | 1577 extrapreimport = [] # run before commit |
1400 extrapostimport = [] # run after commit | 1578 extrapostimport = [] # run after commit |
1401 # mapping from identifier to actual import function | 1579 # mapping from identifier to actual import function |
1402 # | 1580 # |
1403 # 'preimport' are run before the commit is made and are provided the following | 1581 # 'preimport' are run before the commit is made and are provided the following |
1404 # arguments: | 1582 # arguments: |
1405 # - repo: the localrepository instance, | 1583 # - repo: the localrepository instance, |
1412 extrapreimportmap = {} | 1590 extrapreimportmap = {} |
1413 # 'postimport' are run after the commit is made and are provided the following | 1591 # 'postimport' are run after the commit is made and are provided the following |
1414 # argument: | 1592 # argument: |
1415 # - ctx: the changectx created by import. | 1593 # - ctx: the changectx created by import. |
1416 extrapostimportmap = {} | 1594 extrapostimportmap = {} |
1595 | |
1417 | 1596 |
1418 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc): | 1597 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc): |
1419 """Utility function used by commands.import to import a single patch | 1598 """Utility function used by commands.import to import a single patch |
1420 | 1599 |
1421 This function is explicitly defined here to help the evolve extension to | 1600 This function is explicitly defined here to help the evolve extension to |
1487 p1 = parents[0] | 1666 p1 = parents[0] |
1488 p2 = repo[nullid] | 1667 p2 = repo[nullid] |
1489 except error.RepoError: | 1668 except error.RepoError: |
1490 p1, p2 = parents | 1669 p1, p2 = parents |
1491 if p2.node() == nullid: | 1670 if p2.node() == nullid: |
1492 ui.warn(_("warning: import the patch as a normal revision\n" | 1671 ui.warn( |
1493 "(use --exact to import the patch as a merge)\n")) | 1672 _( |
1673 "warning: import the patch as a normal revision\n" | |
1674 "(use --exact to import the patch as a merge)\n" | |
1675 ) | |
1676 ) | |
1494 else: | 1677 else: |
1495 p1, p2 = parents | 1678 p1, p2 = parents |
1496 | 1679 |
1497 n = None | 1680 n = None |
1498 if update: | 1681 if update: |
1505 repo.dirstate.setbranch(branch or 'default') | 1688 repo.dirstate.setbranch(branch or 'default') |
1506 | 1689 |
1507 partial = opts.get('partial', False) | 1690 partial = opts.get('partial', False) |
1508 files = set() | 1691 files = set() |
1509 try: | 1692 try: |
1510 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix, | 1693 patch.patch( |
1511 files=files, eolmode=None, similarity=sim / 100.0) | 1694 ui, |
1695 repo, | |
1696 tmpname, | |
1697 strip=strip, | |
1698 prefix=prefix, | |
1699 files=files, | |
1700 eolmode=None, | |
1701 similarity=sim / 100.0, | |
1702 ) | |
1512 except error.PatchError as e: | 1703 except error.PatchError as e: |
1513 if not partial: | 1704 if not partial: |
1514 raise error.Abort(pycompat.bytestr(e)) | 1705 raise error.Abort(pycompat.bytestr(e)) |
1515 if partial: | 1706 if partial: |
1516 rejects = True | 1707 rejects = True |
1529 m = scmutil.matchfiles(repo, files or []) | 1720 m = scmutil.matchfiles(repo, files or []) |
1530 editform = mergeeditform(repo[None], 'import.normal') | 1721 editform = mergeeditform(repo[None], 'import.normal') |
1531 if opts.get('exact'): | 1722 if opts.get('exact'): |
1532 editor = None | 1723 editor = None |
1533 else: | 1724 else: |
1534 editor = getcommiteditor(editform=editform, | 1725 editor = getcommiteditor( |
1535 **pycompat.strkwargs(opts)) | 1726 editform=editform, **pycompat.strkwargs(opts) |
1727 ) | |
1536 extra = {} | 1728 extra = {} |
1537 for idfunc in extrapreimport: | 1729 for idfunc in extrapreimport: |
1538 extrapreimportmap[idfunc](repo, patchdata, extra, opts) | 1730 extrapreimportmap[idfunc](repo, patchdata, extra, opts) |
1539 overrides = {} | 1731 overrides = {} |
1540 if partial: | 1732 if partial: |
1541 overrides[('ui', 'allowemptycommit')] = True | 1733 overrides[('ui', 'allowemptycommit')] = True |
1542 with repo.ui.configoverride(overrides, 'import'): | 1734 with repo.ui.configoverride(overrides, 'import'): |
1543 n = repo.commit(message, user, | 1735 n = repo.commit( |
1544 date, match=m, | 1736 message, user, date, match=m, editor=editor, extra=extra |
1545 editor=editor, extra=extra) | 1737 ) |
1546 for idfunc in extrapostimport: | 1738 for idfunc in extrapostimport: |
1547 extrapostimportmap[idfunc](repo[n]) | 1739 extrapostimportmap[idfunc](repo[n]) |
1548 else: | 1740 else: |
1549 if opts.get('exact') or importbranch: | 1741 if opts.get('exact') or importbranch: |
1550 branch = branch or 'default' | 1742 branch = branch or 'default' |
1552 branch = p1.branch() | 1744 branch = p1.branch() |
1553 store = patch.filestore() | 1745 store = patch.filestore() |
1554 try: | 1746 try: |
1555 files = set() | 1747 files = set() |
1556 try: | 1748 try: |
1557 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix, | 1749 patch.patchrepo( |
1558 files, eolmode=None) | 1750 ui, |
1751 repo, | |
1752 p1, | |
1753 store, | |
1754 tmpname, | |
1755 strip, | |
1756 prefix, | |
1757 files, | |
1758 eolmode=None, | |
1759 ) | |
1559 except error.PatchError as e: | 1760 except error.PatchError as e: |
1560 raise error.Abort(stringutil.forcebytestr(e)) | 1761 raise error.Abort(stringutil.forcebytestr(e)) |
1561 if opts.get('exact'): | 1762 if opts.get('exact'): |
1562 editor = None | 1763 editor = None |
1563 else: | 1764 else: |
1564 editor = getcommiteditor(editform='import.bypass') | 1765 editor = getcommiteditor(editform='import.bypass') |
1565 memctx = context.memctx(repo, (p1.node(), p2.node()), | 1766 memctx = context.memctx( |
1566 message, | 1767 repo, |
1567 files=files, | 1768 (p1.node(), p2.node()), |
1568 filectxfn=store, | 1769 message, |
1569 user=user, | 1770 files=files, |
1570 date=date, | 1771 filectxfn=store, |
1571 branch=branch, | 1772 user=user, |
1572 editor=editor) | 1773 date=date, |
1774 branch=branch, | |
1775 editor=editor, | |
1776 ) | |
1573 n = memctx.commit() | 1777 n = memctx.commit() |
1574 finally: | 1778 finally: |
1575 store.close() | 1779 store.close() |
1576 if opts.get('exact') and nocommit: | 1780 if opts.get('exact') and nocommit: |
1577 # --exact with --no-commit is still useful in that it does merge | 1781 # --exact with --no-commit is still useful in that it does merge |
1583 if n: | 1787 if n: |
1584 # i18n: refers to a short changeset id | 1788 # i18n: refers to a short changeset id |
1585 msg = _('created %s') % short(n) | 1789 msg = _('created %s') % short(n) |
1586 return msg, n, rejects | 1790 return msg, n, rejects |
1587 | 1791 |
1792 | |
1588 # facility to let extensions include additional data in an exported patch | 1793 # facility to let extensions include additional data in an exported patch |
1589 # list of identifiers to be executed in order | 1794 # list of identifiers to be executed in order |
1590 extraexport = [] | 1795 extraexport = [] |
1591 # mapping from identifier to actual export function | 1796 # mapping from identifier to actual export function |
1592 # function as to return a string to be added to the header or None | 1797 # function as to return a string to be added to the header or None |
1593 # it is given two arguments (sequencenumber, changectx) | 1798 # it is given two arguments (sequencenumber, changectx) |
1594 extraexportmap = {} | 1799 extraexportmap = {} |
1595 | 1800 |
1801 | |
1596 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts): | 1802 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts): |
1597 node = scmutil.binnode(ctx) | 1803 node = scmutil.binnode(ctx) |
1598 parents = [p.node() for p in ctx.parents() if p] | 1804 parents = [p.node() for p in ctx.parents() if p] |
1599 branch = ctx.branch() | 1805 branch = ctx.branch() |
1600 if switch_parent: | 1806 if switch_parent: |
1608 fm.context(ctx=ctx) | 1814 fm.context(ctx=ctx) |
1609 fm.plain('# HG changeset patch\n') | 1815 fm.plain('# HG changeset patch\n') |
1610 fm.write('user', '# User %s\n', ctx.user()) | 1816 fm.write('user', '# User %s\n', ctx.user()) |
1611 fm.plain('# Date %d %d\n' % ctx.date()) | 1817 fm.plain('# Date %d %d\n' % ctx.date()) |
1612 fm.write('date', '# %s\n', fm.formatdate(ctx.date())) | 1818 fm.write('date', '# %s\n', fm.formatdate(ctx.date())) |
1613 fm.condwrite(branch and branch != 'default', | 1819 fm.condwrite( |
1614 'branch', '# Branch %s\n', branch) | 1820 branch and branch != 'default', 'branch', '# Branch %s\n', branch |
1821 ) | |
1615 fm.write('node', '# Node ID %s\n', hex(node)) | 1822 fm.write('node', '# Node ID %s\n', hex(node)) |
1616 fm.plain('# Parent %s\n' % hex(prev)) | 1823 fm.plain('# Parent %s\n' % hex(prev)) |
1617 if len(parents) > 1: | 1824 if len(parents) > 1: |
1618 fm.plain('# Parent %s\n' % hex(parents[1])) | 1825 fm.plain('# Parent %s\n' % hex(parents[1])) |
1619 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node')) | 1826 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name='node')) |
1634 else: | 1841 else: |
1635 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts) | 1842 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts) |
1636 # TODO: make it structured? | 1843 # TODO: make it structured? |
1637 fm.data(diff=b''.join(chunkiter)) | 1844 fm.data(diff=b''.join(chunkiter)) |
1638 | 1845 |
1846 | |
1639 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match): | 1847 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match): |
1640 """Export changesets to stdout or a single file""" | 1848 """Export changesets to stdout or a single file""" |
1641 for seqno, rev in enumerate(revs, 1): | 1849 for seqno, rev in enumerate(revs, 1): |
1642 ctx = repo[rev] | 1850 ctx = repo[rev] |
1643 if not dest.startswith('<'): | 1851 if not dest.startswith('<'): |
1644 repo.ui.note("%s\n" % dest) | 1852 repo.ui.note("%s\n" % dest) |
1645 fm.startitem() | 1853 fm.startitem() |
1646 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts) | 1854 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts) |
1647 | 1855 |
1648 def _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, diffopts, | 1856 |
1649 match): | 1857 def _exportfntemplate( |
1858 repo, revs, basefm, fntemplate, switch_parent, diffopts, match | |
1859 ): | |
1650 """Export changesets to possibly multiple files""" | 1860 """Export changesets to possibly multiple files""" |
1651 total = len(revs) | 1861 total = len(revs) |
1652 revwidth = max(len(str(rev)) for rev in revs) | 1862 revwidth = max(len(str(rev)) for rev in revs) |
1653 filemap = util.sortdict() # filename: [(seqno, rev), ...] | 1863 filemap = util.sortdict() # filename: [(seqno, rev), ...] |
1654 | 1864 |
1655 for seqno, rev in enumerate(revs, 1): | 1865 for seqno, rev in enumerate(revs, 1): |
1656 ctx = repo[rev] | 1866 ctx = repo[rev] |
1657 dest = makefilename(ctx, fntemplate, | 1867 dest = makefilename( |
1658 total=total, seqno=seqno, revwidth=revwidth) | 1868 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth |
1869 ) | |
1659 filemap.setdefault(dest, []).append((seqno, rev)) | 1870 filemap.setdefault(dest, []).append((seqno, rev)) |
1660 | 1871 |
1661 for dest in filemap: | 1872 for dest in filemap: |
1662 with formatter.maybereopen(basefm, dest) as fm: | 1873 with formatter.maybereopen(basefm, dest) as fm: |
1663 repo.ui.note("%s\n" % dest) | 1874 repo.ui.note("%s\n" % dest) |
1664 for seqno, rev in filemap[dest]: | 1875 for seqno, rev in filemap[dest]: |
1665 fm.startitem() | 1876 fm.startitem() |
1666 ctx = repo[rev] | 1877 ctx = repo[rev] |
1667 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, | 1878 _exportsingle( |
1668 diffopts) | 1879 repo, ctx, fm, match, switch_parent, seqno, diffopts |
1880 ) | |
1881 | |
1669 | 1882 |
1670 def _prefetchchangedfiles(repo, revs, match): | 1883 def _prefetchchangedfiles(repo, revs, match): |
1671 allfiles = set() | 1884 allfiles = set() |
1672 for rev in revs: | 1885 for rev in revs: |
1673 for file in repo[rev].files(): | 1886 for file in repo[rev].files(): |
1674 if not match or match(file): | 1887 if not match or match(file): |
1675 allfiles.add(file) | 1888 allfiles.add(file) |
1676 scmutil.prefetchfiles(repo, revs, scmutil.matchfiles(repo, allfiles)) | 1889 scmutil.prefetchfiles(repo, revs, scmutil.matchfiles(repo, allfiles)) |
1677 | 1890 |
1678 def export(repo, revs, basefm, fntemplate='hg-%h.patch', switch_parent=False, | 1891 |
1679 opts=None, match=None): | 1892 def export( |
1893 repo, | |
1894 revs, | |
1895 basefm, | |
1896 fntemplate='hg-%h.patch', | |
1897 switch_parent=False, | |
1898 opts=None, | |
1899 match=None, | |
1900 ): | |
1680 '''export changesets as hg patches | 1901 '''export changesets as hg patches |
1681 | 1902 |
1682 Args: | 1903 Args: |
1683 repo: The repository from which we're exporting revisions. | 1904 repo: The repository from which we're exporting revisions. |
1684 revs: A list of revisions to export as revision numbers. | 1905 revs: A list of revisions to export as revision numbers. |
1702 _prefetchchangedfiles(repo, revs, match) | 1923 _prefetchchangedfiles(repo, revs, match) |
1703 | 1924 |
1704 if not fntemplate: | 1925 if not fntemplate: |
1705 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match) | 1926 _exportfile(repo, revs, basefm, '<unnamed>', switch_parent, opts, match) |
1706 else: | 1927 else: |
1707 _exportfntemplate(repo, revs, basefm, fntemplate, switch_parent, opts, | 1928 _exportfntemplate( |
1708 match) | 1929 repo, revs, basefm, fntemplate, switch_parent, opts, match |
1930 ) | |
1931 | |
1709 | 1932 |
1710 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None): | 1933 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None): |
1711 """Export changesets to the given file stream""" | 1934 """Export changesets to the given file stream""" |
1712 _prefetchchangedfiles(repo, revs, match) | 1935 _prefetchchangedfiles(repo, revs, match) |
1713 | 1936 |
1714 dest = getattr(fp, 'name', '<unnamed>') | 1937 dest = getattr(fp, 'name', '<unnamed>') |
1715 with formatter.formatter(repo.ui, fp, 'export', {}) as fm: | 1938 with formatter.formatter(repo.ui, fp, 'export', {}) as fm: |
1716 _exportfile(repo, revs, fm, dest, switch_parent, opts, match) | 1939 _exportfile(repo, revs, fm, dest, switch_parent, opts, match) |
1940 | |
1717 | 1941 |
1718 def showmarker(fm, marker, index=None): | 1942 def showmarker(fm, marker, index=None): |
1719 """utility function to display obsolescence marker in a readable way | 1943 """utility function to display obsolescence marker in a readable way |
1720 | 1944 |
1721 To be used by debug function.""" | 1945 To be used by debug function.""" |
1722 if index is not None: | 1946 if index is not None: |
1723 fm.write('index', '%i ', index) | 1947 fm.write('index', '%i ', index) |
1724 fm.write('prednode', '%s ', hex(marker.prednode())) | 1948 fm.write('prednode', '%s ', hex(marker.prednode())) |
1725 succs = marker.succnodes() | 1949 succs = marker.succnodes() |
1726 fm.condwrite(succs, 'succnodes', '%s ', | 1950 fm.condwrite( |
1727 fm.formatlist(map(hex, succs), name='node')) | 1951 succs, 'succnodes', '%s ', fm.formatlist(map(hex, succs), name='node') |
1952 ) | |
1728 fm.write('flag', '%X ', marker.flags()) | 1953 fm.write('flag', '%X ', marker.flags()) |
1729 parents = marker.parentnodes() | 1954 parents = marker.parentnodes() |
1730 if parents is not None: | 1955 if parents is not None: |
1731 fm.write('parentnodes', '{%s} ', | 1956 fm.write( |
1732 fm.formatlist(map(hex, parents), name='node', sep=', ')) | 1957 'parentnodes', |
1958 '{%s} ', | |
1959 fm.formatlist(map(hex, parents), name='node', sep=', '), | |
1960 ) | |
1733 fm.write('date', '(%s) ', fm.formatdate(marker.date())) | 1961 fm.write('date', '(%s) ', fm.formatdate(marker.date())) |
1734 meta = marker.metadata().copy() | 1962 meta = marker.metadata().copy() |
1735 meta.pop('date', None) | 1963 meta.pop('date', None) |
1736 smeta = pycompat.rapply(pycompat.maybebytestr, meta) | 1964 smeta = pycompat.rapply(pycompat.maybebytestr, meta) |
1737 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', ')) | 1965 fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', ')) |
1738 fm.plain('\n') | 1966 fm.plain('\n') |
1739 | 1967 |
1968 | |
1740 def finddate(ui, repo, date): | 1969 def finddate(ui, repo, date): |
1741 """Find the tipmost changeset that matches the given date spec""" | 1970 """Find the tipmost changeset that matches the given date spec""" |
1742 | 1971 |
1743 df = dateutil.matchdate(date) | 1972 df = dateutil.matchdate(date) |
1744 m = scmutil.matchall(repo) | 1973 m = scmutil.matchall(repo) |
1750 results[ctx.rev()] = d | 1979 results[ctx.rev()] = d |
1751 | 1980 |
1752 for ctx in walkchangerevs(repo, m, {'rev': None}, prep): | 1981 for ctx in walkchangerevs(repo, m, {'rev': None}, prep): |
1753 rev = ctx.rev() | 1982 rev = ctx.rev() |
1754 if rev in results: | 1983 if rev in results: |
1755 ui.status(_("found revision %s from %s\n") % | 1984 ui.status( |
1756 (rev, dateutil.datestr(results[rev]))) | 1985 _("found revision %s from %s\n") |
1986 % (rev, dateutil.datestr(results[rev])) | |
1987 ) | |
1757 return '%d' % rev | 1988 return '%d' % rev |
1758 | 1989 |
1759 raise error.Abort(_("revision matching date not found")) | 1990 raise error.Abort(_("revision matching date not found")) |
1991 | |
1760 | 1992 |
1761 def increasingwindows(windowsize=8, sizelimit=512): | 1993 def increasingwindows(windowsize=8, sizelimit=512): |
1762 while True: | 1994 while True: |
1763 yield windowsize | 1995 yield windowsize |
1764 if windowsize < sizelimit: | 1996 if windowsize < sizelimit: |
1765 windowsize *= 2 | 1997 windowsize *= 2 |
1998 | |
1766 | 1999 |
1767 def _walkrevs(repo, opts): | 2000 def _walkrevs(repo, opts): |
1768 # Default --rev value depends on --follow but --follow behavior | 2001 # Default --rev value depends on --follow but --follow behavior |
1769 # depends on revisions resolved from --rev... | 2002 # depends on revisions resolved from --rev... |
1770 follow = opts.get('follow') or opts.get('follow_first') | 2003 follow = opts.get('follow') or opts.get('follow_first') |
1777 else: | 2010 else: |
1778 revs = smartset.spanset(repo) | 2011 revs = smartset.spanset(repo) |
1779 revs.reverse() | 2012 revs.reverse() |
1780 return revs | 2013 return revs |
1781 | 2014 |
2015 | |
1782 class FileWalkError(Exception): | 2016 class FileWalkError(Exception): |
1783 pass | 2017 pass |
2018 | |
1784 | 2019 |
1785 def walkfilerevs(repo, match, follow, revs, fncache): | 2020 def walkfilerevs(repo, match, follow, revs, fncache): |
1786 '''Walks the file history for the matched files. | 2021 '''Walks the file history for the matched files. |
1787 | 2022 |
1788 Returns the changeset revs that are involved in the file history. | 2023 Returns the changeset revs that are involved in the file history. |
1791 filelogs alone. | 2026 filelogs alone. |
1792 ''' | 2027 ''' |
1793 wanted = set() | 2028 wanted = set() |
1794 copies = [] | 2029 copies = [] |
1795 minrev, maxrev = min(revs), max(revs) | 2030 minrev, maxrev = min(revs), max(revs) |
2031 | |
1796 def filerevs(filelog, last): | 2032 def filerevs(filelog, last): |
1797 """ | 2033 """ |
1798 Only files, no patterns. Check the history of each file. | 2034 Only files, no patterns. Check the history of each file. |
1799 | 2035 |
1800 Examines filelog entries within minrev, maxrev linkrev range | 2036 Examines filelog entries within minrev, maxrev linkrev range |
1815 parentlinkrevs = [] | 2051 parentlinkrevs = [] |
1816 for p in filelog.parentrevs(j): | 2052 for p in filelog.parentrevs(j): |
1817 if p != nullrev: | 2053 if p != nullrev: |
1818 parentlinkrevs.append(filelog.linkrev(p)) | 2054 parentlinkrevs.append(filelog.linkrev(p)) |
1819 n = filelog.node(j) | 2055 n = filelog.node(j) |
1820 revs.append((linkrev, parentlinkrevs, | 2056 revs.append( |
1821 follow and filelog.renamed(n))) | 2057 (linkrev, parentlinkrevs, follow and filelog.renamed(n)) |
2058 ) | |
1822 | 2059 |
1823 return reversed(revs) | 2060 return reversed(revs) |
2061 | |
1824 def iterfiles(): | 2062 def iterfiles(): |
1825 pctx = repo['.'] | 2063 pctx = repo['.'] |
1826 for filename in match.files(): | 2064 for filename in match.files(): |
1827 if follow: | 2065 if follow: |
1828 if filename not in pctx: | 2066 if filename not in pctx: |
1829 raise error.Abort(_('cannot follow file not in parent ' | 2067 raise error.Abort( |
1830 'revision: "%s"') % filename) | 2068 _('cannot follow file not in parent ' 'revision: "%s"') |
2069 % filename | |
2070 ) | |
1831 yield filename, pctx[filename].filenode() | 2071 yield filename, pctx[filename].filenode() |
1832 else: | 2072 else: |
1833 yield filename, None | 2073 yield filename, None |
1834 for filename_node in copies: | 2074 for filename_node in copies: |
1835 yield filename_node | 2075 yield filename_node |
1840 if node is None: | 2080 if node is None: |
1841 # A zero count may be a directory or deleted file, so | 2081 # A zero count may be a directory or deleted file, so |
1842 # try to find matching entries on the slow path. | 2082 # try to find matching entries on the slow path. |
1843 if follow: | 2083 if follow: |
1844 raise error.Abort( | 2084 raise error.Abort( |
1845 _('cannot follow nonexistent file: "%s"') % file_) | 2085 _('cannot follow nonexistent file: "%s"') % file_ |
2086 ) | |
1846 raise FileWalkError("Cannot walk via filelog") | 2087 raise FileWalkError("Cannot walk via filelog") |
1847 else: | 2088 else: |
1848 continue | 2089 continue |
1849 | 2090 |
1850 if node is None: | 2091 if node is None: |
1877 if copied: | 2118 if copied: |
1878 copies.append(copied) | 2119 copies.append(copied) |
1879 | 2120 |
1880 return wanted | 2121 return wanted |
1881 | 2122 |
2123 | |
1882 class _followfilter(object): | 2124 class _followfilter(object): |
1883 def __init__(self, repo, onlyfirst=False): | 2125 def __init__(self, repo, onlyfirst=False): |
1884 self.repo = repo | 2126 self.repo = repo |
1885 self.startrev = nullrev | 2127 self.startrev = nullrev |
1886 self.roots = set() | 2128 self.roots = set() |
1889 def match(self, rev): | 2131 def match(self, rev): |
1890 def realparents(rev): | 2132 def realparents(rev): |
1891 if self.onlyfirst: | 2133 if self.onlyfirst: |
1892 return self.repo.changelog.parentrevs(rev)[0:1] | 2134 return self.repo.changelog.parentrevs(rev)[0:1] |
1893 else: | 2135 else: |
1894 return filter(lambda x: x != nullrev, | 2136 return filter( |
1895 self.repo.changelog.parentrevs(rev)) | 2137 lambda x: x != nullrev, self.repo.changelog.parentrevs(rev) |
2138 ) | |
1896 | 2139 |
1897 if self.startrev == nullrev: | 2140 if self.startrev == nullrev: |
1898 self.startrev = rev | 2141 self.startrev = rev |
1899 return True | 2142 return True |
1900 | 2143 |
1915 self.roots.update(realparents(rev)) | 2158 self.roots.update(realparents(rev)) |
1916 return True | 2159 return True |
1917 | 2160 |
1918 return False | 2161 return False |
1919 | 2162 |
2163 | |
1920 def walkchangerevs(repo, match, opts, prepare): | 2164 def walkchangerevs(repo, match, opts, prepare): |
1921 '''Iterate over files and the revs in which they changed. | 2165 '''Iterate over files and the revs in which they changed. |
1922 | 2166 |
1923 Callers most commonly need to iterate backwards over the history | 2167 Callers most commonly need to iterate backwards over the history |
1924 in which they are interested. Doing so has awful (quadratic-looking) | 2168 in which they are interested. Doing so has awful (quadratic-looking) |
1970 if slowpath: | 2214 if slowpath: |
1971 # We have to read the changelog to match filenames against | 2215 # We have to read the changelog to match filenames against |
1972 # changed files | 2216 # changed files |
1973 | 2217 |
1974 if follow: | 2218 if follow: |
1975 raise error.Abort(_('can only follow copies/renames for explicit ' | 2219 raise error.Abort( |
1976 'filenames')) | 2220 _('can only follow copies/renames for explicit ' 'filenames') |
2221 ) | |
1977 | 2222 |
1978 # The slow path checks files modified in every changeset. | 2223 # The slow path checks files modified in every changeset. |
1979 # This is really slow on large repos, so compute the set lazily. | 2224 # This is really slow on large repos, so compute the set lazily. |
1980 class lazywantedset(object): | 2225 class lazywantedset(object): |
1981 def __init__(self): | 2226 def __init__(self): |
2021 # Now that wanted is correctly initialized, we can iterate over the | 2266 # Now that wanted is correctly initialized, we can iterate over the |
2022 # revision range, yielding only revisions in wanted. | 2267 # revision range, yielding only revisions in wanted. |
2023 def iterate(): | 2268 def iterate(): |
2024 if follow and match.always(): | 2269 if follow and match.always(): |
2025 ff = _followfilter(repo, onlyfirst=opts.get('follow_first')) | 2270 ff = _followfilter(repo, onlyfirst=opts.get('follow_first')) |
2271 | |
2026 def want(rev): | 2272 def want(rev): |
2027 return ff.match(rev) and rev in wanted | 2273 return ff.match(rev) and rev in wanted |
2274 | |
2028 else: | 2275 else: |
2276 | |
2029 def want(rev): | 2277 def want(rev): |
2030 return rev in wanted | 2278 return rev in wanted |
2031 | 2279 |
2032 it = iter(revs) | 2280 it = iter(revs) |
2033 stopiteration = False | 2281 stopiteration = False |
2042 nrevs.append(rev) | 2290 nrevs.append(rev) |
2043 for rev in sorted(nrevs): | 2291 for rev in sorted(nrevs): |
2044 fns = fncache.get(rev) | 2292 fns = fncache.get(rev) |
2045 ctx = change(rev) | 2293 ctx = change(rev) |
2046 if not fns: | 2294 if not fns: |
2295 | |
2047 def fns_generator(): | 2296 def fns_generator(): |
2048 if allfiles: | 2297 if allfiles: |
2049 fiter = iter(ctx) | 2298 fiter = iter(ctx) |
2050 else: | 2299 else: |
2051 fiter = ctx.files() | 2300 fiter = ctx.files() |
2052 for f in fiter: | 2301 for f in fiter: |
2053 if match(f): | 2302 if match(f): |
2054 yield f | 2303 yield f |
2304 | |
2055 fns = fns_generator() | 2305 fns = fns_generator() |
2056 prepare(ctx, fns) | 2306 prepare(ctx, fns) |
2057 for rev in nrevs: | 2307 for rev in nrevs: |
2058 yield change(rev) | 2308 yield change(rev) |
2059 | 2309 |
2060 if stopiteration: | 2310 if stopiteration: |
2061 break | 2311 break |
2062 | 2312 |
2063 return iterate() | 2313 return iterate() |
2314 | |
2064 | 2315 |
2065 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts): | 2316 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts): |
2066 bad = [] | 2317 bad = [] |
2067 | 2318 |
2068 badfn = lambda x, y: bad.append(x) or match.bad(x, y) | 2319 badfn = lambda x, y: bad.append(x) or match.bad(x, y) |
2076 match = repo.narrowmatch(match, includeexact=True) | 2327 match = repo.narrowmatch(match, includeexact=True) |
2077 badmatch = matchmod.badmatch(match, badfn) | 2328 badmatch = matchmod.badmatch(match, badfn) |
2078 dirstate = repo.dirstate | 2329 dirstate = repo.dirstate |
2079 # We don't want to just call wctx.walk here, since it would return a lot of | 2330 # We don't want to just call wctx.walk here, since it would return a lot of |
2080 # clean files, which we aren't interested in and takes time. | 2331 # clean files, which we aren't interested in and takes time. |
2081 for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate), | 2332 for f in sorted( |
2082 unknown=True, ignored=False, full=False)): | 2333 dirstate.walk( |
2334 badmatch, | |
2335 subrepos=sorted(wctx.substate), | |
2336 unknown=True, | |
2337 ignored=False, | |
2338 full=False, | |
2339 ) | |
2340 ): | |
2083 exact = match.exact(f) | 2341 exact = match.exact(f) |
2084 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f): | 2342 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f): |
2085 if cca: | 2343 if cca: |
2086 cca(f) | 2344 cca(f) |
2087 names.append(f) | 2345 names.append(f) |
2088 if ui.verbose or not exact: | 2346 if ui.verbose or not exact: |
2089 ui.status(_('adding %s\n') % uipathfn(f), | 2347 ui.status( |
2090 label='ui.addremove.added') | 2348 _('adding %s\n') % uipathfn(f), label='ui.addremove.added' |
2349 ) | |
2091 | 2350 |
2092 for subpath in sorted(wctx.substate): | 2351 for subpath in sorted(wctx.substate): |
2093 sub = wctx.sub(subpath) | 2352 sub = wctx.sub(subpath) |
2094 try: | 2353 try: |
2095 submatch = matchmod.subdirmatcher(subpath, match) | 2354 submatch = matchmod.subdirmatcher(subpath, match) |
2096 subprefix = repo.wvfs.reljoin(prefix, subpath) | 2355 subprefix = repo.wvfs.reljoin(prefix, subpath) |
2097 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn) | 2356 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn) |
2098 if opts.get(r'subrepos'): | 2357 if opts.get(r'subrepos'): |
2099 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, False, | 2358 bad.extend( |
2100 **opts)) | 2359 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts) |
2360 ) | |
2101 else: | 2361 else: |
2102 bad.extend(sub.add(ui, submatch, subprefix, subuipathfn, True, | 2362 bad.extend( |
2103 **opts)) | 2363 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts) |
2364 ) | |
2104 except error.LookupError: | 2365 except error.LookupError: |
2105 ui.status(_("skipping missing subrepository: %s\n") | 2366 ui.status( |
2106 % uipathfn(subpath)) | 2367 _("skipping missing subrepository: %s\n") % uipathfn(subpath) |
2368 ) | |
2107 | 2369 |
2108 if not opts.get(r'dry_run'): | 2370 if not opts.get(r'dry_run'): |
2109 rejected = wctx.add(names, prefix) | 2371 rejected = wctx.add(names, prefix) |
2110 bad.extend(f for f in rejected if f in match.files()) | 2372 bad.extend(f for f in rejected if f in match.files()) |
2111 return bad | 2373 return bad |
2112 | 2374 |
2375 | |
2113 def addwebdirpath(repo, serverpath, webconf): | 2376 def addwebdirpath(repo, serverpath, webconf): |
2114 webconf[serverpath] = repo.root | 2377 webconf[serverpath] = repo.root |
2115 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root)) | 2378 repo.ui.debug('adding %s = %s\n' % (serverpath, repo.root)) |
2116 | 2379 |
2117 for r in repo.revs('filelog("path:.hgsub")'): | 2380 for r in repo.revs('filelog("path:.hgsub")'): |
2118 ctx = repo[r] | 2381 ctx = repo[r] |
2119 for subpath in ctx.substate: | 2382 for subpath in ctx.substate: |
2120 ctx.sub(subpath).addwebdirpath(serverpath, webconf) | 2383 ctx.sub(subpath).addwebdirpath(serverpath, webconf) |
2121 | 2384 |
2122 def forget(ui, repo, match, prefix, uipathfn, explicitonly, dryrun, | 2385 |
2123 interactive): | 2386 def forget( |
2387 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive | |
2388 ): | |
2124 if dryrun and interactive: | 2389 if dryrun and interactive: |
2125 raise error.Abort(_("cannot specify both --dry-run and --interactive")) | 2390 raise error.Abort(_("cannot specify both --dry-run and --interactive")) |
2126 bad = [] | 2391 bad = [] |
2127 badfn = lambda x, y: bad.append(x) or match.bad(x, y) | 2392 badfn = lambda x, y: bad.append(x) or match.bad(x, y) |
2128 wctx = repo[None] | 2393 wctx = repo[None] |
2137 sub = wctx.sub(subpath) | 2402 sub = wctx.sub(subpath) |
2138 submatch = matchmod.subdirmatcher(subpath, match) | 2403 submatch = matchmod.subdirmatcher(subpath, match) |
2139 subprefix = repo.wvfs.reljoin(prefix, subpath) | 2404 subprefix = repo.wvfs.reljoin(prefix, subpath) |
2140 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn) | 2405 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn) |
2141 try: | 2406 try: |
2142 subbad, subforgot = sub.forget(submatch, subprefix, subuipathfn, | 2407 subbad, subforgot = sub.forget( |
2143 dryrun=dryrun, | 2408 submatch, |
2144 interactive=interactive) | 2409 subprefix, |
2410 subuipathfn, | |
2411 dryrun=dryrun, | |
2412 interactive=interactive, | |
2413 ) | |
2145 bad.extend([subpath + '/' + f for f in subbad]) | 2414 bad.extend([subpath + '/' + f for f in subbad]) |
2146 forgot.extend([subpath + '/' + f for f in subforgot]) | 2415 forgot.extend([subpath + '/' + f for f in subforgot]) |
2147 except error.LookupError: | 2416 except error.LookupError: |
2148 ui.status(_("skipping missing subrepository: %s\n") | 2417 ui.status( |
2149 % uipathfn(subpath)) | 2418 _("skipping missing subrepository: %s\n") % uipathfn(subpath) |
2419 ) | |
2150 | 2420 |
2151 if not explicitonly: | 2421 if not explicitonly: |
2152 for f in match.files(): | 2422 for f in match.files(): |
2153 if f not in repo.dirstate and not repo.wvfs.isdir(f): | 2423 if f not in repo.dirstate and not repo.wvfs.isdir(f): |
2154 if f not in forgot: | 2424 if f not in forgot: |
2157 # But don't do this until after checking 'forgot', so | 2427 # But don't do this until after checking 'forgot', so |
2158 # that subrepo files aren't normalized, and this op is | 2428 # that subrepo files aren't normalized, and this op is |
2159 # purely from data cached by the status walk above. | 2429 # purely from data cached by the status walk above. |
2160 if repo.dirstate.normalize(f) in repo.dirstate: | 2430 if repo.dirstate.normalize(f) in repo.dirstate: |
2161 continue | 2431 continue |
2162 ui.warn(_('not removing %s: ' | 2432 ui.warn( |
2163 'file is already untracked\n') | 2433 _('not removing %s: ' 'file is already untracked\n') |
2164 % uipathfn(f)) | 2434 % uipathfn(f) |
2435 ) | |
2165 bad.append(f) | 2436 bad.append(f) |
2166 | 2437 |
2167 if interactive: | 2438 if interactive: |
2168 responses = _('[Ynsa?]' | 2439 responses = _( |
2169 '$$ &Yes, forget this file' | 2440 '[Ynsa?]' |
2170 '$$ &No, skip this file' | 2441 '$$ &Yes, forget this file' |
2171 '$$ &Skip remaining files' | 2442 '$$ &No, skip this file' |
2172 '$$ Include &all remaining files' | 2443 '$$ &Skip remaining files' |
2173 '$$ &? (display help)') | 2444 '$$ Include &all remaining files' |
2445 '$$ &? (display help)' | |
2446 ) | |
2174 for filename in forget[:]: | 2447 for filename in forget[:]: |
2175 r = ui.promptchoice(_('forget %s %s') % | 2448 r = ui.promptchoice( |
2176 (uipathfn(filename), responses)) | 2449 _('forget %s %s') % (uipathfn(filename), responses) |
2177 if r == 4: # ? | 2450 ) |
2451 if r == 4: # ? | |
2178 while r == 4: | 2452 while r == 4: |
2179 for c, t in ui.extractchoices(responses)[1]: | 2453 for c, t in ui.extractchoices(responses)[1]: |
2180 ui.write('%s - %s\n' % (c, encoding.lower(t))) | 2454 ui.write('%s - %s\n' % (c, encoding.lower(t))) |
2181 r = ui.promptchoice(_('forget %s %s') % | 2455 r = ui.promptchoice( |
2182 (uipathfn(filename), responses)) | 2456 _('forget %s %s') % (uipathfn(filename), responses) |
2183 if r == 0: # yes | 2457 ) |
2458 if r == 0: # yes | |
2184 continue | 2459 continue |
2185 elif r == 1: # no | 2460 elif r == 1: # no |
2186 forget.remove(filename) | 2461 forget.remove(filename) |
2187 elif r == 2: # Skip | 2462 elif r == 2: # Skip |
2188 fnindex = forget.index(filename) | 2463 fnindex = forget.index(filename) |
2189 del forget[fnindex:] | 2464 del forget[fnindex:] |
2190 break | 2465 break |
2191 elif r == 3: # All | 2466 elif r == 3: # All |
2192 break | 2467 break |
2193 | 2468 |
2194 for f in forget: | 2469 for f in forget: |
2195 if ui.verbose or not match.exact(f) or interactive: | 2470 if ui.verbose or not match.exact(f) or interactive: |
2196 ui.status(_('removing %s\n') % uipathfn(f), | 2471 ui.status( |
2197 label='ui.addremove.removed') | 2472 _('removing %s\n') % uipathfn(f), label='ui.addremove.removed' |
2473 ) | |
2198 | 2474 |
2199 if not dryrun: | 2475 if not dryrun: |
2200 rejected = wctx.forget(forget, prefix) | 2476 rejected = wctx.forget(forget, prefix) |
2201 bad.extend(f for f in rejected if f in match.files()) | 2477 bad.extend(f for f in rejected if f in match.files()) |
2202 forgot.extend(f for f in forget if f not in rejected) | 2478 forgot.extend(f for f in forget if f not in rejected) |
2203 return bad, forgot | 2479 return bad, forgot |
2480 | |
2204 | 2481 |
2205 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos): | 2482 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos): |
2206 ret = 1 | 2483 ret = 1 |
2207 | 2484 |
2208 needsfctx = ui.verbose or {'size', 'flags'} & fm.datahint() | 2485 needsfctx = ui.verbose or {'size', 'flags'} & fm.datahint() |
2217 ret = 0 | 2494 ret = 0 |
2218 | 2495 |
2219 for subpath in sorted(ctx.substate): | 2496 for subpath in sorted(ctx.substate): |
2220 submatch = matchmod.subdirmatcher(subpath, m) | 2497 submatch = matchmod.subdirmatcher(subpath, m) |
2221 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn) | 2498 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn) |
2222 if (subrepos or m.exact(subpath) or any(submatch.files())): | 2499 if subrepos or m.exact(subpath) or any(submatch.files()): |
2223 sub = ctx.sub(subpath) | 2500 sub = ctx.sub(subpath) |
2224 try: | 2501 try: |
2225 recurse = m.exact(subpath) or subrepos | 2502 recurse = m.exact(subpath) or subrepos |
2226 if sub.printfiles(ui, submatch, subuipathfn, fm, fmt, | 2503 if ( |
2227 recurse) == 0: | 2504 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse) |
2505 == 0 | |
2506 ): | |
2228 ret = 0 | 2507 ret = 0 |
2229 except error.LookupError: | 2508 except error.LookupError: |
2230 ui.status(_("skipping missing subrepository: %s\n") | 2509 ui.status( |
2231 % uipathfn(subpath)) | 2510 _("skipping missing subrepository: %s\n") |
2511 % uipathfn(subpath) | |
2512 ) | |
2232 | 2513 |
2233 return ret | 2514 return ret |
2234 | 2515 |
2235 def remove(ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, | 2516 |
2236 warnings=None): | 2517 def remove( |
2518 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None | |
2519 ): | |
2237 ret = 0 | 2520 ret = 0 |
2238 s = repo.status(match=m, clean=True) | 2521 s = repo.status(match=m, clean=True) |
2239 modified, added, deleted, clean = s[0], s[1], s[3], s[6] | 2522 modified, added, deleted, clean = s[0], s[1], s[3], s[6] |
2240 | 2523 |
2241 wctx = repo[None] | 2524 wctx = repo[None] |
2245 warn = True | 2528 warn = True |
2246 else: | 2529 else: |
2247 warn = False | 2530 warn = False |
2248 | 2531 |
2249 subs = sorted(wctx.substate) | 2532 subs = sorted(wctx.substate) |
2250 progress = ui.makeprogress(_('searching'), total=len(subs), | 2533 progress = ui.makeprogress( |
2251 unit=_('subrepos')) | 2534 _('searching'), total=len(subs), unit=_('subrepos') |
2535 ) | |
2252 for subpath in subs: | 2536 for subpath in subs: |
2253 submatch = matchmod.subdirmatcher(subpath, m) | 2537 submatch = matchmod.subdirmatcher(subpath, m) |
2254 subprefix = repo.wvfs.reljoin(prefix, subpath) | 2538 subprefix = repo.wvfs.reljoin(prefix, subpath) |
2255 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn) | 2539 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn) |
2256 if subrepos or m.exact(subpath) or any(submatch.files()): | 2540 if subrepos or m.exact(subpath) or any(submatch.files()): |
2257 progress.increment() | 2541 progress.increment() |
2258 sub = wctx.sub(subpath) | 2542 sub = wctx.sub(subpath) |
2259 try: | 2543 try: |
2260 if sub.removefiles(submatch, subprefix, subuipathfn, after, | 2544 if sub.removefiles( |
2261 force, subrepos, dryrun, warnings): | 2545 submatch, |
2546 subprefix, | |
2547 subuipathfn, | |
2548 after, | |
2549 force, | |
2550 subrepos, | |
2551 dryrun, | |
2552 warnings, | |
2553 ): | |
2262 ret = 1 | 2554 ret = 1 |
2263 except error.LookupError: | 2555 except error.LookupError: |
2264 warnings.append(_("skipping missing subrepository: %s\n") | 2556 warnings.append( |
2265 % uipathfn(subpath)) | 2557 _("skipping missing subrepository: %s\n") |
2558 % uipathfn(subpath) | |
2559 ) | |
2266 progress.complete() | 2560 progress.complete() |
2267 | 2561 |
2268 # warn about failure to delete explicit files/dirs | 2562 # warn about failure to delete explicit files/dirs |
2269 deleteddirs = util.dirs(deleted) | 2563 deleteddirs = util.dirs(deleted) |
2270 files = m.files() | 2564 files = m.files() |
2271 progress = ui.makeprogress(_('deleting'), total=len(files), | 2565 progress = ui.makeprogress(_('deleting'), total=len(files), unit=_('files')) |
2272 unit=_('files')) | |
2273 for f in files: | 2566 for f in files: |
2567 | |
2274 def insubrepo(): | 2568 def insubrepo(): |
2275 for subpath in wctx.substate: | 2569 for subpath in wctx.substate: |
2276 if f.startswith(subpath + '/'): | 2570 if f.startswith(subpath + '/'): |
2277 return True | 2571 return True |
2278 return False | 2572 return False |
2279 | 2573 |
2280 progress.increment() | 2574 progress.increment() |
2281 isdir = f in deleteddirs or wctx.hasdir(f) | 2575 isdir = f in deleteddirs or wctx.hasdir(f) |
2282 if (f in repo.dirstate or isdir or f == '.' | 2576 if f in repo.dirstate or isdir or f == '.' or insubrepo() or f in subs: |
2283 or insubrepo() or f in subs): | |
2284 continue | 2577 continue |
2285 | 2578 |
2286 if repo.wvfs.exists(f): | 2579 if repo.wvfs.exists(f): |
2287 if repo.wvfs.isdir(f): | 2580 if repo.wvfs.isdir(f): |
2288 warnings.append(_('not removing %s: no tracked files\n') | 2581 warnings.append( |
2289 % uipathfn(f)) | 2582 _('not removing %s: no tracked files\n') % uipathfn(f) |
2583 ) | |
2290 else: | 2584 else: |
2291 warnings.append(_('not removing %s: file is untracked\n') | 2585 warnings.append( |
2292 % uipathfn(f)) | 2586 _('not removing %s: file is untracked\n') % uipathfn(f) |
2587 ) | |
2293 # missing files will generate a warning elsewhere | 2588 # missing files will generate a warning elsewhere |
2294 ret = 1 | 2589 ret = 1 |
2295 progress.complete() | 2590 progress.complete() |
2296 | 2591 |
2297 if force: | 2592 if force: |
2298 list = modified + deleted + clean + added | 2593 list = modified + deleted + clean + added |
2299 elif after: | 2594 elif after: |
2300 list = deleted | 2595 list = deleted |
2301 remaining = modified + added + clean | 2596 remaining = modified + added + clean |
2302 progress = ui.makeprogress(_('skipping'), total=len(remaining), | 2597 progress = ui.makeprogress( |
2303 unit=_('files')) | 2598 _('skipping'), total=len(remaining), unit=_('files') |
2599 ) | |
2304 for f in remaining: | 2600 for f in remaining: |
2305 progress.increment() | 2601 progress.increment() |
2306 if ui.verbose or (f in files): | 2602 if ui.verbose or (f in files): |
2307 warnings.append(_('not removing %s: file still exists\n') | 2603 warnings.append( |
2308 % uipathfn(f)) | 2604 _('not removing %s: file still exists\n') % uipathfn(f) |
2605 ) | |
2309 ret = 1 | 2606 ret = 1 |
2310 progress.complete() | 2607 progress.complete() |
2311 else: | 2608 else: |
2312 list = deleted + clean | 2609 list = deleted + clean |
2313 progress = ui.makeprogress(_('skipping'), | 2610 progress = ui.makeprogress( |
2314 total=(len(modified) + len(added)), | 2611 _('skipping'), total=(len(modified) + len(added)), unit=_('files') |
2315 unit=_('files')) | 2612 ) |
2316 for f in modified: | 2613 for f in modified: |
2317 progress.increment() | 2614 progress.increment() |
2318 warnings.append(_('not removing %s: file is modified (use -f' | 2615 warnings.append( |
2319 ' to force removal)\n') % uipathfn(f)) | 2616 _( |
2617 'not removing %s: file is modified (use -f' | |
2618 ' to force removal)\n' | |
2619 ) | |
2620 % uipathfn(f) | |
2621 ) | |
2320 ret = 1 | 2622 ret = 1 |
2321 for f in added: | 2623 for f in added: |
2322 progress.increment() | 2624 progress.increment() |
2323 warnings.append(_("not removing %s: file has been marked for add" | 2625 warnings.append( |
2324 " (use 'hg forget' to undo add)\n") % uipathfn(f)) | 2626 _( |
2627 "not removing %s: file has been marked for add" | |
2628 " (use 'hg forget' to undo add)\n" | |
2629 ) | |
2630 % uipathfn(f) | |
2631 ) | |
2325 ret = 1 | 2632 ret = 1 |
2326 progress.complete() | 2633 progress.complete() |
2327 | 2634 |
2328 list = sorted(list) | 2635 list = sorted(list) |
2329 progress = ui.makeprogress(_('deleting'), total=len(list), | 2636 progress = ui.makeprogress(_('deleting'), total=len(list), unit=_('files')) |
2330 unit=_('files')) | |
2331 for f in list: | 2637 for f in list: |
2332 if ui.verbose or not m.exact(f): | 2638 if ui.verbose or not m.exact(f): |
2333 progress.increment() | 2639 progress.increment() |
2334 ui.status(_('removing %s\n') % uipathfn(f), | 2640 ui.status( |
2335 label='ui.addremove.removed') | 2641 _('removing %s\n') % uipathfn(f), label='ui.addremove.removed' |
2642 ) | |
2336 progress.complete() | 2643 progress.complete() |
2337 | 2644 |
2338 if not dryrun: | 2645 if not dryrun: |
2339 with repo.wlock(): | 2646 with repo.wlock(): |
2340 if not after: | 2647 if not after: |
2341 for f in list: | 2648 for f in list: |
2342 if f in added: | 2649 if f in added: |
2343 continue # we never unlink added files on remove | 2650 continue # we never unlink added files on remove |
2344 rmdir = repo.ui.configbool('experimental', | 2651 rmdir = repo.ui.configbool( |
2345 'removeemptydirs') | 2652 'experimental', 'removeemptydirs' |
2653 ) | |
2346 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir) | 2654 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir) |
2347 repo[None].forget(list) | 2655 repo[None].forget(list) |
2348 | 2656 |
2349 if warn: | 2657 if warn: |
2350 for warning in warnings: | 2658 for warning in warnings: |
2351 ui.warn(warning) | 2659 ui.warn(warning) |
2352 | 2660 |
2353 return ret | 2661 return ret |
2354 | 2662 |
2663 | |
2355 def _catfmtneedsdata(fm): | 2664 def _catfmtneedsdata(fm): |
2356 return not fm.datahint() or 'data' in fm.datahint() | 2665 return not fm.datahint() or 'data' in fm.datahint() |
2666 | |
2357 | 2667 |
2358 def _updatecatformatter(fm, ctx, matcher, path, decode): | 2668 def _updatecatformatter(fm, ctx, matcher, path, decode): |
2359 """Hook for adding data to the formatter used by ``hg cat``. | 2669 """Hook for adding data to the formatter used by ``hg cat``. |
2360 | 2670 |
2361 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call | 2671 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call |
2371 fm.startitem() | 2681 fm.startitem() |
2372 fm.context(ctx=ctx) | 2682 fm.context(ctx=ctx) |
2373 fm.write('data', '%s', data) | 2683 fm.write('data', '%s', data) |
2374 fm.data(path=path) | 2684 fm.data(path=path) |
2375 | 2685 |
2686 | |
2376 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts): | 2687 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts): |
2377 err = 1 | 2688 err = 1 |
2378 opts = pycompat.byteskwargs(opts) | 2689 opts = pycompat.byteskwargs(opts) |
2379 | 2690 |
2380 def write(path): | 2691 def write(path): |
2381 filename = None | 2692 filename = None |
2382 if fntemplate: | 2693 if fntemplate: |
2383 filename = makefilename(ctx, fntemplate, | 2694 filename = makefilename( |
2384 pathname=os.path.join(prefix, path)) | 2695 ctx, fntemplate, pathname=os.path.join(prefix, path) |
2696 ) | |
2385 # attempt to create the directory if it does not already exist | 2697 # attempt to create the directory if it does not already exist |
2386 try: | 2698 try: |
2387 os.makedirs(os.path.dirname(filename)) | 2699 os.makedirs(os.path.dirname(filename)) |
2388 except OSError: | 2700 except OSError: |
2389 pass | 2701 pass |
2416 for subpath in sorted(ctx.substate): | 2728 for subpath in sorted(ctx.substate): |
2417 sub = ctx.sub(subpath) | 2729 sub = ctx.sub(subpath) |
2418 try: | 2730 try: |
2419 submatch = matchmod.subdirmatcher(subpath, matcher) | 2731 submatch = matchmod.subdirmatcher(subpath, matcher) |
2420 subprefix = os.path.join(prefix, subpath) | 2732 subprefix = os.path.join(prefix, subpath) |
2421 if not sub.cat(submatch, basefm, fntemplate, subprefix, | 2733 if not sub.cat( |
2422 **pycompat.strkwargs(opts)): | 2734 submatch, |
2735 basefm, | |
2736 fntemplate, | |
2737 subprefix, | |
2738 **pycompat.strkwargs(opts) | |
2739 ): | |
2423 err = 0 | 2740 err = 0 |
2424 except error.RepoLookupError: | 2741 except error.RepoLookupError: |
2425 ui.status(_("skipping missing subrepository: %s\n") % | 2742 ui.status( |
2426 uipathfn(subpath)) | 2743 _("skipping missing subrepository: %s\n") % uipathfn(subpath) |
2744 ) | |
2427 | 2745 |
2428 return err | 2746 return err |
2747 | |
2429 | 2748 |
2430 def commit(ui, repo, commitfunc, pats, opts): | 2749 def commit(ui, repo, commitfunc, pats, opts): |
2431 '''commit the specified files or all outstanding changes''' | 2750 '''commit the specified files or all outstanding changes''' |
2432 date = opts.get('date') | 2751 date = opts.get('date') |
2433 if date: | 2752 if date: |
2444 if dsguard: | 2763 if dsguard: |
2445 relative = scmutil.anypats(pats, opts) | 2764 relative = scmutil.anypats(pats, opts) |
2446 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative) | 2765 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative) |
2447 if scmutil.addremove(repo, matcher, "", uipathfn, opts) != 0: | 2766 if scmutil.addremove(repo, matcher, "", uipathfn, opts) != 0: |
2448 raise error.Abort( | 2767 raise error.Abort( |
2449 _("failed to mark all new/missing files as added/removed")) | 2768 _("failed to mark all new/missing files as added/removed") |
2769 ) | |
2450 | 2770 |
2451 return commitfunc(ui, repo, message, matcher, opts) | 2771 return commitfunc(ui, repo, message, matcher, opts) |
2772 | |
2452 | 2773 |
2453 def samefile(f, ctx1, ctx2): | 2774 def samefile(f, ctx1, ctx2): |
2454 if f in ctx1.manifest(): | 2775 if f in ctx1.manifest(): |
2455 a = ctx1.filectx(f) | 2776 a = ctx1.filectx(f) |
2456 if f in ctx2.manifest(): | 2777 if f in ctx2.manifest(): |
2457 b = ctx2.filectx(f) | 2778 b = ctx2.filectx(f) |
2458 return (not a.cmp(b) | 2779 return not a.cmp(b) and a.flags() == b.flags() |
2459 and a.flags() == b.flags()) | |
2460 else: | 2780 else: |
2461 return False | 2781 return False |
2462 else: | 2782 else: |
2463 return f not in ctx2.manifest() | 2783 return f not in ctx2.manifest() |
2464 | 2784 |
2785 | |
2465 def amend(ui, repo, old, extra, pats, opts): | 2786 def amend(ui, repo, old, extra, pats, opts): |
2466 # avoid cycle context -> subrepo -> cmdutil | 2787 # avoid cycle context -> subrepo -> cmdutil |
2467 from . import context | 2788 from . import context |
2468 | 2789 |
2469 # amend will reuse the existing user if not specified, but the obsolete | 2790 # amend will reuse the existing user if not specified, but the obsolete |
2470 # marker creation requires that the current user's name is specified. | 2791 # marker creation requires that the current user's name is specified. |
2471 if obsolete.isenabled(repo, obsolete.createmarkersopt): | 2792 if obsolete.isenabled(repo, obsolete.createmarkersopt): |
2472 ui.username() # raise exception if username not set | 2793 ui.username() # raise exception if username not set |
2473 | 2794 |
2474 ui.note(_('amending changeset %s\n') % old) | 2795 ui.note(_('amending changeset %s\n') % old) |
2475 base = old.p1() | 2796 base = old.p1() |
2476 | 2797 |
2477 with repo.wlock(), repo.lock(), repo.transaction('amend'): | 2798 with repo.wlock(), repo.lock(), repo.transaction('amend'): |
2512 # add/remove the files to the working copy if the "addremove" option | 2833 # add/remove the files to the working copy if the "addremove" option |
2513 # was specified. | 2834 # was specified. |
2514 matcher = scmutil.match(wctx, pats, opts) | 2835 matcher = scmutil.match(wctx, pats, opts) |
2515 relative = scmutil.anypats(pats, opts) | 2836 relative = scmutil.anypats(pats, opts) |
2516 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative) | 2837 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative) |
2517 if (opts.get('addremove') | 2838 if opts.get('addremove') and scmutil.addremove( |
2518 and scmutil.addremove(repo, matcher, "", uipathfn, opts)): | 2839 repo, matcher, "", uipathfn, opts |
2840 ): | |
2519 raise error.Abort( | 2841 raise error.Abort( |
2520 _("failed to mark all new/missing files as added/removed")) | 2842 _("failed to mark all new/missing files as added/removed") |
2843 ) | |
2521 | 2844 |
2522 # Check subrepos. This depends on in-place wctx._status update in | 2845 # Check subrepos. This depends on in-place wctx._status update in |
2523 # subrepo.precommit(). To minimize the risk of this hack, we do | 2846 # subrepo.precommit(). To minimize the risk of this hack, we do |
2524 # nothing if .hgsub does not exist. | 2847 # nothing if .hgsub does not exist. |
2525 if '.hgsub' in wctx or '.hgsub' in old: | 2848 if '.hgsub' in wctx or '.hgsub' in old: |
2526 subs, commitsubs, newsubstate = subrepoutil.precommit( | 2849 subs, commitsubs, newsubstate = subrepoutil.precommit( |
2527 ui, wctx, wctx._status, matcher) | 2850 ui, wctx, wctx._status, matcher |
2851 ) | |
2528 # amend should abort if commitsubrepos is enabled | 2852 # amend should abort if commitsubrepos is enabled |
2529 assert not commitsubs | 2853 assert not commitsubs |
2530 if subs: | 2854 if subs: |
2531 subrepoutil.writestate(repo, newsubstate) | 2855 subrepoutil.writestate(repo, newsubstate) |
2532 | 2856 |
2533 ms = mergemod.mergestate.read(repo) | 2857 ms = mergemod.mergestate.read(repo) |
2534 mergeutil.checkunresolved(ms) | 2858 mergeutil.checkunresolved(ms) |
2535 | 2859 |
2536 filestoamend = set(f for f in wctx.files() if matcher(f)) | 2860 filestoamend = set(f for f in wctx.files() if matcher(f)) |
2537 | 2861 |
2538 changes = (len(filestoamend) > 0) | 2862 changes = len(filestoamend) > 0 |
2539 if changes: | 2863 if changes: |
2540 # Recompute copies (avoid recording a -> b -> a) | 2864 # Recompute copies (avoid recording a -> b -> a) |
2541 copied = copies.pathcopies(base, wctx, matcher) | 2865 copied = copies.pathcopies(base, wctx, matcher) |
2542 if old.p2: | 2866 if old.p2: |
2543 copied.update(copies.pathcopies(old.p2(), wctx, matcher)) | 2867 copied.update(copies.pathcopies(old.p2(), wctx, matcher)) |
2547 # copy, then those two files are the same and | 2871 # copy, then those two files are the same and |
2548 # we can discard X from our list of files. Likewise if X | 2872 # we can discard X from our list of files. Likewise if X |
2549 # was removed, it's no longer relevant. If X is missing (aka | 2873 # was removed, it's no longer relevant. If X is missing (aka |
2550 # deleted), old X must be preserved. | 2874 # deleted), old X must be preserved. |
2551 files.update(filestoamend) | 2875 files.update(filestoamend) |
2552 files = [f for f in files if (f not in filestoamend | 2876 files = [ |
2553 or not samefile(f, wctx, base))] | 2877 f |
2878 for f in files | |
2879 if (f not in filestoamend or not samefile(f, wctx, base)) | |
2880 ] | |
2554 | 2881 |
2555 def filectxfn(repo, ctx_, path): | 2882 def filectxfn(repo, ctx_, path): |
2556 try: | 2883 try: |
2557 # If the file being considered is not amongst the files | 2884 # If the file being considered is not amongst the files |
2558 # to be amended, we should return the file context from the | 2885 # to be amended, we should return the file context from the |
2566 if path in wctx.removed(): | 2893 if path in wctx.removed(): |
2567 return None | 2894 return None |
2568 | 2895 |
2569 fctx = wctx[path] | 2896 fctx = wctx[path] |
2570 flags = fctx.flags() | 2897 flags = fctx.flags() |
2571 mctx = context.memfilectx(repo, ctx_, | 2898 mctx = context.memfilectx( |
2572 fctx.path(), fctx.data(), | 2899 repo, |
2573 islink='l' in flags, | 2900 ctx_, |
2574 isexec='x' in flags, | 2901 fctx.path(), |
2575 copysource=copied.get(path)) | 2902 fctx.data(), |
2903 islink='l' in flags, | |
2904 isexec='x' in flags, | |
2905 copysource=copied.get(path), | |
2906 ) | |
2576 return mctx | 2907 return mctx |
2577 except KeyError: | 2908 except KeyError: |
2578 return None | 2909 return None |
2910 | |
2579 else: | 2911 else: |
2580 ui.note(_('copying changeset %s to %s\n') % (old, base)) | 2912 ui.note(_('copying changeset %s to %s\n') % (old, base)) |
2581 | 2913 |
2582 # Use version of files as in the old cset | 2914 # Use version of files as in the old cset |
2583 def filectxfn(repo, ctx_, path): | 2915 def filectxfn(repo, ctx_, path): |
2605 editor = getcommiteditor(edit=doedit, editform=editform) | 2937 editor = getcommiteditor(edit=doedit, editform=editform) |
2606 | 2938 |
2607 pureextra = extra.copy() | 2939 pureextra = extra.copy() |
2608 extra['amend_source'] = old.hex() | 2940 extra['amend_source'] = old.hex() |
2609 | 2941 |
2610 new = context.memctx(repo, | 2942 new = context.memctx( |
2611 parents=[base.node(), old.p2().node()], | 2943 repo, |
2612 text=message, | 2944 parents=[base.node(), old.p2().node()], |
2613 files=files, | 2945 text=message, |
2614 filectxfn=filectxfn, | 2946 files=files, |
2615 user=user, | 2947 filectxfn=filectxfn, |
2616 date=date, | 2948 user=user, |
2617 extra=extra, | 2949 date=date, |
2618 editor=editor) | 2950 extra=extra, |
2951 editor=editor, | |
2952 ) | |
2619 | 2953 |
2620 newdesc = changelog.stripdesc(new.description()) | 2954 newdesc = changelog.stripdesc(new.description()) |
2621 if ((not changes) | 2955 if ( |
2956 (not changes) | |
2622 and newdesc == old.description() | 2957 and newdesc == old.description() |
2623 and user == old.user() | 2958 and user == old.user() |
2624 and (date == old.date() or datemaydiffer) | 2959 and (date == old.date() or datemaydiffer) |
2625 and pureextra == old.extra()): | 2960 and pureextra == old.extra() |
2961 ): | |
2626 # nothing changed. continuing here would create a new node | 2962 # nothing changed. continuing here would create a new node |
2627 # anyway because of the amend_source noise. | 2963 # anyway because of the amend_source noise. |
2628 # | 2964 # |
2629 # This not what we expect from amend. | 2965 # This not what we expect from amend. |
2630 return old.node() | 2966 return old.node() |
2639 mapping = {old.node(): (newid,)} | 2975 mapping = {old.node(): (newid,)} |
2640 obsmetadata = None | 2976 obsmetadata = None |
2641 if opts.get('note'): | 2977 if opts.get('note'): |
2642 obsmetadata = {'note': encoding.fromlocal(opts['note'])} | 2978 obsmetadata = {'note': encoding.fromlocal(opts['note'])} |
2643 backup = ui.configbool('rewrite', 'backup-bundle') | 2979 backup = ui.configbool('rewrite', 'backup-bundle') |
2644 scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata, | 2980 scmutil.cleanupnodes( |
2645 fixphase=True, targetphase=commitphase, | 2981 repo, |
2646 backup=backup) | 2982 mapping, |
2983 'amend', | |
2984 metadata=obsmetadata, | |
2985 fixphase=True, | |
2986 targetphase=commitphase, | |
2987 backup=backup, | |
2988 ) | |
2647 | 2989 |
2648 # Fixing the dirstate because localrepo.commitctx does not update | 2990 # Fixing the dirstate because localrepo.commitctx does not update |
2649 # it. This is rather convenient because we did not need to update | 2991 # it. This is rather convenient because we did not need to update |
2650 # the dirstate for all the files in the new commit which commitctx | 2992 # the dirstate for all the files in the new commit which commitctx |
2651 # could have done if it updated the dirstate. Now, we can | 2993 # could have done if it updated the dirstate. Now, we can |
2664 for f in removedfiles: | 3006 for f in removedfiles: |
2665 dirstate.drop(f) | 3007 dirstate.drop(f) |
2666 | 3008 |
2667 return newid | 3009 return newid |
2668 | 3010 |
3011 | |
2669 def commiteditor(repo, ctx, subs, editform=''): | 3012 def commiteditor(repo, ctx, subs, editform=''): |
2670 if ctx.description(): | 3013 if ctx.description(): |
2671 return ctx.description() | 3014 return ctx.description() |
2672 return commitforceeditor(repo, ctx, subs, editform=editform, | 3015 return commitforceeditor( |
2673 unchangedmessagedetection=True) | 3016 repo, ctx, subs, editform=editform, unchangedmessagedetection=True |
2674 | 3017 ) |
2675 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None, | 3018 |
2676 editform='', unchangedmessagedetection=False): | 3019 |
3020 def commitforceeditor( | |
3021 repo, | |
3022 ctx, | |
3023 subs, | |
3024 finishdesc=None, | |
3025 extramsg=None, | |
3026 editform='', | |
3027 unchangedmessagedetection=False, | |
3028 ): | |
2677 if not extramsg: | 3029 if not extramsg: |
2678 extramsg = _("Leave message empty to abort commit.") | 3030 extramsg = _("Leave message empty to abort commit.") |
2679 | 3031 |
2680 forms = [e for e in editform.split('.') if e] | 3032 forms = [e for e in editform.split('.') if e] |
2681 forms.insert(0, 'changeset') | 3033 forms.insert(0, 'changeset') |
2682 templatetext = None | 3034 templatetext = None |
2683 while forms: | 3035 while forms: |
2684 ref = '.'.join(forms) | 3036 ref = '.'.join(forms) |
2685 if repo.ui.config('committemplate', ref): | 3037 if repo.ui.config('committemplate', ref): |
2686 templatetext = committext = buildcommittemplate( | 3038 templatetext = committext = buildcommittemplate( |
2687 repo, ctx, subs, extramsg, ref) | 3039 repo, ctx, subs, extramsg, ref |
3040 ) | |
2688 break | 3041 break |
2689 forms.pop() | 3042 forms.pop() |
2690 else: | 3043 else: |
2691 committext = buildcommittext(repo, ctx, subs, extramsg) | 3044 committext = buildcommittext(repo, ctx, subs, extramsg) |
2692 | 3045 |
2697 # make in-memory changes visible to external process | 3050 # make in-memory changes visible to external process |
2698 tr = repo.currenttransaction() | 3051 tr = repo.currenttransaction() |
2699 repo.dirstate.write(tr) | 3052 repo.dirstate.write(tr) |
2700 pending = tr and tr.writepending() and repo.root | 3053 pending = tr and tr.writepending() and repo.root |
2701 | 3054 |
2702 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(), | 3055 editortext = repo.ui.edit( |
2703 editform=editform, pending=pending, | 3056 committext, |
2704 repopath=repo.path, action='commit') | 3057 ctx.user(), |
3058 ctx.extra(), | |
3059 editform=editform, | |
3060 pending=pending, | |
3061 repopath=repo.path, | |
3062 action='commit', | |
3063 ) | |
2705 text = editortext | 3064 text = editortext |
2706 | 3065 |
2707 # strip away anything below this special string (used for editors that want | 3066 # strip away anything below this special string (used for editors that want |
2708 # to display the diff) | 3067 # to display the diff) |
2709 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE) | 3068 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE) |
2710 if stripbelow: | 3069 if stripbelow: |
2711 text = text[:stripbelow.start()] | 3070 text = text[: stripbelow.start()] |
2712 | 3071 |
2713 text = re.sub("(?m)^HG:.*(\n|$)", "", text) | 3072 text = re.sub("(?m)^HG:.*(\n|$)", "", text) |
2714 os.chdir(olddir) | 3073 os.chdir(olddir) |
2715 | 3074 |
2716 if finishdesc: | 3075 if finishdesc: |
2720 if unchangedmessagedetection and editortext == templatetext: | 3079 if unchangedmessagedetection and editortext == templatetext: |
2721 raise error.Abort(_("commit message unchanged")) | 3080 raise error.Abort(_("commit message unchanged")) |
2722 | 3081 |
2723 return text | 3082 return text |
2724 | 3083 |
3084 | |
2725 def buildcommittemplate(repo, ctx, subs, extramsg, ref): | 3085 def buildcommittemplate(repo, ctx, subs, extramsg, ref): |
2726 ui = repo.ui | 3086 ui = repo.ui |
2727 spec = formatter.templatespec(ref, None, None) | 3087 spec = formatter.templatespec(ref, None, None) |
2728 t = logcmdutil.changesettemplater(ui, repo, spec) | 3088 t = logcmdutil.changesettemplater(ui, repo, spec) |
2729 t.t.cache.update((k, templater.unquotestring(v)) | 3089 t.t.cache.update( |
2730 for k, v in repo.ui.configitems('committemplate')) | 3090 (k, templater.unquotestring(v)) |
3091 for k, v in repo.ui.configitems('committemplate') | |
3092 ) | |
2731 | 3093 |
2732 if not extramsg: | 3094 if not extramsg: |
2733 extramsg = '' # ensure that extramsg is string | 3095 extramsg = '' # ensure that extramsg is string |
2734 | 3096 |
2735 ui.pushbuffer() | 3097 ui.pushbuffer() |
2736 t.show(ctx, extramsg=extramsg) | 3098 t.show(ctx, extramsg=extramsg) |
2737 return ui.popbuffer() | 3099 return ui.popbuffer() |
2738 | 3100 |
3101 | |
2739 def hgprefix(msg): | 3102 def hgprefix(msg): |
2740 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a]) | 3103 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a]) |
3104 | |
2741 | 3105 |
2742 def buildcommittext(repo, ctx, subs, extramsg): | 3106 def buildcommittext(repo, ctx, subs, extramsg): |
2743 edittext = [] | 3107 edittext = [] |
2744 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed() | 3108 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed() |
2745 if ctx.description(): | 3109 if ctx.description(): |
2746 edittext.append(ctx.description()) | 3110 edittext.append(ctx.description()) |
2747 edittext.append("") | 3111 edittext.append("") |
2748 edittext.append("") # Empty line between message and comments. | 3112 edittext.append("") # Empty line between message and comments. |
2749 edittext.append(hgprefix(_("Enter commit message." | 3113 edittext.append( |
2750 " Lines beginning with 'HG:' are removed."))) | 3114 hgprefix( |
3115 _( | |
3116 "Enter commit message." | |
3117 " Lines beginning with 'HG:' are removed." | |
3118 ) | |
3119 ) | |
3120 ) | |
2751 edittext.append(hgprefix(extramsg)) | 3121 edittext.append(hgprefix(extramsg)) |
2752 edittext.append("HG: --") | 3122 edittext.append("HG: --") |
2753 edittext.append(hgprefix(_("user: %s") % ctx.user())) | 3123 edittext.append(hgprefix(_("user: %s") % ctx.user())) |
2754 if ctx.p2(): | 3124 if ctx.p2(): |
2755 edittext.append(hgprefix(_("branch merge"))) | 3125 edittext.append(hgprefix(_("branch merge"))) |
2765 edittext.append(hgprefix(_("no files changed"))) | 3135 edittext.append(hgprefix(_("no files changed"))) |
2766 edittext.append("") | 3136 edittext.append("") |
2767 | 3137 |
2768 return "\n".join(edittext) | 3138 return "\n".join(edittext) |
2769 | 3139 |
3140 | |
2770 def commitstatus(repo, node, branch, bheads=None, opts=None): | 3141 def commitstatus(repo, node, branch, bheads=None, opts=None): |
2771 if opts is None: | 3142 if opts is None: |
2772 opts = {} | 3143 opts = {} |
2773 ctx = repo[node] | 3144 ctx = repo[node] |
2774 parents = ctx.parents() | 3145 parents = ctx.parents() |
2775 | 3146 |
2776 if (not opts.get('amend') and bheads and node not in bheads and not | 3147 if ( |
2777 [x for x in parents if x.node() in bheads and x.branch() == branch]): | 3148 not opts.get('amend') |
3149 and bheads | |
3150 and node not in bheads | |
3151 and not [ | |
3152 x for x in parents if x.node() in bheads and x.branch() == branch | |
3153 ] | |
3154 ): | |
2778 repo.ui.status(_('created new head\n')) | 3155 repo.ui.status(_('created new head\n')) |
2779 # The message is not printed for initial roots. For the other | 3156 # The message is not printed for initial roots. For the other |
2780 # changesets, it is printed in the following situations: | 3157 # changesets, it is printed in the following situations: |
2781 # | 3158 # |
2782 # Par column: for the 2 parents with ... | 3159 # Par column: for the 2 parents with ... |
2813 if repo.ui.debugflag: | 3190 if repo.ui.debugflag: |
2814 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())) | 3191 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())) |
2815 elif repo.ui.verbose: | 3192 elif repo.ui.verbose: |
2816 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx)) | 3193 repo.ui.write(_('committed changeset %d:%s\n') % (ctx.rev(), ctx)) |
2817 | 3194 |
3195 | |
2818 def postcommitstatus(repo, pats, opts): | 3196 def postcommitstatus(repo, pats, opts): |
2819 return repo.status(match=scmutil.match(repo[None], pats, opts)) | 3197 return repo.status(match=scmutil.match(repo[None], pats, opts)) |
3198 | |
2820 | 3199 |
2821 def revert(ui, repo, ctx, parents, *pats, **opts): | 3200 def revert(ui, repo, ctx, parents, *pats, **opts): |
2822 opts = pycompat.byteskwargs(opts) | 3201 opts = pycompat.byteskwargs(opts) |
2823 parent, p2 = parents | 3202 parent, p2 = parents |
2824 node = ctx.node() | 3203 node = ctx.node() |
2873 names[abs] = m.exact(abs) | 3252 names[abs] = m.exact(abs) |
2874 | 3253 |
2875 # Find status of all file in `names`. | 3254 # Find status of all file in `names`. |
2876 m = scmutil.matchfiles(repo, names) | 3255 m = scmutil.matchfiles(repo, names) |
2877 | 3256 |
2878 changes = repo.status(node1=node, match=m, | 3257 changes = repo.status( |
2879 unknown=True, ignored=True, clean=True) | 3258 node1=node, match=m, unknown=True, ignored=True, clean=True |
3259 ) | |
2880 else: | 3260 else: |
2881 changes = repo.status(node1=node, match=m) | 3261 changes = repo.status(node1=node, match=m) |
2882 for kind in changes: | 3262 for kind in changes: |
2883 for abs in kind: | 3263 for abs in kind: |
2884 names[abs] = m.exact(abs) | 3264 names[abs] = m.exact(abs) |
2885 | 3265 |
2886 m = scmutil.matchfiles(repo, names) | 3266 m = scmutil.matchfiles(repo, names) |
2887 | 3267 |
2888 modified = set(changes.modified) | 3268 modified = set(changes.modified) |
2889 added = set(changes.added) | 3269 added = set(changes.added) |
2890 removed = set(changes.removed) | 3270 removed = set(changes.removed) |
2891 _deleted = set(changes.deleted) | 3271 _deleted = set(changes.deleted) |
2892 unknown = set(changes.unknown) | 3272 unknown = set(changes.unknown) |
2893 unknown.update(changes.ignored) | 3273 unknown.update(changes.ignored) |
2894 clean = set(changes.clean) | 3274 clean = set(changes.clean) |
2895 modadded = set() | 3275 modadded = set() |
2896 | 3276 |
2897 # We need to account for the state of the file in the dirstate, | 3277 # We need to account for the state of the file in the dirstate, |
2898 # even when we revert against something else than parent. This will | 3278 # even when we revert against something else than parent. This will |
2899 # slightly alter the behavior of revert (doing back up or not, delete | 3279 # slightly alter the behavior of revert (doing back up or not, delete |
2906 localchanges = dsmodified | dsadded | 3286 localchanges = dsmodified | dsadded |
2907 modified, added, removed = set(), set(), set() | 3287 modified, added, removed = set(), set(), set() |
2908 else: | 3288 else: |
2909 changes = repo.status(node1=parent, match=m) | 3289 changes = repo.status(node1=parent, match=m) |
2910 dsmodified = set(changes.modified) | 3290 dsmodified = set(changes.modified) |
2911 dsadded = set(changes.added) | 3291 dsadded = set(changes.added) |
2912 dsremoved = set(changes.removed) | 3292 dsremoved = set(changes.removed) |
2913 # store all local modifications, useful later for rename detection | 3293 # store all local modifications, useful later for rename detection |
2914 localchanges = dsmodified | dsadded | 3294 localchanges = dsmodified | dsadded |
2915 | 3295 |
2916 # only take into account for removes between wc and target | 3296 # only take into account for removes between wc and target |
2917 clean |= dsremoved - removed | 3297 clean |= dsremoved - removed |
2922 modadded = added & dsmodified | 3302 modadded = added & dsmodified |
2923 added -= modadded | 3303 added -= modadded |
2924 | 3304 |
2925 # tell newly modified apart. | 3305 # tell newly modified apart. |
2926 dsmodified &= modified | 3306 dsmodified &= modified |
2927 dsmodified |= modified & dsadded # dirstate added may need backup | 3307 dsmodified |= modified & dsadded # dirstate added may need backup |
2928 modified -= dsmodified | 3308 modified -= dsmodified |
2929 | 3309 |
2930 # We need to wait for some post-processing to update this set | 3310 # We need to wait for some post-processing to update this set |
2931 # before making the distinction. The dirstate will be used for | 3311 # before making the distinction. The dirstate will be used for |
2932 # that purpose. | 3312 # that purpose. |
2987 dsremovunk.add(abs) | 3367 dsremovunk.add(abs) |
2988 dsremoved -= dsremovunk | 3368 dsremoved -= dsremovunk |
2989 | 3369 |
2990 # action to be actually performed by revert | 3370 # action to be actually performed by revert |
2991 # (<list of file>, message>) tuple | 3371 # (<list of file>, message>) tuple |
2992 actions = {'revert': ([], _('reverting %s\n')), | 3372 actions = { |
2993 'add': ([], _('adding %s\n')), | 3373 'revert': ([], _('reverting %s\n')), |
2994 'remove': ([], _('removing %s\n')), | 3374 'add': ([], _('adding %s\n')), |
2995 'drop': ([], _('removing %s\n')), | 3375 'remove': ([], _('removing %s\n')), |
2996 'forget': ([], _('forgetting %s\n')), | 3376 'drop': ([], _('removing %s\n')), |
2997 'undelete': ([], _('undeleting %s\n')), | 3377 'forget': ([], _('forgetting %s\n')), |
2998 'noop': (None, _('no changes needed to %s\n')), | 3378 'undelete': ([], _('undeleting %s\n')), |
2999 'unknown': (None, _('file not managed: %s\n')), | 3379 'noop': (None, _('no changes needed to %s\n')), |
3000 } | 3380 'unknown': (None, _('file not managed: %s\n')), |
3381 } | |
3001 | 3382 |
3002 # "constant" that convey the backup strategy. | 3383 # "constant" that convey the backup strategy. |
3003 # All set to `discard` if `no-backup` is set do avoid checking | 3384 # All set to `discard` if `no-backup` is set do avoid checking |
3004 # no_backup lower in the code. | 3385 # no_backup lower in the code. |
3005 # These values are ordered for comparison purposes | 3386 # These values are ordered for comparison purposes |
3006 backupinteractive = 3 # do backup if interactively modified | 3387 backupinteractive = 3 # do backup if interactively modified |
3007 backup = 2 # unconditionally do backup | 3388 backup = 2 # unconditionally do backup |
3008 check = 1 # check if the existing file differs from target | 3389 check = 1 # check if the existing file differs from target |
3009 discard = 0 # never do backup | 3390 discard = 0 # never do backup |
3010 if opts.get('no_backup'): | 3391 if opts.get('no_backup'): |
3011 backupinteractive = backup = check = discard | 3392 backupinteractive = backup = check = discard |
3012 if interactive: | 3393 if interactive: |
3013 dsmodifiedbackup = backupinteractive | 3394 dsmodifiedbackup = backupinteractive |
3014 else: | 3395 else: |
3022 disptable = ( | 3403 disptable = ( |
3023 # dispatch table: | 3404 # dispatch table: |
3024 # file state | 3405 # file state |
3025 # action | 3406 # action |
3026 # make backup | 3407 # make backup |
3027 | |
3028 ## Sets that results that will change file on disk | 3408 ## Sets that results that will change file on disk |
3029 # Modified compared to target, no local change | 3409 # Modified compared to target, no local change |
3030 (modified, actions['revert'], discard), | 3410 (modified, actions['revert'], discard), |
3031 # Modified compared to target, but local file is deleted | 3411 # Modified compared to target, but local file is deleted |
3032 (deleted, actions['revert'], discard), | 3412 (deleted, actions['revert'], discard), |
3033 # Modified compared to target, local change | 3413 # Modified compared to target, local change |
3034 (dsmodified, actions['revert'], dsmodifiedbackup), | 3414 (dsmodified, actions['revert'], dsmodifiedbackup), |
3035 # Added since target | 3415 # Added since target |
3036 (added, actions['remove'], discard), | 3416 (added, actions['remove'], discard), |
3037 # Added in working directory | 3417 # Added in working directory |
3038 (dsadded, actions['forget'], discard), | 3418 (dsadded, actions['forget'], discard), |
3039 # Added since target, have local modification | 3419 # Added since target, have local modification |
3040 (modadded, backupanddel, backup), | 3420 (modadded, backupanddel, backup), |
3041 # Added since target but file is missing in working directory | 3421 # Added since target but file is missing in working directory |
3042 (deladded, actions['drop'], discard), | 3422 (deladded, actions['drop'], discard), |
3043 # Removed since target, before working copy parent | 3423 # Removed since target, before working copy parent |
3044 (removed, actions['add'], discard), | 3424 (removed, actions['add'], discard), |
3045 # Same as `removed` but an unknown file exists at the same path | 3425 # Same as `removed` but an unknown file exists at the same path |
3046 (removunk, actions['add'], check), | 3426 (removunk, actions['add'], check), |
3047 # Removed since targe, marked as such in working copy parent | 3427 # Removed since targe, marked as such in working copy parent |
3048 (dsremoved, actions['undelete'], discard), | 3428 (dsremoved, actions['undelete'], discard), |
3049 # Same as `dsremoved` but an unknown file exists at the same path | 3429 # Same as `dsremoved` but an unknown file exists at the same path |
3050 (dsremovunk, actions['undelete'], check), | 3430 (dsremovunk, actions['undelete'], check), |
3051 ## the following sets does not result in any file changes | 3431 ## the following sets does not result in any file changes |
3052 # File with no modification | 3432 # File with no modification |
3053 (clean, actions['noop'], discard), | 3433 (clean, actions['noop'], discard), |
3054 # Existing file, not tracked anywhere | 3434 # Existing file, not tracked anywhere |
3055 (unknown, actions['unknown'], discard), | 3435 (unknown, actions['unknown'], discard), |
3056 ) | 3436 ) |
3057 | 3437 |
3058 for abs, exact in sorted(names.items()): | 3438 for abs, exact in sorted(names.items()): |
3059 # target file to be touch on disk (relative to cwd) | 3439 # target file to be touch on disk (relative to cwd) |
3060 target = repo.wjoin(abs) | 3440 target = repo.wjoin(abs) |
3061 # search the entry in the dispatch table. | 3441 # search the entry in the dispatch table. |
3069 if dobackup: | 3449 if dobackup: |
3070 # If in interactive mode, don't automatically create | 3450 # If in interactive mode, don't automatically create |
3071 # .orig files (issue4793) | 3451 # .orig files (issue4793) |
3072 if dobackup == backupinteractive: | 3452 if dobackup == backupinteractive: |
3073 tobackup.add(abs) | 3453 tobackup.add(abs) |
3074 elif (backup <= dobackup or wctx[abs].cmp(ctx[abs])): | 3454 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]): |
3075 absbakname = scmutil.backuppath(ui, repo, abs) | 3455 absbakname = scmutil.backuppath(ui, repo, abs) |
3076 bakname = os.path.relpath(absbakname, | 3456 bakname = os.path.relpath( |
3077 start=repo.root) | 3457 absbakname, start=repo.root |
3078 ui.note(_('saving current version of %s as %s\n') % | 3458 ) |
3079 (uipathfn(abs), uipathfn(bakname))) | 3459 ui.note( |
3460 _('saving current version of %s as %s\n') | |
3461 % (uipathfn(abs), uipathfn(bakname)) | |
3462 ) | |
3080 if not opts.get('dry_run'): | 3463 if not opts.get('dry_run'): |
3081 if interactive: | 3464 if interactive: |
3082 util.copyfile(target, absbakname) | 3465 util.copyfile(target, absbakname) |
3083 else: | 3466 else: |
3084 util.rename(target, absbakname) | 3467 util.rename(target, absbakname) |
3092 if not opts.get('dry_run'): | 3475 if not opts.get('dry_run'): |
3093 needdata = ('revert', 'add', 'undelete') | 3476 needdata = ('revert', 'add', 'undelete') |
3094 oplist = [actions[name][0] for name in needdata] | 3477 oplist = [actions[name][0] for name in needdata] |
3095 prefetch = scmutil.prefetchfiles | 3478 prefetch = scmutil.prefetchfiles |
3096 matchfiles = scmutil.matchfiles | 3479 matchfiles = scmutil.matchfiles |
3097 prefetch(repo, [ctx.rev()], | 3480 prefetch( |
3098 matchfiles(repo, | 3481 repo, |
3099 [f for sublist in oplist for f in sublist])) | 3482 [ctx.rev()], |
3483 matchfiles(repo, [f for sublist in oplist for f in sublist]), | |
3484 ) | |
3100 match = scmutil.match(repo[None], pats) | 3485 match = scmutil.match(repo[None], pats) |
3101 _performrevert(repo, parents, ctx, names, uipathfn, actions, | 3486 _performrevert( |
3102 match, interactive, tobackup) | 3487 repo, |
3488 parents, | |
3489 ctx, | |
3490 names, | |
3491 uipathfn, | |
3492 actions, | |
3493 match, | |
3494 interactive, | |
3495 tobackup, | |
3496 ) | |
3103 | 3497 |
3104 if targetsubs: | 3498 if targetsubs: |
3105 # Revert the subrepos on the revert list | 3499 # Revert the subrepos on the revert list |
3106 for sub in targetsubs: | 3500 for sub in targetsubs: |
3107 try: | 3501 try: |
3108 wctx.sub(sub).revert(ctx.substate[sub], *pats, | 3502 wctx.sub(sub).revert( |
3109 **pycompat.strkwargs(opts)) | 3503 ctx.substate[sub], *pats, **pycompat.strkwargs(opts) |
3504 ) | |
3110 except KeyError: | 3505 except KeyError: |
3111 raise error.Abort("subrepository '%s' does not exist in %s!" | 3506 raise error.Abort( |
3112 % (sub, short(ctx.node()))) | 3507 "subrepository '%s' does not exist in %s!" |
3113 | 3508 % (sub, short(ctx.node())) |
3114 def _performrevert(repo, parents, ctx, names, uipathfn, actions, | 3509 ) |
3115 match, interactive=False, tobackup=None): | 3510 |
3511 | |
3512 def _performrevert( | |
3513 repo, | |
3514 parents, | |
3515 ctx, | |
3516 names, | |
3517 uipathfn, | |
3518 actions, | |
3519 match, | |
3520 interactive=False, | |
3521 tobackup=None, | |
3522 ): | |
3116 """function that actually perform all the actions computed for revert | 3523 """function that actually perform all the actions computed for revert |
3117 | 3524 |
3118 This is an independent function to let extension to plug in and react to | 3525 This is an independent function to let extension to plug in and react to |
3119 the imminent revert. | 3526 the imminent revert. |
3120 | 3527 |
3143 | 3550 |
3144 audit_path = pathutil.pathauditor(repo.root, cached=True) | 3551 audit_path = pathutil.pathauditor(repo.root, cached=True) |
3145 for f in actions['forget'][0]: | 3552 for f in actions['forget'][0]: |
3146 if interactive: | 3553 if interactive: |
3147 choice = repo.ui.promptchoice( | 3554 choice = repo.ui.promptchoice( |
3148 _("forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)) | 3555 _("forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f) |
3556 ) | |
3149 if choice == 0: | 3557 if choice == 0: |
3150 prntstatusmsg('forget', f) | 3558 prntstatusmsg('forget', f) |
3151 repo.dirstate.drop(f) | 3559 repo.dirstate.drop(f) |
3152 else: | 3560 else: |
3153 excluded_files.append(f) | 3561 excluded_files.append(f) |
3156 repo.dirstate.drop(f) | 3564 repo.dirstate.drop(f) |
3157 for f in actions['remove'][0]: | 3565 for f in actions['remove'][0]: |
3158 audit_path(f) | 3566 audit_path(f) |
3159 if interactive: | 3567 if interactive: |
3160 choice = repo.ui.promptchoice( | 3568 choice = repo.ui.promptchoice( |
3161 _("remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)) | 3569 _("remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f) |
3570 ) | |
3162 if choice == 0: | 3571 if choice == 0: |
3163 prntstatusmsg('remove', f) | 3572 prntstatusmsg('remove', f) |
3164 doremove(f) | 3573 doremove(f) |
3165 else: | 3574 else: |
3166 excluded_files.append(f) | 3575 excluded_files.append(f) |
3185 newlyaddedandmodifiedfiles = set() | 3594 newlyaddedandmodifiedfiles = set() |
3186 if interactive: | 3595 if interactive: |
3187 # Prompt the user for changes to revert | 3596 # Prompt the user for changes to revert |
3188 torevert = [f for f in actions['revert'][0] if f not in excluded_files] | 3597 torevert = [f for f in actions['revert'][0] if f not in excluded_files] |
3189 m = scmutil.matchfiles(repo, torevert) | 3598 m = scmutil.matchfiles(repo, torevert) |
3190 diffopts = patch.difffeatureopts(repo.ui, whitespace=True, | 3599 diffopts = patch.difffeatureopts( |
3191 section='commands', | 3600 repo.ui, |
3192 configprefix='revert.interactive.') | 3601 whitespace=True, |
3602 section='commands', | |
3603 configprefix='revert.interactive.', | |
3604 ) | |
3193 diffopts.nodates = True | 3605 diffopts.nodates = True |
3194 diffopts.git = True | 3606 diffopts.git = True |
3195 operation = 'apply' | 3607 operation = 'apply' |
3196 if node == parent: | 3608 if node == parent: |
3197 if repo.ui.configbool('experimental', | 3609 if repo.ui.configbool( |
3198 'revert.interactive.select-to-keep'): | 3610 'experimental', 'revert.interactive.select-to-keep' |
3611 ): | |
3199 operation = 'keep' | 3612 operation = 'keep' |
3200 else: | 3613 else: |
3201 operation = 'discard' | 3614 operation = 'discard' |
3202 | 3615 |
3203 if operation == 'apply': | 3616 if operation == 'apply': |
3206 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts) | 3619 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts) |
3207 originalchunks = patch.parsepatch(diff) | 3620 originalchunks = patch.parsepatch(diff) |
3208 | 3621 |
3209 try: | 3622 try: |
3210 | 3623 |
3211 chunks, opts = recordfilter(repo.ui, originalchunks, match, | 3624 chunks, opts = recordfilter( |
3212 operation=operation) | 3625 repo.ui, originalchunks, match, operation=operation |
3626 ) | |
3213 if operation == 'discard': | 3627 if operation == 'discard': |
3214 chunks = patch.reversehunks(chunks) | 3628 chunks = patch.reversehunks(chunks) |
3215 | 3629 |
3216 except error.PatchError as err: | 3630 except error.PatchError as err: |
3217 raise error.Abort(_('error parsing patch: %s') % err) | 3631 raise error.Abort(_('error parsing patch: %s') % err) |
3220 # performing a partial revert of the added file, the only option is | 3634 # performing a partial revert of the added file, the only option is |
3221 # "remove added file <name> (Yn)?", so we don't need to worry about the | 3635 # "remove added file <name> (Yn)?", so we don't need to worry about the |
3222 # alsorestore value. Ideally we'd be able to partially revert | 3636 # alsorestore value. Ideally we'd be able to partially revert |
3223 # copied/renamed files. | 3637 # copied/renamed files. |
3224 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified( | 3638 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified( |
3225 chunks, originalchunks) | 3639 chunks, originalchunks |
3640 ) | |
3226 if tobackup is None: | 3641 if tobackup is None: |
3227 tobackup = set() | 3642 tobackup = set() |
3228 # Apply changes | 3643 # Apply changes |
3229 fp = stringio() | 3644 fp = stringio() |
3230 # chunks are serialized per file, but files aren't sorted | 3645 # chunks are serialized per file, but files aren't sorted |
3271 if node == parent and p2 == nullid: | 3686 if node == parent and p2 == nullid: |
3272 normal = repo.dirstate.normal | 3687 normal = repo.dirstate.normal |
3273 for f in actions['undelete'][0]: | 3688 for f in actions['undelete'][0]: |
3274 if interactive: | 3689 if interactive: |
3275 choice = repo.ui.promptchoice( | 3690 choice = repo.ui.promptchoice( |
3276 _("add back removed file %s (Yn)?$$ &Yes $$ &No") % f) | 3691 _("add back removed file %s (Yn)?$$ &Yes $$ &No") % f |
3692 ) | |
3277 if choice == 0: | 3693 if choice == 0: |
3278 prntstatusmsg('undelete', f) | 3694 prntstatusmsg('undelete', f) |
3279 checkout(f) | 3695 checkout(f) |
3280 normal(f) | 3696 normal(f) |
3281 else: | 3697 else: |
3289 | 3705 |
3290 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]: | 3706 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]: |
3291 if f in copied: | 3707 if f in copied: |
3292 repo.dirstate.copy(copied[f], f) | 3708 repo.dirstate.copy(copied[f], f) |
3293 | 3709 |
3710 | |
3294 # a list of (ui, repo, otherpeer, opts, missing) functions called by | 3711 # a list of (ui, repo, otherpeer, opts, missing) functions called by |
3295 # commands.outgoing. "missing" is "missing" of the result of | 3712 # commands.outgoing. "missing" is "missing" of the result of |
3296 # "findcommonoutgoing()" | 3713 # "findcommonoutgoing()" |
3297 outgoinghooks = util.hooks() | 3714 outgoinghooks = util.hooks() |
3298 | 3715 |
3316 bailifchanged(). | 3733 bailifchanged(). |
3317 ''' | 3734 ''' |
3318 # Check for non-clearable states first, so things like rebase will take | 3735 # Check for non-clearable states first, so things like rebase will take |
3319 # precedence over update. | 3736 # precedence over update. |
3320 for state in statemod._unfinishedstates: | 3737 for state in statemod._unfinishedstates: |
3321 if (state._clearable or (commit and state._allowcommit) or | 3738 if ( |
3322 state._reportonly): | 3739 state._clearable |
3740 or (commit and state._allowcommit) | |
3741 or state._reportonly | |
3742 ): | |
3323 continue | 3743 continue |
3324 if state.isunfinished(repo): | 3744 if state.isunfinished(repo): |
3325 raise error.Abort(state.msg(), hint=state.hint()) | 3745 raise error.Abort(state.msg(), hint=state.hint()) |
3326 | 3746 |
3327 for s in statemod._unfinishedstates: | 3747 for s in statemod._unfinishedstates: |
3328 if (not s._clearable or (commit and s._allowcommit) or | 3748 if ( |
3329 (s._opname == 'merge' and skipmerge) or s._reportonly): | 3749 not s._clearable |
3750 or (commit and s._allowcommit) | |
3751 or (s._opname == 'merge' and skipmerge) | |
3752 or s._reportonly | |
3753 ): | |
3330 continue | 3754 continue |
3331 if s.isunfinished(repo): | 3755 if s.isunfinished(repo): |
3332 raise error.Abort(s.msg(), hint=s.hint()) | 3756 raise error.Abort(s.msg(), hint=s.hint()) |
3757 | |
3333 | 3758 |
3334 def clearunfinished(repo): | 3759 def clearunfinished(repo): |
3335 '''Check for unfinished operations (as above), and clear the ones | 3760 '''Check for unfinished operations (as above), and clear the ones |
3336 that are clearable. | 3761 that are clearable. |
3337 ''' | 3762 ''' |
3345 if s._opname == 'merge' or state._reportonly: | 3770 if s._opname == 'merge' or state._reportonly: |
3346 continue | 3771 continue |
3347 if s._clearable and s.isunfinished(repo): | 3772 if s._clearable and s.isunfinished(repo): |
3348 util.unlink(repo.vfs.join(s._fname)) | 3773 util.unlink(repo.vfs.join(s._fname)) |
3349 | 3774 |
3775 | |
3350 def getunfinishedstate(repo): | 3776 def getunfinishedstate(repo): |
3351 ''' Checks for unfinished operations and returns statecheck object | 3777 ''' Checks for unfinished operations and returns statecheck object |
3352 for it''' | 3778 for it''' |
3353 for state in statemod._unfinishedstates: | 3779 for state in statemod._unfinishedstates: |
3354 if state.isunfinished(repo): | 3780 if state.isunfinished(repo): |
3355 return state | 3781 return state |
3356 return None | 3782 return None |
3783 | |
3357 | 3784 |
3358 def howtocontinue(repo): | 3785 def howtocontinue(repo): |
3359 '''Check for an unfinished operation and return the command to finish | 3786 '''Check for an unfinished operation and return the command to finish |
3360 it. | 3787 it. |
3361 | 3788 |
3374 return contmsg % state.continuemsg(), True | 3801 return contmsg % state.continuemsg(), True |
3375 if repo[None].dirty(missing=True, merge=False, branch=False): | 3802 if repo[None].dirty(missing=True, merge=False, branch=False): |
3376 return contmsg % _("hg commit"), False | 3803 return contmsg % _("hg commit"), False |
3377 return None, None | 3804 return None, None |
3378 | 3805 |
3806 | |
3379 def checkafterresolved(repo): | 3807 def checkafterresolved(repo): |
3380 '''Inform the user about the next action after completing hg resolve | 3808 '''Inform the user about the next action after completing hg resolve |
3381 | 3809 |
3382 If there's a an unfinished operation that supports continue flag, | 3810 If there's a an unfinished operation that supports continue flag, |
3383 howtocontinue will yield repo.ui.warn as the reporter. | 3811 howtocontinue will yield repo.ui.warn as the reporter. |
3389 if warning: | 3817 if warning: |
3390 repo.ui.warn("%s\n" % msg) | 3818 repo.ui.warn("%s\n" % msg) |
3391 else: | 3819 else: |
3392 repo.ui.note("%s\n" % msg) | 3820 repo.ui.note("%s\n" % msg) |
3393 | 3821 |
3822 | |
3394 def wrongtooltocontinue(repo, task): | 3823 def wrongtooltocontinue(repo, task): |
3395 '''Raise an abort suggesting how to properly continue if there is an | 3824 '''Raise an abort suggesting how to properly continue if there is an |
3396 active task. | 3825 active task. |
3397 | 3826 |
3398 Uses howtocontinue() to find the active task. | 3827 Uses howtocontinue() to find the active task. |
3403 after = howtocontinue(repo) | 3832 after = howtocontinue(repo) |
3404 hint = None | 3833 hint = None |
3405 if after[1]: | 3834 if after[1]: |
3406 hint = after[0] | 3835 hint = after[0] |
3407 raise error.Abort(_('no %s in progress') % task, hint=hint) | 3836 raise error.Abort(_('no %s in progress') % task, hint=hint) |
3837 | |
3408 | 3838 |
3409 def abortgraft(ui, repo, graftstate): | 3839 def abortgraft(ui, repo, graftstate): |
3410 """abort the interrupted graft and rollbacks to the state before interrupted | 3840 """abort the interrupted graft and rollbacks to the state before interrupted |
3411 graft""" | 3841 graft""" |
3412 if not graftstate.exists(): | 3842 if not graftstate.exists(): |
3424 else: | 3854 else: |
3425 startctx = repo['.'] | 3855 startctx = repo['.'] |
3426 # whether to strip or not | 3856 # whether to strip or not |
3427 cleanup = False | 3857 cleanup = False |
3428 from . import hg | 3858 from . import hg |
3859 | |
3429 if newnodes: | 3860 if newnodes: |
3430 newnodes = [repo[r].rev() for r in newnodes] | 3861 newnodes = [repo[r].rev() for r in newnodes] |
3431 cleanup = True | 3862 cleanup = True |
3432 # checking that none of the newnodes turned public or is public | 3863 # checking that none of the newnodes turned public or is public |
3433 immutable = [c for c in newnodes if not repo[c].mutable()] | 3864 immutable = [c for c in newnodes if not repo[c].mutable()] |
3434 if immutable: | 3865 if immutable: |
3435 repo.ui.warn(_("cannot clean up public changesets %s\n") | 3866 repo.ui.warn( |
3436 % ', '.join(bytes(repo[r]) for r in immutable), | 3867 _("cannot clean up public changesets %s\n") |
3437 hint=_("see 'hg help phases' for details")) | 3868 % ', '.join(bytes(repo[r]) for r in immutable), |
3869 hint=_("see 'hg help phases' for details"), | |
3870 ) | |
3438 cleanup = False | 3871 cleanup = False |
3439 | 3872 |
3440 # checking that no new nodes are created on top of grafted revs | 3873 # checking that no new nodes are created on top of grafted revs |
3441 desc = set(repo.changelog.descendants(newnodes)) | 3874 desc = set(repo.changelog.descendants(newnodes)) |
3442 if desc - set(newnodes): | 3875 if desc - set(newnodes): |
3443 repo.ui.warn(_("new changesets detected on destination " | 3876 repo.ui.warn( |
3444 "branch, can't strip\n")) | 3877 _( |
3878 "new changesets detected on destination " | |
3879 "branch, can't strip\n" | |
3880 ) | |
3881 ) | |
3445 cleanup = False | 3882 cleanup = False |
3446 | 3883 |
3447 if cleanup: | 3884 if cleanup: |
3448 with repo.wlock(), repo.lock(): | 3885 with repo.wlock(), repo.lock(): |
3449 hg.updaterepo(repo, startctx.node(), overwrite=True) | 3886 hg.updaterepo(repo, startctx.node(), overwrite=True) |
3450 # stripping the new nodes created | 3887 # stripping the new nodes created |
3451 strippoints = [c.node() for c in repo.set("roots(%ld)", | 3888 strippoints = [ |
3452 newnodes)] | 3889 c.node() for c in repo.set("roots(%ld)", newnodes) |
3890 ] | |
3453 repair.strip(repo.ui, repo, strippoints, backup=False) | 3891 repair.strip(repo.ui, repo, strippoints, backup=False) |
3454 | 3892 |
3455 if not cleanup: | 3893 if not cleanup: |
3456 # we don't update to the startnode if we can't strip | 3894 # we don't update to the startnode if we can't strip |
3457 startctx = repo['.'] | 3895 startctx = repo['.'] |
3459 | 3897 |
3460 ui.status(_("graft aborted\n")) | 3898 ui.status(_("graft aborted\n")) |
3461 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12]) | 3899 ui.status(_("working directory is now at %s\n") % startctx.hex()[:12]) |
3462 graftstate.delete() | 3900 graftstate.delete() |
3463 return 0 | 3901 return 0 |
3902 | |
3464 | 3903 |
3465 def readgraftstate(repo, graftstate): | 3904 def readgraftstate(repo, graftstate): |
3466 """read the graft state file and return a dict of the data stored in it""" | 3905 """read the graft state file and return a dict of the data stored in it""" |
3467 try: | 3906 try: |
3468 return graftstate.read() | 3907 return graftstate.read() |
3469 except error.CorruptedState: | 3908 except error.CorruptedState: |
3470 nodes = repo.vfs.read('graftstate').splitlines() | 3909 nodes = repo.vfs.read('graftstate').splitlines() |
3471 return {'nodes': nodes} | 3910 return {'nodes': nodes} |
3472 | 3911 |
3912 | |
3473 def hgabortgraft(ui, repo): | 3913 def hgabortgraft(ui, repo): |
3474 """ abort logic for aborting graft using 'hg abort'""" | 3914 """ abort logic for aborting graft using 'hg abort'""" |
3475 with repo.wlock(): | 3915 with repo.wlock(): |
3476 graftstate = statemod.cmdstate(repo, 'graftstate') | 3916 graftstate = statemod.cmdstate(repo, 'graftstate') |
3477 return abortgraft(ui, repo, graftstate) | 3917 return abortgraft(ui, repo, graftstate) |