Mercurial > public > mercurial-scm > hg
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. |