##// END OF EJS Templates
record: move dorecord from record to cmdutil...
Laurent Charignon -
r24272:26a1c617 default
parent child Browse files
Show More
@@ -737,13 +737,7 def reposetup(ui, repo):
737 extensions.wrapfunction(patch, 'diff', kw_diff)
737 extensions.wrapfunction(patch, 'diff', kw_diff)
738 extensions.wrapfunction(cmdutil, 'amend', kw_amend)
738 extensions.wrapfunction(cmdutil, 'amend', kw_amend)
739 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
739 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
740 extensions.wrapfunction(cmdutil, 'dorecord', kw_dorecord)
740 for c in 'annotate changeset rev filediff diff'.split():
741 for c in 'annotate changeset rev filediff diff'.split():
741 extensions.wrapfunction(webcommands, c, kwweb_skip)
742 extensions.wrapfunction(webcommands, c, kwweb_skip)
742 for name in recordextensions.split():
743 try:
744 record = extensions.find(name)
745 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
746 except KeyError:
747 pass
748
749 repo.__class__ = kwrepo
743 repo.__class__ = kwrepo
@@ -8,10 +8,8
8 '''commands to interactively select changes for commit/qrefresh'''
8 '''commands to interactively select changes for commit/qrefresh'''
9
9
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11 from mercurial import cmdutil, commands, extensions, patch
11 from mercurial import cmdutil, commands, extensions
12 from mercurial import util
12 from mercurial import util
13 from mercurial import merge as mergemod
14 import cStringIO, errno, os, shutil, tempfile
15
13
16 cmdtable = {}
14 cmdtable = {}
17 command = cmdutil.command(cmdtable)
15 command = cmdutil.command(cmdtable)
@@ -50,7 +48,7 def record(ui, repo, *pats, **opts):
50
48
51 This command is not available when committing a merge.'''
49 This command is not available when committing a merge.'''
52
50
53 dorecord(ui, repo, commands.commit, 'commit', False, *pats, **opts)
51 cmdutil.dorecord(ui, repo, commands.commit, 'commit', False, *pats, **opts)
54
52
55 def qrefresh(origfn, ui, repo, *pats, **opts):
53 def qrefresh(origfn, ui, repo, *pats, **opts):
56 if not opts['interactive']:
54 if not opts['interactive']:
@@ -66,7 +64,7 def qrefresh(origfn, ui, repo, *pats, **
66 mq.refresh(ui, repo, **opts)
64 mq.refresh(ui, repo, **opts)
67
65
68 # backup all changed files
66 # backup all changed files
69 dorecord(ui, repo, committomq, 'qrefresh', True, *pats, **opts)
67 cmdutil.dorecord(ui, repo, committomq, 'qrefresh', True, *pats, **opts)
70
68
71 # This command registration is replaced during uisetup().
69 # This command registration is replaced during uisetup().
72 @command('qrecord',
70 @command('qrecord',
@@ -91,180 +89,13 def qrecord(ui, repo, patch, *pats, **op
91 opts['checkname'] = False
89 opts['checkname'] = False
92 mq.new(ui, repo, patch, *pats, **opts)
90 mq.new(ui, repo, patch, *pats, **opts)
93
91
94 dorecord(ui, repo, committomq, 'qnew', False, *pats, **opts)
92 cmdutil.dorecord(ui, repo, committomq, 'qnew', False, *pats, **opts)
95
93
96 def qnew(origfn, ui, repo, patch, *args, **opts):
94 def qnew(origfn, ui, repo, patch, *args, **opts):
97 if opts['interactive']:
95 if opts['interactive']:
98 return qrecord(ui, repo, patch, *args, **opts)
96 return qrecord(ui, repo, patch, *args, **opts)
99 return origfn(ui, repo, patch, *args, **opts)
97 return origfn(ui, repo, patch, *args, **opts)
100
98
101 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, *pats, **opts):
102 if not ui.interactive():
103 raise util.Abort(_('running non-interactively, use %s instead') %
104 cmdsuggest)
105
106 # make sure username is set before going interactive
107 if not opts.get('user'):
108 ui.username() # raise exception, username not provided
109
110 def recordfunc(ui, repo, message, match, opts):
111 """This is generic record driver.
112
113 Its job is to interactively filter local changes, and
114 accordingly prepare working directory into a state in which the
115 job can be delegated to a non-interactive commit command such as
116 'commit' or 'qrefresh'.
117
118 After the actual job is done by non-interactive command, the
119 working directory is restored to its original state.
120
121 In the end we'll record interesting changes, and everything else
122 will be left in place, so the user can continue working.
123 """
124
125 cmdutil.checkunfinished(repo, commit=True)
126 merge = len(repo[None].parents()) > 1
127 if merge:
128 raise util.Abort(_('cannot partially commit a merge '
129 '(use "hg commit" instead)'))
130
131 status = repo.status(match=match)
132 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
133 diffopts.nodates = True
134 diffopts.git = True
135 originalchunks = patch.diff(repo, changes=status, opts=diffopts)
136 fp = cStringIO.StringIO()
137 fp.write(''.join(originalchunks))
138 fp.seek(0)
139
140 # 1. filter patch, so we have intending-to apply subset of it
141 try:
142 chunks = patch.filterpatch(ui, patch.parsepatch(fp))
143 except patch.PatchError, err:
144 raise util.Abort(_('error parsing patch: %s') % err)
145
146 del fp
147
148 contenders = set()
149 for h in chunks:
150 try:
151 contenders.update(set(h.files()))
152 except AttributeError:
153 pass
154
155 changed = status.modified + status.added + status.removed
156 newfiles = [f for f in changed if f in contenders]
157 if not newfiles:
158 ui.status(_('no changes to record\n'))
159 return 0
160
161 newandmodifiedfiles = set()
162 for h in chunks:
163 ishunk = isinstance(h, patch.recordhunk)
164 isnew = h.filename() in status.added
165 if ishunk and isnew and not h in originalchunks:
166 newandmodifiedfiles.add(h.filename())
167
168 modified = set(status.modified)
169
170 # 2. backup changed files, so we can restore them in the end
171
172 if backupall:
173 tobackup = changed
174 else:
175 tobackup = [f for f in newfiles
176 if f in modified or f in newandmodifiedfiles]
177
178 backups = {}
179 if tobackup:
180 backupdir = repo.join('record-backups')
181 try:
182 os.mkdir(backupdir)
183 except OSError, err:
184 if err.errno != errno.EEXIST:
185 raise
186 try:
187 # backup continues
188 for f in tobackup:
189 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
190 dir=backupdir)
191 os.close(fd)
192 ui.debug('backup %r as %r\n' % (f, tmpname))
193 util.copyfile(repo.wjoin(f), tmpname)
194 shutil.copystat(repo.wjoin(f), tmpname)
195 backups[f] = tmpname
196
197 fp = cStringIO.StringIO()
198 for c in chunks:
199 fname = c.filename()
200 if fname in backups or fname in newandmodifiedfiles:
201 c.write(fp)
202 dopatch = fp.tell()
203 fp.seek(0)
204
205 [os.unlink(c) for c in newandmodifiedfiles]
206
207 # 3a. apply filtered patch to clean repo (clean)
208 if backups:
209 # Equivalent to hg.revert
210 choices = lambda key: key in backups
211 mergemod.update(repo, repo.dirstate.p1(),
212 False, True, choices)
213
214 # 3b. (apply)
215 if dopatch:
216 try:
217 ui.debug('applying patch\n')
218 ui.debug(fp.getvalue())
219 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
220 except patch.PatchError, err:
221 raise util.Abort(str(err))
222 del fp
223
224 # 4. We prepared working directory according to filtered
225 # patch. Now is the time to delegate the job to
226 # commit/qrefresh or the like!
227
228 # Make all of the pathnames absolute.
229 newfiles = [repo.wjoin(nf) for nf in newfiles]
230 commitfunc(ui, repo, *newfiles, **opts)
231
232 return 0
233 finally:
234 # 5. finally restore backed-up files
235 try:
236 for realname, tmpname in backups.iteritems():
237 ui.debug('restoring %r to %r\n' % (tmpname, realname))
238 util.copyfile(tmpname, repo.wjoin(realname))
239 # Our calls to copystat() here and above are a
240 # hack to trick any editors that have f open that
241 # we haven't modified them.
242 #
243 # Also note that this racy as an editor could
244 # notice the file's mtime before we've finished
245 # writing it.
246 shutil.copystat(tmpname, repo.wjoin(realname))
247 os.unlink(tmpname)
248 if tobackup:
249 os.rmdir(backupdir)
250 except OSError:
251 pass
252
253 # wrap ui.write so diff output can be labeled/colorized
254 def wrapwrite(orig, *args, **kw):
255 label = kw.pop('label', '')
256 for chunk, l in patch.difflabel(lambda: args):
257 orig(chunk, label=label + l)
258 oldwrite = ui.write
259
260 def wrap(*args, **kwargs):
261 return wrapwrite(oldwrite, *args, **kwargs)
262 setattr(ui, 'write', wrap)
263
264 try:
265 return cmdutil.commit(ui, repo, recordfunc, pats, opts)
266 finally:
267 ui.write = oldwrite
268
99
269 def uisetup(ui):
100 def uisetup(ui):
270 try:
101 try:
@@ -7,7 +7,7
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, tempfile
10 import os, sys, errno, re, tempfile, cStringIO, shutil
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 import match as matchmod
12 import match as matchmod
13 import context, repair, graphmod, revset, phases, obsolete, pathutil
13 import context, repair, graphmod, revset, phases, obsolete, pathutil
@@ -19,6 +19,177 import lock as lockmod
19 def parsealiases(cmd):
19 def parsealiases(cmd):
20 return cmd.lstrip("^").split("|")
20 return cmd.lstrip("^").split("|")
21
21
22 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall, *pats, **opts):
23 import merge as mergemod
24 if not ui.interactive():
25 raise util.Abort(_('running non-interactively, use %s instead') %
26 cmdsuggest)
27
28 # make sure username is set before going interactive
29 if not opts.get('user'):
30 ui.username() # raise exception, username not provided
31
32 def recordfunc(ui, repo, message, match, opts):
33 """This is generic record driver.
34
35 Its job is to interactively filter local changes, and
36 accordingly prepare working directory into a state in which the
37 job can be delegated to a non-interactive commit command such as
38 'commit' or 'qrefresh'.
39
40 After the actual job is done by non-interactive command, the
41 working directory is restored to its original state.
42
43 In the end we'll record interesting changes, and everything else
44 will be left in place, so the user can continue working.
45 """
46
47 checkunfinished(repo, commit=True)
48 merge = len(repo[None].parents()) > 1
49 if merge:
50 raise util.Abort(_('cannot partially commit a merge '
51 '(use "hg commit" instead)'))
52
53 status = repo.status(match=match)
54 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
55 diffopts.nodates = True
56 diffopts.git = True
57 originalchunks = patch.diff(repo, changes=status, opts=diffopts)
58 fp = cStringIO.StringIO()
59 fp.write(''.join(originalchunks))
60 fp.seek(0)
61
62 # 1. filter patch, so we have intending-to apply subset of it
63 try:
64 chunks = patch.filterpatch(ui, patch.parsepatch(fp))
65 except patch.PatchError, err:
66 raise util.Abort(_('error parsing patch: %s') % err)
67
68 del fp
69
70 contenders = set()
71 for h in chunks:
72 try:
73 contenders.update(set(h.files()))
74 except AttributeError:
75 pass
76
77 changed = status.modified + status.added + status.removed
78 newfiles = [f for f in changed if f in contenders]
79 if not newfiles:
80 ui.status(_('no changes to record\n'))
81 return 0
82
83 newandmodifiedfiles = set()
84 for h in chunks:
85 ishunk = isinstance(h, patch.recordhunk)
86 isnew = h.filename() in status.added
87 if ishunk and isnew and not h in originalchunks:
88 newandmodifiedfiles.add(h.filename())
89
90 modified = set(status.modified)
91
92 # 2. backup changed files, so we can restore them in the end
93
94 if backupall:
95 tobackup = changed
96 else:
97 tobackup = [f for f in newfiles
98 if f in modified or f in newandmodifiedfiles]
99
100 backups = {}
101 if tobackup:
102 backupdir = repo.join('record-backups')
103 try:
104 os.mkdir(backupdir)
105 except OSError, err:
106 if err.errno != errno.EEXIST:
107 raise
108 try:
109 # backup continues
110 for f in tobackup:
111 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
112 dir=backupdir)
113 os.close(fd)
114 ui.debug('backup %r as %r\n' % (f, tmpname))
115 util.copyfile(repo.wjoin(f), tmpname)
116 shutil.copystat(repo.wjoin(f), tmpname)
117 backups[f] = tmpname
118
119 fp = cStringIO.StringIO()
120 for c in chunks:
121 fname = c.filename()
122 if fname in backups or fname in newandmodifiedfiles:
123 c.write(fp)
124 dopatch = fp.tell()
125 fp.seek(0)
126
127 [os.unlink(c) for c in newandmodifiedfiles]
128
129 # 3a. apply filtered patch to clean repo (clean)
130 if backups:
131 # Equivalent to hg.revert
132 choices = lambda key: key in backups
133 mergemod.update(repo, repo.dirstate.p1(),
134 False, True, choices)
135
136
137 # 3b. (apply)
138 if dopatch:
139 try:
140 ui.debug('applying patch\n')
141 ui.debug(fp.getvalue())
142 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
143 except patch.PatchError, err:
144 raise util.Abort(str(err))
145 del fp
146
147 # 4. We prepared working directory according to filtered
148 # patch. Now is the time to delegate the job to
149 # commit/qrefresh or the like!
150
151 # Make all of the pathnames absolute.
152 newfiles = [repo.wjoin(nf) for nf in newfiles]
153 commitfunc(ui, repo, *newfiles, **opts)
154
155 return 0
156 finally:
157 # 5. finally restore backed-up files
158 try:
159 for realname, tmpname in backups.iteritems():
160 ui.debug('restoring %r to %r\n' % (tmpname, realname))
161 util.copyfile(tmpname, repo.wjoin(realname))
162 # Our calls to copystat() here and above are a
163 # hack to trick any editors that have f open that
164 # we haven't modified them.
165 #
166 # Also note that this racy as an editor could
167 # notice the file's mtime before we've finished
168 # writing it.
169 shutil.copystat(tmpname, repo.wjoin(realname))
170 os.unlink(tmpname)
171 if tobackup:
172 os.rmdir(backupdir)
173 except OSError:
174 pass
175
176 # wrap ui.write so diff output can be labeled/colorized
177 def wrapwrite(orig, *args, **kw):
178 label = kw.pop('label', '')
179 for chunk, l in patch.difflabel(lambda: args):
180 orig(chunk, label=label + l)
181
182 oldwrite = ui.write
183 def wrap(*args, **kwargs):
184 return wrapwrite(oldwrite, *args, **kwargs)
185 setattr(ui, 'write', wrap)
186
187 try:
188 return commit(ui, repo, recordfunc, pats, opts)
189 finally:
190 ui.write = oldwrite
191
192
22 def findpossible(cmd, table, strict=False):
193 def findpossible(cmd, table, strict=False):
23 """
194 """
24 Return cmd -> (aliases, command table entry)
195 Return cmd -> (aliases, command table entry)
General Comments 0
You need to be logged in to leave comments. Login now