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