##// END OF EJS Templates
use ui instead of repo.ui when the former is in scope
Martin Geisler -
r8615:94ca38e6 default
parent child Browse files
Show More
@@ -1,359 +1,359 b''
1 1 # Minimal support for git commands on an hg repository
2 2 #
3 3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 '''browsing the repository in a graphical way
9 9
10 10 The hgk extension allows browsing the history of a repository in a
11 11 graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is not
12 12 distributed with Mercurial.)
13 13
14 14 hgk consists of two parts: a Tcl script that does the displaying and
15 15 querying of information, and an extension to mercurial named hgk.py,
16 16 which provides hooks for hgk to get information. hgk can be found in
17 17 the contrib directory, and hgk.py can be found in the hgext directory.
18 18
19 19 To load the hgext.py extension, add it to your .hgrc file (you have to
20 20 use your global $HOME/.hgrc file, not one in a repository). You can
21 21 specify an absolute path:
22 22
23 23 [extensions]
24 24 hgk=/usr/local/lib/hgk.py
25 25
26 26 Mercurial can also scan the default python library path for a file
27 27 named 'hgk.py' if you set hgk empty:
28 28
29 29 [extensions]
30 30 hgk=
31 31
32 32 The hg view command will launch the hgk Tcl script. For this command
33 33 to work, hgk must be in your search path. Alternately, you can specify
34 34 the path to hgk in your .hgrc file:
35 35
36 36 [hgk]
37 37 path=/location/of/hgk
38 38
39 39 hgk can make use of the extdiff extension to visualize revisions.
40 40 Assuming you had already configured extdiff vdiff command, just add:
41 41
42 42 [hgk]
43 43 vdiff=vdiff
44 44
45 45 Revisions context menu will now display additional entries to fire
46 46 vdiff on hovered and selected revisions.'''
47 47
48 48 import os
49 49 from mercurial import commands, util, patch, revlog, cmdutil
50 50 from mercurial.node import nullid, nullrev, short
51 51 from mercurial.i18n import _
52 52
53 53 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
54 54 """diff trees from two commits"""
55 55 def __difftree(repo, node1, node2, files=[]):
56 56 assert node2 is not None
57 57 mmap = repo[node1].manifest()
58 58 mmap2 = repo[node2].manifest()
59 59 m = cmdutil.match(repo, files)
60 60 modified, added, removed = repo.status(node1, node2, m)[:3]
61 61 empty = short(nullid)
62 62
63 63 for f in modified:
64 64 # TODO get file permissions
65 65 ui.write(":100664 100664 %s %s M\t%s\t%s\n" %
66 66 (short(mmap[f]), short(mmap2[f]), f, f))
67 67 for f in added:
68 68 ui.write(":000000 100664 %s %s N\t%s\t%s\n" %
69 69 (empty, short(mmap2[f]), f, f))
70 70 for f in removed:
71 71 ui.write(":100664 000000 %s %s D\t%s\t%s\n" %
72 72 (short(mmap[f]), empty, f, f))
73 73 ##
74 74
75 75 while True:
76 76 if opts['stdin']:
77 77 try:
78 78 line = raw_input().split(' ')
79 79 node1 = line[0]
80 80 if len(line) > 1:
81 81 node2 = line[1]
82 82 else:
83 83 node2 = None
84 84 except EOFError:
85 85 break
86 86 node1 = repo.lookup(node1)
87 87 if node2:
88 88 node2 = repo.lookup(node2)
89 89 else:
90 90 node2 = node1
91 91 node1 = repo.changelog.parents(node1)[0]
92 92 if opts['patch']:
93 93 if opts['pretty']:
94 94 catcommit(ui, repo, node2, "")
95 95 m = cmdutil.match(repo, files)
96 96 chunks = patch.diff(repo, node1, node2, match=m,
97 97 opts=patch.diffopts(ui, {'git': True}))
98 98 for chunk in chunks:
99 repo.ui.write(chunk)
99 ui.write(chunk)
100 100 else:
101 101 __difftree(repo, node1, node2, files=files)
102 102 if not opts['stdin']:
103 103 break
104 104
105 105 def catcommit(ui, repo, n, prefix, ctx=None):
106 106 nlprefix = '\n' + prefix;
107 107 if ctx is None:
108 108 ctx = repo[n]
109 109 ui.write("tree %s\n" % short(ctx.changeset()[0])) # use ctx.node() instead ??
110 110 for p in ctx.parents():
111 111 ui.write("parent %s\n" % p)
112 112
113 113 date = ctx.date()
114 114 description = ctx.description().replace("\0", "")
115 115 lines = description.splitlines()
116 116 if lines and lines[-1].startswith('committer:'):
117 117 committer = lines[-1].split(': ')[1].rstrip()
118 118 else:
119 119 committer = ctx.user()
120 120
121 121 ui.write("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1]))
122 122 ui.write("committer %s %s %s\n" % (committer, int(date[0]), date[1]))
123 123 ui.write("revision %d\n" % ctx.rev())
124 124 ui.write("branch %s\n\n" % ctx.branch())
125 125
126 126 if prefix != "":
127 127 ui.write("%s%s\n" % (prefix, description.replace('\n', nlprefix).strip()))
128 128 else:
129 129 ui.write(description + "\n")
130 130 if prefix:
131 131 ui.write('\0')
132 132
133 133 def base(ui, repo, node1, node2):
134 134 """output common ancestor information"""
135 135 node1 = repo.lookup(node1)
136 136 node2 = repo.lookup(node2)
137 137 n = repo.changelog.ancestor(node1, node2)
138 138 ui.write(short(n) + "\n")
139 139
140 140 def catfile(ui, repo, type=None, r=None, **opts):
141 141 """cat a specific revision"""
142 142 # in stdin mode, every line except the commit is prefixed with two
143 143 # spaces. This way the our caller can find the commit without magic
144 144 # strings
145 145 #
146 146 prefix = ""
147 147 if opts['stdin']:
148 148 try:
149 149 (type, r) = raw_input().split(' ');
150 150 prefix = " "
151 151 except EOFError:
152 152 return
153 153
154 154 else:
155 155 if not type or not r:
156 156 ui.warn(_("cat-file: type or revision not supplied\n"))
157 157 commands.help_(ui, 'cat-file')
158 158
159 159 while r:
160 160 if type != "commit":
161 161 ui.warn(_("aborting hg cat-file only understands commits\n"))
162 162 return 1;
163 163 n = repo.lookup(r)
164 164 catcommit(ui, repo, n, prefix)
165 165 if opts['stdin']:
166 166 try:
167 167 (type, r) = raw_input().split(' ');
168 168 except EOFError:
169 169 break
170 170 else:
171 171 break
172 172
173 173 # git rev-tree is a confusing thing. You can supply a number of
174 174 # commit sha1s on the command line, and it walks the commit history
175 175 # telling you which commits are reachable from the supplied ones via
176 176 # a bitmask based on arg position.
177 177 # you can specify a commit to stop at by starting the sha1 with ^
178 178 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
179 179 def chlogwalk():
180 180 count = len(repo)
181 181 i = count
182 182 l = [0] * 100
183 183 chunk = 100
184 184 while True:
185 185 if chunk > i:
186 186 chunk = i
187 187 i = 0
188 188 else:
189 189 i -= chunk
190 190
191 191 for x in xrange(0, chunk):
192 192 if i + x >= count:
193 193 l[chunk - x:] = [0] * (chunk - x)
194 194 break
195 195 if full != None:
196 196 l[x] = repo[i + x]
197 197 l[x].changeset() # force reading
198 198 else:
199 199 l[x] = 1
200 200 for x in xrange(chunk-1, -1, -1):
201 201 if l[x] != 0:
202 202 yield (i + x, full != None and l[x] or None)
203 203 if i == 0:
204 204 break
205 205
206 206 # calculate and return the reachability bitmask for sha
207 207 def is_reachable(ar, reachable, sha):
208 208 if len(ar) == 0:
209 209 return 1
210 210 mask = 0
211 211 for i in xrange(len(ar)):
212 212 if sha in reachable[i]:
213 213 mask |= 1 << i
214 214
215 215 return mask
216 216
217 217 reachable = []
218 218 stop_sha1 = []
219 219 want_sha1 = []
220 220 count = 0
221 221
222 222 # figure out which commits they are asking for and which ones they
223 223 # want us to stop on
224 224 for i in xrange(len(args)):
225 225 if args[i].startswith('^'):
226 226 s = repo.lookup(args[i][1:])
227 227 stop_sha1.append(s)
228 228 want_sha1.append(s)
229 229 elif args[i] != 'HEAD':
230 230 want_sha1.append(repo.lookup(args[i]))
231 231
232 232 # calculate the graph for the supplied commits
233 233 for i in xrange(len(want_sha1)):
234 234 reachable.append(set());
235 235 n = want_sha1[i];
236 236 visit = [n];
237 237 reachable[i].add(n)
238 238 while visit:
239 239 n = visit.pop(0)
240 240 if n in stop_sha1:
241 241 continue
242 242 for p in repo.changelog.parents(n):
243 243 if p not in reachable[i]:
244 244 reachable[i].add(p)
245 245 visit.append(p)
246 246 if p in stop_sha1:
247 247 continue
248 248
249 249 # walk the repository looking for commits that are in our
250 250 # reachability graph
251 251 for i, ctx in chlogwalk():
252 252 n = repo.changelog.node(i)
253 253 mask = is_reachable(want_sha1, reachable, n)
254 254 if mask:
255 255 parentstr = ""
256 256 if parents:
257 257 pp = repo.changelog.parents(n)
258 258 if pp[0] != nullid:
259 259 parentstr += " " + short(pp[0])
260 260 if pp[1] != nullid:
261 261 parentstr += " " + short(pp[1])
262 262 if not full:
263 263 ui.write("%s%s\n" % (short(n), parentstr))
264 264 elif full == "commit":
265 265 ui.write("%s%s\n" % (short(n), parentstr))
266 266 catcommit(ui, repo, n, ' ', ctx)
267 267 else:
268 268 (p1, p2) = repo.changelog.parents(n)
269 269 (h, h1, h2) = map(short, (n, p1, p2))
270 270 (i1, i2) = map(repo.changelog.rev, (p1, p2))
271 271
272 272 date = ctx.date()[0]
273 273 ui.write("%s %s:%s" % (date, h, mask))
274 274 mask = is_reachable(want_sha1, reachable, p1)
275 275 if i1 != nullrev and mask > 0:
276 276 ui.write("%s:%s " % (h1, mask)),
277 277 mask = is_reachable(want_sha1, reachable, p2)
278 278 if i2 != nullrev and mask > 0:
279 279 ui.write("%s:%s " % (h2, mask))
280 280 ui.write("\n")
281 281 if maxnr and count >= maxnr:
282 282 break
283 283 count += 1
284 284
285 285 def revparse(ui, repo, *revs, **opts):
286 286 """parse given revisions"""
287 287 def revstr(rev):
288 288 if rev == 'HEAD':
289 289 rev = 'tip'
290 290 return revlog.hex(repo.lookup(rev))
291 291
292 292 for r in revs:
293 293 revrange = r.split(':', 1)
294 294 ui.write('%s\n' % revstr(revrange[0]))
295 295 if len(revrange) == 2:
296 296 ui.write('^%s\n' % revstr(revrange[1]))
297 297
298 298 # git rev-list tries to order things by date, and has the ability to stop
299 299 # at a given commit without walking the whole repo. TODO add the stop
300 300 # parameter
301 301 def revlist(ui, repo, *revs, **opts):
302 302 """print revisions"""
303 303 if opts['header']:
304 304 full = "commit"
305 305 else:
306 306 full = None
307 307 copy = [x for x in revs]
308 308 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
309 309
310 310 def config(ui, repo, **opts):
311 311 """print extension options"""
312 312 def writeopt(name, value):
313 313 ui.write('k=%s\nv=%s\n' % (name, value))
314 314
315 315 writeopt('vdiff', ui.config('hgk', 'vdiff', ''))
316 316
317 317
318 318 def view(ui, repo, *etc, **opts):
319 319 "start interactive history viewer"
320 320 os.chdir(repo.root)
321 321 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
322 322 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
323 323 ui.debug(_("running %s\n") % cmd)
324 324 util.system(cmd)
325 325
326 326 cmdtable = {
327 327 "^view":
328 328 (view,
329 329 [('l', 'limit', '', _('limit number of changes displayed'))],
330 330 _('hg view [-l LIMIT] [REVRANGE]')),
331 331 "debug-diff-tree":
332 332 (difftree,
333 333 [('p', 'patch', None, _('generate patch')),
334 334 ('r', 'recursive', None, _('recursive')),
335 335 ('P', 'pretty', None, _('pretty')),
336 336 ('s', 'stdin', None, _('stdin')),
337 337 ('C', 'copy', None, _('detect copies')),
338 338 ('S', 'search', "", _('search'))],
339 339 _('hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...')),
340 340 "debug-cat-file":
341 341 (catfile,
342 342 [('s', 'stdin', None, _('stdin'))],
343 343 _('hg debug-cat-file [OPTION]... TYPE FILE')),
344 344 "debug-config":
345 345 (config, [], _('hg debug-config')),
346 346 "debug-merge-base":
347 347 (base, [], _('hg debug-merge-base node node')),
348 348 "debug-rev-parse":
349 349 (revparse,
350 350 [('', 'default', '', _('ignored'))],
351 351 _('hg debug-rev-parse REV')),
352 352 "debug-rev-list":
353 353 (revlist,
354 354 [('H', 'header', None, _('header')),
355 355 ('t', 'topo-order', None, _('topo-order')),
356 356 ('p', 'parents', None, _('parents')),
357 357 ('n', 'max-count', 0, _('max-count'))],
358 358 _('hg debug-rev-list [options] revs')),
359 359 }
@@ -1,474 +1,474 b''
1 1 # rebase.py - rebasing feature for mercurial
2 2 #
3 3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 '''move sets of revisions to a different ancestor
9 9
10 10 This extension lets you rebase changesets in an existing Mercurial
11 11 repository.
12 12
13 13 For more information:
14 14 http://www.selenic.com/mercurial/wiki/index.cgi/RebaseProject
15 15 '''
16 16
17 17 from mercurial import util, repair, merge, cmdutil, commands, error
18 18 from mercurial import extensions, ancestor, copies, patch
19 19 from mercurial.commands import templateopts
20 20 from mercurial.node import nullrev
21 21 from mercurial.lock import release
22 22 from mercurial.i18n import _
23 23 import os, errno
24 24
25 25 def rebasemerge(repo, rev, first=False):
26 26 'return the correct ancestor'
27 27 oldancestor = ancestor.ancestor
28 28
29 29 def newancestor(a, b, pfunc):
30 30 ancestor.ancestor = oldancestor
31 31 if b == rev:
32 32 return repo[rev].parents()[0].rev()
33 33 return ancestor.ancestor(a, b, pfunc)
34 34
35 35 if not first:
36 36 ancestor.ancestor = newancestor
37 37 else:
38 38 repo.ui.debug(_("first revision, do not change ancestor\n"))
39 39 stats = merge.update(repo, rev, True, True, False)
40 40 return stats
41 41
42 42 def rebase(ui, repo, **opts):
43 43 """move changeset (and descendants) to a different branch
44 44
45 45 Rebase uses repeated merging to graft changesets from one part of
46 46 history onto another. This can be useful for linearizing local
47 47 changes relative to a master development tree.
48 48
49 49 If a rebase is interrupted to manually resolve a merge, it can be
50 50 continued with --continue/-c or aborted with --abort/-a.
51 51 """
52 52 originalwd = target = None
53 53 external = nullrev
54 54 state = {}
55 55 skipped = set()
56 56
57 57 lock = wlock = None
58 58 try:
59 59 lock = repo.lock()
60 60 wlock = repo.wlock()
61 61
62 62 # Validate input and define rebasing points
63 63 destf = opts.get('dest', None)
64 64 srcf = opts.get('source', None)
65 65 basef = opts.get('base', None)
66 66 contf = opts.get('continue')
67 67 abortf = opts.get('abort')
68 68 collapsef = opts.get('collapse', False)
69 69 extrafn = opts.get('extrafn')
70 70 keepf = opts.get('keep', False)
71 71 keepbranchesf = opts.get('keepbranches', False)
72 72
73 73 if contf or abortf:
74 74 if contf and abortf:
75 75 raise error.ParseError('rebase',
76 76 _('cannot use both abort and continue'))
77 77 if collapsef:
78 78 raise error.ParseError(
79 79 'rebase', _('cannot use collapse with continue or abort'))
80 80
81 81 if srcf or basef or destf:
82 82 raise error.ParseError('rebase',
83 83 _('abort and continue do not allow specifying revisions'))
84 84
85 85 (originalwd, target, state, collapsef, keepf,
86 86 keepbranchesf, external) = restorestatus(repo)
87 87 if abortf:
88 88 abort(repo, originalwd, target, state)
89 89 return
90 90 else:
91 91 if srcf and basef:
92 92 raise error.ParseError('rebase', _('cannot specify both a '
93 93 'revision and a base'))
94 94 cmdutil.bail_if_changed(repo)
95 95 result = buildstate(repo, destf, srcf, basef, collapsef)
96 96 if result:
97 97 originalwd, target, state, external = result
98 98 else: # Empty state built, nothing to rebase
99 repo.ui.status(_('nothing to rebase\n'))
99 ui.status(_('nothing to rebase\n'))
100 100 return
101 101
102 102 if keepbranchesf:
103 103 if extrafn:
104 104 raise error.ParseError(
105 105 'rebase', _('cannot use both keepbranches and extrafn'))
106 106 def extrafn(ctx, extra):
107 107 extra['branch'] = ctx.branch()
108 108
109 109 # Rebase
110 110 targetancestors = list(repo.changelog.ancestors(target))
111 111 targetancestors.append(target)
112 112
113 113 for rev in sorted(state):
114 114 if state[rev] == -1:
115 115 storestatus(repo, originalwd, target, state, collapsef, keepf,
116 116 keepbranchesf, external)
117 117 rebasenode(repo, rev, target, state, skipped, targetancestors,
118 118 collapsef, extrafn)
119 119 ui.note(_('rebase merging completed\n'))
120 120
121 121 if collapsef:
122 122 p1, p2 = defineparents(repo, min(state), target,
123 123 state, targetancestors)
124 124 concludenode(repo, rev, p1, external, state, collapsef,
125 125 last=True, skipped=skipped, extrafn=extrafn)
126 126
127 127 if 'qtip' in repo.tags():
128 128 updatemq(repo, state, skipped, **opts)
129 129
130 130 if not keepf:
131 131 # Remove no more useful revisions
132 132 if set(repo.changelog.descendants(min(state))) - set(state):
133 133 ui.warn(_("warning: new changesets detected on source branch, "
134 134 "not stripping\n"))
135 135 else:
136 repair.strip(repo.ui, repo, repo[min(state)].node(), "strip")
136 repair.strip(ui, repo, repo[min(state)].node(), "strip")
137 137
138 138 clearstatus(repo)
139 139 ui.status(_("rebase completed\n"))
140 140 if os.path.exists(repo.sjoin('undo')):
141 141 util.unlink(repo.sjoin('undo'))
142 142 if skipped:
143 143 ui.note(_("%d revisions have been skipped\n") % len(skipped))
144 144 finally:
145 145 release(lock, wlock)
146 146
147 147 def concludenode(repo, rev, p1, p2, state, collapse, last=False, skipped=None,
148 148 extrafn=None):
149 149 """Skip commit if collapsing has been required and rev is not the last
150 150 revision, commit otherwise
151 151 """
152 152 repo.ui.debug(_(" set parents\n"))
153 153 if collapse and not last:
154 154 repo.dirstate.setparents(repo[p1].node())
155 155 return None
156 156
157 157 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
158 158
159 159 if skipped is None:
160 160 skipped = set()
161 161
162 162 # Commit, record the old nodeid
163 163 m, a, r = repo.status()[:3]
164 164 newrev = nullrev
165 165 try:
166 166 if last:
167 167 commitmsg = 'Collapsed revision'
168 168 for rebased in state:
169 169 if rebased not in skipped:
170 170 commitmsg += '\n* %s' % repo[rebased].description()
171 171 commitmsg = repo.ui.edit(commitmsg, repo.ui.username())
172 172 else:
173 173 commitmsg = repo[rev].description()
174 174 # Commit might fail if unresolved files exist
175 175 extra = {'rebase_source': repo[rev].hex()}
176 176 if extrafn:
177 177 extrafn(repo[rev], extra)
178 178 newrev = repo.commit(m+a+r,
179 179 text=commitmsg,
180 180 user=repo[rev].user(),
181 181 date=repo[rev].date(),
182 182 extra=extra)
183 183 repo.dirstate.setbranch(repo[newrev].branch())
184 184 return newrev
185 185 except util.Abort:
186 186 # Invalidate the previous setparents
187 187 repo.dirstate.invalidate()
188 188 raise
189 189
190 190 def rebasenode(repo, rev, target, state, skipped, targetancestors, collapse,
191 191 extrafn):
192 192 'Rebase a single revision'
193 193 repo.ui.debug(_("rebasing %d:%s\n") % (rev, repo[rev]))
194 194
195 195 p1, p2 = defineparents(repo, rev, target, state, targetancestors)
196 196
197 197 repo.ui.debug(_(" future parents are %d and %d\n") % (repo[p1].rev(),
198 198 repo[p2].rev()))
199 199
200 200 # Merge phase
201 201 if len(repo.parents()) != 2:
202 202 # Update to target and merge it with local
203 203 if repo['.'].rev() != repo[p1].rev():
204 204 repo.ui.debug(_(" update to %d:%s\n") % (repo[p1].rev(), repo[p1]))
205 205 merge.update(repo, p1, False, True, False)
206 206 else:
207 207 repo.ui.debug(_(" already in target\n"))
208 208 repo.dirstate.write()
209 209 repo.ui.debug(_(" merge against %d:%s\n") % (repo[rev].rev(), repo[rev]))
210 210 first = repo[rev].rev() == repo[min(state)].rev()
211 211 stats = rebasemerge(repo, rev, first)
212 212
213 213 if stats[3] > 0:
214 214 raise util.Abort(_('fix unresolved conflicts with hg resolve then '
215 215 'run hg rebase --continue'))
216 216 else: # we have an interrupted rebase
217 217 repo.ui.debug(_('resuming interrupted rebase\n'))
218 218
219 219 # Keep track of renamed files in the revision that is going to be rebased
220 220 # Here we simulate the copies and renames in the source changeset
221 221 cop, diver = copies.copies(repo, repo[rev], repo[target], repo[p2], True)
222 222 m1 = repo[rev].manifest()
223 223 m2 = repo[target].manifest()
224 224 for k, v in cop.iteritems():
225 225 if k in m1:
226 226 if v in m1 or v in m2:
227 227 repo.dirstate.copy(v, k)
228 228 if v in m2 and v not in m1:
229 229 repo.dirstate.remove(v)
230 230
231 231 newrev = concludenode(repo, rev, p1, p2, state, collapse,
232 232 extrafn=extrafn)
233 233
234 234 # Update the state
235 235 if newrev is not None:
236 236 state[rev] = repo[newrev].rev()
237 237 else:
238 238 if not collapse:
239 239 repo.ui.note(_('no changes, revision %d skipped\n') % rev)
240 240 repo.ui.debug(_('next revision set to %s\n') % p1)
241 241 skipped.add(rev)
242 242 state[rev] = p1
243 243
244 244 def defineparents(repo, rev, target, state, targetancestors):
245 245 'Return the new parent relationship of the revision that will be rebased'
246 246 parents = repo[rev].parents()
247 247 p1 = p2 = nullrev
248 248
249 249 P1n = parents[0].rev()
250 250 if P1n in targetancestors:
251 251 p1 = target
252 252 elif P1n in state:
253 253 p1 = state[P1n]
254 254 else: # P1n external
255 255 p1 = target
256 256 p2 = P1n
257 257
258 258 if len(parents) == 2 and parents[1].rev() not in targetancestors:
259 259 P2n = parents[1].rev()
260 260 # interesting second parent
261 261 if P2n in state:
262 262 if p1 == target: # P1n in targetancestors or external
263 263 p1 = state[P2n]
264 264 else:
265 265 p2 = state[P2n]
266 266 else: # P2n external
267 267 if p2 != nullrev: # P1n external too => rev is a merged revision
268 268 raise util.Abort(_('cannot use revision %d as base, result '
269 269 'would have 3 parents') % rev)
270 270 p2 = P2n
271 271 return p1, p2
272 272
273 273 def isagitpatch(repo, patchname):
274 274 'Return true if the given patch is in git format'
275 275 mqpatch = os.path.join(repo.mq.path, patchname)
276 276 for line in patch.linereader(file(mqpatch, 'rb')):
277 277 if line.startswith('diff --git'):
278 278 return True
279 279 return False
280 280
281 281 def updatemq(repo, state, skipped, **opts):
282 282 'Update rebased mq patches - finalize and then import them'
283 283 mqrebase = {}
284 284 for p in repo.mq.applied:
285 285 if repo[p.rev].rev() in state:
286 286 repo.ui.debug(_('revision %d is an mq patch (%s), finalize it.\n') %
287 287 (repo[p.rev].rev(), p.name))
288 288 mqrebase[repo[p.rev].rev()] = (p.name, isagitpatch(repo, p.name))
289 289
290 290 if mqrebase:
291 291 repo.mq.finish(repo, mqrebase.keys())
292 292
293 293 # We must start import from the newest revision
294 294 for rev in sorted(mqrebase, reverse=True):
295 295 if rev not in skipped:
296 296 repo.ui.debug(_('import mq patch %d (%s)\n')
297 297 % (state[rev], mqrebase[rev][0]))
298 298 repo.mq.qimport(repo, (), patchname=mqrebase[rev][0],
299 299 git=mqrebase[rev][1],rev=[str(state[rev])])
300 300 repo.mq.save_dirty()
301 301
302 302 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
303 303 external):
304 304 'Store the current status to allow recovery'
305 305 f = repo.opener("rebasestate", "w")
306 306 f.write(repo[originalwd].hex() + '\n')
307 307 f.write(repo[target].hex() + '\n')
308 308 f.write(repo[external].hex() + '\n')
309 309 f.write('%d\n' % int(collapse))
310 310 f.write('%d\n' % int(keep))
311 311 f.write('%d\n' % int(keepbranches))
312 312 for d, v in state.iteritems():
313 313 oldrev = repo[d].hex()
314 314 newrev = repo[v].hex()
315 315 f.write("%s:%s\n" % (oldrev, newrev))
316 316 f.close()
317 317 repo.ui.debug(_('rebase status stored\n'))
318 318
319 319 def clearstatus(repo):
320 320 'Remove the status files'
321 321 if os.path.exists(repo.join("rebasestate")):
322 322 util.unlink(repo.join("rebasestate"))
323 323
324 324 def restorestatus(repo):
325 325 'Restore a previously stored status'
326 326 try:
327 327 target = None
328 328 collapse = False
329 329 external = nullrev
330 330 state = {}
331 331 f = repo.opener("rebasestate")
332 332 for i, l in enumerate(f.read().splitlines()):
333 333 if i == 0:
334 334 originalwd = repo[l].rev()
335 335 elif i == 1:
336 336 target = repo[l].rev()
337 337 elif i == 2:
338 338 external = repo[l].rev()
339 339 elif i == 3:
340 340 collapse = bool(int(l))
341 341 elif i == 4:
342 342 keep = bool(int(l))
343 343 elif i == 5:
344 344 keepbranches = bool(int(l))
345 345 else:
346 346 oldrev, newrev = l.split(':')
347 347 state[repo[oldrev].rev()] = repo[newrev].rev()
348 348 repo.ui.debug(_('rebase status resumed\n'))
349 349 return originalwd, target, state, collapse, keep, keepbranches, external
350 350 except IOError, err:
351 351 if err.errno != errno.ENOENT:
352 352 raise
353 353 raise util.Abort(_('no rebase in progress'))
354 354
355 355 def abort(repo, originalwd, target, state):
356 356 'Restore the repository to its original state'
357 357 if set(repo.changelog.descendants(target)) - set(state.values()):
358 358 repo.ui.warn(_("warning: new changesets detected on target branch, "
359 359 "not stripping\n"))
360 360 else:
361 361 # Strip from the first rebased revision
362 362 merge.update(repo, repo[originalwd].rev(), False, True, False)
363 363 rebased = filter(lambda x: x > -1, state.values())
364 364 if rebased:
365 365 strippoint = min(rebased)
366 366 repair.strip(repo.ui, repo, repo[strippoint].node(), "strip")
367 367 clearstatus(repo)
368 368 repo.ui.status(_('rebase aborted\n'))
369 369
370 370 def buildstate(repo, dest, src, base, collapse):
371 371 'Define which revisions are going to be rebased and where'
372 372 targetancestors = set()
373 373
374 374 if not dest:
375 375 # Destination defaults to the latest revision in the current branch
376 376 branch = repo[None].branch()
377 377 dest = repo[branch].rev()
378 378 else:
379 379 if 'qtip' in repo.tags() and (repo[dest].hex() in
380 380 [s.rev for s in repo.mq.applied]):
381 381 raise util.Abort(_('cannot rebase onto an applied mq patch'))
382 382 dest = repo[dest].rev()
383 383
384 384 if src:
385 385 commonbase = repo[src].ancestor(repo[dest])
386 386 if commonbase == repo[src]:
387 387 raise util.Abort(_('cannot rebase an ancestor'))
388 388 if commonbase == repo[dest]:
389 389 raise util.Abort(_('cannot rebase a descendant'))
390 390 source = repo[src].rev()
391 391 else:
392 392 if base:
393 393 cwd = repo[base].rev()
394 394 else:
395 395 cwd = repo['.'].rev()
396 396
397 397 if cwd == dest:
398 398 repo.ui.debug(_('already working on current\n'))
399 399 return None
400 400
401 401 targetancestors = set(repo.changelog.ancestors(dest))
402 402 if cwd in targetancestors:
403 403 repo.ui.debug(_('already working on the current branch\n'))
404 404 return None
405 405
406 406 cwdancestors = set(repo.changelog.ancestors(cwd))
407 407 cwdancestors.add(cwd)
408 408 rebasingbranch = cwdancestors - targetancestors
409 409 source = min(rebasingbranch)
410 410
411 411 repo.ui.debug(_('rebase onto %d starting from %d\n') % (dest, source))
412 412 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
413 413 external = nullrev
414 414 if collapse:
415 415 if not targetancestors:
416 416 targetancestors = set(repo.changelog.ancestors(dest))
417 417 for rev in state:
418 418 # Check externals and fail if there are more than one
419 419 for p in repo[rev].parents():
420 420 if (p.rev() not in state and p.rev() != source
421 421 and p.rev() not in targetancestors):
422 422 if external != nullrev:
423 423 raise util.Abort(_('unable to collapse, there is more '
424 424 'than one external parent'))
425 425 external = p.rev()
426 426
427 427 state[source] = nullrev
428 428 return repo['.'].rev(), repo[dest].rev(), state, external
429 429
430 430 def pullrebase(orig, ui, repo, *args, **opts):
431 431 'Call rebase after pull if the latter has been invoked with --rebase'
432 432 if opts.get('rebase'):
433 433 if opts.get('update'):
434 434 del opts['update']
435 435 ui.debug(_('--update and --rebase are not compatible, ignoring '
436 436 'the update flag\n'))
437 437
438 438 cmdutil.bail_if_changed(repo)
439 439 revsprepull = len(repo)
440 440 orig(ui, repo, *args, **opts)
441 441 revspostpull = len(repo)
442 442 if revspostpull > revsprepull:
443 443 rebase(ui, repo, **opts)
444 444 branch = repo[None].branch()
445 445 dest = repo[branch].rev()
446 446 if dest != repo['.'].rev():
447 447 # there was nothing to rebase we force an update
448 448 merge.update(repo, dest, False, False, False)
449 449 else:
450 450 orig(ui, repo, *args, **opts)
451 451
452 452 def uisetup(ui):
453 453 'Replace pull with a decorator to provide --rebase option'
454 454 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
455 455 entry[1].append(('', 'rebase', None,
456 456 _("rebase working directory to branch head"))
457 457 )
458 458
459 459 cmdtable = {
460 460 "rebase":
461 461 (rebase,
462 462 [
463 463 ('s', 'source', '', _('rebase from a given revision')),
464 464 ('b', 'base', '', _('rebase from the base of a given revision')),
465 465 ('d', 'dest', '', _('rebase onto a given revision')),
466 466 ('', 'collapse', False, _('collapse the rebased revisions')),
467 467 ('', 'keep', False, _('keep original revisions')),
468 468 ('', 'keepbranches', False, _('keep original branches')),
469 469 ('c', 'continue', False, _('continue an interrupted rebase')),
470 470 ('a', 'abort', False, _('abort an interrupted rebase')),] +
471 471 templateopts,
472 472 _('hg rebase [-s REV | -b REV] [-d REV] [--collapse] [--keep] '
473 473 '[--keepbranches] | [-c] | [-a]')),
474 474 }
@@ -1,602 +1,602 b''
1 1 # Patch transplanting extension for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 '''patch transplanting tool
9 9
10 10 This extension allows you to transplant patches from another branch.
11 11
12 12 Transplanted patches are recorded in .hg/transplant/transplants, as a
13 13 map from a changeset hash to its hash in the source repository.
14 14 '''
15 15
16 16 from mercurial.i18n import _
17 17 import os, tempfile
18 18 from mercurial import bundlerepo, changegroup, cmdutil, hg, merge
19 19 from mercurial import patch, revlog, util, error
20 20
21 21 class transplantentry:
22 22 def __init__(self, lnode, rnode):
23 23 self.lnode = lnode
24 24 self.rnode = rnode
25 25
26 26 class transplants:
27 27 def __init__(self, path=None, transplantfile=None, opener=None):
28 28 self.path = path
29 29 self.transplantfile = transplantfile
30 30 self.opener = opener
31 31
32 32 if not opener:
33 33 self.opener = util.opener(self.path)
34 34 self.transplants = []
35 35 self.dirty = False
36 36 self.read()
37 37
38 38 def read(self):
39 39 abspath = os.path.join(self.path, self.transplantfile)
40 40 if self.transplantfile and os.path.exists(abspath):
41 41 for line in self.opener(self.transplantfile).read().splitlines():
42 42 lnode, rnode = map(revlog.bin, line.split(':'))
43 43 self.transplants.append(transplantentry(lnode, rnode))
44 44
45 45 def write(self):
46 46 if self.dirty and self.transplantfile:
47 47 if not os.path.isdir(self.path):
48 48 os.mkdir(self.path)
49 49 fp = self.opener(self.transplantfile, 'w')
50 50 for c in self.transplants:
51 51 l, r = map(revlog.hex, (c.lnode, c.rnode))
52 52 fp.write(l + ':' + r + '\n')
53 53 fp.close()
54 54 self.dirty = False
55 55
56 56 def get(self, rnode):
57 57 return [t for t in self.transplants if t.rnode == rnode]
58 58
59 59 def set(self, lnode, rnode):
60 60 self.transplants.append(transplantentry(lnode, rnode))
61 61 self.dirty = True
62 62
63 63 def remove(self, transplant):
64 64 del self.transplants[self.transplants.index(transplant)]
65 65 self.dirty = True
66 66
67 67 class transplanter:
68 68 def __init__(self, ui, repo):
69 69 self.ui = ui
70 70 self.path = repo.join('transplant')
71 71 self.opener = util.opener(self.path)
72 72 self.transplants = transplants(self.path, 'transplants',
73 73 opener=self.opener)
74 74
75 75 def applied(self, repo, node, parent):
76 76 '''returns True if a node is already an ancestor of parent
77 77 or has already been transplanted'''
78 78 if hasnode(repo, node):
79 79 if node in repo.changelog.reachable(parent, stop=node):
80 80 return True
81 81 for t in self.transplants.get(node):
82 82 # it might have been stripped
83 83 if not hasnode(repo, t.lnode):
84 84 self.transplants.remove(t)
85 85 return False
86 86 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
87 87 return True
88 88 return False
89 89
90 90 def apply(self, repo, source, revmap, merges, opts={}):
91 91 '''apply the revisions in revmap one by one in revision order'''
92 92 revs = sorted(revmap)
93 93 p1, p2 = repo.dirstate.parents()
94 94 pulls = []
95 95 diffopts = patch.diffopts(self.ui, opts)
96 96 diffopts.git = True
97 97
98 98 lock = wlock = None
99 99 try:
100 100 wlock = repo.wlock()
101 101 lock = repo.lock()
102 102 for rev in revs:
103 103 node = revmap[rev]
104 104 revstr = '%s:%s' % (rev, revlog.short(node))
105 105
106 106 if self.applied(repo, node, p1):
107 107 self.ui.warn(_('skipping already applied revision %s\n') %
108 108 revstr)
109 109 continue
110 110
111 111 parents = source.changelog.parents(node)
112 112 if not opts.get('filter'):
113 113 # If the changeset parent is the same as the
114 114 # wdir's parent, just pull it.
115 115 if parents[0] == p1:
116 116 pulls.append(node)
117 117 p1 = node
118 118 continue
119 119 if pulls:
120 120 if source != repo:
121 121 repo.pull(source, heads=pulls)
122 122 merge.update(repo, pulls[-1], False, False, None)
123 123 p1, p2 = repo.dirstate.parents()
124 124 pulls = []
125 125
126 126 domerge = False
127 127 if node in merges:
128 128 # pulling all the merge revs at once would mean we
129 129 # couldn't transplant after the latest even if
130 130 # transplants before them fail.
131 131 domerge = True
132 132 if not hasnode(repo, node):
133 133 repo.pull(source, heads=[node])
134 134
135 135 if parents[1] != revlog.nullid:
136 136 self.ui.note(_('skipping merge changeset %s:%s\n')
137 137 % (rev, revlog.short(node)))
138 138 patchfile = None
139 139 else:
140 140 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
141 141 fp = os.fdopen(fd, 'w')
142 142 gen = patch.diff(source, parents[0], node, opts=diffopts)
143 143 for chunk in gen:
144 144 fp.write(chunk)
145 145 fp.close()
146 146
147 147 del revmap[rev]
148 148 if patchfile or domerge:
149 149 try:
150 150 n = self.applyone(repo, node,
151 151 source.changelog.read(node),
152 152 patchfile, merge=domerge,
153 153 log=opts.get('log'),
154 154 filter=opts.get('filter'))
155 155 if n and domerge:
156 156 self.ui.status(_('%s merged at %s\n') % (revstr,
157 157 revlog.short(n)))
158 158 elif n:
159 159 self.ui.status(_('%s transplanted to %s\n')
160 160 % (revlog.short(node),
161 161 revlog.short(n)))
162 162 finally:
163 163 if patchfile:
164 164 os.unlink(patchfile)
165 165 if pulls:
166 166 repo.pull(source, heads=pulls)
167 167 merge.update(repo, pulls[-1], False, False, None)
168 168 finally:
169 169 self.saveseries(revmap, merges)
170 170 self.transplants.write()
171 171 lock.release()
172 172 wlock.release()
173 173
174 174 def filter(self, filter, changelog, patchfile):
175 175 '''arbitrarily rewrite changeset before applying it'''
176 176
177 177 self.ui.status(_('filtering %s\n') % patchfile)
178 178 user, date, msg = (changelog[1], changelog[2], changelog[4])
179 179
180 180 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
181 181 fp = os.fdopen(fd, 'w')
182 182 fp.write("# HG changeset patch\n")
183 183 fp.write("# User %s\n" % user)
184 184 fp.write("# Date %d %d\n" % date)
185 185 fp.write(changelog[4])
186 186 fp.close()
187 187
188 188 try:
189 189 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
190 190 util.shellquote(patchfile)),
191 191 environ={'HGUSER': changelog[1]},
192 192 onerr=util.Abort, errprefix=_('filter failed'))
193 193 user, date, msg = self.parselog(file(headerfile))[1:4]
194 194 finally:
195 195 os.unlink(headerfile)
196 196
197 197 return (user, date, msg)
198 198
199 199 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
200 200 filter=None):
201 201 '''apply the patch in patchfile to the repository as a transplant'''
202 202 (manifest, user, (time, timezone), files, message) = cl[:5]
203 203 date = "%d %d" % (time, timezone)
204 204 extra = {'transplant_source': node}
205 205 if filter:
206 206 (user, date, message) = self.filter(filter, cl, patchfile)
207 207
208 208 if log:
209 209 message += '\n(transplanted from %s)' % revlog.hex(node)
210 210
211 211 self.ui.status(_('applying %s\n') % revlog.short(node))
212 212 self.ui.note('%s %s\n%s\n' % (user, date, message))
213 213
214 214 if not patchfile and not merge:
215 215 raise util.Abort(_('can only omit patchfile if merging'))
216 216 if patchfile:
217 217 try:
218 218 files = {}
219 219 try:
220 220 patch.patch(patchfile, self.ui, cwd=repo.root,
221 221 files=files)
222 222 if not files:
223 223 self.ui.warn(_('%s: empty changeset')
224 224 % revlog.hex(node))
225 225 return None
226 226 finally:
227 227 files = patch.updatedir(self.ui, repo, files)
228 228 except Exception, inst:
229 229 if filter:
230 230 os.unlink(patchfile)
231 231 seriespath = os.path.join(self.path, 'series')
232 232 if os.path.exists(seriespath):
233 233 os.unlink(seriespath)
234 234 p1 = repo.dirstate.parents()[0]
235 235 p2 = node
236 236 self.log(user, date, message, p1, p2, merge=merge)
237 237 self.ui.write(str(inst) + '\n')
238 238 raise util.Abort(_('Fix up the merge and run '
239 239 'hg transplant --continue'))
240 240 else:
241 241 files = None
242 242 if merge:
243 243 p1, p2 = repo.dirstate.parents()
244 244 repo.dirstate.setparents(p1, node)
245 245
246 246 n = repo.commit(files, message, user, date, extra=extra)
247 247 if not merge:
248 248 self.transplants.set(n, node)
249 249
250 250 return n
251 251
252 252 def resume(self, repo, source, opts=None):
253 253 '''recover last transaction and apply remaining changesets'''
254 254 if os.path.exists(os.path.join(self.path, 'journal')):
255 255 n, node = self.recover(repo)
256 256 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
257 257 revlog.short(n)))
258 258 seriespath = os.path.join(self.path, 'series')
259 259 if not os.path.exists(seriespath):
260 260 self.transplants.write()
261 261 return
262 262 nodes, merges = self.readseries()
263 263 revmap = {}
264 264 for n in nodes:
265 265 revmap[source.changelog.rev(n)] = n
266 266 os.unlink(seriespath)
267 267
268 268 self.apply(repo, source, revmap, merges, opts)
269 269
270 270 def recover(self, repo):
271 271 '''commit working directory using journal metadata'''
272 272 node, user, date, message, parents = self.readlog()
273 273 merge = len(parents) == 2
274 274
275 275 if not user or not date or not message or not parents[0]:
276 276 raise util.Abort(_('transplant log file is corrupt'))
277 277
278 278 extra = {'transplant_source': node}
279 279 wlock = repo.wlock()
280 280 try:
281 281 p1, p2 = repo.dirstate.parents()
282 282 if p1 != parents[0]:
283 283 raise util.Abort(
284 284 _('working dir not at transplant parent %s') %
285 285 revlog.hex(parents[0]))
286 286 if merge:
287 287 repo.dirstate.setparents(p1, parents[1])
288 288 n = repo.commit(None, message, user, date, extra=extra)
289 289 if not n:
290 290 raise util.Abort(_('commit failed'))
291 291 if not merge:
292 292 self.transplants.set(n, node)
293 293 self.unlog()
294 294
295 295 return n, node
296 296 finally:
297 297 wlock.release()
298 298
299 299 def readseries(self):
300 300 nodes = []
301 301 merges = []
302 302 cur = nodes
303 303 for line in self.opener('series').read().splitlines():
304 304 if line.startswith('# Merges'):
305 305 cur = merges
306 306 continue
307 307 cur.append(revlog.bin(line))
308 308
309 309 return (nodes, merges)
310 310
311 311 def saveseries(self, revmap, merges):
312 312 if not revmap:
313 313 return
314 314
315 315 if not os.path.isdir(self.path):
316 316 os.mkdir(self.path)
317 317 series = self.opener('series', 'w')
318 318 for rev in sorted(revmap):
319 319 series.write(revlog.hex(revmap[rev]) + '\n')
320 320 if merges:
321 321 series.write('# Merges\n')
322 322 for m in merges:
323 323 series.write(revlog.hex(m) + '\n')
324 324 series.close()
325 325
326 326 def parselog(self, fp):
327 327 parents = []
328 328 message = []
329 329 node = revlog.nullid
330 330 inmsg = False
331 331 for line in fp.read().splitlines():
332 332 if inmsg:
333 333 message.append(line)
334 334 elif line.startswith('# User '):
335 335 user = line[7:]
336 336 elif line.startswith('# Date '):
337 337 date = line[7:]
338 338 elif line.startswith('# Node ID '):
339 339 node = revlog.bin(line[10:])
340 340 elif line.startswith('# Parent '):
341 341 parents.append(revlog.bin(line[9:]))
342 342 elif not line.startswith('#'):
343 343 inmsg = True
344 344 message.append(line)
345 345 return (node, user, date, '\n'.join(message), parents)
346 346
347 347 def log(self, user, date, message, p1, p2, merge=False):
348 348 '''journal changelog metadata for later recover'''
349 349
350 350 if not os.path.isdir(self.path):
351 351 os.mkdir(self.path)
352 352 fp = self.opener('journal', 'w')
353 353 fp.write('# User %s\n' % user)
354 354 fp.write('# Date %s\n' % date)
355 355 fp.write('# Node ID %s\n' % revlog.hex(p2))
356 356 fp.write('# Parent ' + revlog.hex(p1) + '\n')
357 357 if merge:
358 358 fp.write('# Parent ' + revlog.hex(p2) + '\n')
359 359 fp.write(message.rstrip() + '\n')
360 360 fp.close()
361 361
362 362 def readlog(self):
363 363 return self.parselog(self.opener('journal'))
364 364
365 365 def unlog(self):
366 366 '''remove changelog journal'''
367 367 absdst = os.path.join(self.path, 'journal')
368 368 if os.path.exists(absdst):
369 369 os.unlink(absdst)
370 370
371 371 def transplantfilter(self, repo, source, root):
372 372 def matchfn(node):
373 373 if self.applied(repo, node, root):
374 374 return False
375 375 if source.changelog.parents(node)[1] != revlog.nullid:
376 376 return False
377 377 extra = source.changelog.read(node)[5]
378 378 cnode = extra.get('transplant_source')
379 379 if cnode and self.applied(repo, cnode, root):
380 380 return False
381 381 return True
382 382
383 383 return matchfn
384 384
385 385 def hasnode(repo, node):
386 386 try:
387 387 return repo.changelog.rev(node) != None
388 388 except error.RevlogError:
389 389 return False
390 390
391 391 def browserevs(ui, repo, nodes, opts):
392 392 '''interactively transplant changesets'''
393 393 def browsehelp(ui):
394 394 ui.write('y: transplant this changeset\n'
395 395 'n: skip this changeset\n'
396 396 'm: merge at this changeset\n'
397 397 'p: show patch\n'
398 398 'c: commit selected changesets\n'
399 399 'q: cancel transplant\n'
400 400 '?: show this help\n')
401 401
402 402 displayer = cmdutil.show_changeset(ui, repo, opts)
403 403 transplants = []
404 404 merges = []
405 405 for node in nodes:
406 406 displayer.show(repo[node])
407 407 action = None
408 408 while not action:
409 409 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
410 410 if action == '?':
411 411 browsehelp(ui)
412 412 action = None
413 413 elif action == 'p':
414 414 parent = repo.changelog.parents(node)[0]
415 415 for chunk in patch.diff(repo, parent, node):
416 repo.ui.write(chunk)
416 ui.write(chunk)
417 417 action = None
418 418 elif action not in ('y', 'n', 'm', 'c', 'q'):
419 419 ui.write('no such option\n')
420 420 action = None
421 421 if action == 'y':
422 422 transplants.append(node)
423 423 elif action == 'm':
424 424 merges.append(node)
425 425 elif action == 'c':
426 426 break
427 427 elif action == 'q':
428 428 transplants = ()
429 429 merges = ()
430 430 break
431 431 return (transplants, merges)
432 432
433 433 def transplant(ui, repo, *revs, **opts):
434 434 '''transplant changesets from another branch
435 435
436 436 Selected changesets will be applied on top of the current working
437 437 directory with the log of the original changeset. If --log is
438 438 specified, log messages will have a comment appended of the form:
439 439
440 440 (transplanted from CHANGESETHASH)
441 441
442 442 You can rewrite the changelog message with the --filter option.
443 443 Its argument will be invoked with the current changelog message as
444 444 $1 and the patch as $2.
445 445
446 446 If --source/-s is specified, selects changesets from the named
447 447 repository. If --branch/-b is specified, selects changesets from
448 448 the branch holding the named revision, up to that revision. If
449 449 --all/-a is specified, all changesets on the branch will be
450 450 transplanted, otherwise you will be prompted to select the
451 451 changesets you want.
452 452
453 453 hg transplant --branch REVISION --all will rebase the selected
454 454 branch (up to the named revision) onto your current working
455 455 directory.
456 456
457 457 You can optionally mark selected transplanted changesets as merge
458 458 changesets. You will not be prompted to transplant any ancestors
459 459 of a merged transplant, and you can merge descendants of them
460 460 normally instead of transplanting them.
461 461
462 462 If no merges or revisions are provided, hg transplant will start
463 463 an interactive changeset browser.
464 464
465 465 If a changeset application fails, you can fix the merge by hand
466 466 and then resume where you left off by calling hg transplant
467 467 --continue/-c.
468 468 '''
469 469 def getremotechanges(repo, url):
470 470 sourcerepo = ui.expandpath(url)
471 471 source = hg.repository(ui, sourcerepo)
472 472 common, incoming, rheads = repo.findcommonincoming(source, force=True)
473 473 if not incoming:
474 474 return (source, None, None)
475 475
476 476 bundle = None
477 477 if not source.local():
478 478 if source.capable('changegroupsubset'):
479 479 cg = source.changegroupsubset(incoming, rheads, 'incoming')
480 480 else:
481 481 cg = source.changegroup(incoming, 'incoming')
482 482 bundle = changegroup.writebundle(cg, None, 'HG10UN')
483 483 source = bundlerepo.bundlerepository(ui, repo.root, bundle)
484 484
485 485 return (source, incoming, bundle)
486 486
487 487 def incwalk(repo, incoming, branches, match=util.always):
488 488 if not branches:
489 489 branches=None
490 490 for node in repo.changelog.nodesbetween(incoming, branches)[0]:
491 491 if match(node):
492 492 yield node
493 493
494 494 def transplantwalk(repo, root, branches, match=util.always):
495 495 if not branches:
496 496 branches = repo.heads()
497 497 ancestors = []
498 498 for branch in branches:
499 499 ancestors.append(repo.changelog.ancestor(root, branch))
500 500 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
501 501 if match(node):
502 502 yield node
503 503
504 504 def checkopts(opts, revs):
505 505 if opts.get('continue'):
506 506 if filter(lambda opt: opts.get(opt), ('branch', 'all', 'merge')):
507 507 raise util.Abort(_('--continue is incompatible with '
508 508 'branch, all or merge'))
509 509 return
510 510 if not (opts.get('source') or revs or
511 511 opts.get('merge') or opts.get('branch')):
512 512 raise util.Abort(_('no source URL, branch tag or revision '
513 513 'list provided'))
514 514 if opts.get('all'):
515 515 if not opts.get('branch'):
516 516 raise util.Abort(_('--all requires a branch revision'))
517 517 if revs:
518 518 raise util.Abort(_('--all is incompatible with a '
519 519 'revision list'))
520 520
521 521 checkopts(opts, revs)
522 522
523 523 if not opts.get('log'):
524 524 opts['log'] = ui.config('transplant', 'log')
525 525 if not opts.get('filter'):
526 526 opts['filter'] = ui.config('transplant', 'filter')
527 527
528 528 tp = transplanter(ui, repo)
529 529
530 530 p1, p2 = repo.dirstate.parents()
531 531 if len(repo) > 0 and p1 == revlog.nullid:
532 532 raise util.Abort(_('no revision checked out'))
533 533 if not opts.get('continue'):
534 534 if p2 != revlog.nullid:
535 535 raise util.Abort(_('outstanding uncommitted merges'))
536 536 m, a, r, d = repo.status()[:4]
537 537 if m or a or r or d:
538 538 raise util.Abort(_('outstanding local changes'))
539 539
540 540 bundle = None
541 541 source = opts.get('source')
542 542 if source:
543 543 (source, incoming, bundle) = getremotechanges(repo, source)
544 544 else:
545 545 source = repo
546 546
547 547 try:
548 548 if opts.get('continue'):
549 549 tp.resume(repo, source, opts)
550 550 return
551 551
552 552 tf=tp.transplantfilter(repo, source, p1)
553 553 if opts.get('prune'):
554 554 prune = [source.lookup(r)
555 555 for r in cmdutil.revrange(source, opts.get('prune'))]
556 556 matchfn = lambda x: tf(x) and x not in prune
557 557 else:
558 558 matchfn = tf
559 559 branches = map(source.lookup, opts.get('branch', ()))
560 560 merges = map(source.lookup, opts.get('merge', ()))
561 561 revmap = {}
562 562 if revs:
563 563 for r in cmdutil.revrange(source, revs):
564 564 revmap[int(r)] = source.lookup(r)
565 565 elif opts.get('all') or not merges:
566 566 if source != repo:
567 567 alltransplants = incwalk(source, incoming, branches,
568 568 match=matchfn)
569 569 else:
570 570 alltransplants = transplantwalk(source, p1, branches,
571 571 match=matchfn)
572 572 if opts.get('all'):
573 573 revs = alltransplants
574 574 else:
575 575 revs, newmerges = browserevs(ui, source, alltransplants, opts)
576 576 merges.extend(newmerges)
577 577 for r in revs:
578 578 revmap[source.changelog.rev(r)] = r
579 579 for r in merges:
580 580 revmap[source.changelog.rev(r)] = r
581 581
582 582 tp.apply(repo, source, revmap, merges, opts)
583 583 finally:
584 584 if bundle:
585 585 source.close()
586 586 os.unlink(bundle)
587 587
588 588 cmdtable = {
589 589 "transplant":
590 590 (transplant,
591 591 [('s', 'source', '', _('pull patches from REPOSITORY')),
592 592 ('b', 'branch', [], _('pull patches from branch BRANCH')),
593 593 ('a', 'all', None, _('pull all changesets up to BRANCH')),
594 594 ('p', 'prune', [], _('skip over REV')),
595 595 ('m', 'merge', [], _('merge at REV')),
596 596 ('', 'log', None, _('append transplant info to log message')),
597 597 ('c', 'continue', None, _('continue last transplant session '
598 598 'after repair')),
599 599 ('', 'filter', '', _('filter changesets through FILTER'))],
600 600 _('hg transplant [-s REPOSITORY] [-b BRANCH [-a]] [-p REV] '
601 601 '[-m REV] [REV]...'))
602 602 }
@@ -1,3462 +1,3462 b''
1 1 # commands.py - command processing for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 from node import hex, nullid, nullrev, short
9 9 from lock import release
10 10 from i18n import _, gettext
11 11 import os, re, sys, textwrap, subprocess, difflib, time
12 12 import hg, util, revlog, bundlerepo, extensions, copies, context, error
13 13 import patch, help, mdiff, tempfile, url, encoding
14 14 import archival, changegroup, cmdutil, sshserver, hbisect
15 15 from hgweb import server
16 16 import merge as merge_
17 17
18 18 # Commands start here, listed alphabetically
19 19
20 20 def add(ui, repo, *pats, **opts):
21 21 """add the specified files on the next commit
22 22
23 23 Schedule files to be version controlled and added to the
24 24 repository.
25 25
26 26 The files will be added to the repository at the next commit. To
27 27 undo an add before that, see hg revert.
28 28
29 29 If no names are given, add all files to the repository.
30 30 """
31 31
32 32 rejected = None
33 33 exacts = {}
34 34 names = []
35 35 m = cmdutil.match(repo, pats, opts)
36 36 m.bad = lambda x,y: True
37 37 for abs in repo.walk(m):
38 38 if m.exact(abs):
39 39 if ui.verbose:
40 40 ui.status(_('adding %s\n') % m.rel(abs))
41 41 names.append(abs)
42 42 exacts[abs] = 1
43 43 elif abs not in repo.dirstate:
44 44 ui.status(_('adding %s\n') % m.rel(abs))
45 45 names.append(abs)
46 46 if not opts.get('dry_run'):
47 47 rejected = repo.add(names)
48 48 rejected = [p for p in rejected if p in exacts]
49 49 return rejected and 1 or 0
50 50
51 51 def addremove(ui, repo, *pats, **opts):
52 52 """add all new files, delete all missing files
53 53
54 54 Add all new files and remove all missing files from the
55 55 repository.
56 56
57 57 New files are ignored if they match any of the patterns in
58 58 .hgignore. As with add, these changes take effect at the next
59 59 commit.
60 60
61 61 Use the -s/--similarity option to detect renamed files. With a
62 62 parameter > 0, this compares every removed file with every added
63 63 file and records those similar enough as renames. This option
64 64 takes a percentage between 0 (disabled) and 100 (files must be
65 65 identical) as its parameter. Detecting renamed files this way can
66 66 be expensive.
67 67 """
68 68 try:
69 69 sim = float(opts.get('similarity') or 0)
70 70 except ValueError:
71 71 raise util.Abort(_('similarity must be a number'))
72 72 if sim < 0 or sim > 100:
73 73 raise util.Abort(_('similarity must be between 0 and 100'))
74 74 return cmdutil.addremove(repo, pats, opts, similarity=sim/100.)
75 75
76 76 def annotate(ui, repo, *pats, **opts):
77 77 """show changeset information per file line
78 78
79 79 List changes in files, showing the revision id responsible for
80 80 each line
81 81
82 82 This command is useful to discover who did a change or when a
83 83 change took place.
84 84
85 85 Without the -a/--text option, annotate will avoid processing files
86 86 it detects as binary. With -a, annotate will generate an
87 87 annotation anyway, probably with undesirable results.
88 88 """
89 89 datefunc = ui.quiet and util.shortdate or util.datestr
90 90 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
91 91
92 92 if not pats:
93 93 raise util.Abort(_('at least one file name or pattern required'))
94 94
95 95 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
96 96 ('number', lambda x: str(x[0].rev())),
97 97 ('changeset', lambda x: short(x[0].node())),
98 98 ('date', getdate),
99 99 ('follow', lambda x: x[0].path()),
100 100 ]
101 101
102 102 if (not opts.get('user') and not opts.get('changeset') and not opts.get('date')
103 103 and not opts.get('follow')):
104 104 opts['number'] = 1
105 105
106 106 linenumber = opts.get('line_number') is not None
107 107 if (linenumber and (not opts.get('changeset')) and (not opts.get('number'))):
108 108 raise util.Abort(_('at least one of -n/-c is required for -l'))
109 109
110 110 funcmap = [func for op, func in opmap if opts.get(op)]
111 111 if linenumber:
112 112 lastfunc = funcmap[-1]
113 113 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
114 114
115 115 ctx = repo[opts.get('rev')]
116 116
117 117 m = cmdutil.match(repo, pats, opts)
118 118 for abs in ctx.walk(m):
119 119 fctx = ctx[abs]
120 120 if not opts.get('text') and util.binary(fctx.data()):
121 121 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
122 122 continue
123 123
124 124 lines = fctx.annotate(follow=opts.get('follow'),
125 125 linenumber=linenumber)
126 126 pieces = []
127 127
128 128 for f in funcmap:
129 129 l = [f(n) for n, dummy in lines]
130 130 if l:
131 131 ml = max(map(len, l))
132 132 pieces.append(["%*s" % (ml, x) for x in l])
133 133
134 134 if pieces:
135 135 for p, l in zip(zip(*pieces), lines):
136 136 ui.write("%s: %s" % (" ".join(p), l[1]))
137 137
138 138 def archive(ui, repo, dest, **opts):
139 139 '''create unversioned archive of a repository revision
140 140
141 141 By default, the revision used is the parent of the working
142 142 directory; use -r/--rev to specify a different revision.
143 143
144 144 To specify the type of archive to create, use -t/--type. Valid
145 145 types are:
146 146
147 147 "files" (default): a directory full of files
148 148 "tar": tar archive, uncompressed
149 149 "tbz2": tar archive, compressed using bzip2
150 150 "tgz": tar archive, compressed using gzip
151 151 "uzip": zip archive, uncompressed
152 152 "zip": zip archive, compressed using deflate
153 153
154 154 The exact name of the destination archive or directory is given
155 155 using a format string; see 'hg help export' for details.
156 156
157 157 Each member added to an archive file has a directory prefix
158 158 prepended. Use -p/--prefix to specify a format string for the
159 159 prefix. The default is the basename of the archive, with suffixes
160 160 removed.
161 161 '''
162 162
163 163 ctx = repo[opts.get('rev')]
164 164 if not ctx:
165 165 raise util.Abort(_('no working directory: please specify a revision'))
166 166 node = ctx.node()
167 167 dest = cmdutil.make_filename(repo, dest, node)
168 168 if os.path.realpath(dest) == repo.root:
169 169 raise util.Abort(_('repository root cannot be destination'))
170 170 matchfn = cmdutil.match(repo, [], opts)
171 171 kind = opts.get('type') or 'files'
172 172 prefix = opts.get('prefix')
173 173 if dest == '-':
174 174 if kind == 'files':
175 175 raise util.Abort(_('cannot archive plain files to stdout'))
176 176 dest = sys.stdout
177 177 if not prefix: prefix = os.path.basename(repo.root) + '-%h'
178 178 prefix = cmdutil.make_filename(repo, prefix, node)
179 179 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
180 180 matchfn, prefix)
181 181
182 182 def backout(ui, repo, node=None, rev=None, **opts):
183 183 '''reverse effect of earlier changeset
184 184
185 185 Commit the backed out changes as a new changeset. The new
186 186 changeset is a child of the backed out changeset.
187 187
188 188 If you back out a changeset other than the tip, a new head is
189 189 created. This head will be the new tip and you should merge this
190 190 backout changeset with another head (current one by default).
191 191
192 192 The --merge option remembers the parent of the working directory
193 193 before starting the backout, then merges the new head with that
194 194 changeset afterwards. This saves you from doing the merge by hand.
195 195 The result of this merge is not committed, as with a normal merge.
196 196
197 197 See 'hg help dates' for a list of formats valid for -d/--date.
198 198 '''
199 199 if rev and node:
200 200 raise util.Abort(_("please specify just one revision"))
201 201
202 202 if not rev:
203 203 rev = node
204 204
205 205 if not rev:
206 206 raise util.Abort(_("please specify a revision to backout"))
207 207
208 208 date = opts.get('date')
209 209 if date:
210 210 opts['date'] = util.parsedate(date)
211 211
212 212 cmdutil.bail_if_changed(repo)
213 213 node = repo.lookup(rev)
214 214
215 215 op1, op2 = repo.dirstate.parents()
216 216 a = repo.changelog.ancestor(op1, node)
217 217 if a != node:
218 218 raise util.Abort(_('cannot back out change on a different branch'))
219 219
220 220 p1, p2 = repo.changelog.parents(node)
221 221 if p1 == nullid:
222 222 raise util.Abort(_('cannot back out a change with no parents'))
223 223 if p2 != nullid:
224 224 if not opts.get('parent'):
225 225 raise util.Abort(_('cannot back out a merge changeset without '
226 226 '--parent'))
227 227 p = repo.lookup(opts['parent'])
228 228 if p not in (p1, p2):
229 229 raise util.Abort(_('%s is not a parent of %s') %
230 230 (short(p), short(node)))
231 231 parent = p
232 232 else:
233 233 if opts.get('parent'):
234 234 raise util.Abort(_('cannot use --parent on non-merge changeset'))
235 235 parent = p1
236 236
237 237 # the backout should appear on the same branch
238 238 branch = repo.dirstate.branch()
239 239 hg.clean(repo, node, show_stats=False)
240 240 repo.dirstate.setbranch(branch)
241 241 revert_opts = opts.copy()
242 242 revert_opts['date'] = None
243 243 revert_opts['all'] = True
244 244 revert_opts['rev'] = hex(parent)
245 245 revert_opts['no_backup'] = None
246 246 revert(ui, repo, **revert_opts)
247 247 commit_opts = opts.copy()
248 248 commit_opts['addremove'] = False
249 249 if not commit_opts['message'] and not commit_opts['logfile']:
250 250 commit_opts['message'] = _("Backed out changeset %s") % (short(node))
251 251 commit_opts['force_editor'] = True
252 252 commit(ui, repo, **commit_opts)
253 253 def nice(node):
254 254 return '%d:%s' % (repo.changelog.rev(node), short(node))
255 255 ui.status(_('changeset %s backs out changeset %s\n') %
256 256 (nice(repo.changelog.tip()), nice(node)))
257 257 if op1 != node:
258 258 hg.clean(repo, op1, show_stats=False)
259 259 if opts.get('merge'):
260 260 ui.status(_('merging with changeset %s\n') % nice(repo.changelog.tip()))
261 261 hg.merge(repo, hex(repo.changelog.tip()))
262 262 else:
263 263 ui.status(_('the backout changeset is a new head - '
264 264 'do not forget to merge\n'))
265 265 ui.status(_('(use "backout --merge" '
266 266 'if you want to auto-merge)\n'))
267 267
268 268 def bisect(ui, repo, rev=None, extra=None, command=None,
269 269 reset=None, good=None, bad=None, skip=None, noupdate=None):
270 270 """subdivision search of changesets
271 271
272 272 This command helps to find changesets which introduce problems. To
273 273 use, mark the earliest changeset you know exhibits the problem as
274 274 bad, then mark the latest changeset which is free from the problem
275 275 as good. Bisect will update your working directory to a revision
276 276 for testing (unless the -U/--noupdate option is specified). Once
277 277 you have performed tests, mark the working directory as bad or
278 278 good and bisect will either update to another candidate changeset
279 279 or announce that it has found the bad revision.
280 280
281 281 As a shortcut, you can also use the revision argument to mark a
282 282 revision as good or bad without checking it out first.
283 283
284 284 If you supply a command it will be used for automatic bisection.
285 285 Its exit status will be used as flag to mark revision as bad or
286 286 good. In case exit status is 0 the revision is marked as good, 125
287 287 - skipped, 127 (command not found) - bisection will be aborted;
288 288 any other status bigger than 0 will mark revision as bad.
289 289 """
290 290 def print_result(nodes, good):
291 291 displayer = cmdutil.show_changeset(ui, repo, {})
292 292 if len(nodes) == 1:
293 293 # narrowed it down to a single revision
294 294 if good:
295 295 ui.write(_("The first good revision is:\n"))
296 296 else:
297 297 ui.write(_("The first bad revision is:\n"))
298 298 displayer.show(repo[nodes[0]])
299 299 else:
300 300 # multiple possible revisions
301 301 if good:
302 302 ui.write(_("Due to skipped revisions, the first "
303 303 "good revision could be any of:\n"))
304 304 else:
305 305 ui.write(_("Due to skipped revisions, the first "
306 306 "bad revision could be any of:\n"))
307 307 for n in nodes:
308 308 displayer.show(repo[n])
309 309
310 310 def check_state(state, interactive=True):
311 311 if not state['good'] or not state['bad']:
312 312 if (good or bad or skip or reset) and interactive:
313 313 return
314 314 if not state['good']:
315 315 raise util.Abort(_('cannot bisect (no known good revisions)'))
316 316 else:
317 317 raise util.Abort(_('cannot bisect (no known bad revisions)'))
318 318 return True
319 319
320 320 # backward compatibility
321 321 if rev in "good bad reset init".split():
322 322 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
323 323 cmd, rev, extra = rev, extra, None
324 324 if cmd == "good":
325 325 good = True
326 326 elif cmd == "bad":
327 327 bad = True
328 328 else:
329 329 reset = True
330 330 elif extra or good + bad + skip + reset + bool(command) > 1:
331 331 raise util.Abort(_('incompatible arguments'))
332 332
333 333 if reset:
334 334 p = repo.join("bisect.state")
335 335 if os.path.exists(p):
336 336 os.unlink(p)
337 337 return
338 338
339 339 state = hbisect.load_state(repo)
340 340
341 341 if command:
342 342 commandpath = util.find_exe(command)
343 343 changesets = 1
344 344 try:
345 345 while changesets:
346 346 # update state
347 347 status = subprocess.call([commandpath])
348 348 if status == 125:
349 349 transition = "skip"
350 350 elif status == 0:
351 351 transition = "good"
352 352 # status < 0 means process was killed
353 353 elif status == 127:
354 354 raise util.Abort(_("failed to execute %s") % command)
355 355 elif status < 0:
356 356 raise util.Abort(_("%s killed") % command)
357 357 else:
358 358 transition = "bad"
359 359 node = repo.lookup(rev or '.')
360 360 state[transition].append(node)
361 361 ui.note(_('Changeset %s: %s\n') % (short(node), transition))
362 362 check_state(state, interactive=False)
363 363 # bisect
364 364 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
365 365 # update to next check
366 366 cmdutil.bail_if_changed(repo)
367 367 hg.clean(repo, nodes[0], show_stats=False)
368 368 finally:
369 369 hbisect.save_state(repo, state)
370 370 return print_result(nodes, not status)
371 371
372 372 # update state
373 373 node = repo.lookup(rev or '.')
374 374 if good:
375 375 state['good'].append(node)
376 376 elif bad:
377 377 state['bad'].append(node)
378 378 elif skip:
379 379 state['skip'].append(node)
380 380
381 381 hbisect.save_state(repo, state)
382 382
383 383 if not check_state(state):
384 384 return
385 385
386 386 # actually bisect
387 387 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
388 388 if changesets == 0:
389 389 print_result(nodes, good)
390 390 else:
391 391 assert len(nodes) == 1 # only a single node can be tested next
392 392 node = nodes[0]
393 393 # compute the approximate number of remaining tests
394 394 tests, size = 0, 2
395 395 while size <= changesets:
396 396 tests, size = tests + 1, size * 2
397 397 rev = repo.changelog.rev(node)
398 398 ui.write(_("Testing changeset %s:%s "
399 399 "(%s changesets remaining, ~%s tests)\n")
400 400 % (rev, short(node), changesets, tests))
401 401 if not noupdate:
402 402 cmdutil.bail_if_changed(repo)
403 403 return hg.clean(repo, node)
404 404
405 405 def branch(ui, repo, label=None, **opts):
406 406 """set or show the current branch name
407 407
408 408 With no argument, show the current branch name. With one argument,
409 409 set the working directory branch name (the branch does not exist
410 410 in the repository until the next commit). It is recommended to use
411 411 the 'default' branch as your primary development branch.
412 412
413 413 Unless -f/--force is specified, branch will not let you set a
414 414 branch name that shadows an existing branch.
415 415
416 416 Use -C/--clean to reset the working directory branch to that of
417 417 the parent of the working directory, negating a previous branch
418 418 change.
419 419
420 420 Use the command 'hg update' to switch to an existing branch.
421 421 """
422 422
423 423 if opts.get('clean'):
424 424 label = repo[None].parents()[0].branch()
425 425 repo.dirstate.setbranch(label)
426 426 ui.status(_('reset working directory to branch %s\n') % label)
427 427 elif label:
428 428 if not opts.get('force') and label in repo.branchtags():
429 429 if label not in [p.branch() for p in repo.parents()]:
430 430 raise util.Abort(_('a branch of the same name already exists'
431 431 ' (use --force to override)'))
432 432 repo.dirstate.setbranch(encoding.fromlocal(label))
433 433 ui.status(_('marked working directory as branch %s\n') % label)
434 434 else:
435 435 ui.write("%s\n" % encoding.tolocal(repo.dirstate.branch()))
436 436
437 437 def branches(ui, repo, active=False):
438 438 """list repository named branches
439 439
440 440 List the repository's named branches, indicating which ones are
441 441 inactive. If active is specified, only show active branches.
442 442
443 443 A branch is considered active if it contains repository heads.
444 444
445 445 Use the command 'hg update' to switch to an existing branch.
446 446 """
447 447 hexfunc = ui.debugflag and hex or short
448 448 activebranches = [encoding.tolocal(repo[n].branch())
449 449 for n in repo.heads(closed=False)]
450 450 branches = sorted([(tag in activebranches, repo.changelog.rev(node), tag)
451 451 for tag, node in repo.branchtags().items()],
452 452 reverse=True)
453 453
454 454 for isactive, node, tag in branches:
455 455 if (not active) or isactive:
456 456 if ui.quiet:
457 457 ui.write("%s\n" % tag)
458 458 else:
459 459 hn = repo.lookup(node)
460 460 if isactive:
461 461 notice = ''
462 462 elif hn not in repo.branchheads(tag, closed=False):
463 463 notice = ' (closed)'
464 464 else:
465 465 notice = ' (inactive)'
466 466 rev = str(node).rjust(31 - encoding.colwidth(tag))
467 467 data = tag, rev, hexfunc(hn), notice
468 468 ui.write("%s %s:%s%s\n" % data)
469 469
470 470 def bundle(ui, repo, fname, dest=None, **opts):
471 471 """create a changegroup file
472 472
473 473 Generate a compressed changegroup file collecting changesets not
474 474 known to be in another repository.
475 475
476 476 If no destination repository is specified the destination is
477 477 assumed to have all the nodes specified by one or more --base
478 478 parameters. To create a bundle containing all changesets, use
479 479 -a/--all (or --base null). To change the compression method
480 480 applied, use the -t/--type option (by default, bundles are
481 481 compressed using bz2).
482 482
483 483 The bundle file can then be transferred using conventional means
484 484 and applied to another repository with the unbundle or pull
485 485 command. This is useful when direct push and pull are not
486 486 available or when exporting an entire repository is undesirable.
487 487
488 488 Applying bundles preserves all changeset contents including
489 489 permissions, copy/rename information, and revision history.
490 490 """
491 491 revs = opts.get('rev') or None
492 492 if revs:
493 493 revs = [repo.lookup(rev) for rev in revs]
494 494 if opts.get('all'):
495 495 base = ['null']
496 496 else:
497 497 base = opts.get('base')
498 498 if base:
499 499 if dest:
500 500 raise util.Abort(_("--base is incompatible with specifiying "
501 501 "a destination"))
502 502 base = [repo.lookup(rev) for rev in base]
503 503 # create the right base
504 504 # XXX: nodesbetween / changegroup* should be "fixed" instead
505 505 o = []
506 506 has = set((nullid,))
507 507 for n in base:
508 508 has.update(repo.changelog.reachable(n))
509 509 if revs:
510 510 visit = list(revs)
511 511 else:
512 512 visit = repo.changelog.heads()
513 513 seen = {}
514 514 while visit:
515 515 n = visit.pop(0)
516 516 parents = [p for p in repo.changelog.parents(n) if p not in has]
517 517 if len(parents) == 0:
518 518 o.insert(0, n)
519 519 else:
520 520 for p in parents:
521 521 if p not in seen:
522 522 seen[p] = 1
523 523 visit.append(p)
524 524 else:
525 525 dest, revs, checkout = hg.parseurl(
526 526 ui.expandpath(dest or 'default-push', dest or 'default'), revs)
527 527 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
528 528 o = repo.findoutgoing(other, force=opts.get('force'))
529 529
530 530 if revs:
531 531 cg = repo.changegroupsubset(o, revs, 'bundle')
532 532 else:
533 533 cg = repo.changegroup(o, 'bundle')
534 534
535 535 bundletype = opts.get('type', 'bzip2').lower()
536 536 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
537 537 bundletype = btypes.get(bundletype)
538 538 if bundletype not in changegroup.bundletypes:
539 539 raise util.Abort(_('unknown bundle type specified with --type'))
540 540
541 541 changegroup.writebundle(cg, fname, bundletype)
542 542
543 543 def cat(ui, repo, file1, *pats, **opts):
544 544 """output the current or given revision of files
545 545
546 546 Print the specified files as they were at the given revision. If
547 547 no revision is given, the parent of the working directory is used,
548 548 or tip if no revision is checked out.
549 549
550 550 Output may be to a file, in which case the name of the file is
551 551 given using a format string. The formatting rules are the same as
552 552 for the export command, with the following additions:
553 553
554 554 %s basename of file being printed
555 555 %d dirname of file being printed, or '.' if in repository root
556 556 %p root-relative path name of file being printed
557 557 """
558 558 ctx = repo[opts.get('rev')]
559 559 err = 1
560 560 m = cmdutil.match(repo, (file1,) + pats, opts)
561 561 for abs in ctx.walk(m):
562 562 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
563 563 data = ctx[abs].data()
564 564 if opts.get('decode'):
565 565 data = repo.wwritedata(abs, data)
566 566 fp.write(data)
567 567 err = 0
568 568 return err
569 569
570 570 def clone(ui, source, dest=None, **opts):
571 571 """make a copy of an existing repository
572 572
573 573 Create a copy of an existing repository in a new directory.
574 574
575 575 If no destination directory name is specified, it defaults to the
576 576 basename of the source.
577 577
578 578 The location of the source is added to the new repository's
579 579 .hg/hgrc file, as the default to be used for future pulls.
580 580
581 581 If you use the -r/--rev option to clone up to a specific revision,
582 582 no subsequent revisions (including subsequent tags) will be
583 583 present in the cloned repository. This option implies --pull, even
584 584 on local repositories.
585 585
586 586 By default, clone will check out the head of the 'default' branch.
587 587 If the -U/--noupdate option is used, the new clone will contain
588 588 only a repository (.hg) and no working copy (the working copy
589 589 parent is the null revision).
590 590
591 591 See 'hg help urls' for valid source format details.
592 592
593 593 It is possible to specify an ssh:// URL as the destination, but no
594 594 .hg/hgrc and working directory will be created on the remote side.
595 595 Look at the help text for URLs for important details about ssh://
596 596 URLs.
597 597
598 598 For efficiency, hardlinks are used for cloning whenever the source
599 599 and destination are on the same filesystem (note this applies only
600 600 to the repository data, not to the checked out files). Some
601 601 filesystems, such as AFS, implement hardlinking incorrectly, but
602 602 do not report errors. In these cases, use the --pull option to
603 603 avoid hardlinking.
604 604
605 605 In some cases, you can clone repositories and checked out files
606 606 using full hardlinks with
607 607
608 608 $ cp -al REPO REPOCLONE
609 609
610 610 This is the fastest way to clone, but it is not always safe. The
611 611 operation is not atomic (making sure REPO is not modified during
612 612 the operation is up to you) and you have to make sure your editor
613 613 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
614 614 this is not compatible with certain extensions that place their
615 615 metadata under the .hg directory, such as mq.
616 616
617 617 """
618 618 hg.clone(cmdutil.remoteui(ui, opts), source, dest,
619 619 pull=opts.get('pull'),
620 620 stream=opts.get('uncompressed'),
621 621 rev=opts.get('rev'),
622 622 update=not opts.get('noupdate'))
623 623
624 624 def commit(ui, repo, *pats, **opts):
625 625 """commit the specified files or all outstanding changes
626 626
627 627 Commit changes to the given files into the repository. Unlike a
628 628 centralized RCS, this operation is a local operation. See hg push
629 629 for means to actively distribute your changes.
630 630
631 631 If a list of files is omitted, all changes reported by "hg status"
632 632 will be committed.
633 633
634 634 If you are committing the result of a merge, do not provide any
635 635 file names or -I/-X filters.
636 636
637 637 If no commit message is specified, the configured editor is
638 638 started to prompt you for a message.
639 639
640 640 See 'hg help dates' for a list of formats valid for -d/--date.
641 641 """
642 642 extra = {}
643 643 if opts.get('close_branch'):
644 644 extra['close'] = 1
645 645 e = cmdutil.commiteditor
646 646 if opts.get('force_editor'):
647 647 e = cmdutil.commitforceeditor
648 648
649 649 def commitfunc(ui, repo, message, match, opts):
650 650 return repo.commit(match.files(), message, opts.get('user'),
651 651 opts.get('date'), match, editor=e, extra=extra)
652 652
653 653 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
654 654 if not node:
655 655 return
656 656 cl = repo.changelog
657 657 rev = cl.rev(node)
658 658 parents = cl.parentrevs(rev)
659 659 if rev - 1 in parents:
660 660 # one of the parents was the old tip
661 661 pass
662 662 elif (parents == (nullrev, nullrev) or
663 663 len(cl.heads(cl.node(parents[0]))) > 1 and
664 664 (parents[1] == nullrev or len(cl.heads(cl.node(parents[1]))) > 1)):
665 665 ui.status(_('created new head\n'))
666 666
667 667 if ui.debugflag:
668 668 ui.write(_('committed changeset %d:%s\n') % (rev,hex(node)))
669 669 elif ui.verbose:
670 670 ui.write(_('committed changeset %d:%s\n') % (rev,short(node)))
671 671
672 672 def copy(ui, repo, *pats, **opts):
673 673 """mark files as copied for the next commit
674 674
675 675 Mark dest as having copies of source files. If dest is a
676 676 directory, copies are put in that directory. If dest is a file,
677 677 the source must be a single file.
678 678
679 679 By default, this command copies the contents of files as they
680 680 stand in the working directory. If invoked with -A/--after, the
681 681 operation is recorded, but no copying is performed.
682 682
683 683 This command takes effect with the next commit. To undo a copy
684 684 before that, see hg revert.
685 685 """
686 686 wlock = repo.wlock(False)
687 687 try:
688 688 return cmdutil.copy(ui, repo, pats, opts)
689 689 finally:
690 690 wlock.release()
691 691
692 692 def debugancestor(ui, repo, *args):
693 693 """find the ancestor revision of two revisions in a given index"""
694 694 if len(args) == 3:
695 695 index, rev1, rev2 = args
696 696 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
697 697 lookup = r.lookup
698 698 elif len(args) == 2:
699 699 if not repo:
700 700 raise util.Abort(_("There is no Mercurial repository here "
701 701 "(.hg not found)"))
702 702 rev1, rev2 = args
703 703 r = repo.changelog
704 704 lookup = repo.lookup
705 705 else:
706 706 raise util.Abort(_('either two or three arguments required'))
707 707 a = r.ancestor(lookup(rev1), lookup(rev2))
708 708 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
709 709
710 710 def debugcommands(ui, cmd='', *args):
711 711 for cmd, vals in sorted(table.iteritems()):
712 712 cmd = cmd.split('|')[0].strip('^')
713 713 opts = ', '.join([i[1] for i in vals[1]])
714 714 ui.write('%s: %s\n' % (cmd, opts))
715 715
716 716 def debugcomplete(ui, cmd='', **opts):
717 717 """returns the completion list associated with the given command"""
718 718
719 719 if opts.get('options'):
720 720 options = []
721 721 otables = [globalopts]
722 722 if cmd:
723 723 aliases, entry = cmdutil.findcmd(cmd, table, False)
724 724 otables.append(entry[1])
725 725 for t in otables:
726 726 for o in t:
727 727 if o[0]:
728 728 options.append('-%s' % o[0])
729 729 options.append('--%s' % o[1])
730 730 ui.write("%s\n" % "\n".join(options))
731 731 return
732 732
733 733 cmdlist = cmdutil.findpossible(cmd, table)
734 734 if ui.verbose:
735 735 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
736 736 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
737 737
738 738 def debugfsinfo(ui, path = "."):
739 739 file('.debugfsinfo', 'w').write('')
740 740 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
741 741 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
742 742 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
743 743 and 'yes' or 'no'))
744 744 os.unlink('.debugfsinfo')
745 745
746 746 def debugrebuildstate(ui, repo, rev="tip"):
747 747 """rebuild the dirstate as it would look like for the given revision"""
748 748 ctx = repo[rev]
749 749 wlock = repo.wlock()
750 750 try:
751 751 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
752 752 finally:
753 753 wlock.release()
754 754
755 755 def debugcheckstate(ui, repo):
756 756 """validate the correctness of the current dirstate"""
757 757 parent1, parent2 = repo.dirstate.parents()
758 758 m1 = repo[parent1].manifest()
759 759 m2 = repo[parent2].manifest()
760 760 errors = 0
761 761 for f in repo.dirstate:
762 762 state = repo.dirstate[f]
763 763 if state in "nr" and f not in m1:
764 764 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
765 765 errors += 1
766 766 if state in "a" and f in m1:
767 767 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
768 768 errors += 1
769 769 if state in "m" and f not in m1 and f not in m2:
770 770 ui.warn(_("%s in state %s, but not in either manifest\n") %
771 771 (f, state))
772 772 errors += 1
773 773 for f in m1:
774 774 state = repo.dirstate[f]
775 775 if state not in "nrm":
776 776 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
777 777 errors += 1
778 778 if errors:
779 779 error = _(".hg/dirstate inconsistent with current parent's manifest")
780 780 raise util.Abort(error)
781 781
782 782 def showconfig(ui, repo, *values, **opts):
783 783 """show combined config settings from all hgrc files
784 784
785 785 With no args, print names and values of all config items.
786 786
787 787 With one arg of the form section.name, print just the value of
788 788 that config item.
789 789
790 790 With multiple args, print names and values of all config items
791 791 with matching section names.
792 792
793 793 With the --debug flag, the source (filename and line number) is
794 794 printed for each config item.
795 795 """
796 796
797 797 untrusted = bool(opts.get('untrusted'))
798 798 if values:
799 799 if len([v for v in values if '.' in v]) > 1:
800 800 raise util.Abort(_('only one config item permitted'))
801 801 for section, name, value in ui.walkconfig(untrusted=untrusted):
802 802 sectname = section + '.' + name
803 803 if values:
804 804 for v in values:
805 805 if v == section:
806 806 ui.debug('%s: ' %
807 807 ui.configsource(section, name, untrusted))
808 808 ui.write('%s=%s\n' % (sectname, value))
809 809 elif v == sectname:
810 810 ui.debug('%s: ' %
811 811 ui.configsource(section, name, untrusted))
812 812 ui.write(value, '\n')
813 813 else:
814 814 ui.debug('%s: ' %
815 815 ui.configsource(section, name, untrusted))
816 816 ui.write('%s=%s\n' % (sectname, value))
817 817
818 818 def debugsetparents(ui, repo, rev1, rev2=None):
819 819 """manually set the parents of the current working directory
820 820
821 821 This is useful for writing repository conversion tools, but should
822 822 be used with care.
823 823 """
824 824
825 825 if not rev2:
826 826 rev2 = hex(nullid)
827 827
828 828 wlock = repo.wlock()
829 829 try:
830 830 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
831 831 finally:
832 832 wlock.release()
833 833
834 834 def debugstate(ui, repo, nodates=None):
835 835 """show the contents of the current dirstate"""
836 836 timestr = ""
837 837 showdate = not nodates
838 838 for file_, ent in sorted(repo.dirstate._map.iteritems()):
839 839 if showdate:
840 840 if ent[3] == -1:
841 841 # Pad or slice to locale representation
842 842 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(0)))
843 843 timestr = 'unset'
844 844 timestr = timestr[:locale_len] + ' '*(locale_len - len(timestr))
845 845 else:
846 846 timestr = time.strftime("%Y-%m-%d %H:%M:%S ", time.localtime(ent[3]))
847 847 if ent[1] & 020000:
848 848 mode = 'lnk'
849 849 else:
850 850 mode = '%3o' % (ent[1] & 0777)
851 851 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
852 852 for f in repo.dirstate.copies():
853 853 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
854 854
855 855 def debugdata(ui, file_, rev):
856 856 """dump the contents of a data file revision"""
857 857 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
858 858 try:
859 859 ui.write(r.revision(r.lookup(rev)))
860 860 except KeyError:
861 861 raise util.Abort(_('invalid revision identifier %s') % rev)
862 862
863 863 def debugdate(ui, date, range=None, **opts):
864 864 """parse and display a date"""
865 865 if opts["extended"]:
866 866 d = util.parsedate(date, util.extendeddateformats)
867 867 else:
868 868 d = util.parsedate(date)
869 869 ui.write("internal: %s %s\n" % d)
870 870 ui.write("standard: %s\n" % util.datestr(d))
871 871 if range:
872 872 m = util.matchdate(range)
873 873 ui.write("match: %s\n" % m(d[0]))
874 874
875 875 def debugindex(ui, file_):
876 876 """dump the contents of an index file"""
877 877 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
878 878 ui.write(" rev offset length base linkrev"
879 879 " nodeid p1 p2\n")
880 880 for i in r:
881 881 node = r.node(i)
882 882 try:
883 883 pp = r.parents(node)
884 884 except:
885 885 pp = [nullid, nullid]
886 886 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
887 887 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
888 888 short(node), short(pp[0]), short(pp[1])))
889 889
890 890 def debugindexdot(ui, file_):
891 891 """dump an index DAG as a .dot file"""
892 892 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
893 893 ui.write("digraph G {\n")
894 894 for i in r:
895 895 node = r.node(i)
896 896 pp = r.parents(node)
897 897 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
898 898 if pp[1] != nullid:
899 899 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
900 900 ui.write("}\n")
901 901
902 902 def debuginstall(ui):
903 903 '''test Mercurial installation'''
904 904
905 905 def writetemp(contents):
906 906 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
907 907 f = os.fdopen(fd, "wb")
908 908 f.write(contents)
909 909 f.close()
910 910 return name
911 911
912 912 problems = 0
913 913
914 914 # encoding
915 915 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
916 916 try:
917 917 encoding.fromlocal("test")
918 918 except util.Abort, inst:
919 919 ui.write(" %s\n" % inst)
920 920 ui.write(_(" (check that your locale is properly set)\n"))
921 921 problems += 1
922 922
923 923 # compiled modules
924 924 ui.status(_("Checking extensions...\n"))
925 925 try:
926 926 import bdiff, mpatch, base85
927 927 except Exception, inst:
928 928 ui.write(" %s\n" % inst)
929 929 ui.write(_(" One or more extensions could not be found"))
930 930 ui.write(_(" (check that you compiled the extensions)\n"))
931 931 problems += 1
932 932
933 933 # templates
934 934 ui.status(_("Checking templates...\n"))
935 935 try:
936 936 import templater
937 937 templater.templater(templater.templatepath("map-cmdline.default"))
938 938 except Exception, inst:
939 939 ui.write(" %s\n" % inst)
940 940 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
941 941 problems += 1
942 942
943 943 # patch
944 944 ui.status(_("Checking patch...\n"))
945 945 patchproblems = 0
946 946 a = "1\n2\n3\n4\n"
947 947 b = "1\n2\n3\ninsert\n4\n"
948 948 fa = writetemp(a)
949 949 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
950 950 os.path.basename(fa))
951 951 fd = writetemp(d)
952 952
953 953 files = {}
954 954 try:
955 955 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
956 956 except util.Abort, e:
957 957 ui.write(_(" patch call failed:\n"))
958 958 ui.write(" " + str(e) + "\n")
959 959 patchproblems += 1
960 960 else:
961 961 if list(files) != [os.path.basename(fa)]:
962 962 ui.write(_(" unexpected patch output!\n"))
963 963 patchproblems += 1
964 964 a = file(fa).read()
965 965 if a != b:
966 966 ui.write(_(" patch test failed!\n"))
967 967 patchproblems += 1
968 968
969 969 if patchproblems:
970 970 if ui.config('ui', 'patch'):
971 971 ui.write(_(" (Current patch tool may be incompatible with patch,"
972 972 " or misconfigured. Please check your .hgrc file)\n"))
973 973 else:
974 974 ui.write(_(" Internal patcher failure, please report this error"
975 975 " to http://www.selenic.com/mercurial/bts\n"))
976 976 problems += patchproblems
977 977
978 978 os.unlink(fa)
979 979 os.unlink(fd)
980 980
981 981 # editor
982 982 ui.status(_("Checking commit editor...\n"))
983 983 editor = ui.geteditor()
984 984 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
985 985 if not cmdpath:
986 986 if editor == 'vi':
987 987 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
988 988 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
989 989 else:
990 990 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
991 991 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
992 992 problems += 1
993 993
994 994 # check username
995 995 ui.status(_("Checking username...\n"))
996 996 user = os.environ.get("HGUSER")
997 997 if user is None:
998 998 user = ui.config("ui", "username")
999 999 if user is None:
1000 1000 user = os.environ.get("EMAIL")
1001 1001 if not user:
1002 1002 ui.warn(" ")
1003 1003 ui.username()
1004 1004 ui.write(_(" (specify a username in your .hgrc file)\n"))
1005 1005
1006 1006 if not problems:
1007 1007 ui.status(_("No problems detected\n"))
1008 1008 else:
1009 1009 ui.write(_("%s problems detected,"
1010 1010 " please check your install!\n") % problems)
1011 1011
1012 1012 return problems
1013 1013
1014 1014 def debugrename(ui, repo, file1, *pats, **opts):
1015 1015 """dump rename information"""
1016 1016
1017 1017 ctx = repo[opts.get('rev')]
1018 1018 m = cmdutil.match(repo, (file1,) + pats, opts)
1019 1019 for abs in ctx.walk(m):
1020 1020 fctx = ctx[abs]
1021 1021 o = fctx.filelog().renamed(fctx.filenode())
1022 1022 rel = m.rel(abs)
1023 1023 if o:
1024 1024 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1025 1025 else:
1026 1026 ui.write(_("%s not renamed\n") % rel)
1027 1027
1028 1028 def debugwalk(ui, repo, *pats, **opts):
1029 1029 """show how files match on given patterns"""
1030 1030 m = cmdutil.match(repo, pats, opts)
1031 1031 items = list(repo.walk(m))
1032 1032 if not items:
1033 1033 return
1034 1034 fmt = 'f %%-%ds %%-%ds %%s' % (
1035 1035 max([len(abs) for abs in items]),
1036 1036 max([len(m.rel(abs)) for abs in items]))
1037 1037 for abs in items:
1038 1038 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1039 1039 ui.write("%s\n" % line.rstrip())
1040 1040
1041 1041 def diff(ui, repo, *pats, **opts):
1042 1042 """diff repository (or selected files)
1043 1043
1044 1044 Show differences between revisions for the specified files.
1045 1045
1046 1046 Differences between files are shown using the unified diff format.
1047 1047
1048 1048 NOTE: diff may generate unexpected results for merges, as it will
1049 1049 default to comparing against the working directory's first parent
1050 1050 changeset if no revisions are specified.
1051 1051
1052 1052 When two revision arguments are given, then changes are shown
1053 1053 between those revisions. If only one revision is specified then
1054 1054 that revision is compared to the working directory, and, when no
1055 1055 revisions are specified, the working directory files are compared
1056 1056 to its parent.
1057 1057
1058 1058 Without the -a/--text option, diff will avoid generating diffs of
1059 1059 files it detects as binary. With -a, diff will generate a diff
1060 1060 anyway, probably with undesirable results.
1061 1061
1062 1062 Use the -g/--git option to generate diffs in the git extended diff
1063 1063 format. For more information, read 'hg help diffs'.
1064 1064 """
1065 1065
1066 1066 revs = opts.get('rev')
1067 1067 change = opts.get('change')
1068 1068
1069 1069 if revs and change:
1070 1070 msg = _('cannot specify --rev and --change at the same time')
1071 1071 raise util.Abort(msg)
1072 1072 elif change:
1073 1073 node2 = repo.lookup(change)
1074 1074 node1 = repo[node2].parents()[0].node()
1075 1075 else:
1076 1076 node1, node2 = cmdutil.revpair(repo, revs)
1077 1077
1078 1078 m = cmdutil.match(repo, pats, opts)
1079 1079 it = patch.diff(repo, node1, node2, match=m, opts=patch.diffopts(ui, opts))
1080 1080 for chunk in it:
1081 repo.ui.write(chunk)
1081 ui.write(chunk)
1082 1082
1083 1083 def export(ui, repo, *changesets, **opts):
1084 1084 """dump the header and diffs for one or more changesets
1085 1085
1086 1086 Print the changeset header and diffs for one or more revisions.
1087 1087
1088 1088 The information shown in the changeset header is: author,
1089 1089 changeset hash, parent(s) and commit comment.
1090 1090
1091 1091 NOTE: export may generate unexpected diff output for merge
1092 1092 changesets, as it will compare the merge changeset against its
1093 1093 first parent only.
1094 1094
1095 1095 Output may be to a file, in which case the name of the file is
1096 1096 given using a format string. The formatting rules are as follows:
1097 1097
1098 1098 %% literal "%" character
1099 1099 %H changeset hash (40 bytes of hexadecimal)
1100 1100 %N number of patches being generated
1101 1101 %R changeset revision number
1102 1102 %b basename of the exporting repository
1103 1103 %h short-form changeset hash (12 bytes of hexadecimal)
1104 1104 %n zero-padded sequence number, starting at 1
1105 1105 %r zero-padded changeset revision number
1106 1106
1107 1107 Without the -a/--text option, export will avoid generating diffs
1108 1108 of files it detects as binary. With -a, export will generate a
1109 1109 diff anyway, probably with undesirable results.
1110 1110
1111 1111 Use the -g/--git option to generate diffs in the git extended diff
1112 1112 format. Read the diffs help topic for more information.
1113 1113
1114 1114 With the --switch-parent option, the diff will be against the
1115 1115 second parent. It can be useful to review a merge.
1116 1116 """
1117 1117 if not changesets:
1118 1118 raise util.Abort(_("export requires at least one changeset"))
1119 1119 revs = cmdutil.revrange(repo, changesets)
1120 1120 if len(revs) > 1:
1121 1121 ui.note(_('exporting patches:\n'))
1122 1122 else:
1123 1123 ui.note(_('exporting patch:\n'))
1124 1124 patch.export(repo, revs, template=opts.get('output'),
1125 1125 switch_parent=opts.get('switch_parent'),
1126 1126 opts=patch.diffopts(ui, opts))
1127 1127
1128 1128 def grep(ui, repo, pattern, *pats, **opts):
1129 1129 """search for a pattern in specified files and revisions
1130 1130
1131 1131 Search revisions of files for a regular expression.
1132 1132
1133 1133 This command behaves differently than Unix grep. It only accepts
1134 1134 Python/Perl regexps. It searches repository history, not the
1135 1135 working directory. It always prints the revision number in which a
1136 1136 match appears.
1137 1137
1138 1138 By default, grep only prints output for the first revision of a
1139 1139 file in which it finds a match. To get it to print every revision
1140 1140 that contains a change in match status ("-" for a match that
1141 1141 becomes a non-match, or "+" for a non-match that becomes a match),
1142 1142 use the --all flag.
1143 1143 """
1144 1144 reflags = 0
1145 1145 if opts.get('ignore_case'):
1146 1146 reflags |= re.I
1147 1147 try:
1148 1148 regexp = re.compile(pattern, reflags)
1149 1149 except Exception, inst:
1150 1150 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1151 1151 return None
1152 1152 sep, eol = ':', '\n'
1153 1153 if opts.get('print0'):
1154 1154 sep = eol = '\0'
1155 1155
1156 1156 fcache = {}
1157 1157 forder = []
1158 1158 def getfile(fn):
1159 1159 if fn not in fcache:
1160 1160 if len(fcache) > 20:
1161 1161 del fcache[forder.pop(0)]
1162 1162 fcache[fn] = repo.file(fn)
1163 1163 else:
1164 1164 forder.remove(fn)
1165 1165
1166 1166 forder.append(fn)
1167 1167 return fcache[fn]
1168 1168
1169 1169 def matchlines(body):
1170 1170 begin = 0
1171 1171 linenum = 0
1172 1172 while True:
1173 1173 match = regexp.search(body, begin)
1174 1174 if not match:
1175 1175 break
1176 1176 mstart, mend = match.span()
1177 1177 linenum += body.count('\n', begin, mstart) + 1
1178 1178 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1179 1179 begin = body.find('\n', mend) + 1 or len(body)
1180 1180 lend = begin - 1
1181 1181 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1182 1182
1183 1183 class linestate(object):
1184 1184 def __init__(self, line, linenum, colstart, colend):
1185 1185 self.line = line
1186 1186 self.linenum = linenum
1187 1187 self.colstart = colstart
1188 1188 self.colend = colend
1189 1189
1190 1190 def __hash__(self):
1191 1191 return hash((self.linenum, self.line))
1192 1192
1193 1193 def __eq__(self, other):
1194 1194 return self.line == other.line
1195 1195
1196 1196 matches = {}
1197 1197 copies = {}
1198 1198 def grepbody(fn, rev, body):
1199 1199 matches[rev].setdefault(fn, [])
1200 1200 m = matches[rev][fn]
1201 1201 for lnum, cstart, cend, line in matchlines(body):
1202 1202 s = linestate(line, lnum, cstart, cend)
1203 1203 m.append(s)
1204 1204
1205 1205 def difflinestates(a, b):
1206 1206 sm = difflib.SequenceMatcher(None, a, b)
1207 1207 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1208 1208 if tag == 'insert':
1209 1209 for i in xrange(blo, bhi):
1210 1210 yield ('+', b[i])
1211 1211 elif tag == 'delete':
1212 1212 for i in xrange(alo, ahi):
1213 1213 yield ('-', a[i])
1214 1214 elif tag == 'replace':
1215 1215 for i in xrange(alo, ahi):
1216 1216 yield ('-', a[i])
1217 1217 for i in xrange(blo, bhi):
1218 1218 yield ('+', b[i])
1219 1219
1220 1220 prev = {}
1221 1221 def display(fn, rev, states, prevstates):
1222 1222 datefunc = ui.quiet and util.shortdate or util.datestr
1223 1223 found = False
1224 1224 filerevmatches = {}
1225 1225 r = prev.get(fn, -1)
1226 1226 if opts.get('all'):
1227 1227 iter = difflinestates(states, prevstates)
1228 1228 else:
1229 1229 iter = [('', l) for l in prevstates]
1230 1230 for change, l in iter:
1231 1231 cols = [fn, str(r)]
1232 1232 if opts.get('line_number'):
1233 1233 cols.append(str(l.linenum))
1234 1234 if opts.get('all'):
1235 1235 cols.append(change)
1236 1236 if opts.get('user'):
1237 1237 cols.append(ui.shortuser(get(r)[1]))
1238 1238 if opts.get('date'):
1239 1239 cols.append(datefunc(get(r)[2]))
1240 1240 if opts.get('files_with_matches'):
1241 1241 c = (fn, r)
1242 1242 if c in filerevmatches:
1243 1243 continue
1244 1244 filerevmatches[c] = 1
1245 1245 else:
1246 1246 cols.append(l.line)
1247 1247 ui.write(sep.join(cols), eol)
1248 1248 found = True
1249 1249 return found
1250 1250
1251 1251 fstate = {}
1252 1252 skip = {}
1253 1253 get = util.cachefunc(lambda r: repo[r].changeset())
1254 1254 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1255 1255 found = False
1256 1256 follow = opts.get('follow')
1257 1257 for st, rev, fns in changeiter:
1258 1258 if st == 'window':
1259 1259 matches.clear()
1260 1260 elif st == 'add':
1261 1261 ctx = repo[rev]
1262 1262 matches[rev] = {}
1263 1263 for fn in fns:
1264 1264 if fn in skip:
1265 1265 continue
1266 1266 try:
1267 1267 grepbody(fn, rev, getfile(fn).read(ctx.filenode(fn)))
1268 1268 fstate.setdefault(fn, [])
1269 1269 if follow:
1270 1270 copied = getfile(fn).renamed(ctx.filenode(fn))
1271 1271 if copied:
1272 1272 copies.setdefault(rev, {})[fn] = copied[0]
1273 1273 except error.LookupError:
1274 1274 pass
1275 1275 elif st == 'iter':
1276 1276 for fn, m in sorted(matches[rev].items()):
1277 1277 copy = copies.get(rev, {}).get(fn)
1278 1278 if fn in skip:
1279 1279 if copy:
1280 1280 skip[copy] = True
1281 1281 continue
1282 1282 if fn in prev or fstate[fn]:
1283 1283 r = display(fn, rev, m, fstate[fn])
1284 1284 found = found or r
1285 1285 if r and not opts.get('all'):
1286 1286 skip[fn] = True
1287 1287 if copy:
1288 1288 skip[copy] = True
1289 1289 fstate[fn] = m
1290 1290 if copy:
1291 1291 fstate[copy] = m
1292 1292 prev[fn] = rev
1293 1293
1294 1294 for fn, state in sorted(fstate.items()):
1295 1295 if fn in skip:
1296 1296 continue
1297 1297 if fn not in copies.get(prev[fn], {}):
1298 1298 found = display(fn, rev, {}, state) or found
1299 1299 return (not found and 1) or 0
1300 1300
1301 1301 def heads(ui, repo, *branchrevs, **opts):
1302 1302 """show current repository heads or show branch heads
1303 1303
1304 1304 With no arguments, show all repository head changesets.
1305 1305
1306 1306 If branch or revisions names are given this will show the heads of
1307 1307 the specified branches or the branches those revisions are tagged
1308 1308 with.
1309 1309
1310 1310 Repository "heads" are changesets that don't have child
1311 1311 changesets. They are where development generally takes place and
1312 1312 are the usual targets for update and merge operations.
1313 1313
1314 1314 Branch heads are changesets that have a given branch tag, but have
1315 1315 no child changesets with that tag. They are usually where
1316 1316 development on the given branch takes place.
1317 1317 """
1318 1318 if opts.get('rev'):
1319 1319 start = repo.lookup(opts['rev'])
1320 1320 else:
1321 1321 start = None
1322 1322 closed = not opts.get('active')
1323 1323 if not branchrevs:
1324 1324 # Assume we're looking repo-wide heads if no revs were specified.
1325 1325 heads = repo.heads(start, closed=closed)
1326 1326 else:
1327 1327 heads = []
1328 1328 visitedset = set()
1329 1329 for branchrev in branchrevs:
1330 1330 branch = repo[branchrev].branch()
1331 1331 if branch in visitedset:
1332 1332 continue
1333 1333 visitedset.add(branch)
1334 1334 bheads = repo.branchheads(branch, start, closed=closed)
1335 1335 if not bheads:
1336 1336 if branch != branchrev:
1337 1337 ui.warn(_("no changes on branch %s containing %s are "
1338 1338 "reachable from %s\n")
1339 1339 % (branch, branchrev, opts.get('rev')))
1340 1340 else:
1341 1341 ui.warn(_("no changes on branch %s are reachable from %s\n")
1342 1342 % (branch, opts.get('rev')))
1343 1343 heads.extend(bheads)
1344 1344 if not heads:
1345 1345 return 1
1346 1346 displayer = cmdutil.show_changeset(ui, repo, opts)
1347 1347 for n in heads:
1348 1348 displayer.show(repo[n])
1349 1349
1350 1350 def help_(ui, name=None, with_version=False):
1351 1351 """show help for a given topic or a help overview
1352 1352
1353 1353 With no arguments, print a list of commands and short help.
1354 1354
1355 1355 Given a topic, extension, or command name, print help for that
1356 1356 topic."""
1357 1357 option_lists = []
1358 1358
1359 1359 def addglobalopts(aliases):
1360 1360 if ui.verbose:
1361 1361 option_lists.append((_("global options:"), globalopts))
1362 1362 if name == 'shortlist':
1363 1363 option_lists.append((_('use "hg help" for the full list '
1364 1364 'of commands'), ()))
1365 1365 else:
1366 1366 if name == 'shortlist':
1367 1367 msg = _('use "hg help" for the full list of commands '
1368 1368 'or "hg -v" for details')
1369 1369 elif aliases:
1370 1370 msg = _('use "hg -v help%s" to show aliases and '
1371 1371 'global options') % (name and " " + name or "")
1372 1372 else:
1373 1373 msg = _('use "hg -v help %s" to show global options') % name
1374 1374 option_lists.append((msg, ()))
1375 1375
1376 1376 def helpcmd(name):
1377 1377 if with_version:
1378 1378 version_(ui)
1379 1379 ui.write('\n')
1380 1380
1381 1381 try:
1382 1382 aliases, i = cmdutil.findcmd(name, table, False)
1383 1383 except error.AmbiguousCommand, inst:
1384 1384 select = lambda c: c.lstrip('^').startswith(inst.args[0])
1385 1385 helplist(_('list of commands:\n\n'), select)
1386 1386 return
1387 1387
1388 1388 # synopsis
1389 1389 if len(i) > 2:
1390 1390 if i[2].startswith('hg'):
1391 1391 ui.write("%s\n" % i[2])
1392 1392 else:
1393 1393 ui.write('hg %s %s\n' % (aliases[0], i[2]))
1394 1394 else:
1395 1395 ui.write('hg %s\n' % aliases[0])
1396 1396
1397 1397 # aliases
1398 1398 if not ui.quiet and len(aliases) > 1:
1399 1399 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1400 1400
1401 1401 # description
1402 1402 doc = gettext(i[0].__doc__)
1403 1403 if not doc:
1404 1404 doc = _("(no help text available)")
1405 1405 if ui.quiet:
1406 1406 doc = doc.splitlines(0)[0]
1407 1407 ui.write("\n%s\n" % doc.rstrip())
1408 1408
1409 1409 if not ui.quiet:
1410 1410 # options
1411 1411 if i[1]:
1412 1412 option_lists.append((_("options:\n"), i[1]))
1413 1413
1414 1414 addglobalopts(False)
1415 1415
1416 1416 def helplist(header, select=None):
1417 1417 h = {}
1418 1418 cmds = {}
1419 1419 for c, e in table.iteritems():
1420 1420 f = c.split("|", 1)[0]
1421 1421 if select and not select(f):
1422 1422 continue
1423 1423 if (not select and name != 'shortlist' and
1424 1424 e[0].__module__ != __name__):
1425 1425 continue
1426 1426 if name == "shortlist" and not f.startswith("^"):
1427 1427 continue
1428 1428 f = f.lstrip("^")
1429 1429 if not ui.debugflag and f.startswith("debug"):
1430 1430 continue
1431 1431 doc = gettext(e[0].__doc__)
1432 1432 if not doc:
1433 1433 doc = _("(no help text available)")
1434 1434 h[f] = doc.splitlines(0)[0].rstrip()
1435 1435 cmds[f] = c.lstrip("^")
1436 1436
1437 1437 if not h:
1438 1438 ui.status(_('no commands defined\n'))
1439 1439 return
1440 1440
1441 1441 ui.status(header)
1442 1442 fns = sorted(h)
1443 1443 m = max(map(len, fns))
1444 1444 for f in fns:
1445 1445 if ui.verbose:
1446 1446 commands = cmds[f].replace("|",", ")
1447 1447 ui.write(" %s:\n %s\n"%(commands, h[f]))
1448 1448 else:
1449 1449 ui.write(' %-*s %s\n' % (m, f, h[f]))
1450 1450
1451 1451 exts = list(extensions.extensions())
1452 1452 if exts and name != 'shortlist':
1453 1453 ui.write(_('\nenabled extensions:\n\n'))
1454 1454 maxlength = 0
1455 1455 exthelps = []
1456 1456 for ename, ext in exts:
1457 1457 doc = (gettext(ext.__doc__) or _('(no help text available)'))
1458 1458 ename = ename.split('.')[-1]
1459 1459 maxlength = max(len(ename), maxlength)
1460 1460 exthelps.append((ename, doc.splitlines(0)[0].strip()))
1461 1461 for ename, text in exthelps:
1462 1462 ui.write(_(' %s %s\n') % (ename.ljust(maxlength), text))
1463 1463
1464 1464 if not ui.quiet:
1465 1465 addglobalopts(True)
1466 1466
1467 1467 def helptopic(name):
1468 1468 for names, header, doc in help.helptable:
1469 1469 if name in names:
1470 1470 break
1471 1471 else:
1472 1472 raise error.UnknownCommand(name)
1473 1473
1474 1474 # description
1475 1475 if not doc:
1476 1476 doc = _("(no help text available)")
1477 1477 if hasattr(doc, '__call__'):
1478 1478 doc = doc()
1479 1479
1480 1480 ui.write("%s\n" % header)
1481 1481 ui.write("%s\n" % doc.rstrip())
1482 1482
1483 1483 def helpext(name):
1484 1484 try:
1485 1485 mod = extensions.find(name)
1486 1486 except KeyError:
1487 1487 raise error.UnknownCommand(name)
1488 1488
1489 1489 doc = gettext(mod.__doc__) or _('no help text available')
1490 1490 doc = doc.splitlines(0)
1491 1491 ui.write(_('%s extension - %s\n') % (name.split('.')[-1], doc[0]))
1492 1492 for d in doc[1:]:
1493 1493 ui.write(d, '\n')
1494 1494
1495 1495 ui.status('\n')
1496 1496
1497 1497 try:
1498 1498 ct = mod.cmdtable
1499 1499 except AttributeError:
1500 1500 ct = {}
1501 1501
1502 1502 modcmds = set([c.split('|', 1)[0] for c in ct])
1503 1503 helplist(_('list of commands:\n\n'), modcmds.__contains__)
1504 1504
1505 1505 if name and name != 'shortlist':
1506 1506 i = None
1507 1507 for f in (helptopic, helpcmd, helpext):
1508 1508 try:
1509 1509 f(name)
1510 1510 i = None
1511 1511 break
1512 1512 except error.UnknownCommand, inst:
1513 1513 i = inst
1514 1514 if i:
1515 1515 raise i
1516 1516
1517 1517 else:
1518 1518 # program name
1519 1519 if ui.verbose or with_version:
1520 1520 version_(ui)
1521 1521 else:
1522 1522 ui.status(_("Mercurial Distributed SCM\n"))
1523 1523 ui.status('\n')
1524 1524
1525 1525 # list of commands
1526 1526 if name == "shortlist":
1527 1527 header = _('basic commands:\n\n')
1528 1528 else:
1529 1529 header = _('list of commands:\n\n')
1530 1530
1531 1531 helplist(header)
1532 1532
1533 1533 # list all option lists
1534 1534 opt_output = []
1535 1535 for title, options in option_lists:
1536 1536 opt_output.append(("\n%s" % title, None))
1537 1537 for shortopt, longopt, default, desc in options:
1538 1538 if "DEPRECATED" in desc and not ui.verbose: continue
1539 1539 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1540 1540 longopt and " --%s" % longopt),
1541 1541 "%s%s" % (desc,
1542 1542 default
1543 1543 and _(" (default: %s)") % default
1544 1544 or "")))
1545 1545
1546 1546 if not name:
1547 1547 ui.write(_("\nadditional help topics:\n\n"))
1548 1548 topics = []
1549 1549 for names, header, doc in help.helptable:
1550 1550 names = [(-len(name), name) for name in names]
1551 1551 names.sort()
1552 1552 topics.append((names[0][1], header))
1553 1553 topics_len = max([len(s[0]) for s in topics])
1554 1554 for t, desc in topics:
1555 1555 ui.write(" %-*s %s\n" % (topics_len, t, desc))
1556 1556
1557 1557 if opt_output:
1558 1558 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1559 1559 for first, second in opt_output:
1560 1560 if second:
1561 1561 # wrap descriptions at 70 characters, just like the
1562 1562 # main help texts
1563 1563 second = textwrap.wrap(second, width=70 - opts_len - 3)
1564 1564 pad = '\n' + ' ' * (opts_len + 3)
1565 1565 ui.write(" %-*s %s\n" % (opts_len, first, pad.join(second)))
1566 1566 else:
1567 1567 ui.write("%s\n" % first)
1568 1568
1569 1569 def identify(ui, repo, source=None,
1570 1570 rev=None, num=None, id=None, branch=None, tags=None):
1571 1571 """identify the working copy or specified revision
1572 1572
1573 1573 With no revision, print a summary of the current state of the
1574 1574 repository.
1575 1575
1576 1576 With a path, do a lookup in another repository.
1577 1577
1578 1578 This summary identifies the repository state using one or two
1579 1579 parent hash identifiers, followed by a "+" if there are
1580 1580 uncommitted changes in the working directory, a list of tags for
1581 1581 this revision and a branch name for non-default branches.
1582 1582 """
1583 1583
1584 1584 if not repo and not source:
1585 1585 raise util.Abort(_("There is no Mercurial repository here "
1586 1586 "(.hg not found)"))
1587 1587
1588 1588 hexfunc = ui.debugflag and hex or short
1589 1589 default = not (num or id or branch or tags)
1590 1590 output = []
1591 1591
1592 1592 revs = []
1593 1593 if source:
1594 1594 source, revs, checkout = hg.parseurl(ui.expandpath(source), [])
1595 1595 repo = hg.repository(ui, source)
1596 1596
1597 1597 if not repo.local():
1598 1598 if not rev and revs:
1599 1599 rev = revs[0]
1600 1600 if not rev:
1601 1601 rev = "tip"
1602 1602 if num or branch or tags:
1603 1603 raise util.Abort(
1604 1604 "can't query remote revision number, branch, or tags")
1605 1605 output = [hexfunc(repo.lookup(rev))]
1606 1606 elif not rev:
1607 1607 ctx = repo[None]
1608 1608 parents = ctx.parents()
1609 1609 changed = False
1610 1610 if default or id or num:
1611 1611 changed = ctx.files() + ctx.deleted()
1612 1612 if default or id:
1613 1613 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1614 1614 (changed) and "+" or "")]
1615 1615 if num:
1616 1616 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1617 1617 (changed) and "+" or ""))
1618 1618 else:
1619 1619 ctx = repo[rev]
1620 1620 if default or id:
1621 1621 output = [hexfunc(ctx.node())]
1622 1622 if num:
1623 1623 output.append(str(ctx.rev()))
1624 1624
1625 1625 if repo.local() and default and not ui.quiet:
1626 1626 b = encoding.tolocal(ctx.branch())
1627 1627 if b != 'default':
1628 1628 output.append("(%s)" % b)
1629 1629
1630 1630 # multiple tags for a single parent separated by '/'
1631 1631 t = "/".join(ctx.tags())
1632 1632 if t:
1633 1633 output.append(t)
1634 1634
1635 1635 if branch:
1636 1636 output.append(encoding.tolocal(ctx.branch()))
1637 1637
1638 1638 if tags:
1639 1639 output.extend(ctx.tags())
1640 1640
1641 1641 ui.write("%s\n" % ' '.join(output))
1642 1642
1643 1643 def import_(ui, repo, patch1, *patches, **opts):
1644 1644 """import an ordered set of patches
1645 1645
1646 1646 Import a list of patches and commit them individually.
1647 1647
1648 1648 If there are outstanding changes in the working directory, import
1649 1649 will abort unless given the -f/--force flag.
1650 1650
1651 1651 You can import a patch straight from a mail message. Even patches
1652 1652 as attachments work (body part must be type text/plain or
1653 1653 text/x-patch to be used). From and Subject headers of email
1654 1654 message are used as default committer and commit message. All
1655 1655 text/plain body parts before first diff are added to commit
1656 1656 message.
1657 1657
1658 1658 If the imported patch was generated by hg export, user and
1659 1659 description from patch override values from message headers and
1660 1660 body. Values given on command line with -m/--message and -u/--user
1661 1661 override these.
1662 1662
1663 1663 If --exact is specified, import will set the working directory to
1664 1664 the parent of each patch before applying it, and will abort if the
1665 1665 resulting changeset has a different ID than the one recorded in
1666 1666 the patch. This may happen due to character set problems or other
1667 1667 deficiencies in the text patch format.
1668 1668
1669 1669 With -s/--similarity, hg will attempt to discover renames and
1670 1670 copies in the patch in the same way as 'addremove'.
1671 1671
1672 1672 To read a patch from standard input, use patch name "-". See 'hg
1673 1673 help dates' for a list of formats valid for -d/--date.
1674 1674 """
1675 1675 patches = (patch1,) + patches
1676 1676
1677 1677 date = opts.get('date')
1678 1678 if date:
1679 1679 opts['date'] = util.parsedate(date)
1680 1680
1681 1681 try:
1682 1682 sim = float(opts.get('similarity') or 0)
1683 1683 except ValueError:
1684 1684 raise util.Abort(_('similarity must be a number'))
1685 1685 if sim < 0 or sim > 100:
1686 1686 raise util.Abort(_('similarity must be between 0 and 100'))
1687 1687
1688 1688 if opts.get('exact') or not opts.get('force'):
1689 1689 cmdutil.bail_if_changed(repo)
1690 1690
1691 1691 d = opts["base"]
1692 1692 strip = opts["strip"]
1693 1693 wlock = lock = None
1694 1694 try:
1695 1695 wlock = repo.wlock()
1696 1696 lock = repo.lock()
1697 1697 for p in patches:
1698 1698 pf = os.path.join(d, p)
1699 1699
1700 1700 if pf == '-':
1701 1701 ui.status(_("applying patch from stdin\n"))
1702 1702 pf = sys.stdin
1703 1703 else:
1704 1704 ui.status(_("applying %s\n") % p)
1705 1705 pf = url.open(ui, pf)
1706 1706 data = patch.extract(ui, pf)
1707 1707 tmpname, message, user, date, branch, nodeid, p1, p2 = data
1708 1708
1709 1709 if tmpname is None:
1710 1710 raise util.Abort(_('no diffs found'))
1711 1711
1712 1712 try:
1713 1713 cmdline_message = cmdutil.logmessage(opts)
1714 1714 if cmdline_message:
1715 1715 # pickup the cmdline msg
1716 1716 message = cmdline_message
1717 1717 elif message:
1718 1718 # pickup the patch msg
1719 1719 message = message.strip()
1720 1720 else:
1721 1721 # launch the editor
1722 1722 message = None
1723 1723 ui.debug(_('message:\n%s\n') % message)
1724 1724
1725 1725 wp = repo.parents()
1726 1726 if opts.get('exact'):
1727 1727 if not nodeid or not p1:
1728 1728 raise util.Abort(_('not a mercurial patch'))
1729 1729 p1 = repo.lookup(p1)
1730 1730 p2 = repo.lookup(p2 or hex(nullid))
1731 1731
1732 1732 if p1 != wp[0].node():
1733 1733 hg.clean(repo, p1)
1734 1734 repo.dirstate.setparents(p1, p2)
1735 1735 elif p2:
1736 1736 try:
1737 1737 p1 = repo.lookup(p1)
1738 1738 p2 = repo.lookup(p2)
1739 1739 if p1 == wp[0].node():
1740 1740 repo.dirstate.setparents(p1, p2)
1741 1741 except error.RepoError:
1742 1742 pass
1743 1743 if opts.get('exact') or opts.get('import_branch'):
1744 1744 repo.dirstate.setbranch(branch or 'default')
1745 1745
1746 1746 files = {}
1747 1747 try:
1748 1748 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
1749 1749 files=files)
1750 1750 finally:
1751 1751 files = patch.updatedir(ui, repo, files, similarity=sim/100.)
1752 1752 if not opts.get('no_commit'):
1753 1753 n = repo.commit(files, message, opts.get('user') or user,
1754 1754 opts.get('date') or date,
1755 1755 editor=cmdutil.commiteditor)
1756 1756 if opts.get('exact'):
1757 1757 if hex(n) != nodeid:
1758 1758 repo.rollback()
1759 1759 raise util.Abort(_('patch is damaged'
1760 1760 ' or loses information'))
1761 1761 # Force a dirstate write so that the next transaction
1762 1762 # backups an up-do-date file.
1763 1763 repo.dirstate.write()
1764 1764 finally:
1765 1765 os.unlink(tmpname)
1766 1766 finally:
1767 1767 release(lock, wlock)
1768 1768
1769 1769 def incoming(ui, repo, source="default", **opts):
1770 1770 """show new changesets found in source
1771 1771
1772 1772 Show new changesets found in the specified path/URL or the default
1773 1773 pull location. These are the changesets that would be pulled if a
1774 1774 pull was requested.
1775 1775
1776 1776 For remote repository, using --bundle avoids downloading the
1777 1777 changesets twice if the incoming is followed by a pull.
1778 1778
1779 1779 See pull for valid source format details.
1780 1780 """
1781 1781 limit = cmdutil.loglimit(opts)
1782 1782 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev'))
1783 1783 other = hg.repository(cmdutil.remoteui(repo, opts), source)
1784 1784 ui.status(_('comparing with %s\n') % url.hidepassword(source))
1785 1785 if revs:
1786 1786 revs = [other.lookup(rev) for rev in revs]
1787 1787 common, incoming, rheads = repo.findcommonincoming(other, heads=revs,
1788 1788 force=opts["force"])
1789 1789 if not incoming:
1790 1790 try:
1791 1791 os.unlink(opts["bundle"])
1792 1792 except:
1793 1793 pass
1794 1794 ui.status(_("no changes found\n"))
1795 1795 return 1
1796 1796
1797 1797 cleanup = None
1798 1798 try:
1799 1799 fname = opts["bundle"]
1800 1800 if fname or not other.local():
1801 1801 # create a bundle (uncompressed if other repo is not local)
1802 1802
1803 1803 if revs is None and other.capable('changegroupsubset'):
1804 1804 revs = rheads
1805 1805
1806 1806 if revs is None:
1807 1807 cg = other.changegroup(incoming, "incoming")
1808 1808 else:
1809 1809 cg = other.changegroupsubset(incoming, revs, 'incoming')
1810 1810 bundletype = other.local() and "HG10BZ" or "HG10UN"
1811 1811 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
1812 1812 # keep written bundle?
1813 1813 if opts["bundle"]:
1814 1814 cleanup = None
1815 1815 if not other.local():
1816 1816 # use the created uncompressed bundlerepo
1817 1817 other = bundlerepo.bundlerepository(ui, repo.root, fname)
1818 1818
1819 1819 o = other.changelog.nodesbetween(incoming, revs)[0]
1820 1820 if opts.get('newest_first'):
1821 1821 o.reverse()
1822 1822 displayer = cmdutil.show_changeset(ui, other, opts)
1823 1823 count = 0
1824 1824 for n in o:
1825 1825 if count >= limit:
1826 1826 break
1827 1827 parents = [p for p in other.changelog.parents(n) if p != nullid]
1828 1828 if opts.get('no_merges') and len(parents) == 2:
1829 1829 continue
1830 1830 count += 1
1831 1831 displayer.show(other[n])
1832 1832 finally:
1833 1833 if hasattr(other, 'close'):
1834 1834 other.close()
1835 1835 if cleanup:
1836 1836 os.unlink(cleanup)
1837 1837
1838 1838 def init(ui, dest=".", **opts):
1839 1839 """create a new repository in the given directory
1840 1840
1841 1841 Initialize a new repository in the given directory. If the given
1842 1842 directory does not exist, it is created.
1843 1843
1844 1844 If no directory is given, the current directory is used.
1845 1845
1846 1846 It is possible to specify an ssh:// URL as the destination.
1847 1847 See 'hg help urls' for more information.
1848 1848 """
1849 1849 hg.repository(cmdutil.remoteui(ui, opts), dest, create=1)
1850 1850
1851 1851 def locate(ui, repo, *pats, **opts):
1852 1852 """locate files matching specific patterns
1853 1853
1854 1854 Print all files under Mercurial control whose names match the
1855 1855 given patterns.
1856 1856
1857 1857 This command searches the entire repository by default. To search
1858 1858 just the current directory and its subdirectories, use
1859 1859 "--include .".
1860 1860
1861 1861 If no patterns are given to match, this command prints all file
1862 1862 names.
1863 1863
1864 1864 If you want to feed the output of this command into the "xargs"
1865 1865 command, use the -0 option to both this command and "xargs". This
1866 1866 will avoid the problem of "xargs" treating single filenames that
1867 1867 contain white space as multiple filenames.
1868 1868 """
1869 1869 end = opts.get('print0') and '\0' or '\n'
1870 1870 rev = opts.get('rev') or None
1871 1871
1872 1872 ret = 1
1873 1873 m = cmdutil.match(repo, pats, opts, default='relglob')
1874 1874 m.bad = lambda x,y: False
1875 1875 for abs in repo[rev].walk(m):
1876 1876 if not rev and abs not in repo.dirstate:
1877 1877 continue
1878 1878 if opts.get('fullpath'):
1879 1879 ui.write(repo.wjoin(abs), end)
1880 1880 else:
1881 1881 ui.write(((pats and m.rel(abs)) or abs), end)
1882 1882 ret = 0
1883 1883
1884 1884 return ret
1885 1885
1886 1886 def log(ui, repo, *pats, **opts):
1887 1887 """show revision history of entire repository or files
1888 1888
1889 1889 Print the revision history of the specified files or the entire
1890 1890 project.
1891 1891
1892 1892 File history is shown without following rename or copy history of
1893 1893 files. Use -f/--follow with a file name to follow history across
1894 1894 renames and copies. --follow without a file name will only show
1895 1895 ancestors or descendants of the starting revision. --follow-first
1896 1896 only follows the first parent of merge revisions.
1897 1897
1898 1898 If no revision range is specified, the default is tip:0 unless
1899 1899 --follow is set, in which case the working directory parent is
1900 1900 used as the starting revision.
1901 1901
1902 1902 See 'hg help dates' for a list of formats valid for -d/--date.
1903 1903
1904 1904 By default this command outputs: changeset id and hash, tags,
1905 1905 non-trivial parents, user, date and time, and a summary for each
1906 1906 commit. When the -v/--verbose switch is used, the list of changed
1907 1907 files and full commit message is shown.
1908 1908
1909 1909 NOTE: log -p/--patch may generate unexpected diff output for merge
1910 1910 changesets, as it will only compare the merge changeset against
1911 1911 its first parent. Also, the files: list will only reflect files
1912 1912 that are different from BOTH parents.
1913 1913
1914 1914 """
1915 1915
1916 1916 get = util.cachefunc(lambda r: repo[r].changeset())
1917 1917 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1918 1918
1919 1919 limit = cmdutil.loglimit(opts)
1920 1920 count = 0
1921 1921
1922 1922 if opts.get('copies') and opts.get('rev'):
1923 1923 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
1924 1924 else:
1925 1925 endrev = len(repo)
1926 1926 rcache = {}
1927 1927 ncache = {}
1928 1928 def getrenamed(fn, rev):
1929 1929 '''looks up all renames for a file (up to endrev) the first
1930 1930 time the file is given. It indexes on the changerev and only
1931 1931 parses the manifest if linkrev != changerev.
1932 1932 Returns rename info for fn at changerev rev.'''
1933 1933 if fn not in rcache:
1934 1934 rcache[fn] = {}
1935 1935 ncache[fn] = {}
1936 1936 fl = repo.file(fn)
1937 1937 for i in fl:
1938 1938 node = fl.node(i)
1939 1939 lr = fl.linkrev(i)
1940 1940 renamed = fl.renamed(node)
1941 1941 rcache[fn][lr] = renamed
1942 1942 if renamed:
1943 1943 ncache[fn][node] = renamed
1944 1944 if lr >= endrev:
1945 1945 break
1946 1946 if rev in rcache[fn]:
1947 1947 return rcache[fn][rev]
1948 1948
1949 1949 # If linkrev != rev (i.e. rev not found in rcache) fallback to
1950 1950 # filectx logic.
1951 1951
1952 1952 try:
1953 1953 return repo[rev][fn].renamed()
1954 1954 except error.LookupError:
1955 1955 pass
1956 1956 return None
1957 1957
1958 1958 df = False
1959 1959 if opts["date"]:
1960 1960 df = util.matchdate(opts["date"])
1961 1961
1962 1962 only_branches = opts.get('only_branch')
1963 1963
1964 1964 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
1965 1965 for st, rev, fns in changeiter:
1966 1966 if st == 'add':
1967 1967 parents = [p for p in repo.changelog.parentrevs(rev)
1968 1968 if p != nullrev]
1969 1969 if opts.get('no_merges') and len(parents) == 2:
1970 1970 continue
1971 1971 if opts.get('only_merges') and len(parents) != 2:
1972 1972 continue
1973 1973
1974 1974 if only_branches:
1975 1975 revbranch = get(rev)[5]['branch']
1976 1976 if revbranch not in only_branches:
1977 1977 continue
1978 1978
1979 1979 if df:
1980 1980 changes = get(rev)
1981 1981 if not df(changes[2][0]):
1982 1982 continue
1983 1983
1984 1984 if opts.get('keyword'):
1985 1985 changes = get(rev)
1986 1986 miss = 0
1987 1987 for k in [kw.lower() for kw in opts['keyword']]:
1988 1988 if not (k in changes[1].lower() or
1989 1989 k in changes[4].lower() or
1990 1990 k in " ".join(changes[3]).lower()):
1991 1991 miss = 1
1992 1992 break
1993 1993 if miss:
1994 1994 continue
1995 1995
1996 1996 if opts['user']:
1997 1997 changes = get(rev)
1998 1998 if not [k for k in opts['user'] if k in changes[1]]:
1999 1999 continue
2000 2000
2001 2001 copies = []
2002 2002 if opts.get('copies') and rev:
2003 2003 for fn in get(rev)[3]:
2004 2004 rename = getrenamed(fn, rev)
2005 2005 if rename:
2006 2006 copies.append((fn, rename[0]))
2007 2007 displayer.show(context.changectx(repo, rev), copies=copies)
2008 2008 elif st == 'iter':
2009 2009 if count == limit: break
2010 2010 if displayer.flush(rev):
2011 2011 count += 1
2012 2012
2013 2013 def manifest(ui, repo, node=None, rev=None):
2014 2014 """output the current or given revision of the project manifest
2015 2015
2016 2016 Print a list of version controlled files for the given revision.
2017 2017 If no revision is given, the first parent of the working directory
2018 2018 is used, or the null revision if none is checked out.
2019 2019
2020 2020 With -v flag, print file permissions, symlink and executable bits.
2021 2021 With --debug flag, print file revision hashes.
2022 2022 """
2023 2023
2024 2024 if rev and node:
2025 2025 raise util.Abort(_("please specify just one revision"))
2026 2026
2027 2027 if not node:
2028 2028 node = rev
2029 2029
2030 2030 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2031 2031 ctx = repo[node]
2032 2032 for f in ctx:
2033 2033 if ui.debugflag:
2034 2034 ui.write("%40s " % hex(ctx.manifest()[f]))
2035 2035 if ui.verbose:
2036 2036 ui.write(decor[ctx.flags(f)])
2037 2037 ui.write("%s\n" % f)
2038 2038
2039 2039 def merge(ui, repo, node=None, **opts):
2040 2040 """merge working directory with another revision
2041 2041
2042 2042 The contents of the current working directory is updated with all
2043 2043 changes made in the requested revision since the last common
2044 2044 predecessor revision.
2045 2045
2046 2046 Files that changed between either parent are marked as changed for
2047 2047 the next commit and a commit must be performed before any further
2048 2048 updates are allowed. The next commit has two parents.
2049 2049
2050 2050 If no revision is specified, the working directory's parent is a
2051 2051 head revision, and the current branch contains exactly one other
2052 2052 head, the other head is merged with by default. Otherwise, an
2053 2053 explicit revision to merge with must be provided.
2054 2054 """
2055 2055
2056 2056 if opts.get('rev') and node:
2057 2057 raise util.Abort(_("please specify just one revision"))
2058 2058 if not node:
2059 2059 node = opts.get('rev')
2060 2060
2061 2061 if not node:
2062 2062 branch = repo.changectx(None).branch()
2063 2063 bheads = repo.branchheads(branch)
2064 2064 if len(bheads) > 2:
2065 2065 raise util.Abort(_("branch '%s' has %d heads - "
2066 2066 "please merge with an explicit rev") %
2067 2067 (branch, len(bheads)))
2068 2068
2069 2069 parent = repo.dirstate.parents()[0]
2070 2070 if len(bheads) == 1:
2071 2071 if len(repo.heads()) > 1:
2072 2072 raise util.Abort(_("branch '%s' has one head - "
2073 2073 "please merge with an explicit rev") %
2074 2074 branch)
2075 2075 msg = _('there is nothing to merge')
2076 2076 if parent != repo.lookup(repo[None].branch()):
2077 2077 msg = _('%s - use "hg update" instead') % msg
2078 2078 raise util.Abort(msg)
2079 2079
2080 2080 if parent not in bheads:
2081 2081 raise util.Abort(_('working dir not at a head rev - '
2082 2082 'use "hg update" or merge with an explicit rev'))
2083 2083 node = parent == bheads[0] and bheads[-1] or bheads[0]
2084 2084
2085 2085 if opts.get('show'):
2086 2086 p1 = repo['.']
2087 2087 p2 = repo[node]
2088 2088 common = p1.ancestor(p2)
2089 2089 roots, heads = [common.node()], [p2.node()]
2090 2090 displayer = cmdutil.show_changeset(ui, repo, opts)
2091 2091 for node in repo.changelog.nodesbetween(roots=roots, heads=heads)[0]:
2092 2092 displayer.show(repo[node])
2093 2093 return 0
2094 2094
2095 2095 return hg.merge(repo, node, force=opts.get('force'))
2096 2096
2097 2097 def outgoing(ui, repo, dest=None, **opts):
2098 2098 """show changesets not found in destination
2099 2099
2100 2100 Show changesets not found in the specified destination repository
2101 2101 or the default push location. These are the changesets that would
2102 2102 be pushed if a push was requested.
2103 2103
2104 2104 See pull for valid destination format details.
2105 2105 """
2106 2106 limit = cmdutil.loglimit(opts)
2107 2107 dest, revs, checkout = hg.parseurl(
2108 2108 ui.expandpath(dest or 'default-push', dest or 'default'), opts.get('rev'))
2109 2109 if revs:
2110 2110 revs = [repo.lookup(rev) for rev in revs]
2111 2111
2112 2112 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
2113 2113 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2114 2114 o = repo.findoutgoing(other, force=opts.get('force'))
2115 2115 if not o:
2116 2116 ui.status(_("no changes found\n"))
2117 2117 return 1
2118 2118 o = repo.changelog.nodesbetween(o, revs)[0]
2119 2119 if opts.get('newest_first'):
2120 2120 o.reverse()
2121 2121 displayer = cmdutil.show_changeset(ui, repo, opts)
2122 2122 count = 0
2123 2123 for n in o:
2124 2124 if count >= limit:
2125 2125 break
2126 2126 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2127 2127 if opts.get('no_merges') and len(parents) == 2:
2128 2128 continue
2129 2129 count += 1
2130 2130 displayer.show(repo[n])
2131 2131
2132 2132 def parents(ui, repo, file_=None, **opts):
2133 2133 """show the parents of the working directory or revision
2134 2134
2135 2135 Print the working directory's parent revisions. If a revision is
2136 2136 given via -r/--rev, the parent of that revision will be printed.
2137 2137 If a file argument is given, revision in which the file was last
2138 2138 changed (before the working directory revision or the argument to
2139 2139 --rev if given) is printed.
2140 2140 """
2141 2141 rev = opts.get('rev')
2142 2142 if rev:
2143 2143 ctx = repo[rev]
2144 2144 else:
2145 2145 ctx = repo[None]
2146 2146
2147 2147 if file_:
2148 2148 m = cmdutil.match(repo, (file_,), opts)
2149 2149 if m.anypats() or len(m.files()) != 1:
2150 2150 raise util.Abort(_('can only specify an explicit file name'))
2151 2151 file_ = m.files()[0]
2152 2152 filenodes = []
2153 2153 for cp in ctx.parents():
2154 2154 if not cp:
2155 2155 continue
2156 2156 try:
2157 2157 filenodes.append(cp.filenode(file_))
2158 2158 except error.LookupError:
2159 2159 pass
2160 2160 if not filenodes:
2161 2161 raise util.Abort(_("'%s' not found in manifest!") % file_)
2162 2162 fl = repo.file(file_)
2163 2163 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2164 2164 else:
2165 2165 p = [cp.node() for cp in ctx.parents()]
2166 2166
2167 2167 displayer = cmdutil.show_changeset(ui, repo, opts)
2168 2168 for n in p:
2169 2169 if n != nullid:
2170 2170 displayer.show(repo[n])
2171 2171
2172 2172 def paths(ui, repo, search=None):
2173 2173 """show aliases for remote repositories
2174 2174
2175 2175 Show definition of symbolic path name NAME. If no name is given,
2176 2176 show definition of available names.
2177 2177
2178 2178 Path names are defined in the [paths] section of /etc/mercurial/hgrc
2179 2179 and $HOME/.hgrc. If run inside a repository, .hg/hgrc is used, too.
2180 2180
2181 2181 See 'hg help urls' for more information.
2182 2182 """
2183 2183 if search:
2184 2184 for name, path in ui.configitems("paths"):
2185 2185 if name == search:
2186 2186 ui.write("%s\n" % url.hidepassword(path))
2187 2187 return
2188 2188 ui.warn(_("not found!\n"))
2189 2189 return 1
2190 2190 else:
2191 2191 for name, path in ui.configitems("paths"):
2192 2192 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2193 2193
2194 2194 def postincoming(ui, repo, modheads, optupdate, checkout):
2195 2195 if modheads == 0:
2196 2196 return
2197 2197 if optupdate:
2198 2198 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2199 2199 return hg.update(repo, checkout)
2200 2200 else:
2201 2201 ui.status(_("not updating, since new heads added\n"))
2202 2202 if modheads > 1:
2203 2203 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2204 2204 else:
2205 2205 ui.status(_("(run 'hg update' to get a working copy)\n"))
2206 2206
2207 2207 def pull(ui, repo, source="default", **opts):
2208 2208 """pull changes from the specified source
2209 2209
2210 2210 Pull changes from a remote repository to the local one.
2211 2211
2212 2212 This finds all changes from the repository at the specified path
2213 2213 or URL and adds them to the local repository. By default, this
2214 2214 does not update the copy of the project in the working directory.
2215 2215
2216 2216 Use hg incoming if you want to see what will be added by the next
2217 2217 pull without actually adding the changes to the repository.
2218 2218
2219 2219 If SOURCE is omitted, the 'default' path will be used.
2220 2220 See 'hg help urls' for more information.
2221 2221 """
2222 2222 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev'))
2223 2223 other = hg.repository(cmdutil.remoteui(repo, opts), source)
2224 2224 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2225 2225 if revs:
2226 2226 try:
2227 2227 revs = [other.lookup(rev) for rev in revs]
2228 2228 except error.CapabilityError:
2229 2229 err = _("Other repository doesn't support revision lookup, "
2230 2230 "so a rev cannot be specified.")
2231 2231 raise util.Abort(err)
2232 2232
2233 2233 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2234 2234 return postincoming(ui, repo, modheads, opts.get('update'), checkout)
2235 2235
2236 2236 def push(ui, repo, dest=None, **opts):
2237 2237 """push changes to the specified destination
2238 2238
2239 2239 Push changes from the local repository to the given destination.
2240 2240
2241 2241 This is the symmetrical operation for pull. It moves changes from
2242 2242 the current repository to a different one. If the destination is
2243 2243 local this is identical to a pull in that directory from the
2244 2244 current one.
2245 2245
2246 2246 By default, push will refuse to run if it detects the result would
2247 2247 increase the number of remote heads. This generally indicates the
2248 2248 the client has forgotten to pull and merge before pushing.
2249 2249
2250 2250 If -r/--rev is used, the named revision and all its ancestors will
2251 2251 be pushed to the remote repository.
2252 2252
2253 2253 Look at the help text for URLs for important details about ssh://
2254 2254 URLs. If DESTINATION is omitted, a default path will be used.
2255 2255 See 'hg help urls' for more information.
2256 2256 """
2257 2257 dest, revs, checkout = hg.parseurl(
2258 2258 ui.expandpath(dest or 'default-push', dest or 'default'), opts.get('rev'))
2259 2259 other = hg.repository(cmdutil.remoteui(repo, opts), dest)
2260 2260 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2261 2261 if revs:
2262 2262 revs = [repo.lookup(rev) for rev in revs]
2263 2263 r = repo.push(other, opts.get('force'), revs=revs)
2264 2264 return r == 0
2265 2265
2266 2266 def recover(ui, repo):
2267 2267 """roll back an interrupted transaction
2268 2268
2269 2269 Recover from an interrupted commit or pull.
2270 2270
2271 2271 This command tries to fix the repository status after an
2272 2272 interrupted operation. It should only be necessary when Mercurial
2273 2273 suggests it.
2274 2274 """
2275 2275 if repo.recover():
2276 2276 return hg.verify(repo)
2277 2277 return 1
2278 2278
2279 2279 def remove(ui, repo, *pats, **opts):
2280 2280 """remove the specified files on the next commit
2281 2281
2282 2282 Schedule the indicated files for removal from the repository.
2283 2283
2284 2284 This only removes files from the current branch, not from the
2285 2285 entire project history. -A/--after can be used to remove only
2286 2286 files that have already been deleted, -f/--force can be used to
2287 2287 force deletion, and -Af can be used to remove files from the next
2288 2288 revision without deleting them.
2289 2289
2290 2290 The following table details the behavior of remove for different
2291 2291 file states (columns) and option combinations (rows). The file
2292 2292 states are Added, Clean, Modified and Missing (as reported by hg
2293 2293 status). The actions are Warn, Remove (from branch) and Delete
2294 2294 (from disk).
2295 2295
2296 2296 A C M !
2297 2297 none W RD W R
2298 2298 -f R RD RD R
2299 2299 -A W W W R
2300 2300 -Af R R R R
2301 2301
2302 2302 This command schedules the files to be removed at the next commit.
2303 2303 To undo a remove before that, see hg revert.
2304 2304 """
2305 2305
2306 2306 after, force = opts.get('after'), opts.get('force')
2307 2307 if not pats and not after:
2308 2308 raise util.Abort(_('no files specified'))
2309 2309
2310 2310 m = cmdutil.match(repo, pats, opts)
2311 2311 s = repo.status(match=m, clean=True)
2312 2312 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2313 2313
2314 2314 for f in m.files():
2315 2315 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2316 2316 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
2317 2317
2318 2318 def warn(files, reason):
2319 2319 for f in files:
2320 2320 ui.warn(_('not removing %s: file %s (use -f to force removal)\n')
2321 2321 % (m.rel(f), reason))
2322 2322
2323 2323 if force:
2324 2324 remove, forget = modified + deleted + clean, added
2325 2325 elif after:
2326 2326 remove, forget = deleted, []
2327 2327 warn(modified + added + clean, _('still exists'))
2328 2328 else:
2329 2329 remove, forget = deleted + clean, []
2330 2330 warn(modified, _('is modified'))
2331 2331 warn(added, _('has been marked for add'))
2332 2332
2333 2333 for f in sorted(remove + forget):
2334 2334 if ui.verbose or not m.exact(f):
2335 2335 ui.status(_('removing %s\n') % m.rel(f))
2336 2336
2337 2337 repo.forget(forget)
2338 2338 repo.remove(remove, unlink=not after)
2339 2339
2340 2340 def rename(ui, repo, *pats, **opts):
2341 2341 """rename files; equivalent of copy + remove
2342 2342
2343 2343 Mark dest as copies of sources; mark sources for deletion. If dest
2344 2344 is a directory, copies are put in that directory. If dest is a
2345 2345 file, there can only be one source.
2346 2346
2347 2347 By default, this command copies the contents of files as they
2348 2348 exist in the working directory. If invoked with -A/--after, the
2349 2349 operation is recorded, but no copying is performed.
2350 2350
2351 2351 This command takes effect at the next commit. To undo a rename
2352 2352 before that, see hg revert.
2353 2353 """
2354 2354 wlock = repo.wlock(False)
2355 2355 try:
2356 2356 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2357 2357 finally:
2358 2358 wlock.release()
2359 2359
2360 2360 def resolve(ui, repo, *pats, **opts):
2361 2361 """retry file merges from a merge or update
2362 2362
2363 2363 This command will cleanly retry unresolved file merges using file
2364 2364 revisions preserved from the last update or merge. To attempt to
2365 2365 resolve all unresolved files, use the -a/--all switch.
2366 2366
2367 2367 If a conflict is resolved manually, please note that the changes
2368 2368 will be overwritten if the merge is retried with resolve. The
2369 2369 -m/--mark switch should be used to mark the file as resolved.
2370 2370
2371 2371 This command will also allow listing resolved files and manually
2372 2372 marking and unmarking files as resolved. All files must be marked
2373 2373 as resolved before the new commits are permitted.
2374 2374
2375 2375 The codes used to show the status of files are:
2376 2376 U = unresolved
2377 2377 R = resolved
2378 2378 """
2379 2379
2380 2380 all, mark, unmark, show = [opts.get(o) for o in 'all mark unmark list'.split()]
2381 2381
2382 2382 if (show and (mark or unmark)) or (mark and unmark):
2383 2383 raise util.Abort(_("too many options specified"))
2384 2384 if pats and all:
2385 2385 raise util.Abort(_("can't specify --all and patterns"))
2386 2386 if not (all or pats or show or mark or unmark):
2387 2387 raise util.Abort(_('no files or directories specified; '
2388 2388 'use --all to remerge all files'))
2389 2389
2390 2390 ms = merge_.mergestate(repo)
2391 2391 m = cmdutil.match(repo, pats, opts)
2392 2392
2393 2393 for f in ms:
2394 2394 if m(f):
2395 2395 if show:
2396 2396 ui.write("%s %s\n" % (ms[f].upper(), f))
2397 2397 elif mark:
2398 2398 ms.mark(f, "r")
2399 2399 elif unmark:
2400 2400 ms.mark(f, "u")
2401 2401 else:
2402 2402 wctx = repo[None]
2403 2403 mctx = wctx.parents()[-1]
2404 2404
2405 2405 # backup pre-resolve (merge uses .orig for its own purposes)
2406 2406 a = repo.wjoin(f)
2407 2407 util.copyfile(a, a + ".resolve")
2408 2408
2409 2409 # resolve file
2410 2410 ms.resolve(f, wctx, mctx)
2411 2411
2412 2412 # replace filemerge's .orig file with our resolve file
2413 2413 util.rename(a + ".resolve", a + ".orig")
2414 2414
2415 2415 def revert(ui, repo, *pats, **opts):
2416 2416 """restore individual files or directories to an earlier state
2417 2417
2418 2418 (Use update -r to check out earlier revisions, revert does not
2419 2419 change the working directory parents.)
2420 2420
2421 2421 With no revision specified, revert the named files or directories
2422 2422 to the contents they had in the parent of the working directory.
2423 2423 This restores the contents of the affected files to an unmodified
2424 2424 state and unschedules adds, removes, copies, and renames. If the
2425 2425 working directory has two parents, you must explicitly specify the
2426 2426 revision to revert to.
2427 2427
2428 2428 Using the -r/--rev option, revert the given files or directories
2429 2429 to their contents as of a specific revision. This can be helpful
2430 2430 to "roll back" some or all of an earlier change. See 'hg help
2431 2431 dates' for a list of formats valid for -d/--date.
2432 2432
2433 2433 Revert modifies the working directory. It does not commit any
2434 2434 changes, or change the parent of the working directory. If you
2435 2435 revert to a revision other than the parent of the working
2436 2436 directory, the reverted files will thus appear modified
2437 2437 afterwards.
2438 2438
2439 2439 If a file has been deleted, it is restored. If the executable mode
2440 2440 of a file was changed, it is reset.
2441 2441
2442 2442 If names are given, all files matching the names are reverted.
2443 2443 If no arguments are given, no files are reverted.
2444 2444
2445 2445 Modified files are saved with a .orig suffix before reverting.
2446 2446 To disable these backups, use --no-backup.
2447 2447 """
2448 2448
2449 2449 if opts["date"]:
2450 2450 if opts["rev"]:
2451 2451 raise util.Abort(_("you can't specify a revision and a date"))
2452 2452 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2453 2453
2454 2454 if not pats and not opts.get('all'):
2455 2455 raise util.Abort(_('no files or directories specified; '
2456 2456 'use --all to revert the whole repo'))
2457 2457
2458 2458 parent, p2 = repo.dirstate.parents()
2459 2459 if not opts.get('rev') and p2 != nullid:
2460 2460 raise util.Abort(_('uncommitted merge - please provide a '
2461 2461 'specific revision'))
2462 2462 ctx = repo[opts.get('rev')]
2463 2463 node = ctx.node()
2464 2464 mf = ctx.manifest()
2465 2465 if node == parent:
2466 2466 pmf = mf
2467 2467 else:
2468 2468 pmf = None
2469 2469
2470 2470 # need all matching names in dirstate and manifest of target rev,
2471 2471 # so have to walk both. do not print errors if files exist in one
2472 2472 # but not other.
2473 2473
2474 2474 names = {}
2475 2475
2476 2476 wlock = repo.wlock()
2477 2477 try:
2478 2478 # walk dirstate.
2479 2479
2480 2480 m = cmdutil.match(repo, pats, opts)
2481 2481 m.bad = lambda x,y: False
2482 2482 for abs in repo.walk(m):
2483 2483 names[abs] = m.rel(abs), m.exact(abs)
2484 2484
2485 2485 # walk target manifest.
2486 2486
2487 2487 def badfn(path, msg):
2488 2488 if path in names:
2489 2489 return False
2490 2490 path_ = path + '/'
2491 2491 for f in names:
2492 2492 if f.startswith(path_):
2493 2493 return False
2494 repo.ui.warn("%s: %s\n" % (m.rel(path), msg))
2494 ui.warn("%s: %s\n" % (m.rel(path), msg))
2495 2495 return False
2496 2496
2497 2497 m = cmdutil.match(repo, pats, opts)
2498 2498 m.bad = badfn
2499 2499 for abs in repo[node].walk(m):
2500 2500 if abs not in names:
2501 2501 names[abs] = m.rel(abs), m.exact(abs)
2502 2502
2503 2503 m = cmdutil.matchfiles(repo, names)
2504 2504 changes = repo.status(match=m)[:4]
2505 2505 modified, added, removed, deleted = map(set, changes)
2506 2506
2507 2507 # if f is a rename, also revert the source
2508 2508 cwd = repo.getcwd()
2509 2509 for f in added:
2510 2510 src = repo.dirstate.copied(f)
2511 2511 if src and src not in names and repo.dirstate[src] == 'r':
2512 2512 removed.add(src)
2513 2513 names[src] = (repo.pathto(src, cwd), True)
2514 2514
2515 2515 def removeforget(abs):
2516 2516 if repo.dirstate[abs] == 'a':
2517 2517 return _('forgetting %s\n')
2518 2518 return _('removing %s\n')
2519 2519
2520 2520 revert = ([], _('reverting %s\n'))
2521 2521 add = ([], _('adding %s\n'))
2522 2522 remove = ([], removeforget)
2523 2523 undelete = ([], _('undeleting %s\n'))
2524 2524
2525 2525 disptable = (
2526 2526 # dispatch table:
2527 2527 # file state
2528 2528 # action if in target manifest
2529 2529 # action if not in target manifest
2530 2530 # make backup if in target manifest
2531 2531 # make backup if not in target manifest
2532 2532 (modified, revert, remove, True, True),
2533 2533 (added, revert, remove, True, False),
2534 2534 (removed, undelete, None, False, False),
2535 2535 (deleted, revert, remove, False, False),
2536 2536 )
2537 2537
2538 2538 for abs, (rel, exact) in sorted(names.items()):
2539 2539 mfentry = mf.get(abs)
2540 2540 target = repo.wjoin(abs)
2541 2541 def handle(xlist, dobackup):
2542 2542 xlist[0].append(abs)
2543 2543 if dobackup and not opts.get('no_backup') and util.lexists(target):
2544 2544 bakname = "%s.orig" % rel
2545 2545 ui.note(_('saving current version of %s as %s\n') %
2546 2546 (rel, bakname))
2547 2547 if not opts.get('dry_run'):
2548 2548 util.copyfile(target, bakname)
2549 2549 if ui.verbose or not exact:
2550 2550 msg = xlist[1]
2551 2551 if not isinstance(msg, basestring):
2552 2552 msg = msg(abs)
2553 2553 ui.status(msg % rel)
2554 2554 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2555 2555 if abs not in table: continue
2556 2556 # file has changed in dirstate
2557 2557 if mfentry:
2558 2558 handle(hitlist, backuphit)
2559 2559 elif misslist is not None:
2560 2560 handle(misslist, backupmiss)
2561 2561 break
2562 2562 else:
2563 2563 if abs not in repo.dirstate:
2564 2564 if mfentry:
2565 2565 handle(add, True)
2566 2566 elif exact:
2567 2567 ui.warn(_('file not managed: %s\n') % rel)
2568 2568 continue
2569 2569 # file has not changed in dirstate
2570 2570 if node == parent:
2571 2571 if exact: ui.warn(_('no changes needed to %s\n') % rel)
2572 2572 continue
2573 2573 if pmf is None:
2574 2574 # only need parent manifest in this unlikely case,
2575 2575 # so do not read by default
2576 2576 pmf = repo[parent].manifest()
2577 2577 if abs in pmf:
2578 2578 if mfentry:
2579 2579 # if version of file is same in parent and target
2580 2580 # manifests, do nothing
2581 2581 if (pmf[abs] != mfentry or
2582 2582 pmf.flags(abs) != mf.flags(abs)):
2583 2583 handle(revert, False)
2584 2584 else:
2585 2585 handle(remove, False)
2586 2586
2587 2587 if not opts.get('dry_run'):
2588 2588 def checkout(f):
2589 2589 fc = ctx[f]
2590 2590 repo.wwrite(f, fc.data(), fc.flags())
2591 2591
2592 2592 audit_path = util.path_auditor(repo.root)
2593 2593 for f in remove[0]:
2594 2594 if repo.dirstate[f] == 'a':
2595 2595 repo.dirstate.forget(f)
2596 2596 continue
2597 2597 audit_path(f)
2598 2598 try:
2599 2599 util.unlink(repo.wjoin(f))
2600 2600 except OSError:
2601 2601 pass
2602 2602 repo.dirstate.remove(f)
2603 2603
2604 2604 normal = None
2605 2605 if node == parent:
2606 2606 # We're reverting to our parent. If possible, we'd like status
2607 2607 # to report the file as clean. We have to use normallookup for
2608 2608 # merges to avoid losing information about merged/dirty files.
2609 2609 if p2 != nullid:
2610 2610 normal = repo.dirstate.normallookup
2611 2611 else:
2612 2612 normal = repo.dirstate.normal
2613 2613 for f in revert[0]:
2614 2614 checkout(f)
2615 2615 if normal:
2616 2616 normal(f)
2617 2617
2618 2618 for f in add[0]:
2619 2619 checkout(f)
2620 2620 repo.dirstate.add(f)
2621 2621
2622 2622 normal = repo.dirstate.normallookup
2623 2623 if node == parent and p2 == nullid:
2624 2624 normal = repo.dirstate.normal
2625 2625 for f in undelete[0]:
2626 2626 checkout(f)
2627 2627 normal(f)
2628 2628
2629 2629 finally:
2630 2630 wlock.release()
2631 2631
2632 2632 def rollback(ui, repo):
2633 2633 """roll back the last transaction
2634 2634
2635 2635 This command should be used with care. There is only one level of
2636 2636 rollback, and there is no way to undo a rollback. It will also
2637 2637 restore the dirstate at the time of the last transaction, losing
2638 2638 any dirstate changes since that time.
2639 2639
2640 2640 Transactions are used to encapsulate the effects of all commands
2641 2641 that create new changesets or propagate existing changesets into a
2642 2642 repository. For example, the following commands are transactional,
2643 2643 and their effects can be rolled back:
2644 2644
2645 2645 commit
2646 2646 import
2647 2647 pull
2648 2648 push (with this repository as destination)
2649 2649 unbundle
2650 2650
2651 2651 This command is not intended for use on public repositories. Once
2652 2652 changes are visible for pull by other users, rolling a transaction
2653 2653 back locally is ineffective (someone else may already have pulled
2654 2654 the changes). Furthermore, a race is possible with readers of the
2655 2655 repository; for example an in-progress pull from the repository
2656 2656 may fail if a rollback is performed.
2657 2657 """
2658 2658 repo.rollback()
2659 2659
2660 2660 def root(ui, repo):
2661 2661 """print the root (top) of the current working directory
2662 2662
2663 2663 Print the root directory of the current repository.
2664 2664 """
2665 2665 ui.write(repo.root + "\n")
2666 2666
2667 2667 def serve(ui, repo, **opts):
2668 2668 """export the repository via HTTP
2669 2669
2670 2670 Start a local HTTP repository browser and pull server.
2671 2671
2672 2672 By default, the server logs accesses to stdout and errors to
2673 2673 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
2674 2674 files.
2675 2675 """
2676 2676
2677 2677 if opts["stdio"]:
2678 2678 if repo is None:
2679 2679 raise error.RepoError(_("There is no Mercurial repository here"
2680 2680 " (.hg not found)"))
2681 2681 s = sshserver.sshserver(ui, repo)
2682 2682 s.serve_forever()
2683 2683
2684 2684 baseui = repo and repo.baseui or ui
2685 2685 optlist = ("name templates style address port prefix ipv6"
2686 2686 " accesslog errorlog webdir_conf certificate")
2687 2687 for o in optlist.split():
2688 2688 if opts[o]:
2689 2689 baseui.setconfig("web", o, str(opts[o]))
2690 2690 if (repo is not None) and (repo.ui != baseui):
2691 2691 repo.ui.setconfig("web", o, str(opts[o]))
2692 2692
2693 2693 if repo is None and not ui.config("web", "webdir_conf"):
2694 2694 raise error.RepoError(_("There is no Mercurial repository here"
2695 2695 " (.hg not found)"))
2696 2696
2697 2697 class service:
2698 2698 def init(self):
2699 2699 util.set_signal_handler()
2700 2700 self.httpd = server.create_server(baseui, repo)
2701 2701
2702 2702 if not ui.verbose: return
2703 2703
2704 2704 if self.httpd.prefix:
2705 2705 prefix = self.httpd.prefix.strip('/') + '/'
2706 2706 else:
2707 2707 prefix = ''
2708 2708
2709 2709 port = ':%d' % self.httpd.port
2710 2710 if port == ':80':
2711 2711 port = ''
2712 2712
2713 2713 bindaddr = self.httpd.addr
2714 2714 if bindaddr == '0.0.0.0':
2715 2715 bindaddr = '*'
2716 2716 elif ':' in bindaddr: # IPv6
2717 2717 bindaddr = '[%s]' % bindaddr
2718 2718
2719 2719 fqaddr = self.httpd.fqaddr
2720 2720 if ':' in fqaddr:
2721 2721 fqaddr = '[%s]' % fqaddr
2722 2722 ui.status(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
2723 2723 (fqaddr, port, prefix, bindaddr, self.httpd.port))
2724 2724
2725 2725 def run(self):
2726 2726 self.httpd.serve_forever()
2727 2727
2728 2728 service = service()
2729 2729
2730 2730 cmdutil.service(opts, initfn=service.init, runfn=service.run)
2731 2731
2732 2732 def status(ui, repo, *pats, **opts):
2733 2733 """show changed files in the working directory
2734 2734
2735 2735 Show status of files in the repository. If names are given, only
2736 2736 files that match are shown. Files that are clean or ignored or
2737 2737 source of a copy/move operation, are not listed unless -c/--clean,
2738 2738 -i/--ignored, -C/--copies or -A/--all is given. Unless options
2739 2739 described with "show only ..." are given, the options -mardu are
2740 2740 used.
2741 2741
2742 2742 Option -q/--quiet hides untracked (unknown and ignored) files
2743 2743 unless explicitly requested with -u/--unknown or -i/--ignored.
2744 2744
2745 2745 NOTE: status may appear to disagree with diff if permissions have
2746 2746 changed or a merge has occurred. The standard diff format does not
2747 2747 report permission changes and diff only reports changes relative
2748 2748 to one merge parent.
2749 2749
2750 2750 If one revision is given, it is used as the base revision.
2751 2751 If two revisions are given, the difference between them is shown.
2752 2752
2753 2753 The codes used to show the status of files are:
2754 2754 M = modified
2755 2755 A = added
2756 2756 R = removed
2757 2757 C = clean
2758 2758 ! = missing (deleted by non-hg command, but still tracked)
2759 2759 ? = not tracked
2760 2760 I = ignored
2761 2761 = the previous added file was copied from here
2762 2762 """
2763 2763
2764 2764 node1, node2 = cmdutil.revpair(repo, opts.get('rev'))
2765 2765 cwd = (pats and repo.getcwd()) or ''
2766 2766 end = opts.get('print0') and '\0' or '\n'
2767 2767 copy = {}
2768 2768 states = 'modified added removed deleted unknown ignored clean'.split()
2769 2769 show = [k for k in states if opts.get(k)]
2770 2770 if opts.get('all'):
2771 2771 show += ui.quiet and (states[:4] + ['clean']) or states
2772 2772 if not show:
2773 2773 show = ui.quiet and states[:4] or states[:5]
2774 2774
2775 2775 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
2776 2776 'ignored' in show, 'clean' in show, 'unknown' in show)
2777 2777 changestates = zip(states, 'MAR!?IC', stat)
2778 2778
2779 2779 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
2780 2780 ctxn = repo[nullid]
2781 2781 ctx1 = repo[node1]
2782 2782 ctx2 = repo[node2]
2783 2783 added = stat[1]
2784 2784 if node2 is None:
2785 2785 added = stat[0] + stat[1] # merged?
2786 2786
2787 2787 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
2788 2788 if k in added:
2789 2789 copy[k] = v
2790 2790 elif v in added:
2791 2791 copy[v] = k
2792 2792
2793 2793 for state, char, files in changestates:
2794 2794 if state in show:
2795 2795 format = "%s %%s%s" % (char, end)
2796 2796 if opts.get('no_status'):
2797 2797 format = "%%s%s" % end
2798 2798
2799 2799 for f in files:
2800 2800 ui.write(format % repo.pathto(f, cwd))
2801 2801 if f in copy:
2802 2802 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end))
2803 2803
2804 2804 def tag(ui, repo, name1, *names, **opts):
2805 2805 """add one or more tags for the current or given revision
2806 2806
2807 2807 Name a particular revision using <name>.
2808 2808
2809 2809 Tags are used to name particular revisions of the repository and are
2810 2810 very useful to compare different revisions, to go back to significant
2811 2811 earlier versions or to mark branch points as releases, etc.
2812 2812
2813 2813 If no revision is given, the parent of the working directory is
2814 2814 used, or tip if no revision is checked out.
2815 2815
2816 2816 To facilitate version control, distribution, and merging of tags,
2817 2817 they are stored as a file named ".hgtags" which is managed
2818 2818 similarly to other project files and can be hand-edited if
2819 2819 necessary. The file '.hg/localtags' is used for local tags (not
2820 2820 shared among repositories).
2821 2821
2822 2822 See 'hg help dates' for a list of formats valid for -d/--date.
2823 2823 """
2824 2824
2825 2825 rev_ = "."
2826 2826 names = (name1,) + names
2827 2827 if len(names) != len(set(names)):
2828 2828 raise util.Abort(_('tag names must be unique'))
2829 2829 for n in names:
2830 2830 if n in ['tip', '.', 'null']:
2831 2831 raise util.Abort(_('the name \'%s\' is reserved') % n)
2832 2832 if opts.get('rev') and opts.get('remove'):
2833 2833 raise util.Abort(_("--rev and --remove are incompatible"))
2834 2834 if opts.get('rev'):
2835 2835 rev_ = opts['rev']
2836 2836 message = opts.get('message')
2837 2837 if opts.get('remove'):
2838 2838 expectedtype = opts.get('local') and 'local' or 'global'
2839 2839 for n in names:
2840 2840 if not repo.tagtype(n):
2841 2841 raise util.Abort(_('tag \'%s\' does not exist') % n)
2842 2842 if repo.tagtype(n) != expectedtype:
2843 2843 if expectedtype == 'global':
2844 2844 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
2845 2845 else:
2846 2846 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
2847 2847 rev_ = nullid
2848 2848 if not message:
2849 2849 message = _('Removed tag %s') % ', '.join(names)
2850 2850 elif not opts.get('force'):
2851 2851 for n in names:
2852 2852 if n in repo.tags():
2853 2853 raise util.Abort(_('tag \'%s\' already exists '
2854 2854 '(use -f to force)') % n)
2855 2855 if not rev_ and repo.dirstate.parents()[1] != nullid:
2856 2856 raise util.Abort(_('uncommitted merge - please provide a '
2857 2857 'specific revision'))
2858 2858 r = repo[rev_].node()
2859 2859
2860 2860 if not message:
2861 2861 message = (_('Added tag %s for changeset %s') %
2862 2862 (', '.join(names), short(r)))
2863 2863
2864 2864 date = opts.get('date')
2865 2865 if date:
2866 2866 date = util.parsedate(date)
2867 2867
2868 2868 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
2869 2869
2870 2870 def tags(ui, repo):
2871 2871 """list repository tags
2872 2872
2873 2873 This lists both regular and local tags. When the -v/--verbose
2874 2874 switch is used, a third column "local" is printed for local tags.
2875 2875 """
2876 2876
2877 2877 hexfunc = ui.debugflag and hex or short
2878 2878 tagtype = ""
2879 2879
2880 2880 for t, n in reversed(repo.tagslist()):
2881 2881 if ui.quiet:
2882 2882 ui.write("%s\n" % t)
2883 2883 continue
2884 2884
2885 2885 try:
2886 2886 hn = hexfunc(n)
2887 2887 r = "%5d:%s" % (repo.changelog.rev(n), hn)
2888 2888 except error.LookupError:
2889 2889 r = " ?:%s" % hn
2890 2890 else:
2891 2891 spaces = " " * (30 - encoding.colwidth(t))
2892 2892 if ui.verbose:
2893 2893 if repo.tagtype(t) == 'local':
2894 2894 tagtype = " local"
2895 2895 else:
2896 2896 tagtype = ""
2897 2897 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
2898 2898
2899 2899 def tip(ui, repo, **opts):
2900 2900 """show the tip revision
2901 2901
2902 2902 The tip revision (usually just called the tip) is the most
2903 2903 recently added changeset in the repository, the most recently
2904 2904 changed head.
2905 2905
2906 2906 If you have just made a commit, that commit will be the tip. If
2907 2907 you have just pulled changes from another repository, the tip of
2908 2908 that repository becomes the current tip. The "tip" tag is special
2909 2909 and cannot be renamed or assigned to a different changeset.
2910 2910 """
2911 2911 cmdutil.show_changeset(ui, repo, opts).show(repo[len(repo) - 1])
2912 2912
2913 2913 def unbundle(ui, repo, fname1, *fnames, **opts):
2914 2914 """apply one or more changegroup files
2915 2915
2916 2916 Apply one or more compressed changegroup files generated by the
2917 2917 bundle command.
2918 2918 """
2919 2919 fnames = (fname1,) + fnames
2920 2920
2921 2921 lock = repo.lock()
2922 2922 try:
2923 2923 for fname in fnames:
2924 2924 f = url.open(ui, fname)
2925 2925 gen = changegroup.readbundle(f, fname)
2926 2926 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
2927 2927 finally:
2928 2928 lock.release()
2929 2929
2930 2930 return postincoming(ui, repo, modheads, opts.get('update'), None)
2931 2931
2932 2932 def update(ui, repo, node=None, rev=None, clean=False, date=None):
2933 2933 """update working directory
2934 2934
2935 2935 Update the repository's working directory to the specified
2936 2936 revision, or the tip of the current branch if none is specified.
2937 2937 Use null as the revision to remove the working copy (like 'hg
2938 2938 clone -U').
2939 2939
2940 2940 When the working directory contains no uncommitted changes, it
2941 2941 will be replaced by the state of the requested revision from the
2942 2942 repository. When the requested revision is on a different branch,
2943 2943 the working directory will additionally be switched to that
2944 2944 branch.
2945 2945
2946 2946 When there are uncommitted changes, use option -C/--clean to
2947 2947 discard them, forcibly replacing the state of the working
2948 2948 directory with the requested revision.
2949 2949
2950 2950 When there are uncommitted changes and option -C/--clean is not
2951 2951 used, and the parent revision and requested revision are on the
2952 2952 same branch, and one of them is an ancestor of the other, then the
2953 2953 new working directory will contain the requested revision merged
2954 2954 with the uncommitted changes. Otherwise, the update will fail with
2955 2955 a suggestion to use 'merge' or 'update -C' instead.
2956 2956
2957 2957 If you want to update just one file to an older revision, use
2958 2958 revert.
2959 2959
2960 2960 See 'hg help dates' for a list of formats valid for -d/--date.
2961 2961 """
2962 2962 if rev and node:
2963 2963 raise util.Abort(_("please specify just one revision"))
2964 2964
2965 2965 if not rev:
2966 2966 rev = node
2967 2967
2968 2968 if date:
2969 2969 if rev:
2970 2970 raise util.Abort(_("you can't specify a revision and a date"))
2971 2971 rev = cmdutil.finddate(ui, repo, date)
2972 2972
2973 2973 if clean:
2974 2974 return hg.clean(repo, rev)
2975 2975 else:
2976 2976 return hg.update(repo, rev)
2977 2977
2978 2978 def verify(ui, repo):
2979 2979 """verify the integrity of the repository
2980 2980
2981 2981 Verify the integrity of the current repository.
2982 2982
2983 2983 This will perform an extensive check of the repository's
2984 2984 integrity, validating the hashes and checksums of each entry in
2985 2985 the changelog, manifest, and tracked files, as well as the
2986 2986 integrity of their crosslinks and indices.
2987 2987 """
2988 2988 return hg.verify(repo)
2989 2989
2990 2990 def version_(ui):
2991 2991 """output version and copyright information"""
2992 2992 ui.write(_("Mercurial Distributed SCM (version %s)\n")
2993 2993 % util.version())
2994 2994 ui.status(_(
2995 2995 "\nCopyright (C) 2005-2009 Matt Mackall <mpm@selenic.com> and others\n"
2996 2996 "This is free software; see the source for copying conditions. "
2997 2997 "There is NO\nwarranty; "
2998 2998 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
2999 2999 ))
3000 3000
3001 3001 # Command options and aliases are listed here, alphabetically
3002 3002
3003 3003 globalopts = [
3004 3004 ('R', 'repository', '',
3005 3005 _('repository root directory or symbolic path name')),
3006 3006 ('', 'cwd', '', _('change working directory')),
3007 3007 ('y', 'noninteractive', None,
3008 3008 _('do not prompt, assume \'yes\' for any required answers')),
3009 3009 ('q', 'quiet', None, _('suppress output')),
3010 3010 ('v', 'verbose', None, _('enable additional output')),
3011 3011 ('', 'config', [], _('set/override config option')),
3012 3012 ('', 'debug', None, _('enable debugging output')),
3013 3013 ('', 'debugger', None, _('start debugger')),
3014 3014 ('', 'encoding', encoding.encoding, _('set the charset encoding')),
3015 3015 ('', 'encodingmode', encoding.encodingmode,
3016 3016 _('set the charset encoding mode')),
3017 3017 ('', 'traceback', None, _('print traceback on exception')),
3018 3018 ('', 'time', None, _('time how long the command takes')),
3019 3019 ('', 'profile', None, _('print command execution profile')),
3020 3020 ('', 'version', None, _('output version information and exit')),
3021 3021 ('h', 'help', None, _('display help and exit')),
3022 3022 ]
3023 3023
3024 3024 dryrunopts = [('n', 'dry-run', None,
3025 3025 _('do not perform actions, just print output'))]
3026 3026
3027 3027 remoteopts = [
3028 3028 ('e', 'ssh', '', _('specify ssh command to use')),
3029 3029 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
3030 3030 ]
3031 3031
3032 3032 walkopts = [
3033 3033 ('I', 'include', [], _('include names matching the given patterns')),
3034 3034 ('X', 'exclude', [], _('exclude names matching the given patterns')),
3035 3035 ]
3036 3036
3037 3037 commitopts = [
3038 3038 ('m', 'message', '', _('use <text> as commit message')),
3039 3039 ('l', 'logfile', '', _('read commit message from <file>')),
3040 3040 ]
3041 3041
3042 3042 commitopts2 = [
3043 3043 ('d', 'date', '', _('record datecode as commit date')),
3044 3044 ('u', 'user', '', _('record the specified user as committer')),
3045 3045 ]
3046 3046
3047 3047 templateopts = [
3048 3048 ('', 'style', '', _('display using template map file')),
3049 3049 ('', 'template', '', _('display with template')),
3050 3050 ]
3051 3051
3052 3052 logopts = [
3053 3053 ('p', 'patch', None, _('show patch')),
3054 3054 ('g', 'git', None, _('use git extended diff format')),
3055 3055 ('l', 'limit', '', _('limit number of changes displayed')),
3056 3056 ('M', 'no-merges', None, _('do not show merges')),
3057 3057 ] + templateopts
3058 3058
3059 3059 diffopts = [
3060 3060 ('a', 'text', None, _('treat all files as text')),
3061 3061 ('g', 'git', None, _('use git extended diff format')),
3062 3062 ('', 'nodates', None, _("don't include dates in diff headers"))
3063 3063 ]
3064 3064
3065 3065 diffopts2 = [
3066 3066 ('p', 'show-function', None, _('show which function each change is in')),
3067 3067 ('w', 'ignore-all-space', None,
3068 3068 _('ignore white space when comparing lines')),
3069 3069 ('b', 'ignore-space-change', None,
3070 3070 _('ignore changes in the amount of white space')),
3071 3071 ('B', 'ignore-blank-lines', None,
3072 3072 _('ignore changes whose lines are all blank')),
3073 3073 ('U', 'unified', '', _('number of lines of context to show'))
3074 3074 ]
3075 3075
3076 3076 similarityopts = [
3077 3077 ('s', 'similarity', '',
3078 3078 _('guess renamed files by similarity (0<=s<=100)'))
3079 3079 ]
3080 3080
3081 3081 table = {
3082 3082 "^add": (add, walkopts + dryrunopts, _('[OPTION]... [FILE]...')),
3083 3083 "addremove":
3084 3084 (addremove, similarityopts + walkopts + dryrunopts,
3085 3085 _('[OPTION]... [FILE]...')),
3086 3086 "^annotate|blame":
3087 3087 (annotate,
3088 3088 [('r', 'rev', '', _('annotate the specified revision')),
3089 3089 ('f', 'follow', None, _('follow file copies and renames')),
3090 3090 ('a', 'text', None, _('treat all files as text')),
3091 3091 ('u', 'user', None, _('list the author (long with -v)')),
3092 3092 ('d', 'date', None, _('list the date (short with -q)')),
3093 3093 ('n', 'number', None, _('list the revision number (default)')),
3094 3094 ('c', 'changeset', None, _('list the changeset')),
3095 3095 ('l', 'line-number', None,
3096 3096 _('show line number at the first appearance'))
3097 3097 ] + walkopts,
3098 3098 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
3099 3099 "archive":
3100 3100 (archive,
3101 3101 [('', 'no-decode', None, _('do not pass files through decoders')),
3102 3102 ('p', 'prefix', '', _('directory prefix for files in archive')),
3103 3103 ('r', 'rev', '', _('revision to distribute')),
3104 3104 ('t', 'type', '', _('type of distribution to create')),
3105 3105 ] + walkopts,
3106 3106 _('[OPTION]... DEST')),
3107 3107 "backout":
3108 3108 (backout,
3109 3109 [('', 'merge', None,
3110 3110 _('merge with old dirstate parent after backout')),
3111 3111 ('', 'parent', '', _('parent to choose when backing out merge')),
3112 3112 ('r', 'rev', '', _('revision to backout')),
3113 3113 ] + walkopts + commitopts + commitopts2,
3114 3114 _('[OPTION]... [-r] REV')),
3115 3115 "bisect":
3116 3116 (bisect,
3117 3117 [('r', 'reset', False, _('reset bisect state')),
3118 3118 ('g', 'good', False, _('mark changeset good')),
3119 3119 ('b', 'bad', False, _('mark changeset bad')),
3120 3120 ('s', 'skip', False, _('skip testing changeset')),
3121 3121 ('c', 'command', '', _('use command to check changeset state')),
3122 3122 ('U', 'noupdate', False, _('do not update to target'))],
3123 3123 _("[-gbsr] [-c CMD] [REV]")),
3124 3124 "branch":
3125 3125 (branch,
3126 3126 [('f', 'force', None,
3127 3127 _('set branch name even if it shadows an existing branch')),
3128 3128 ('C', 'clean', None, _('reset branch name to parent branch name'))],
3129 3129 _('[-fC] [NAME]')),
3130 3130 "branches":
3131 3131 (branches,
3132 3132 [('a', 'active', False,
3133 3133 _('show only branches that have unmerged heads'))],
3134 3134 _('[-a]')),
3135 3135 "bundle":
3136 3136 (bundle,
3137 3137 [('f', 'force', None,
3138 3138 _('run even when remote repository is unrelated')),
3139 3139 ('r', 'rev', [],
3140 3140 _('a changeset up to which you would like to bundle')),
3141 3141 ('', 'base', [],
3142 3142 _('a base changeset to specify instead of a destination')),
3143 3143 ('a', 'all', None, _('bundle all changesets in the repository')),
3144 3144 ('t', 'type', 'bzip2', _('bundle compression type to use')),
3145 3145 ] + remoteopts,
3146 3146 _('[-f] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
3147 3147 "cat":
3148 3148 (cat,
3149 3149 [('o', 'output', '', _('print output to file with formatted name')),
3150 3150 ('r', 'rev', '', _('print the given revision')),
3151 3151 ('', 'decode', None, _('apply any matching decode filter')),
3152 3152 ] + walkopts,
3153 3153 _('[OPTION]... FILE...')),
3154 3154 "^clone":
3155 3155 (clone,
3156 3156 [('U', 'noupdate', None,
3157 3157 _('the clone will only contain a repository (no working copy)')),
3158 3158 ('r', 'rev', [],
3159 3159 _('a changeset you would like to have after cloning')),
3160 3160 ('', 'pull', None, _('use pull protocol to copy metadata')),
3161 3161 ('', 'uncompressed', None,
3162 3162 _('use uncompressed transfer (fast over LAN)')),
3163 3163 ] + remoteopts,
3164 3164 _('[OPTION]... SOURCE [DEST]')),
3165 3165 "^commit|ci":
3166 3166 (commit,
3167 3167 [('A', 'addremove', None,
3168 3168 _('mark new/missing files as added/removed before committing')),
3169 3169 ('', 'close-branch', None,
3170 3170 _('mark a branch as closed, hiding it from the branch list')),
3171 3171 ] + walkopts + commitopts + commitopts2,
3172 3172 _('[OPTION]... [FILE]...')),
3173 3173 "copy|cp":
3174 3174 (copy,
3175 3175 [('A', 'after', None, _('record a copy that has already occurred')),
3176 3176 ('f', 'force', None,
3177 3177 _('forcibly copy over an existing managed file')),
3178 3178 ] + walkopts + dryrunopts,
3179 3179 _('[OPTION]... [SOURCE]... DEST')),
3180 3180 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
3181 3181 "debugcheckstate": (debugcheckstate, []),
3182 3182 "debugcommands": (debugcommands, [], _('[COMMAND]')),
3183 3183 "debugcomplete":
3184 3184 (debugcomplete,
3185 3185 [('o', 'options', None, _('show the command options'))],
3186 3186 _('[-o] CMD')),
3187 3187 "debugdate":
3188 3188 (debugdate,
3189 3189 [('e', 'extended', None, _('try extended date formats'))],
3190 3190 _('[-e] DATE [RANGE]')),
3191 3191 "debugdata": (debugdata, [], _('FILE REV')),
3192 3192 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
3193 3193 "debugindex": (debugindex, [], _('FILE')),
3194 3194 "debugindexdot": (debugindexdot, [], _('FILE')),
3195 3195 "debuginstall": (debuginstall, []),
3196 3196 "debugrebuildstate":
3197 3197 (debugrebuildstate,
3198 3198 [('r', 'rev', '', _('revision to rebuild to'))],
3199 3199 _('[-r REV] [REV]')),
3200 3200 "debugrename":
3201 3201 (debugrename,
3202 3202 [('r', 'rev', '', _('revision to debug'))],
3203 3203 _('[-r REV] FILE')),
3204 3204 "debugsetparents":
3205 3205 (debugsetparents, [], _('REV1 [REV2]')),
3206 3206 "debugstate":
3207 3207 (debugstate,
3208 3208 [('', 'nodates', None, _('do not display the saved mtime'))],
3209 3209 _('[OPTION]...')),
3210 3210 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
3211 3211 "^diff":
3212 3212 (diff,
3213 3213 [('r', 'rev', [], _('revision')),
3214 3214 ('c', 'change', '', _('change made by revision'))
3215 3215 ] + diffopts + diffopts2 + walkopts,
3216 3216 _('[OPTION]... [-r REV1 [-r REV2]] [FILE]...')),
3217 3217 "^export":
3218 3218 (export,
3219 3219 [('o', 'output', '', _('print output to file with formatted name')),
3220 3220 ('', 'switch-parent', None, _('diff against the second parent'))
3221 3221 ] + diffopts,
3222 3222 _('[OPTION]... [-o OUTFILESPEC] REV...')),
3223 3223 "grep":
3224 3224 (grep,
3225 3225 [('0', 'print0', None, _('end fields with NUL')),
3226 3226 ('', 'all', None, _('print all revisions that match')),
3227 3227 ('f', 'follow', None,
3228 3228 _('follow changeset history, or file history across copies and renames')),
3229 3229 ('i', 'ignore-case', None, _('ignore case when matching')),
3230 3230 ('l', 'files-with-matches', None,
3231 3231 _('print only filenames and revisions that match')),
3232 3232 ('n', 'line-number', None, _('print matching line numbers')),
3233 3233 ('r', 'rev', [], _('search in given revision range')),
3234 3234 ('u', 'user', None, _('list the author (long with -v)')),
3235 3235 ('d', 'date', None, _('list the date (short with -q)')),
3236 3236 ] + walkopts,
3237 3237 _('[OPTION]... PATTERN [FILE]...')),
3238 3238 "heads":
3239 3239 (heads,
3240 3240 [('r', 'rev', '', _('show only heads which are descendants of REV')),
3241 3241 ('a', 'active', False,
3242 3242 _('show only the active heads from open branches')),
3243 3243 ] + templateopts,
3244 3244 _('[-r REV] [REV]...')),
3245 3245 "help": (help_, [], _('[TOPIC]')),
3246 3246 "identify|id":
3247 3247 (identify,
3248 3248 [('r', 'rev', '', _('identify the specified revision')),
3249 3249 ('n', 'num', None, _('show local revision number')),
3250 3250 ('i', 'id', None, _('show global revision id')),
3251 3251 ('b', 'branch', None, _('show branch')),
3252 3252 ('t', 'tags', None, _('show tags'))],
3253 3253 _('[-nibt] [-r REV] [SOURCE]')),
3254 3254 "import|patch":
3255 3255 (import_,
3256 3256 [('p', 'strip', 1,
3257 3257 _('directory strip option for patch. This has the same '
3258 3258 'meaning as the corresponding patch option')),
3259 3259 ('b', 'base', '', _('base path')),
3260 3260 ('f', 'force', None,
3261 3261 _('skip check for outstanding uncommitted changes')),
3262 3262 ('', 'no-commit', None, _("don't commit, just update the working directory")),
3263 3263 ('', 'exact', None,
3264 3264 _('apply patch to the nodes from which it was generated')),
3265 3265 ('', 'import-branch', None,
3266 3266 _('use any branch information in patch (implied by --exact)'))] +
3267 3267 commitopts + commitopts2 + similarityopts,
3268 3268 _('[OPTION]... PATCH...')),
3269 3269 "incoming|in":
3270 3270 (incoming,
3271 3271 [('f', 'force', None,
3272 3272 _('run even when remote repository is unrelated')),
3273 3273 ('n', 'newest-first', None, _('show newest record first')),
3274 3274 ('', 'bundle', '', _('file to store the bundles into')),
3275 3275 ('r', 'rev', [],
3276 3276 _('a specific revision up to which you would like to pull')),
3277 3277 ] + logopts + remoteopts,
3278 3278 _('[-p] [-n] [-M] [-f] [-r REV]...'
3279 3279 ' [--bundle FILENAME] [SOURCE]')),
3280 3280 "^init":
3281 3281 (init,
3282 3282 remoteopts,
3283 3283 _('[-e CMD] [--remotecmd CMD] [DEST]')),
3284 3284 "locate":
3285 3285 (locate,
3286 3286 [('r', 'rev', '', _('search the repository as it stood at REV')),
3287 3287 ('0', 'print0', None,
3288 3288 _('end filenames with NUL, for use with xargs')),
3289 3289 ('f', 'fullpath', None,
3290 3290 _('print complete paths from the filesystem root')),
3291 3291 ] + walkopts,
3292 3292 _('[OPTION]... [PATTERN]...')),
3293 3293 "^log|history":
3294 3294 (log,
3295 3295 [('f', 'follow', None,
3296 3296 _('follow changeset history, or file history across copies and renames')),
3297 3297 ('', 'follow-first', None,
3298 3298 _('only follow the first parent of merge changesets')),
3299 3299 ('d', 'date', '', _('show revisions matching date spec')),
3300 3300 ('C', 'copies', None, _('show copied files')),
3301 3301 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
3302 3302 ('r', 'rev', [], _('show the specified revision or range')),
3303 3303 ('', 'removed', None, _('include revisions where files were removed')),
3304 3304 ('m', 'only-merges', None, _('show only merges')),
3305 3305 ('u', 'user', [], _('revisions committed by user')),
3306 3306 ('b', 'only-branch', [],
3307 3307 _('show only changesets within the given named branch')),
3308 3308 ('P', 'prune', [], _('do not display revision or any of its ancestors')),
3309 3309 ] + logopts + walkopts,
3310 3310 _('[OPTION]... [FILE]')),
3311 3311 "manifest":
3312 3312 (manifest,
3313 3313 [('r', 'rev', '', _('revision to display'))],
3314 3314 _('[-r REV]')),
3315 3315 "^merge":
3316 3316 (merge,
3317 3317 [('f', 'force', None, _('force a merge with outstanding changes')),
3318 3318 ('r', 'rev', '', _('revision to merge')),
3319 3319 ('S', 'show', None,
3320 3320 _('review revisions to merge (no merge is performed)'))],
3321 3321 _('[-f] [[-r] REV]')),
3322 3322 "outgoing|out":
3323 3323 (outgoing,
3324 3324 [('f', 'force', None,
3325 3325 _('run even when remote repository is unrelated')),
3326 3326 ('r', 'rev', [],
3327 3327 _('a specific revision up to which you would like to push')),
3328 3328 ('n', 'newest-first', None, _('show newest record first')),
3329 3329 ] + logopts + remoteopts,
3330 3330 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3331 3331 "^parents":
3332 3332 (parents,
3333 3333 [('r', 'rev', '', _('show parents from the specified revision')),
3334 3334 ] + templateopts,
3335 3335 _('[-r REV] [FILE]')),
3336 3336 "paths": (paths, [], _('[NAME]')),
3337 3337 "^pull":
3338 3338 (pull,
3339 3339 [('u', 'update', None,
3340 3340 _('update to new tip if changesets were pulled')),
3341 3341 ('f', 'force', None,
3342 3342 _('run even when remote repository is unrelated')),
3343 3343 ('r', 'rev', [],
3344 3344 _('a specific revision up to which you would like to pull')),
3345 3345 ] + remoteopts,
3346 3346 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3347 3347 "^push":
3348 3348 (push,
3349 3349 [('f', 'force', None, _('force push')),
3350 3350 ('r', 'rev', [],
3351 3351 _('a specific revision up to which you would like to push')),
3352 3352 ] + remoteopts,
3353 3353 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
3354 3354 "recover": (recover, []),
3355 3355 "^remove|rm":
3356 3356 (remove,
3357 3357 [('A', 'after', None, _('record delete for missing files')),
3358 3358 ('f', 'force', None,
3359 3359 _('remove (and delete) file even if added or modified')),
3360 3360 ] + walkopts,
3361 3361 _('[OPTION]... FILE...')),
3362 3362 "rename|mv":
3363 3363 (rename,
3364 3364 [('A', 'after', None, _('record a rename that has already occurred')),
3365 3365 ('f', 'force', None,
3366 3366 _('forcibly copy over an existing managed file')),
3367 3367 ] + walkopts + dryrunopts,
3368 3368 _('[OPTION]... SOURCE... DEST')),
3369 3369 "resolve":
3370 3370 (resolve,
3371 3371 [('a', 'all', None, _('remerge all unresolved files')),
3372 3372 ('l', 'list', None, _('list state of files needing merge')),
3373 3373 ('m', 'mark', None, _('mark files as resolved')),
3374 3374 ('u', 'unmark', None, _('unmark files as resolved'))]
3375 3375 + walkopts,
3376 3376 _('[OPTION]... [FILE]...')),
3377 3377 "revert":
3378 3378 (revert,
3379 3379 [('a', 'all', None, _('revert all changes when no arguments given')),
3380 3380 ('d', 'date', '', _('tipmost revision matching date')),
3381 3381 ('r', 'rev', '', _('revision to revert to')),
3382 3382 ('', 'no-backup', None, _('do not save backup copies of files')),
3383 3383 ] + walkopts + dryrunopts,
3384 3384 _('[OPTION]... [-r REV] [NAME]...')),
3385 3385 "rollback": (rollback, []),
3386 3386 "root": (root, []),
3387 3387 "^serve":
3388 3388 (serve,
3389 3389 [('A', 'accesslog', '', _('name of access log file to write to')),
3390 3390 ('d', 'daemon', None, _('run server in background')),
3391 3391 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
3392 3392 ('E', 'errorlog', '', _('name of error log file to write to')),
3393 3393 ('p', 'port', 0, _('port to listen on (default: 8000)')),
3394 3394 ('a', 'address', '', _('address to listen on (default: all interfaces)')),
3395 3395 ('', 'prefix', '', _('prefix path to serve from (default: server root)')),
3396 3396 ('n', 'name', '',
3397 3397 _('name to show in web pages (default: working directory)')),
3398 3398 ('', 'webdir-conf', '', _('name of the webdir config file'
3399 3399 ' (serve more than one repository)')),
3400 3400 ('', 'pid-file', '', _('name of file to write process ID to')),
3401 3401 ('', 'stdio', None, _('for remote clients')),
3402 3402 ('t', 'templates', '', _('web templates to use')),
3403 3403 ('', 'style', '', _('template style to use')),
3404 3404 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
3405 3405 ('', 'certificate', '', _('SSL certificate file'))],
3406 3406 _('[OPTION]...')),
3407 3407 "showconfig|debugconfig":
3408 3408 (showconfig,
3409 3409 [('u', 'untrusted', None, _('show untrusted configuration options'))],
3410 3410 _('[-u] [NAME]...')),
3411 3411 "^status|st":
3412 3412 (status,
3413 3413 [('A', 'all', None, _('show status of all files')),
3414 3414 ('m', 'modified', None, _('show only modified files')),
3415 3415 ('a', 'added', None, _('show only added files')),
3416 3416 ('r', 'removed', None, _('show only removed files')),
3417 3417 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
3418 3418 ('c', 'clean', None, _('show only files without changes')),
3419 3419 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
3420 3420 ('i', 'ignored', None, _('show only ignored files')),
3421 3421 ('n', 'no-status', None, _('hide status prefix')),
3422 3422 ('C', 'copies', None, _('show source of copied files')),
3423 3423 ('0', 'print0', None,
3424 3424 _('end filenames with NUL, for use with xargs')),
3425 3425 ('', 'rev', [], _('show difference from revision')),
3426 3426 ] + walkopts,
3427 3427 _('[OPTION]... [FILE]...')),
3428 3428 "tag":
3429 3429 (tag,
3430 3430 [('f', 'force', None, _('replace existing tag')),
3431 3431 ('l', 'local', None, _('make the tag local')),
3432 3432 ('r', 'rev', '', _('revision to tag')),
3433 3433 ('', 'remove', None, _('remove a tag')),
3434 3434 # -l/--local is already there, commitopts cannot be used
3435 3435 ('m', 'message', '', _('use <text> as commit message')),
3436 3436 ] + commitopts2,
3437 3437 _('[-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
3438 3438 "tags": (tags, []),
3439 3439 "tip":
3440 3440 (tip,
3441 3441 [('p', 'patch', None, _('show patch')),
3442 3442 ('g', 'git', None, _('use git extended diff format')),
3443 3443 ] + templateopts,
3444 3444 _('[-p]')),
3445 3445 "unbundle":
3446 3446 (unbundle,
3447 3447 [('u', 'update', None,
3448 3448 _('update to new tip if changesets were unbundled'))],
3449 3449 _('[-u] FILE...')),
3450 3450 "^update|up|checkout|co":
3451 3451 (update,
3452 3452 [('C', 'clean', None, _('overwrite locally modified files (no backup)')),
3453 3453 ('d', 'date', '', _('tipmost revision matching date')),
3454 3454 ('r', 'rev', '', _('revision'))],
3455 3455 _('[-C] [-d DATE] [[-r] REV]')),
3456 3456 "verify": (verify, []),
3457 3457 "version": (version_, []),
3458 3458 }
3459 3459
3460 3460 norepo = ("clone init version help debugcommands debugcomplete debugdata"
3461 3461 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
3462 3462 optionalrepo = ("identify paths serve showconfig debugancestor")
@@ -1,222 +1,222 b''
1 1 # filemerge.py - file-level merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 from node import short
9 9 from i18n import _
10 10 import util, simplemerge, match
11 11 import os, tempfile, re, filecmp
12 12
13 13 def _toolstr(ui, tool, part, default=""):
14 14 return ui.config("merge-tools", tool + "." + part, default)
15 15
16 16 def _toolbool(ui, tool, part, default=False):
17 17 return ui.configbool("merge-tools", tool + "." + part, default)
18 18
19 19 def _findtool(ui, tool):
20 20 if tool in ("internal:fail", "internal:local", "internal:other"):
21 21 return tool
22 22 k = _toolstr(ui, tool, "regkey")
23 23 if k:
24 24 p = util.lookup_reg(k, _toolstr(ui, tool, "regname"))
25 25 if p:
26 26 p = util.find_exe(p + _toolstr(ui, tool, "regappend"))
27 27 if p:
28 28 return p
29 29 return util.find_exe(_toolstr(ui, tool, "executable", tool))
30 30
31 31 def _picktool(repo, ui, path, binary, symlink):
32 32 def check(tool, pat, symlink, binary):
33 33 tmsg = tool
34 34 if pat:
35 35 tmsg += " specified for " + pat
36 36 if not _findtool(ui, tool):
37 37 if pat: # explicitly requested tool deserves a warning
38 38 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
39 39 else: # configured but non-existing tools are more silent
40 40 ui.note(_("couldn't find merge tool %s\n") % tmsg)
41 41 elif symlink and not _toolbool(ui, tool, "symlink"):
42 42 ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
43 43 elif binary and not _toolbool(ui, tool, "binary"):
44 44 ui.warn(_("tool %s can't handle binary\n") % tmsg)
45 45 elif not util.gui() and _toolbool(ui, tool, "gui"):
46 46 ui.warn(_("tool %s requires a GUI\n") % tmsg)
47 47 else:
48 48 return True
49 49 return False
50 50
51 51 # HGMERGE takes precedence
52 52 hgmerge = os.environ.get("HGMERGE")
53 53 if hgmerge:
54 54 return (hgmerge, hgmerge)
55 55
56 56 # then patterns
57 57 for pat, tool in ui.configitems("merge-patterns"):
58 58 mf = match.match(repo.root, '', [pat])
59 59 if mf(path) and check(tool, pat, symlink, False):
60 60 toolpath = _findtool(ui, tool)
61 61 return (tool, '"' + toolpath + '"')
62 62
63 63 # then merge tools
64 64 tools = {}
65 65 for k,v in ui.configitems("merge-tools"):
66 66 t = k.split('.')[0]
67 67 if t not in tools:
68 68 tools[t] = int(_toolstr(ui, t, "priority", "0"))
69 69 names = tools.keys()
70 70 tools = sorted([(-p,t) for t,p in tools.items()])
71 71 uimerge = ui.config("ui", "merge")
72 72 if uimerge:
73 73 if uimerge not in names:
74 74 return (uimerge, uimerge)
75 75 tools.insert(0, (None, uimerge)) # highest priority
76 76 tools.append((None, "hgmerge")) # the old default, if found
77 77 for p,t in tools:
78 78 if check(t, None, symlink, binary):
79 79 toolpath = _findtool(ui, t)
80 80 return (t, '"' + toolpath + '"')
81 81 # internal merge as last resort
82 82 return (not (symlink or binary) and "internal:merge" or None, None)
83 83
84 84 def _eoltype(data):
85 85 "Guess the EOL type of a file"
86 86 if '\0' in data: # binary
87 87 return None
88 88 if '\r\n' in data: # Windows
89 89 return '\r\n'
90 90 if '\r' in data: # Old Mac
91 91 return '\r'
92 92 if '\n' in data: # UNIX
93 93 return '\n'
94 94 return None # unknown
95 95
96 96 def _matcheol(file, origfile):
97 97 "Convert EOL markers in a file to match origfile"
98 98 tostyle = _eoltype(open(origfile, "rb").read())
99 99 if tostyle:
100 100 data = open(file, "rb").read()
101 101 style = _eoltype(data)
102 102 if style:
103 103 newdata = data.replace(style, tostyle)
104 104 if newdata != data:
105 105 open(file, "wb").write(newdata)
106 106
107 107 def filemerge(repo, mynode, orig, fcd, fco, fca):
108 108 """perform a 3-way merge in the working directory
109 109
110 110 mynode = parent node before merge
111 111 orig = original local filename before merge
112 112 fco = other file context
113 113 fca = ancestor file context
114 114 fcd = local file context for current/destination file
115 115 """
116 116
117 117 def temp(prefix, ctx):
118 118 pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
119 119 (fd, name) = tempfile.mkstemp(prefix=pre)
120 120 data = repo.wwritedata(ctx.path(), ctx.data())
121 121 f = os.fdopen(fd, "wb")
122 122 f.write(data)
123 123 f.close()
124 124 return name
125 125
126 126 def isbin(ctx):
127 127 try:
128 128 return util.binary(ctx.data())
129 129 except IOError:
130 130 return False
131 131
132 132 if not fco.cmp(fcd.data()): # files identical?
133 133 return None
134 134
135 135 ui = repo.ui
136 136 fd = fcd.path()
137 137 binary = isbin(fcd) or isbin(fco) or isbin(fca)
138 138 symlink = 'l' in fcd.flags() + fco.flags()
139 139 tool, toolpath = _picktool(repo, ui, fd, binary, symlink)
140 140 ui.debug(_("picked tool '%s' for %s (binary %s symlink %s)\n") %
141 141 (tool, fd, binary, symlink))
142 142
143 143 if not tool:
144 144 tool = "internal:local"
145 145 if ui.prompt(_(" no tool found to merge %s\n"
146 146 "keep (l)ocal or take (o)ther?") % fd,
147 147 (_("&Local"), _("&Other")), _("l")) != _("l"):
148 148 tool = "internal:other"
149 149 if tool == "internal:local":
150 150 return 0
151 151 if tool == "internal:other":
152 152 repo.wwrite(fd, fco.data(), fco.flags())
153 153 return 0
154 154 if tool == "internal:fail":
155 155 return 1
156 156
157 157 # do the actual merge
158 158 a = repo.wjoin(fd)
159 159 b = temp("base", fca)
160 160 c = temp("other", fco)
161 161 out = ""
162 162 back = a + ".orig"
163 163 util.copyfile(a, back)
164 164
165 165 if orig != fco.path():
166 repo.ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
166 ui.status(_("merging %s and %s to %s\n") % (orig, fco.path(), fd))
167 167 else:
168 repo.ui.status(_("merging %s\n") % fd)
168 ui.status(_("merging %s\n") % fd)
169 169
170 repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcd, fco, fca))
170 ui.debug(_("my %s other %s ancestor %s\n") % (fcd, fco, fca))
171 171
172 172 # do we attempt to simplemerge first?
173 173 if _toolbool(ui, tool, "premerge", not (binary or symlink)):
174 174 r = simplemerge.simplemerge(ui, a, b, c, quiet=True)
175 175 if not r:
176 176 ui.debug(_(" premerge successful\n"))
177 177 os.unlink(back)
178 178 os.unlink(b)
179 179 os.unlink(c)
180 180 return 0
181 181 util.copyfile(back, a) # restore from backup and try again
182 182
183 183 env = dict(HG_FILE=fd,
184 184 HG_MY_NODE=short(mynode),
185 185 HG_OTHER_NODE=str(fco.changectx()),
186 186 HG_MY_ISLINK='l' in fcd.flags(),
187 187 HG_OTHER_ISLINK='l' in fco.flags(),
188 188 HG_BASE_ISLINK='l' in fca.flags())
189 189
190 190 if tool == "internal:merge":
191 191 r = simplemerge.simplemerge(ui, a, b, c, label=['local', 'other'])
192 192 else:
193 193 args = _toolstr(ui, tool, "args", '$local $base $other')
194 194 if "$output" in args:
195 195 out, a = a, back # read input from backup, write to original
196 196 replace = dict(local=a, base=b, other=c, output=out)
197 197 args = re.sub("\$(local|base|other|output)",
198 198 lambda x: '"%s"' % replace[x.group()[1:]], args)
199 199 r = util.system(toolpath + ' ' + args, cwd=repo.root, environ=env)
200 200
201 201 if not r and _toolbool(ui, tool, "checkconflicts"):
202 202 if re.match("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data()):
203 203 r = 1
204 204
205 205 if not r and _toolbool(ui, tool, "checkchanged"):
206 206 if filecmp.cmp(repo.wjoin(fd), back):
207 207 if ui.prompt(_(" output file %s appears unchanged\n"
208 208 "was merge successful (yn)?") % fd,
209 209 (_("&Yes"), _("&No")), _("n")) != _("y"):
210 210 r = 1
211 211
212 212 if _toolbool(ui, tool, "fixeol"):
213 213 _matcheol(repo.wjoin(fd), back)
214 214
215 215 if r:
216 repo.ui.warn(_("merging %s failed!\n") % fd)
216 ui.warn(_("merging %s failed!\n") % fd)
217 217 else:
218 218 os.unlink(back)
219 219
220 220 os.unlink(b)
221 221 os.unlink(c)
222 222 return r
General Comments 0
You need to be logged in to leave comments. Login now