comparison mercurial/cmdutil.py @ 24272:26a1c617e047

record: move dorecord from record to cmdutil Part of a serie of patches to move record from hgext to core
author Laurent Charignon <lcharignon@fb.com>
date Tue, 10 Mar 2015 17:14:33 -0700
parents 76225ab5a5da
children e1cb460a3524
comparison
equal deleted inserted replaced
24271:18792f2e38bb 24272:26a1c617e047
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 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
14 import changelog 14 import changelog
15 import bookmarks 15 import bookmarks
16 import encoding 16 import encoding
17 import lock as lockmod 17 import lock as lockmod
18 18
19 def parsealiases(cmd): 19 def parsealiases(cmd):
20 return cmd.lstrip("^").split("|") 20 return cmd.lstrip("^").split("|")
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
21 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)
25 for each matching command. 196 for each matching command.