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 |
|
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