changeset 43076 | 2372284d9457 |
parent 42933 | 7e9997041781 |
child 43077 | 687b865b95ad |
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) |