89 |
87 |
90 def committomq(ui, repo, *pats, **opts): |
88 def committomq(ui, repo, *pats, **opts): |
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: |
271 mq = extensions.find('mq') |
102 mq = extensions.find('mq') |
272 except KeyError: |
103 except KeyError: |