##// END OF EJS Templates
help: show value requirement and multiple occurrence of options...
FUJIWARA Katsunori -
r11321:40c06bbf default
parent child Browse files
Show More
@@ -1,115 +1,119 b''
1 import os, sys
1 import os, sys
2 # import from the live mercurial repo
2 # import from the live mercurial repo
3 sys.path.insert(0, "..")
3 sys.path.insert(0, "..")
4 # fall back to pure modules if required C extensions are not available
4 # fall back to pure modules if required C extensions are not available
5 sys.path.append(os.path.join('..', 'mercurial', 'pure'))
5 sys.path.append(os.path.join('..', 'mercurial', 'pure'))
6 from mercurial import demandimport; demandimport.enable()
6 from mercurial import demandimport; demandimport.enable()
7 from mercurial import encoding
7 from mercurial import encoding
8 from mercurial.commands import table, globalopts
8 from mercurial.commands import table, globalopts
9 from mercurial.i18n import _
9 from mercurial.i18n import _
10 from mercurial.help import helptable
10 from mercurial.help import helptable
11
11
12 def get_desc(docstr):
12 def get_desc(docstr):
13 if not docstr:
13 if not docstr:
14 return "", ""
14 return "", ""
15 # sanitize
15 # sanitize
16 docstr = docstr.strip("\n")
16 docstr = docstr.strip("\n")
17 docstr = docstr.rstrip()
17 docstr = docstr.rstrip()
18 shortdesc = docstr.splitlines()[0].strip()
18 shortdesc = docstr.splitlines()[0].strip()
19
19
20 i = docstr.find("\n")
20 i = docstr.find("\n")
21 if i != -1:
21 if i != -1:
22 desc = docstr[i + 2:]
22 desc = docstr[i + 2:]
23 else:
23 else:
24 desc = " %s" % shortdesc
24 desc = " %s" % shortdesc
25 return (shortdesc, desc)
25 return (shortdesc, desc)
26
26
27 def get_opts(opts):
27 def get_opts(opts):
28 for shortopt, longopt, default, desc in opts:
28 for opt in opts:
29 if len(opt) == 5:
30 shortopt, longopt, default, desc, optlabel = opt
31 else:
32 shortopt, longopt, default, desc = opt
29 allopts = []
33 allopts = []
30 if shortopt:
34 if shortopt:
31 allopts.append("-%s" % shortopt)
35 allopts.append("-%s" % shortopt)
32 if longopt:
36 if longopt:
33 allopts.append("--%s" % longopt)
37 allopts.append("--%s" % longopt)
34 desc += default and _(" (default: %s)") % default or ""
38 desc += default and _(" (default: %s)") % default or ""
35 yield(", ".join(allopts), desc)
39 yield(", ".join(allopts), desc)
36
40
37 def get_cmd(cmd):
41 def get_cmd(cmd):
38 d = {}
42 d = {}
39 attr = table[cmd]
43 attr = table[cmd]
40 cmds = cmd.lstrip("^").split("|")
44 cmds = cmd.lstrip("^").split("|")
41
45
42 d['cmd'] = cmds[0]
46 d['cmd'] = cmds[0]
43 d['aliases'] = cmd.split("|")[1:]
47 d['aliases'] = cmd.split("|")[1:]
44 d['desc'] = get_desc(attr[0].__doc__)
48 d['desc'] = get_desc(attr[0].__doc__)
45 d['opts'] = list(get_opts(attr[1]))
49 d['opts'] = list(get_opts(attr[1]))
46
50
47 s = 'hg ' + cmds[0]
51 s = 'hg ' + cmds[0]
48 if len(attr) > 2:
52 if len(attr) > 2:
49 if not attr[2].startswith('hg'):
53 if not attr[2].startswith('hg'):
50 s += ' ' + attr[2]
54 s += ' ' + attr[2]
51 else:
55 else:
52 s = attr[2]
56 s = attr[2]
53 d['synopsis'] = s.strip()
57 d['synopsis'] = s.strip()
54
58
55 return d
59 return d
56
60
57 def show_doc(ui):
61 def show_doc(ui):
58 def section(s):
62 def section(s):
59 ui.write("%s\n%s\n\n" % (s, "-" * encoding.colwidth(s)))
63 ui.write("%s\n%s\n\n" % (s, "-" * encoding.colwidth(s)))
60 def subsection(s):
64 def subsection(s):
61 ui.write("%s\n%s\n\n" % (s, '"' * encoding.colwidth(s)))
65 ui.write("%s\n%s\n\n" % (s, '"' * encoding.colwidth(s)))
62
66
63 # print options
67 # print options
64 section(_("Options"))
68 section(_("Options"))
65 for optstr, desc in get_opts(globalopts):
69 for optstr, desc in get_opts(globalopts):
66 ui.write("%s\n %s\n\n" % (optstr, desc))
70 ui.write("%s\n %s\n\n" % (optstr, desc))
67
71
68 # print cmds
72 # print cmds
69 section(_("Commands"))
73 section(_("Commands"))
70 h = {}
74 h = {}
71 for c, attr in table.items():
75 for c, attr in table.items():
72 f = c.split("|")[0]
76 f = c.split("|")[0]
73 f = f.lstrip("^")
77 f = f.lstrip("^")
74 h[f] = c
78 h[f] = c
75 cmds = h.keys()
79 cmds = h.keys()
76 cmds.sort()
80 cmds.sort()
77
81
78 for f in cmds:
82 for f in cmds:
79 if f.startswith("debug"):
83 if f.startswith("debug"):
80 continue
84 continue
81 d = get_cmd(h[f])
85 d = get_cmd(h[f])
82 # synopsis
86 # synopsis
83 ui.write(".. _%s:\n\n" % d['cmd'])
87 ui.write(".. _%s:\n\n" % d['cmd'])
84 ui.write("``%s``\n" % d['synopsis'].replace("hg ","", 1))
88 ui.write("``%s``\n" % d['synopsis'].replace("hg ","", 1))
85 # description
89 # description
86 ui.write("%s\n\n" % d['desc'][1])
90 ui.write("%s\n\n" % d['desc'][1])
87 # options
91 # options
88 opt_output = list(d['opts'])
92 opt_output = list(d['opts'])
89 if opt_output:
93 if opt_output:
90 opts_len = max([len(line[0]) for line in opt_output])
94 opts_len = max([len(line[0]) for line in opt_output])
91 ui.write(_(" options:\n\n"))
95 ui.write(_(" options:\n\n"))
92 for optstr, desc in opt_output:
96 for optstr, desc in opt_output:
93 if desc:
97 if desc:
94 s = "%-*s %s" % (opts_len, optstr, desc)
98 s = "%-*s %s" % (opts_len, optstr, desc)
95 else:
99 else:
96 s = optstr
100 s = optstr
97 ui.write(" %s\n" % s)
101 ui.write(" %s\n" % s)
98 ui.write("\n")
102 ui.write("\n")
99 # aliases
103 # aliases
100 if d['aliases']:
104 if d['aliases']:
101 ui.write(_(" aliases: %s\n\n") % " ".join(d['aliases']))
105 ui.write(_(" aliases: %s\n\n") % " ".join(d['aliases']))
102
106
103 # print topics
107 # print topics
104 for names, sec, doc in helptable:
108 for names, sec, doc in helptable:
105 for name in names:
109 for name in names:
106 ui.write(".. _%s:\n" % name)
110 ui.write(".. _%s:\n" % name)
107 ui.write("\n")
111 ui.write("\n")
108 section(sec)
112 section(sec)
109 if callable(doc):
113 if callable(doc):
110 doc = doc()
114 doc = doc()
111 ui.write(doc)
115 ui.write(doc)
112 ui.write("\n")
116 ui.write("\n")
113
117
114 if __name__ == "__main__":
118 if __name__ == "__main__":
115 show_doc(sys.stdout)
119 show_doc(sys.stdout)
@@ -1,343 +1,343 b''
1 # Mercurial extension to provide the 'hg bookmark' command
1 # Mercurial extension to provide the 'hg bookmark' command
2 #
2 #
3 # Copyright 2008 David Soria Parra <dsp@php.net>
3 # Copyright 2008 David Soria Parra <dsp@php.net>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''track a line of development with movable markers
8 '''track a line of development with movable markers
9
9
10 Bookmarks are local movable markers to changesets. Every bookmark
10 Bookmarks are local movable markers to changesets. Every bookmark
11 points to a changeset identified by its hash. If you commit a
11 points to a changeset identified by its hash. If you commit a
12 changeset that is based on a changeset that has a bookmark on it, the
12 changeset that is based on a changeset that has a bookmark on it, the
13 bookmark shifts to the new changeset.
13 bookmark shifts to the new changeset.
14
14
15 It is possible to use bookmark names in every revision lookup (e.g.
15 It is possible to use bookmark names in every revision lookup (e.g.
16 :hg:`merge`, :hg:`update`).
16 :hg:`merge`, :hg:`update`).
17
17
18 By default, when several bookmarks point to the same changeset, they
18 By default, when several bookmarks point to the same changeset, they
19 will all move forward together. It is possible to obtain a more
19 will all move forward together. It is possible to obtain a more
20 git-like experience by adding the following configuration option to
20 git-like experience by adding the following configuration option to
21 your .hgrc::
21 your .hgrc::
22
22
23 [bookmarks]
23 [bookmarks]
24 track.current = True
24 track.current = True
25
25
26 This will cause Mercurial to track the bookmark that you are currently
26 This will cause Mercurial to track the bookmark that you are currently
27 using, and only update it. This is similar to git's approach to
27 using, and only update it. This is similar to git's approach to
28 branching.
28 branching.
29 '''
29 '''
30
30
31 from mercurial.i18n import _
31 from mercurial.i18n import _
32 from mercurial.node import nullid, nullrev, hex, short
32 from mercurial.node import nullid, nullrev, hex, short
33 from mercurial import util, commands, repair, extensions
33 from mercurial import util, commands, repair, extensions
34 import os
34 import os
35
35
36 def write(repo):
36 def write(repo):
37 '''Write bookmarks
37 '''Write bookmarks
38
38
39 Write the given bookmark => hash dictionary to the .hg/bookmarks file
39 Write the given bookmark => hash dictionary to the .hg/bookmarks file
40 in a format equal to those of localtags.
40 in a format equal to those of localtags.
41
41
42 We also store a backup of the previous state in undo.bookmarks that
42 We also store a backup of the previous state in undo.bookmarks that
43 can be copied back on rollback.
43 can be copied back on rollback.
44 '''
44 '''
45 refs = repo._bookmarks
45 refs = repo._bookmarks
46 if os.path.exists(repo.join('bookmarks')):
46 if os.path.exists(repo.join('bookmarks')):
47 util.copyfile(repo.join('bookmarks'), repo.join('undo.bookmarks'))
47 util.copyfile(repo.join('bookmarks'), repo.join('undo.bookmarks'))
48 if repo._bookmarkcurrent not in refs:
48 if repo._bookmarkcurrent not in refs:
49 setcurrent(repo, None)
49 setcurrent(repo, None)
50 wlock = repo.wlock()
50 wlock = repo.wlock()
51 try:
51 try:
52 file = repo.opener('bookmarks', 'w', atomictemp=True)
52 file = repo.opener('bookmarks', 'w', atomictemp=True)
53 for refspec, node in refs.iteritems():
53 for refspec, node in refs.iteritems():
54 file.write("%s %s\n" % (hex(node), refspec))
54 file.write("%s %s\n" % (hex(node), refspec))
55 file.rename()
55 file.rename()
56 finally:
56 finally:
57 wlock.release()
57 wlock.release()
58
58
59 def setcurrent(repo, mark):
59 def setcurrent(repo, mark):
60 '''Set the name of the bookmark that we are currently on
60 '''Set the name of the bookmark that we are currently on
61
61
62 Set the name of the bookmark that we are on (hg update <bookmark>).
62 Set the name of the bookmark that we are on (hg update <bookmark>).
63 The name is recorded in .hg/bookmarks.current
63 The name is recorded in .hg/bookmarks.current
64 '''
64 '''
65 current = repo._bookmarkcurrent
65 current = repo._bookmarkcurrent
66 if current == mark:
66 if current == mark:
67 return
67 return
68
68
69 refs = repo._bookmarks
69 refs = repo._bookmarks
70
70
71 # do not update if we do update to a rev equal to the current bookmark
71 # do not update if we do update to a rev equal to the current bookmark
72 if (mark and mark not in refs and
72 if (mark and mark not in refs and
73 current and refs[current] == repo.changectx('.').node()):
73 current and refs[current] == repo.changectx('.').node()):
74 return
74 return
75 if mark not in refs:
75 if mark not in refs:
76 mark = ''
76 mark = ''
77 wlock = repo.wlock()
77 wlock = repo.wlock()
78 try:
78 try:
79 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
79 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
80 file.write(mark)
80 file.write(mark)
81 file.rename()
81 file.rename()
82 finally:
82 finally:
83 wlock.release()
83 wlock.release()
84 repo._bookmarkcurrent = mark
84 repo._bookmarkcurrent = mark
85
85
86 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
86 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
87 '''track a line of development with movable markers
87 '''track a line of development with movable markers
88
88
89 Bookmarks are pointers to certain commits that move when
89 Bookmarks are pointers to certain commits that move when
90 committing. Bookmarks are local. They can be renamed, copied and
90 committing. Bookmarks are local. They can be renamed, copied and
91 deleted. It is possible to use bookmark names in :hg:`merge` and
91 deleted. It is possible to use bookmark names in :hg:`merge` and
92 :hg:`update` to merge and update respectively to a given bookmark.
92 :hg:`update` to merge and update respectively to a given bookmark.
93
93
94 You can use :hg:`bookmark NAME` to set a bookmark on the working
94 You can use :hg:`bookmark NAME` to set a bookmark on the working
95 directory's parent revision with the given name. If you specify
95 directory's parent revision with the given name. If you specify
96 a revision using -r REV (where REV may be an existing bookmark),
96 a revision using -r REV (where REV may be an existing bookmark),
97 the bookmark is assigned to that revision.
97 the bookmark is assigned to that revision.
98 '''
98 '''
99 hexfn = ui.debugflag and hex or short
99 hexfn = ui.debugflag and hex or short
100 marks = repo._bookmarks
100 marks = repo._bookmarks
101 cur = repo.changectx('.').node()
101 cur = repo.changectx('.').node()
102
102
103 if rename:
103 if rename:
104 if rename not in marks:
104 if rename not in marks:
105 raise util.Abort(_("a bookmark of this name does not exist"))
105 raise util.Abort(_("a bookmark of this name does not exist"))
106 if mark in marks and not force:
106 if mark in marks and not force:
107 raise util.Abort(_("a bookmark of the same name already exists"))
107 raise util.Abort(_("a bookmark of the same name already exists"))
108 if mark is None:
108 if mark is None:
109 raise util.Abort(_("new bookmark name required"))
109 raise util.Abort(_("new bookmark name required"))
110 marks[mark] = marks[rename]
110 marks[mark] = marks[rename]
111 del marks[rename]
111 del marks[rename]
112 if repo._bookmarkcurrent == rename:
112 if repo._bookmarkcurrent == rename:
113 setcurrent(repo, mark)
113 setcurrent(repo, mark)
114 write(repo)
114 write(repo)
115 return
115 return
116
116
117 if delete:
117 if delete:
118 if mark is None:
118 if mark is None:
119 raise util.Abort(_("bookmark name required"))
119 raise util.Abort(_("bookmark name required"))
120 if mark not in marks:
120 if mark not in marks:
121 raise util.Abort(_("a bookmark of this name does not exist"))
121 raise util.Abort(_("a bookmark of this name does not exist"))
122 if mark == repo._bookmarkcurrent:
122 if mark == repo._bookmarkcurrent:
123 setcurrent(repo, None)
123 setcurrent(repo, None)
124 del marks[mark]
124 del marks[mark]
125 write(repo)
125 write(repo)
126 return
126 return
127
127
128 if mark != None:
128 if mark != None:
129 if "\n" in mark:
129 if "\n" in mark:
130 raise util.Abort(_("bookmark name cannot contain newlines"))
130 raise util.Abort(_("bookmark name cannot contain newlines"))
131 mark = mark.strip()
131 mark = mark.strip()
132 if mark in marks and not force:
132 if mark in marks and not force:
133 raise util.Abort(_("a bookmark of the same name already exists"))
133 raise util.Abort(_("a bookmark of the same name already exists"))
134 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
134 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
135 and not force):
135 and not force):
136 raise util.Abort(
136 raise util.Abort(
137 _("a bookmark cannot have the name of an existing branch"))
137 _("a bookmark cannot have the name of an existing branch"))
138 if rev:
138 if rev:
139 marks[mark] = repo.lookup(rev)
139 marks[mark] = repo.lookup(rev)
140 else:
140 else:
141 marks[mark] = repo.changectx('.').node()
141 marks[mark] = repo.changectx('.').node()
142 setcurrent(repo, mark)
142 setcurrent(repo, mark)
143 write(repo)
143 write(repo)
144 return
144 return
145
145
146 if mark is None:
146 if mark is None:
147 if rev:
147 if rev:
148 raise util.Abort(_("bookmark name required"))
148 raise util.Abort(_("bookmark name required"))
149 if len(marks) == 0:
149 if len(marks) == 0:
150 ui.status(_("no bookmarks set\n"))
150 ui.status(_("no bookmarks set\n"))
151 else:
151 else:
152 for bmark, n in marks.iteritems():
152 for bmark, n in marks.iteritems():
153 if ui.configbool('bookmarks', 'track.current'):
153 if ui.configbool('bookmarks', 'track.current'):
154 current = repo._bookmarkcurrent
154 current = repo._bookmarkcurrent
155 if bmark == current and n == cur:
155 if bmark == current and n == cur:
156 prefix, label = '*', 'bookmarks.current'
156 prefix, label = '*', 'bookmarks.current'
157 else:
157 else:
158 prefix, label = ' ', ''
158 prefix, label = ' ', ''
159 else:
159 else:
160 if n == cur:
160 if n == cur:
161 prefix, label = '*', 'bookmarks.current'
161 prefix, label = '*', 'bookmarks.current'
162 else:
162 else:
163 prefix, label = ' ', ''
163 prefix, label = ' ', ''
164
164
165 if ui.quiet:
165 if ui.quiet:
166 ui.write("%s\n" % bmark, label=label)
166 ui.write("%s\n" % bmark, label=label)
167 else:
167 else:
168 ui.write(" %s %-25s %d:%s\n" % (
168 ui.write(" %s %-25s %d:%s\n" % (
169 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
169 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
170 label=label)
170 label=label)
171 return
171 return
172
172
173 def _revstostrip(changelog, node):
173 def _revstostrip(changelog, node):
174 srev = changelog.rev(node)
174 srev = changelog.rev(node)
175 tostrip = [srev]
175 tostrip = [srev]
176 saveheads = []
176 saveheads = []
177 for r in xrange(srev, len(changelog)):
177 for r in xrange(srev, len(changelog)):
178 parents = changelog.parentrevs(r)
178 parents = changelog.parentrevs(r)
179 if parents[0] in tostrip or parents[1] in tostrip:
179 if parents[0] in tostrip or parents[1] in tostrip:
180 tostrip.append(r)
180 tostrip.append(r)
181 if parents[1] != nullrev:
181 if parents[1] != nullrev:
182 for p in parents:
182 for p in parents:
183 if p not in tostrip and p > srev:
183 if p not in tostrip and p > srev:
184 saveheads.append(p)
184 saveheads.append(p)
185 return [r for r in tostrip if r not in saveheads]
185 return [r for r in tostrip if r not in saveheads]
186
186
187 def strip(oldstrip, ui, repo, node, backup="all"):
187 def strip(oldstrip, ui, repo, node, backup="all"):
188 """Strip bookmarks if revisions are stripped using
188 """Strip bookmarks if revisions are stripped using
189 the mercurial.strip method. This usually happens during
189 the mercurial.strip method. This usually happens during
190 qpush and qpop"""
190 qpush and qpop"""
191 revisions = _revstostrip(repo.changelog, node)
191 revisions = _revstostrip(repo.changelog, node)
192 marks = repo._bookmarks
192 marks = repo._bookmarks
193 update = []
193 update = []
194 for mark, n in marks.iteritems():
194 for mark, n in marks.iteritems():
195 if repo.changelog.rev(n) in revisions:
195 if repo.changelog.rev(n) in revisions:
196 update.append(mark)
196 update.append(mark)
197 oldstrip(ui, repo, node, backup)
197 oldstrip(ui, repo, node, backup)
198 if len(update) > 0:
198 if len(update) > 0:
199 for m in update:
199 for m in update:
200 marks[m] = repo.changectx('.').node()
200 marks[m] = repo.changectx('.').node()
201 write(repo)
201 write(repo)
202
202
203 def reposetup(ui, repo):
203 def reposetup(ui, repo):
204 if not repo.local():
204 if not repo.local():
205 return
205 return
206
206
207 class bookmark_repo(repo.__class__):
207 class bookmark_repo(repo.__class__):
208
208
209 @util.propertycache
209 @util.propertycache
210 def _bookmarks(self):
210 def _bookmarks(self):
211 '''Parse .hg/bookmarks file and return a dictionary
211 '''Parse .hg/bookmarks file and return a dictionary
212
212
213 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
213 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
214 in the .hg/bookmarks file. They are read returned as a dictionary
214 in the .hg/bookmarks file. They are read returned as a dictionary
215 with name => hash values.
215 with name => hash values.
216 '''
216 '''
217 try:
217 try:
218 bookmarks = {}
218 bookmarks = {}
219 for line in self.opener('bookmarks'):
219 for line in self.opener('bookmarks'):
220 sha, refspec = line.strip().split(' ', 1)
220 sha, refspec = line.strip().split(' ', 1)
221 bookmarks[refspec] = super(bookmark_repo, self).lookup(sha)
221 bookmarks[refspec] = super(bookmark_repo, self).lookup(sha)
222 except:
222 except:
223 pass
223 pass
224 return bookmarks
224 return bookmarks
225
225
226 @util.propertycache
226 @util.propertycache
227 def _bookmarkcurrent(self):
227 def _bookmarkcurrent(self):
228 '''Get the current bookmark
228 '''Get the current bookmark
229
229
230 If we use gittishsh branches we have a current bookmark that
230 If we use gittishsh branches we have a current bookmark that
231 we are on. This function returns the name of the bookmark. It
231 we are on. This function returns the name of the bookmark. It
232 is stored in .hg/bookmarks.current
232 is stored in .hg/bookmarks.current
233 '''
233 '''
234 mark = None
234 mark = None
235 if os.path.exists(self.join('bookmarks.current')):
235 if os.path.exists(self.join('bookmarks.current')):
236 file = self.opener('bookmarks.current')
236 file = self.opener('bookmarks.current')
237 # No readline() in posixfile_nt, reading everything is cheap
237 # No readline() in posixfile_nt, reading everything is cheap
238 mark = (file.readlines() or [''])[0]
238 mark = (file.readlines() or [''])[0]
239 if mark == '':
239 if mark == '':
240 mark = None
240 mark = None
241 file.close()
241 file.close()
242 return mark
242 return mark
243
243
244 def rollback(self, *args):
244 def rollback(self, *args):
245 if os.path.exists(self.join('undo.bookmarks')):
245 if os.path.exists(self.join('undo.bookmarks')):
246 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
246 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
247 return super(bookmark_repo, self).rollback(*args)
247 return super(bookmark_repo, self).rollback(*args)
248
248
249 def lookup(self, key):
249 def lookup(self, key):
250 if key in self._bookmarks:
250 if key in self._bookmarks:
251 key = self._bookmarks[key]
251 key = self._bookmarks[key]
252 return super(bookmark_repo, self).lookup(key)
252 return super(bookmark_repo, self).lookup(key)
253
253
254 def _bookmarksupdate(self, parents, node):
254 def _bookmarksupdate(self, parents, node):
255 marks = self._bookmarks
255 marks = self._bookmarks
256 update = False
256 update = False
257 if ui.configbool('bookmarks', 'track.current'):
257 if ui.configbool('bookmarks', 'track.current'):
258 mark = self._bookmarkcurrent
258 mark = self._bookmarkcurrent
259 if mark and marks[mark] in parents:
259 if mark and marks[mark] in parents:
260 marks[mark] = node
260 marks[mark] = node
261 update = True
261 update = True
262 else:
262 else:
263 for mark, n in marks.items():
263 for mark, n in marks.items():
264 if n in parents:
264 if n in parents:
265 marks[mark] = node
265 marks[mark] = node
266 update = True
266 update = True
267 if update:
267 if update:
268 write(self)
268 write(self)
269
269
270 def commitctx(self, ctx, error=False):
270 def commitctx(self, ctx, error=False):
271 """Add a revision to the repository and
271 """Add a revision to the repository and
272 move the bookmark"""
272 move the bookmark"""
273 wlock = self.wlock() # do both commit and bookmark with lock held
273 wlock = self.wlock() # do both commit and bookmark with lock held
274 try:
274 try:
275 node = super(bookmark_repo, self).commitctx(ctx, error)
275 node = super(bookmark_repo, self).commitctx(ctx, error)
276 if node is None:
276 if node is None:
277 return None
277 return None
278 parents = self.changelog.parents(node)
278 parents = self.changelog.parents(node)
279 if parents[1] == nullid:
279 if parents[1] == nullid:
280 parents = (parents[0],)
280 parents = (parents[0],)
281
281
282 self._bookmarksupdate(parents, node)
282 self._bookmarksupdate(parents, node)
283 return node
283 return node
284 finally:
284 finally:
285 wlock.release()
285 wlock.release()
286
286
287 def addchangegroup(self, source, srctype, url, emptyok=False):
287 def addchangegroup(self, source, srctype, url, emptyok=False):
288 parents = self.dirstate.parents()
288 parents = self.dirstate.parents()
289
289
290 result = super(bookmark_repo, self).addchangegroup(
290 result = super(bookmark_repo, self).addchangegroup(
291 source, srctype, url, emptyok)
291 source, srctype, url, emptyok)
292 if result > 1:
292 if result > 1:
293 # We have more heads than before
293 # We have more heads than before
294 return result
294 return result
295 node = self.changelog.tip()
295 node = self.changelog.tip()
296
296
297 self._bookmarksupdate(parents, node)
297 self._bookmarksupdate(parents, node)
298 return result
298 return result
299
299
300 def _findtags(self):
300 def _findtags(self):
301 """Merge bookmarks with normal tags"""
301 """Merge bookmarks with normal tags"""
302 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
302 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
303 tags.update(self._bookmarks)
303 tags.update(self._bookmarks)
304 return (tags, tagtypes)
304 return (tags, tagtypes)
305
305
306 if hasattr(repo, 'invalidate'):
306 if hasattr(repo, 'invalidate'):
307 def invalidate(self):
307 def invalidate(self):
308 super(bookmark_repo, self).invalidate()
308 super(bookmark_repo, self).invalidate()
309 for attr in ('_bookmarks', '_bookmarkcurrent'):
309 for attr in ('_bookmarks', '_bookmarkcurrent'):
310 if attr in self.__dict__:
310 if attr in self.__dict__:
311 delattr(self, attr)
311 delattr(self, attr)
312
312
313 repo.__class__ = bookmark_repo
313 repo.__class__ = bookmark_repo
314
314
315 def uisetup(ui):
315 def uisetup(ui):
316 extensions.wrapfunction(repair, "strip", strip)
316 extensions.wrapfunction(repair, "strip", strip)
317 if ui.configbool('bookmarks', 'track.current'):
317 if ui.configbool('bookmarks', 'track.current'):
318 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
318 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
319
319
320 def updatecurbookmark(orig, ui, repo, *args, **opts):
320 def updatecurbookmark(orig, ui, repo, *args, **opts):
321 '''Set the current bookmark
321 '''Set the current bookmark
322
322
323 If the user updates to a bookmark we update the .hg/bookmarks.current
323 If the user updates to a bookmark we update the .hg/bookmarks.current
324 file.
324 file.
325 '''
325 '''
326 res = orig(ui, repo, *args, **opts)
326 res = orig(ui, repo, *args, **opts)
327 rev = opts['rev']
327 rev = opts['rev']
328 if not rev and len(args) > 0:
328 if not rev and len(args) > 0:
329 rev = args[0]
329 rev = args[0]
330 setcurrent(repo, rev)
330 setcurrent(repo, rev)
331 return res
331 return res
332
332
333 cmdtable = {
333 cmdtable = {
334 "bookmarks":
334 "bookmarks":
335 (bookmark,
335 (bookmark,
336 [('f', 'force', False, _('force')),
336 [('f', 'force', False, _('force')),
337 ('r', 'rev', '', _('revision')),
337 ('r', 'rev', '', _('revision'), _('REV')),
338 ('d', 'delete', False, _('delete a given bookmark')),
338 ('d', 'delete', False, _('delete a given bookmark')),
339 ('m', 'rename', '', _('rename a given bookmark'))],
339 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
340 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
340 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
341 }
341 }
342
342
343 colortable = {'bookmarks.current': 'green'}
343 colortable = {'bookmarks.current': 'green'}
@@ -1,44 +1,45 b''
1 # Mercurial extension to provide the 'hg children' command
1 # Mercurial extension to provide the 'hg children' command
2 #
2 #
3 # Copyright 2007 by Intevation GmbH <intevation@intevation.de>
3 # Copyright 2007 by Intevation GmbH <intevation@intevation.de>
4 #
4 #
5 # Author(s):
5 # Author(s):
6 # Thomas Arendsen Hein <thomas@intevation.de>
6 # Thomas Arendsen Hein <thomas@intevation.de>
7 #
7 #
8 # This software may be used and distributed according to the terms of the
8 # This software may be used and distributed according to the terms of the
9 # GNU General Public License version 2 or any later version.
9 # GNU General Public License version 2 or any later version.
10
10
11 '''command to display child changesets'''
11 '''command to display child changesets'''
12
12
13 from mercurial import cmdutil
13 from mercurial import cmdutil
14 from mercurial.commands import templateopts
14 from mercurial.commands import templateopts
15 from mercurial.i18n import _
15 from mercurial.i18n import _
16
16
17
17
18 def children(ui, repo, file_=None, **opts):
18 def children(ui, repo, file_=None, **opts):
19 """show the children of the given or working directory revision
19 """show the children of the given or working directory revision
20
20
21 Print the children of the working directory's revisions. If a
21 Print the children of the working directory's revisions. If a
22 revision is given via -r/--rev, the children of that revision will
22 revision is given via -r/--rev, the children of that revision will
23 be printed. If a file argument is given, revision in which the
23 be printed. If a file argument is given, revision in which the
24 file was last changed (after the working directory revision or the
24 file was last changed (after the working directory revision or the
25 argument to --rev if given) is printed.
25 argument to --rev if given) is printed.
26 """
26 """
27 rev = opts.get('rev')
27 rev = opts.get('rev')
28 if file_:
28 if file_:
29 ctx = repo.filectx(file_, changeid=rev)
29 ctx = repo.filectx(file_, changeid=rev)
30 else:
30 else:
31 ctx = repo[rev]
31 ctx = repo[rev]
32
32
33 displayer = cmdutil.show_changeset(ui, repo, opts)
33 displayer = cmdutil.show_changeset(ui, repo, opts)
34 for cctx in ctx.children():
34 for cctx in ctx.children():
35 displayer.show(cctx)
35 displayer.show(cctx)
36 displayer.close()
36 displayer.close()
37
37
38 cmdtable = {
38 cmdtable = {
39 "children":
39 "children":
40 (children,
40 (children,
41 [('r', 'rev', '', _('show children of the specified revision')),
41 [('r', 'rev', '',
42 _('show children of the specified revision'), _('REV')),
42 ] + templateopts,
43 ] + templateopts,
43 _('hg children [-r REV] [FILE]')),
44 _('hg children [-r REV] [FILE]')),
44 }
45 }
@@ -1,187 +1,190 b''
1 # churn.py - create a graph of revisions count grouped by template
1 # churn.py - create a graph of revisions count grouped by template
2 #
2 #
3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
4 # Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
4 # Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''command to display statistics about repository history'''
9 '''command to display statistics about repository history'''
10
10
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12 from mercurial import patch, cmdutil, util, templater, commands
12 from mercurial import patch, cmdutil, util, templater, commands
13 import os
13 import os
14 import time, datetime
14 import time, datetime
15
15
16 def maketemplater(ui, repo, tmpl):
16 def maketemplater(ui, repo, tmpl):
17 tmpl = templater.parsestring(tmpl, quoted=False)
17 tmpl = templater.parsestring(tmpl, quoted=False)
18 try:
18 try:
19 t = cmdutil.changeset_templater(ui, repo, False, None, None, False)
19 t = cmdutil.changeset_templater(ui, repo, False, None, None, False)
20 except SyntaxError, inst:
20 except SyntaxError, inst:
21 raise util.Abort(inst.args[0])
21 raise util.Abort(inst.args[0])
22 t.use_template(tmpl)
22 t.use_template(tmpl)
23 return t
23 return t
24
24
25 def changedlines(ui, repo, ctx1, ctx2, fns):
25 def changedlines(ui, repo, ctx1, ctx2, fns):
26 added, removed = 0, 0
26 added, removed = 0, 0
27 fmatch = cmdutil.matchfiles(repo, fns)
27 fmatch = cmdutil.matchfiles(repo, fns)
28 diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
28 diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
29 for l in diff.split('\n'):
29 for l in diff.split('\n'):
30 if l.startswith("+") and not l.startswith("+++ "):
30 if l.startswith("+") and not l.startswith("+++ "):
31 added += 1
31 added += 1
32 elif l.startswith("-") and not l.startswith("--- "):
32 elif l.startswith("-") and not l.startswith("--- "):
33 removed += 1
33 removed += 1
34 return (added, removed)
34 return (added, removed)
35
35
36 def countrate(ui, repo, amap, *pats, **opts):
36 def countrate(ui, repo, amap, *pats, **opts):
37 """Calculate stats"""
37 """Calculate stats"""
38 if opts.get('dateformat'):
38 if opts.get('dateformat'):
39 def getkey(ctx):
39 def getkey(ctx):
40 t, tz = ctx.date()
40 t, tz = ctx.date()
41 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
41 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
42 return date.strftime(opts['dateformat'])
42 return date.strftime(opts['dateformat'])
43 else:
43 else:
44 tmpl = opts.get('template', '{author|email}')
44 tmpl = opts.get('template', '{author|email}')
45 tmpl = maketemplater(ui, repo, tmpl)
45 tmpl = maketemplater(ui, repo, tmpl)
46 def getkey(ctx):
46 def getkey(ctx):
47 ui.pushbuffer()
47 ui.pushbuffer()
48 tmpl.show(ctx)
48 tmpl.show(ctx)
49 return ui.popbuffer()
49 return ui.popbuffer()
50
50
51 state = {'count': 0}
51 state = {'count': 0}
52 rate = {}
52 rate = {}
53 df = False
53 df = False
54 if opts.get('date'):
54 if opts.get('date'):
55 df = util.matchdate(opts['date'])
55 df = util.matchdate(opts['date'])
56
56
57 m = cmdutil.match(repo, pats, opts)
57 m = cmdutil.match(repo, pats, opts)
58 def prep(ctx, fns):
58 def prep(ctx, fns):
59 rev = ctx.rev()
59 rev = ctx.rev()
60 if df and not df(ctx.date()[0]): # doesn't match date format
60 if df and not df(ctx.date()[0]): # doesn't match date format
61 return
61 return
62
62
63 key = getkey(ctx)
63 key = getkey(ctx)
64 key = amap.get(key, key) # alias remap
64 key = amap.get(key, key) # alias remap
65 if opts.get('changesets'):
65 if opts.get('changesets'):
66 rate[key] = (rate.get(key, (0,))[0] + 1, 0)
66 rate[key] = (rate.get(key, (0,))[0] + 1, 0)
67 else:
67 else:
68 parents = ctx.parents()
68 parents = ctx.parents()
69 if len(parents) > 1:
69 if len(parents) > 1:
70 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
70 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
71 return
71 return
72
72
73 ctx1 = parents[0]
73 ctx1 = parents[0]
74 lines = changedlines(ui, repo, ctx1, ctx, fns)
74 lines = changedlines(ui, repo, ctx1, ctx, fns)
75 rate[key] = [r + l for r, l in zip(rate.get(key, (0, 0)), lines)]
75 rate[key] = [r + l for r, l in zip(rate.get(key, (0, 0)), lines)]
76
76
77 state['count'] += 1
77 state['count'] += 1
78 ui.progress(_('analyzing'), state['count'], total=len(repo))
78 ui.progress(_('analyzing'), state['count'], total=len(repo))
79
79
80 for ctx in cmdutil.walkchangerevs(repo, m, opts, prep):
80 for ctx in cmdutil.walkchangerevs(repo, m, opts, prep):
81 continue
81 continue
82
82
83 ui.progress(_('analyzing'), None)
83 ui.progress(_('analyzing'), None)
84
84
85 return rate
85 return rate
86
86
87
87
88 def churn(ui, repo, *pats, **opts):
88 def churn(ui, repo, *pats, **opts):
89 '''histogram of changes to the repository
89 '''histogram of changes to the repository
90
90
91 This command will display a histogram representing the number
91 This command will display a histogram representing the number
92 of changed lines or revisions, grouped according to the given
92 of changed lines or revisions, grouped according to the given
93 template. The default template will group changes by author.
93 template. The default template will group changes by author.
94 The --dateformat option may be used to group the results by
94 The --dateformat option may be used to group the results by
95 date instead.
95 date instead.
96
96
97 Statistics are based on the number of changed lines, or
97 Statistics are based on the number of changed lines, or
98 alternatively the number of matching revisions if the
98 alternatively the number of matching revisions if the
99 --changesets option is specified.
99 --changesets option is specified.
100
100
101 Examples::
101 Examples::
102
102
103 # display count of changed lines for every committer
103 # display count of changed lines for every committer
104 hg churn -t '{author|email}'
104 hg churn -t '{author|email}'
105
105
106 # display daily activity graph
106 # display daily activity graph
107 hg churn -f '%H' -s -c
107 hg churn -f '%H' -s -c
108
108
109 # display activity of developers by month
109 # display activity of developers by month
110 hg churn -f '%Y-%m' -s -c
110 hg churn -f '%Y-%m' -s -c
111
111
112 # display count of lines changed in every year
112 # display count of lines changed in every year
113 hg churn -f '%Y' -s
113 hg churn -f '%Y' -s
114
114
115 It is possible to map alternate email addresses to a main address
115 It is possible to map alternate email addresses to a main address
116 by providing a file using the following format::
116 by providing a file using the following format::
117
117
118 <alias email> = <actual email>
118 <alias email> = <actual email>
119
119
120 Such a file may be specified with the --aliases option, otherwise
120 Such a file may be specified with the --aliases option, otherwise
121 a .hgchurn file will be looked for in the working directory root.
121 a .hgchurn file will be looked for in the working directory root.
122 '''
122 '''
123 def pad(s, l):
123 def pad(s, l):
124 return (s + " " * l)[:l]
124 return (s + " " * l)[:l]
125
125
126 amap = {}
126 amap = {}
127 aliases = opts.get('aliases')
127 aliases = opts.get('aliases')
128 if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
128 if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
129 aliases = repo.wjoin('.hgchurn')
129 aliases = repo.wjoin('.hgchurn')
130 if aliases:
130 if aliases:
131 for l in open(aliases, "r"):
131 for l in open(aliases, "r"):
132 alias, actual = l.split('=' in l and '=' or None, 1)
132 alias, actual = l.split('=' in l and '=' or None, 1)
133 amap[alias.strip()] = actual.strip()
133 amap[alias.strip()] = actual.strip()
134
134
135 rate = countrate(ui, repo, amap, *pats, **opts).items()
135 rate = countrate(ui, repo, amap, *pats, **opts).items()
136 if not rate:
136 if not rate:
137 return
137 return
138
138
139 sortkey = ((not opts.get('sort')) and (lambda x: -sum(x[1])) or None)
139 sortkey = ((not opts.get('sort')) and (lambda x: -sum(x[1])) or None)
140 rate.sort(key=sortkey)
140 rate.sort(key=sortkey)
141
141
142 # Be careful not to have a zero maxcount (issue833)
142 # Be careful not to have a zero maxcount (issue833)
143 maxcount = float(max(sum(v) for k, v in rate)) or 1.0
143 maxcount = float(max(sum(v) for k, v in rate)) or 1.0
144 maxname = max(len(k) for k, v in rate)
144 maxname = max(len(k) for k, v in rate)
145
145
146 ttywidth = util.termwidth()
146 ttywidth = util.termwidth()
147 ui.debug("assuming %i character terminal\n" % ttywidth)
147 ui.debug("assuming %i character terminal\n" % ttywidth)
148 width = ttywidth - maxname - 2 - 2 - 2
148 width = ttywidth - maxname - 2 - 2 - 2
149
149
150 if opts.get('diffstat'):
150 if opts.get('diffstat'):
151 width -= 15
151 width -= 15
152 def format(name, (added, removed)):
152 def format(name, (added, removed)):
153 return "%s %15s %s%s\n" % (pad(name, maxname),
153 return "%s %15s %s%s\n" % (pad(name, maxname),
154 '+%d/-%d' % (added, removed),
154 '+%d/-%d' % (added, removed),
155 ui.label('+' * charnum(added),
155 ui.label('+' * charnum(added),
156 'diffstat.inserted'),
156 'diffstat.inserted'),
157 ui.label('-' * charnum(removed),
157 ui.label('-' * charnum(removed),
158 'diffstat.deleted'))
158 'diffstat.deleted'))
159 else:
159 else:
160 width -= 6
160 width -= 6
161 def format(name, count):
161 def format(name, count):
162 return "%s %6d %s\n" % (pad(name, maxname), sum(count),
162 return "%s %6d %s\n" % (pad(name, maxname), sum(count),
163 '*' * charnum(sum(count)))
163 '*' * charnum(sum(count)))
164
164
165 def charnum(count):
165 def charnum(count):
166 return int(round(count * width / maxcount))
166 return int(round(count * width / maxcount))
167
167
168 for name, count in rate:
168 for name, count in rate:
169 ui.write(format(name, count))
169 ui.write(format(name, count))
170
170
171
171
172 cmdtable = {
172 cmdtable = {
173 "churn":
173 "churn":
174 (churn,
174 (churn,
175 [('r', 'rev', [], _('count rate for the specified revision or range')),
175 [('r', 'rev', [],
176 ('d', 'date', '', _('count rate for revisions matching date spec')),
176 _('count rate for the specified revision or range'), _('REV')),
177 ('d', 'date', '',
178 _('count rate for revisions matching date spec'), _('DATE')),
177 ('t', 'template', '{author|email}',
179 ('t', 'template', '{author|email}',
178 _('template to group changesets')),
180 _('template to group changesets'), _('TEMPLATE')),
179 ('f', 'dateformat', '',
181 ('f', 'dateformat', '',
180 _('strftime-compatible format for grouping by date')),
182 _('strftime-compatible format for grouping by date'), _('FORMAT')),
181 ('c', 'changesets', False, _('count rate by number of changesets')),
183 ('c', 'changesets', False, _('count rate by number of changesets')),
182 ('s', 'sort', False, _('sort by key (default: sort by count)')),
184 ('s', 'sort', False, _('sort by key (default: sort by count)')),
183 ('', 'diffstat', False, _('display added/removed lines separately')),
185 ('', 'diffstat', False, _('display added/removed lines separately')),
184 ('', 'aliases', '', _('file with email aliases')),
186 ('', 'aliases', '',
187 _('file with email aliases'), _('FILE')),
185 ] + commands.walkopts,
188 ] + commands.walkopts,
186 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]")),
189 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]")),
187 }
190 }
@@ -1,291 +1,292 b''
1 # color.py color output for the status and qseries commands
1 # color.py color output for the status and qseries commands
2 #
2 #
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
4 #
4 #
5 # This program is free software; you can redistribute it and/or modify it
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation; either version 2 of the License, or (at your
7 # Free Software Foundation; either version 2 of the License, or (at your
8 # option) any later version.
8 # option) any later version.
9 #
9 #
10 # This program is distributed in the hope that it will be useful, but
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 # Public License for more details.
13 # Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License along
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
18
19 '''colorize output from some commands
19 '''colorize output from some commands
20
20
21 This extension modifies the status and resolve commands to add color to their
21 This extension modifies the status and resolve commands to add color to their
22 output to reflect file status, the qseries command to add color to reflect
22 output to reflect file status, the qseries command to add color to reflect
23 patch status (applied, unapplied, missing), and to diff-related
23 patch status (applied, unapplied, missing), and to diff-related
24 commands to highlight additions, removals, diff headers, and trailing
24 commands to highlight additions, removals, diff headers, and trailing
25 whitespace.
25 whitespace.
26
26
27 Other effects in addition to color, like bold and underlined text, are
27 Other effects in addition to color, like bold and underlined text, are
28 also available. Effects are rendered with the ECMA-48 SGR control
28 also available. Effects are rendered with the ECMA-48 SGR control
29 function (aka ANSI escape codes). This module also provides the
29 function (aka ANSI escape codes). This module also provides the
30 render_text function, which can be used to add effects to any text.
30 render_text function, which can be used to add effects to any text.
31
31
32 Default effects may be overridden from the .hgrc file::
32 Default effects may be overridden from the .hgrc file::
33
33
34 [color]
34 [color]
35 status.modified = blue bold underline red_background
35 status.modified = blue bold underline red_background
36 status.added = green bold
36 status.added = green bold
37 status.removed = red bold blue_background
37 status.removed = red bold blue_background
38 status.deleted = cyan bold underline
38 status.deleted = cyan bold underline
39 status.unknown = magenta bold underline
39 status.unknown = magenta bold underline
40 status.ignored = black bold
40 status.ignored = black bold
41
41
42 # 'none' turns off all effects
42 # 'none' turns off all effects
43 status.clean = none
43 status.clean = none
44 status.copied = none
44 status.copied = none
45
45
46 qseries.applied = blue bold underline
46 qseries.applied = blue bold underline
47 qseries.unapplied = black bold
47 qseries.unapplied = black bold
48 qseries.missing = red bold
48 qseries.missing = red bold
49
49
50 diff.diffline = bold
50 diff.diffline = bold
51 diff.extended = cyan bold
51 diff.extended = cyan bold
52 diff.file_a = red bold
52 diff.file_a = red bold
53 diff.file_b = green bold
53 diff.file_b = green bold
54 diff.hunk = magenta
54 diff.hunk = magenta
55 diff.deleted = red
55 diff.deleted = red
56 diff.inserted = green
56 diff.inserted = green
57 diff.changed = white
57 diff.changed = white
58 diff.trailingwhitespace = bold red_background
58 diff.trailingwhitespace = bold red_background
59
59
60 resolve.unresolved = red bold
60 resolve.unresolved = red bold
61 resolve.resolved = green bold
61 resolve.resolved = green bold
62
62
63 bookmarks.current = green
63 bookmarks.current = green
64
64
65 The color extension will try to detect whether to use ANSI codes or
65 The color extension will try to detect whether to use ANSI codes or
66 Win32 console APIs, unless it is made explicit::
66 Win32 console APIs, unless it is made explicit::
67
67
68 [color]
68 [color]
69 mode = ansi
69 mode = ansi
70
70
71 Any value other than 'ansi', 'win32', or 'auto' will disable color.
71 Any value other than 'ansi', 'win32', or 'auto' will disable color.
72
72
73 '''
73 '''
74
74
75 import os, sys
75 import os, sys
76
76
77 from mercurial import commands, dispatch, extensions
77 from mercurial import commands, dispatch, extensions
78 from mercurial.i18n import _
78 from mercurial.i18n import _
79
79
80 # start and stop parameters for effects
80 # start and stop parameters for effects
81 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
81 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
82 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
82 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
83 'italic': 3, 'underline': 4, 'inverse': 7,
83 'italic': 3, 'underline': 4, 'inverse': 7,
84 'black_background': 40, 'red_background': 41,
84 'black_background': 40, 'red_background': 41,
85 'green_background': 42, 'yellow_background': 43,
85 'green_background': 42, 'yellow_background': 43,
86 'blue_background': 44, 'purple_background': 45,
86 'blue_background': 44, 'purple_background': 45,
87 'cyan_background': 46, 'white_background': 47}
87 'cyan_background': 46, 'white_background': 47}
88
88
89 _styles = {'grep.match': 'red bold',
89 _styles = {'grep.match': 'red bold',
90 'diff.changed': 'white',
90 'diff.changed': 'white',
91 'diff.deleted': 'red',
91 'diff.deleted': 'red',
92 'diff.diffline': 'bold',
92 'diff.diffline': 'bold',
93 'diff.extended': 'cyan bold',
93 'diff.extended': 'cyan bold',
94 'diff.file_a': 'red bold',
94 'diff.file_a': 'red bold',
95 'diff.file_b': 'green bold',
95 'diff.file_b': 'green bold',
96 'diff.hunk': 'magenta',
96 'diff.hunk': 'magenta',
97 'diff.inserted': 'green',
97 'diff.inserted': 'green',
98 'diff.trailingwhitespace': 'bold red_background',
98 'diff.trailingwhitespace': 'bold red_background',
99 'diffstat.deleted': 'red',
99 'diffstat.deleted': 'red',
100 'diffstat.inserted': 'green',
100 'diffstat.inserted': 'green',
101 'log.changeset': 'yellow',
101 'log.changeset': 'yellow',
102 'resolve.resolved': 'green bold',
102 'resolve.resolved': 'green bold',
103 'resolve.unresolved': 'red bold',
103 'resolve.unresolved': 'red bold',
104 'status.added': 'green bold',
104 'status.added': 'green bold',
105 'status.clean': 'none',
105 'status.clean': 'none',
106 'status.copied': 'none',
106 'status.copied': 'none',
107 'status.deleted': 'cyan bold underline',
107 'status.deleted': 'cyan bold underline',
108 'status.ignored': 'black bold',
108 'status.ignored': 'black bold',
109 'status.modified': 'blue bold',
109 'status.modified': 'blue bold',
110 'status.removed': 'red bold',
110 'status.removed': 'red bold',
111 'status.unknown': 'magenta bold underline'}
111 'status.unknown': 'magenta bold underline'}
112
112
113
113
114 def render_effects(text, effects):
114 def render_effects(text, effects):
115 'Wrap text in commands to turn on each effect.'
115 'Wrap text in commands to turn on each effect.'
116 if not text:
116 if not text:
117 return text
117 return text
118 start = [str(_effects[e]) for e in ['none'] + effects.split()]
118 start = [str(_effects[e]) for e in ['none'] + effects.split()]
119 start = '\033[' + ';'.join(start) + 'm'
119 start = '\033[' + ';'.join(start) + 'm'
120 stop = '\033[' + str(_effects['none']) + 'm'
120 stop = '\033[' + str(_effects['none']) + 'm'
121 return ''.join([start, text, stop])
121 return ''.join([start, text, stop])
122
122
123 def extstyles():
123 def extstyles():
124 for name, ext in extensions.extensions():
124 for name, ext in extensions.extensions():
125 _styles.update(getattr(ext, 'colortable', {}))
125 _styles.update(getattr(ext, 'colortable', {}))
126
126
127 def configstyles(ui):
127 def configstyles(ui):
128 for status, cfgeffects in ui.configitems('color'):
128 for status, cfgeffects in ui.configitems('color'):
129 if '.' not in status:
129 if '.' not in status:
130 continue
130 continue
131 cfgeffects = ui.configlist('color', status)
131 cfgeffects = ui.configlist('color', status)
132 if cfgeffects:
132 if cfgeffects:
133 good = []
133 good = []
134 for e in cfgeffects:
134 for e in cfgeffects:
135 if e in _effects:
135 if e in _effects:
136 good.append(e)
136 good.append(e)
137 else:
137 else:
138 ui.warn(_("ignoring unknown color/effect %r "
138 ui.warn(_("ignoring unknown color/effect %r "
139 "(configured in color.%s)\n")
139 "(configured in color.%s)\n")
140 % (e, status))
140 % (e, status))
141 _styles[status] = ' '.join(good)
141 _styles[status] = ' '.join(good)
142
142
143 _buffers = None
143 _buffers = None
144 def style(msg, label):
144 def style(msg, label):
145 effects = []
145 effects = []
146 for l in label.split():
146 for l in label.split():
147 s = _styles.get(l, '')
147 s = _styles.get(l, '')
148 if s:
148 if s:
149 effects.append(s)
149 effects.append(s)
150 effects = ''.join(effects)
150 effects = ''.join(effects)
151 if effects:
151 if effects:
152 return '\n'.join([render_effects(s, effects)
152 return '\n'.join([render_effects(s, effects)
153 for s in msg.split('\n')])
153 for s in msg.split('\n')])
154 return msg
154 return msg
155
155
156 def popbuffer(orig, labeled=False):
156 def popbuffer(orig, labeled=False):
157 global _buffers
157 global _buffers
158 if labeled:
158 if labeled:
159 return ''.join(style(a, label) for a, label in _buffers.pop())
159 return ''.join(style(a, label) for a, label in _buffers.pop())
160 return ''.join(a for a, label in _buffers.pop())
160 return ''.join(a for a, label in _buffers.pop())
161
161
162 mode = 'ansi'
162 mode = 'ansi'
163 def write(orig, *args, **opts):
163 def write(orig, *args, **opts):
164 label = opts.get('label', '')
164 label = opts.get('label', '')
165 global _buffers
165 global _buffers
166 if _buffers:
166 if _buffers:
167 _buffers[-1].extend([(str(a), label) for a in args])
167 _buffers[-1].extend([(str(a), label) for a in args])
168 elif mode == 'win32':
168 elif mode == 'win32':
169 for a in args:
169 for a in args:
170 win32print(a, orig, **opts)
170 win32print(a, orig, **opts)
171 else:
171 else:
172 return orig(*[style(str(a), label) for a in args], **opts)
172 return orig(*[style(str(a), label) for a in args], **opts)
173
173
174 def write_err(orig, *args, **opts):
174 def write_err(orig, *args, **opts):
175 label = opts.get('label', '')
175 label = opts.get('label', '')
176 if mode == 'win32':
176 if mode == 'win32':
177 for a in args:
177 for a in args:
178 win32print(a, orig, **opts)
178 win32print(a, orig, **opts)
179 else:
179 else:
180 return orig(*[style(str(a), label) for a in args], **opts)
180 return orig(*[style(str(a), label) for a in args], **opts)
181
181
182 def uisetup(ui):
182 def uisetup(ui):
183 if ui.plain():
183 if ui.plain():
184 return
184 return
185 global mode
185 global mode
186 mode = ui.config('color', 'mode', 'auto')
186 mode = ui.config('color', 'mode', 'auto')
187 if mode == 'auto':
187 if mode == 'auto':
188 if os.name == 'nt' and 'TERM' not in os.environ:
188 if os.name == 'nt' and 'TERM' not in os.environ:
189 # looks line a cmd.exe console, use win32 API or nothing
189 # looks line a cmd.exe console, use win32 API or nothing
190 mode = w32effects and 'win32' or 'none'
190 mode = w32effects and 'win32' or 'none'
191 else:
191 else:
192 mode = 'ansi'
192 mode = 'ansi'
193 if mode == 'win32':
193 if mode == 'win32':
194 if w32effects is None:
194 if w32effects is None:
195 # only warn if color.mode is explicitly set to win32
195 # only warn if color.mode is explicitly set to win32
196 ui.warn(_('win32console not found, please install pywin32\n'))
196 ui.warn(_('win32console not found, please install pywin32\n'))
197 return
197 return
198 _effects.update(w32effects)
198 _effects.update(w32effects)
199 elif mode != 'ansi':
199 elif mode != 'ansi':
200 return
200 return
201
201
202 # check isatty() before anything else changes it (like pager)
202 # check isatty() before anything else changes it (like pager)
203 isatty = sys.__stdout__.isatty()
203 isatty = sys.__stdout__.isatty()
204
204
205 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
205 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
206 if (opts['color'] == 'always' or
206 if (opts['color'] == 'always' or
207 (opts['color'] == 'auto' and (os.environ.get('TERM') != 'dumb'
207 (opts['color'] == 'auto' and (os.environ.get('TERM') != 'dumb'
208 and isatty))):
208 and isatty))):
209 global _buffers
209 global _buffers
210 _buffers = ui_._buffers
210 _buffers = ui_._buffers
211 extensions.wrapfunction(ui_, 'popbuffer', popbuffer)
211 extensions.wrapfunction(ui_, 'popbuffer', popbuffer)
212 extensions.wrapfunction(ui_, 'write', write)
212 extensions.wrapfunction(ui_, 'write', write)
213 extensions.wrapfunction(ui_, 'write_err', write_err)
213 extensions.wrapfunction(ui_, 'write_err', write_err)
214 ui_.label = style
214 ui_.label = style
215 extstyles()
215 extstyles()
216 configstyles(ui)
216 configstyles(ui)
217 return orig(ui_, opts, cmd, cmdfunc)
217 return orig(ui_, opts, cmd, cmdfunc)
218 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
218 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
219
219
220 commands.globalopts.append(('', 'color', 'auto',
220 commands.globalopts.append(('', 'color', 'auto',
221 _("when to colorize (always, auto, or never)")))
221 _("when to colorize (always, auto, or never)"),
222 _('TYPE')))
222
223
223 try:
224 try:
224 import re, pywintypes
225 import re, pywintypes
225 from win32console import *
226 from win32console import *
226
227
227 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
228 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
228 w32effects = {
229 w32effects = {
229 'none': 0,
230 'none': 0,
230 'black': 0,
231 'black': 0,
231 'red': FOREGROUND_RED,
232 'red': FOREGROUND_RED,
232 'green': FOREGROUND_GREEN,
233 'green': FOREGROUND_GREEN,
233 'yellow': FOREGROUND_RED | FOREGROUND_GREEN,
234 'yellow': FOREGROUND_RED | FOREGROUND_GREEN,
234 'blue': FOREGROUND_BLUE,
235 'blue': FOREGROUND_BLUE,
235 'magenta': FOREGROUND_BLUE | FOREGROUND_RED,
236 'magenta': FOREGROUND_BLUE | FOREGROUND_RED,
236 'cyan': FOREGROUND_BLUE | FOREGROUND_GREEN,
237 'cyan': FOREGROUND_BLUE | FOREGROUND_GREEN,
237 'white': FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,
238 'white': FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE,
238 'bold': FOREGROUND_INTENSITY,
239 'bold': FOREGROUND_INTENSITY,
239 'black_background': 0,
240 'black_background': 0,
240 'red_background': BACKGROUND_RED,
241 'red_background': BACKGROUND_RED,
241 'green_background': BACKGROUND_GREEN,
242 'green_background': BACKGROUND_GREEN,
242 'yellow_background': BACKGROUND_RED | BACKGROUND_GREEN,
243 'yellow_background': BACKGROUND_RED | BACKGROUND_GREEN,
243 'blue_background': BACKGROUND_BLUE,
244 'blue_background': BACKGROUND_BLUE,
244 'purple_background': BACKGROUND_BLUE | BACKGROUND_RED,
245 'purple_background': BACKGROUND_BLUE | BACKGROUND_RED,
245 'cyan_background': BACKGROUND_BLUE | BACKGROUND_GREEN,
246 'cyan_background': BACKGROUND_BLUE | BACKGROUND_GREEN,
246 'white_background': BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE,
247 'white_background': BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE,
247 'bold_background': BACKGROUND_INTENSITY,
248 'bold_background': BACKGROUND_INTENSITY,
248 'underline': COMMON_LVB_UNDERSCORE, # double-byte charsets only
249 'underline': COMMON_LVB_UNDERSCORE, # double-byte charsets only
249 'inverse': COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
250 'inverse': COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
250 }
251 }
251
252
252 stdout = GetStdHandle(STD_OUTPUT_HANDLE)
253 stdout = GetStdHandle(STD_OUTPUT_HANDLE)
253 try:
254 try:
254 origattr = stdout.GetConsoleScreenBufferInfo()['Attributes']
255 origattr = stdout.GetConsoleScreenBufferInfo()['Attributes']
255 except pywintypes.error:
256 except pywintypes.error:
256 # stdout may be defined but not support
257 # stdout may be defined but not support
257 # GetConsoleScreenBufferInfo(), when called from subprocess or
258 # GetConsoleScreenBufferInfo(), when called from subprocess or
258 # redirected.
259 # redirected.
259 raise ImportError()
260 raise ImportError()
260 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)', re.MULTILINE | re.DOTALL)
261 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)', re.MULTILINE | re.DOTALL)
261
262
262 def win32print(text, orig, **opts):
263 def win32print(text, orig, **opts):
263 label = opts.get('label', '')
264 label = opts.get('label', '')
264 attr = 0
265 attr = 0
265
266
266 # determine console attributes based on labels
267 # determine console attributes based on labels
267 for l in label.split():
268 for l in label.split():
268 style = _styles.get(l, '')
269 style = _styles.get(l, '')
269 for effect in style.split():
270 for effect in style.split():
270 attr |= w32effects[effect]
271 attr |= w32effects[effect]
271
272
272 # hack to ensure regexp finds data
273 # hack to ensure regexp finds data
273 if not text.startswith('\033['):
274 if not text.startswith('\033['):
274 text = '\033[m' + text
275 text = '\033[m' + text
275
276
276 # Look for ANSI-like codes embedded in text
277 # Look for ANSI-like codes embedded in text
277 m = re.match(ansire, text)
278 m = re.match(ansire, text)
278 while m:
279 while m:
279 for sattr in m.group(1).split(';'):
280 for sattr in m.group(1).split(';'):
280 if sattr:
281 if sattr:
281 val = int(sattr)
282 val = int(sattr)
282 attr = val and attr|val or 0
283 attr = val and attr|val or 0
283 stdout.SetConsoleTextAttribute(attr or origattr)
284 stdout.SetConsoleTextAttribute(attr or origattr)
284 orig(m.group(2), **opts)
285 orig(m.group(2), **opts)
285 m = re.match(ansire, m.group(3))
286 m = re.match(ansire, m.group(3))
286
287
287 # Explicity reset original attributes
288 # Explicity reset original attributes
288 stdout.SetConsoleTextAttribute(origattr)
289 stdout.SetConsoleTextAttribute(origattr)
289
290
290 except ImportError:
291 except ImportError:
291 w32effects = None
292 w32effects = None
@@ -1,295 +1,302 b''
1 # convert.py Foreign SCM converter
1 # convert.py Foreign SCM converter
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''import revisions from foreign VCS repositories into Mercurial'''
8 '''import revisions from foreign VCS repositories into Mercurial'''
9
9
10 import convcmd
10 import convcmd
11 import cvsps
11 import cvsps
12 import subversion
12 import subversion
13 from mercurial import commands
13 from mercurial import commands
14 from mercurial.i18n import _
14 from mercurial.i18n import _
15
15
16 # Commands definition was moved elsewhere to ease demandload job.
16 # Commands definition was moved elsewhere to ease demandload job.
17
17
18 def convert(ui, src, dest=None, revmapfile=None, **opts):
18 def convert(ui, src, dest=None, revmapfile=None, **opts):
19 """convert a foreign SCM repository to a Mercurial one.
19 """convert a foreign SCM repository to a Mercurial one.
20
20
21 Accepted source formats [identifiers]:
21 Accepted source formats [identifiers]:
22
22
23 - Mercurial [hg]
23 - Mercurial [hg]
24 - CVS [cvs]
24 - CVS [cvs]
25 - Darcs [darcs]
25 - Darcs [darcs]
26 - git [git]
26 - git [git]
27 - Subversion [svn]
27 - Subversion [svn]
28 - Monotone [mtn]
28 - Monotone [mtn]
29 - GNU Arch [gnuarch]
29 - GNU Arch [gnuarch]
30 - Bazaar [bzr]
30 - Bazaar [bzr]
31 - Perforce [p4]
31 - Perforce [p4]
32
32
33 Accepted destination formats [identifiers]:
33 Accepted destination formats [identifiers]:
34
34
35 - Mercurial [hg]
35 - Mercurial [hg]
36 - Subversion [svn] (history on branches is not preserved)
36 - Subversion [svn] (history on branches is not preserved)
37
37
38 If no revision is given, all revisions will be converted.
38 If no revision is given, all revisions will be converted.
39 Otherwise, convert will only import up to the named revision
39 Otherwise, convert will only import up to the named revision
40 (given in a format understood by the source).
40 (given in a format understood by the source).
41
41
42 If no destination directory name is specified, it defaults to the
42 If no destination directory name is specified, it defaults to the
43 basename of the source with '-hg' appended. If the destination
43 basename of the source with '-hg' appended. If the destination
44 repository doesn't exist, it will be created.
44 repository doesn't exist, it will be created.
45
45
46 By default, all sources except Mercurial will use --branchsort.
46 By default, all sources except Mercurial will use --branchsort.
47 Mercurial uses --sourcesort to preserve original revision numbers
47 Mercurial uses --sourcesort to preserve original revision numbers
48 order. Sort modes have the following effects:
48 order. Sort modes have the following effects:
49
49
50 --branchsort convert from parent to child revision when possible,
50 --branchsort convert from parent to child revision when possible,
51 which means branches are usually converted one after
51 which means branches are usually converted one after
52 the other. It generates more compact repositories.
52 the other. It generates more compact repositories.
53
53
54 --datesort sort revisions by date. Converted repositories have
54 --datesort sort revisions by date. Converted repositories have
55 good-looking changelogs but are often an order of
55 good-looking changelogs but are often an order of
56 magnitude larger than the same ones generated by
56 magnitude larger than the same ones generated by
57 --branchsort.
57 --branchsort.
58
58
59 --sourcesort try to preserve source revisions order, only
59 --sourcesort try to preserve source revisions order, only
60 supported by Mercurial sources.
60 supported by Mercurial sources.
61
61
62 If <REVMAP> isn't given, it will be put in a default location
62 If <REVMAP> isn't given, it will be put in a default location
63 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file
63 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file
64 that maps each source commit ID to the destination ID for that
64 that maps each source commit ID to the destination ID for that
65 revision, like so::
65 revision, like so::
66
66
67 <source ID> <destination ID>
67 <source ID> <destination ID>
68
68
69 If the file doesn't exist, it's automatically created. It's
69 If the file doesn't exist, it's automatically created. It's
70 updated on each commit copied, so convert-repo can be interrupted
70 updated on each commit copied, so convert-repo can be interrupted
71 and can be run repeatedly to copy new commits.
71 and can be run repeatedly to copy new commits.
72
72
73 The [username mapping] file is a simple text file that maps each
73 The [username mapping] file is a simple text file that maps each
74 source commit author to a destination commit author. It is handy
74 source commit author to a destination commit author. It is handy
75 for source SCMs that use unix logins to identify authors (eg:
75 for source SCMs that use unix logins to identify authors (eg:
76 CVS). One line per author mapping and the line format is:
76 CVS). One line per author mapping and the line format is:
77 srcauthor=whatever string you want
77 srcauthor=whatever string you want
78
78
79 The filemap is a file that allows filtering and remapping of files
79 The filemap is a file that allows filtering and remapping of files
80 and directories. Comment lines start with '#'. Each line can
80 and directories. Comment lines start with '#'. Each line can
81 contain one of the following directives::
81 contain one of the following directives::
82
82
83 include path/to/file
83 include path/to/file
84
84
85 exclude path/to/file
85 exclude path/to/file
86
86
87 rename from/file to/file
87 rename from/file to/file
88
88
89 The 'include' directive causes a file, or all files under a
89 The 'include' directive causes a file, or all files under a
90 directory, to be included in the destination repository, and the
90 directory, to be included in the destination repository, and the
91 exclusion of all other files and directories not explicitly
91 exclusion of all other files and directories not explicitly
92 included. The 'exclude' directive causes files or directories to
92 included. The 'exclude' directive causes files or directories to
93 be omitted. The 'rename' directive renames a file or directory. To
93 be omitted. The 'rename' directive renames a file or directory. To
94 rename from a subdirectory into the root of the repository, use
94 rename from a subdirectory into the root of the repository, use
95 '.' as the path to rename to.
95 '.' as the path to rename to.
96
96
97 The splicemap is a file that allows insertion of synthetic
97 The splicemap is a file that allows insertion of synthetic
98 history, letting you specify the parents of a revision. This is
98 history, letting you specify the parents of a revision. This is
99 useful if you want to e.g. give a Subversion merge two parents, or
99 useful if you want to e.g. give a Subversion merge two parents, or
100 graft two disconnected series of history together. Each entry
100 graft two disconnected series of history together. Each entry
101 contains a key, followed by a space, followed by one or two
101 contains a key, followed by a space, followed by one or two
102 comma-separated values. The key is the revision ID in the source
102 comma-separated values. The key is the revision ID in the source
103 revision control system whose parents should be modified (same
103 revision control system whose parents should be modified (same
104 format as a key in .hg/shamap). The values are the revision IDs
104 format as a key in .hg/shamap). The values are the revision IDs
105 (in either the source or destination revision control system) that
105 (in either the source or destination revision control system) that
106 should be used as the new parents for that node. For example, if
106 should be used as the new parents for that node. For example, if
107 you have merged "release-1.0" into "trunk", then you should
107 you have merged "release-1.0" into "trunk", then you should
108 specify the revision on "trunk" as the first parent and the one on
108 specify the revision on "trunk" as the first parent and the one on
109 the "release-1.0" branch as the second.
109 the "release-1.0" branch as the second.
110
110
111 The branchmap is a file that allows you to rename a branch when it is
111 The branchmap is a file that allows you to rename a branch when it is
112 being brought in from whatever external repository. When used in
112 being brought in from whatever external repository. When used in
113 conjunction with a splicemap, it allows for a powerful combination
113 conjunction with a splicemap, it allows for a powerful combination
114 to help fix even the most badly mismanaged repositories and turn them
114 to help fix even the most badly mismanaged repositories and turn them
115 into nicely structured Mercurial repositories. The branchmap contains
115 into nicely structured Mercurial repositories. The branchmap contains
116 lines of the form "original_branch_name new_branch_name".
116 lines of the form "original_branch_name new_branch_name".
117 "original_branch_name" is the name of the branch in the source
117 "original_branch_name" is the name of the branch in the source
118 repository, and "new_branch_name" is the name of the branch is the
118 repository, and "new_branch_name" is the name of the branch is the
119 destination repository. This can be used to (for instance) move code
119 destination repository. This can be used to (for instance) move code
120 in one repository from "default" to a named branch.
120 in one repository from "default" to a named branch.
121
121
122 Mercurial Source
122 Mercurial Source
123 ----------------
123 ----------------
124
124
125 --config convert.hg.ignoreerrors=False (boolean)
125 --config convert.hg.ignoreerrors=False (boolean)
126 ignore integrity errors when reading. Use it to fix Mercurial
126 ignore integrity errors when reading. Use it to fix Mercurial
127 repositories with missing revlogs, by converting from and to
127 repositories with missing revlogs, by converting from and to
128 Mercurial.
128 Mercurial.
129 --config convert.hg.saverev=False (boolean)
129 --config convert.hg.saverev=False (boolean)
130 store original revision ID in changeset (forces target IDs to
130 store original revision ID in changeset (forces target IDs to
131 change)
131 change)
132 --config convert.hg.startrev=0 (hg revision identifier)
132 --config convert.hg.startrev=0 (hg revision identifier)
133 convert start revision and its descendants
133 convert start revision and its descendants
134
134
135 CVS Source
135 CVS Source
136 ----------
136 ----------
137
137
138 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
138 CVS source will use a sandbox (i.e. a checked-out copy) from CVS
139 to indicate the starting point of what will be converted. Direct
139 to indicate the starting point of what will be converted. Direct
140 access to the repository files is not needed, unless of course the
140 access to the repository files is not needed, unless of course the
141 repository is :local:. The conversion uses the top level directory
141 repository is :local:. The conversion uses the top level directory
142 in the sandbox to find the CVS repository, and then uses CVS rlog
142 in the sandbox to find the CVS repository, and then uses CVS rlog
143 commands to find files to convert. This means that unless a
143 commands to find files to convert. This means that unless a
144 filemap is given, all files under the starting directory will be
144 filemap is given, all files under the starting directory will be
145 converted, and that any directory reorganization in the CVS
145 converted, and that any directory reorganization in the CVS
146 sandbox is ignored.
146 sandbox is ignored.
147
147
148 The options shown are the defaults.
148 The options shown are the defaults.
149
149
150 --config convert.cvsps.cache=True (boolean)
150 --config convert.cvsps.cache=True (boolean)
151 Set to False to disable remote log caching, for testing and
151 Set to False to disable remote log caching, for testing and
152 debugging purposes.
152 debugging purposes.
153 --config convert.cvsps.fuzz=60 (integer)
153 --config convert.cvsps.fuzz=60 (integer)
154 Specify the maximum time (in seconds) that is allowed between
154 Specify the maximum time (in seconds) that is allowed between
155 commits with identical user and log message in a single
155 commits with identical user and log message in a single
156 changeset. When very large files were checked in as part of a
156 changeset. When very large files were checked in as part of a
157 changeset then the default may not be long enough.
157 changeset then the default may not be long enough.
158 --config convert.cvsps.mergeto='{{mergetobranch ([-\\w]+)}}'
158 --config convert.cvsps.mergeto='{{mergetobranch ([-\\w]+)}}'
159 Specify a regular expression to which commit log messages are
159 Specify a regular expression to which commit log messages are
160 matched. If a match occurs, then the conversion process will
160 matched. If a match occurs, then the conversion process will
161 insert a dummy revision merging the branch on which this log
161 insert a dummy revision merging the branch on which this log
162 message occurs to the branch indicated in the regex.
162 message occurs to the branch indicated in the regex.
163 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\\w]+)}}'
163 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\\w]+)}}'
164 Specify a regular expression to which commit log messages are
164 Specify a regular expression to which commit log messages are
165 matched. If a match occurs, then the conversion process will
165 matched. If a match occurs, then the conversion process will
166 add the most recent revision on the branch indicated in the
166 add the most recent revision on the branch indicated in the
167 regex as the second parent of the changeset.
167 regex as the second parent of the changeset.
168 --config hook.cvslog
168 --config hook.cvslog
169 Specify a Python function to be called at the end of gathering
169 Specify a Python function to be called at the end of gathering
170 the CVS log. The function is passed a list with the log entries,
170 the CVS log. The function is passed a list with the log entries,
171 and can modify the entries in-place, or add or delete them.
171 and can modify the entries in-place, or add or delete them.
172 --config hook.cvschangesets
172 --config hook.cvschangesets
173 Specify a Python function to be called after the changesets
173 Specify a Python function to be called after the changesets
174 are calculated from the the CVS log. The function is passed
174 are calculated from the the CVS log. The function is passed
175 a list with the changeset entries, and can modify the changesets
175 a list with the changeset entries, and can modify the changesets
176 in-place, or add or delete them.
176 in-place, or add or delete them.
177
177
178 An additional "debugcvsps" Mercurial command allows the builtin
178 An additional "debugcvsps" Mercurial command allows the builtin
179 changeset merging code to be run without doing a conversion. Its
179 changeset merging code to be run without doing a conversion. Its
180 parameters and output are similar to that of cvsps 2.1. Please see
180 parameters and output are similar to that of cvsps 2.1. Please see
181 the command help for more details.
181 the command help for more details.
182
182
183 Subversion Source
183 Subversion Source
184 -----------------
184 -----------------
185
185
186 Subversion source detects classical trunk/branches/tags layouts.
186 Subversion source detects classical trunk/branches/tags layouts.
187 By default, the supplied "svn://repo/path/" source URL is
187 By default, the supplied "svn://repo/path/" source URL is
188 converted as a single branch. If "svn://repo/path/trunk" exists it
188 converted as a single branch. If "svn://repo/path/trunk" exists it
189 replaces the default branch. If "svn://repo/path/branches" exists,
189 replaces the default branch. If "svn://repo/path/branches" exists,
190 its subdirectories are listed as possible branches. If
190 its subdirectories are listed as possible branches. If
191 "svn://repo/path/tags" exists, it is looked for tags referencing
191 "svn://repo/path/tags" exists, it is looked for tags referencing
192 converted branches. Default "trunk", "branches" and "tags" values
192 converted branches. Default "trunk", "branches" and "tags" values
193 can be overridden with following options. Set them to paths
193 can be overridden with following options. Set them to paths
194 relative to the source URL, or leave them blank to disable auto
194 relative to the source URL, or leave them blank to disable auto
195 detection.
195 detection.
196
196
197 --config convert.svn.branches=branches (directory name)
197 --config convert.svn.branches=branches (directory name)
198 specify the directory containing branches
198 specify the directory containing branches
199 --config convert.svn.tags=tags (directory name)
199 --config convert.svn.tags=tags (directory name)
200 specify the directory containing tags
200 specify the directory containing tags
201 --config convert.svn.trunk=trunk (directory name)
201 --config convert.svn.trunk=trunk (directory name)
202 specify the name of the trunk branch
202 specify the name of the trunk branch
203
203
204 Source history can be retrieved starting at a specific revision,
204 Source history can be retrieved starting at a specific revision,
205 instead of being integrally converted. Only single branch
205 instead of being integrally converted. Only single branch
206 conversions are supported.
206 conversions are supported.
207
207
208 --config convert.svn.startrev=0 (svn revision number)
208 --config convert.svn.startrev=0 (svn revision number)
209 specify start Subversion revision.
209 specify start Subversion revision.
210
210
211 Perforce Source
211 Perforce Source
212 ---------------
212 ---------------
213
213
214 The Perforce (P4) importer can be given a p4 depot path or a
214 The Perforce (P4) importer can be given a p4 depot path or a
215 client specification as source. It will convert all files in the
215 client specification as source. It will convert all files in the
216 source to a flat Mercurial repository, ignoring labels, branches
216 source to a flat Mercurial repository, ignoring labels, branches
217 and integrations. Note that when a depot path is given you then
217 and integrations. Note that when a depot path is given you then
218 usually should specify a target directory, because otherwise the
218 usually should specify a target directory, because otherwise the
219 target may be named ...-hg.
219 target may be named ...-hg.
220
220
221 It is possible to limit the amount of source history to be
221 It is possible to limit the amount of source history to be
222 converted by specifying an initial Perforce revision.
222 converted by specifying an initial Perforce revision.
223
223
224 --config convert.p4.startrev=0 (perforce changelist number)
224 --config convert.p4.startrev=0 (perforce changelist number)
225 specify initial Perforce revision.
225 specify initial Perforce revision.
226
226
227 Mercurial Destination
227 Mercurial Destination
228 ---------------------
228 ---------------------
229
229
230 --config convert.hg.clonebranches=False (boolean)
230 --config convert.hg.clonebranches=False (boolean)
231 dispatch source branches in separate clones.
231 dispatch source branches in separate clones.
232 --config convert.hg.tagsbranch=default (branch name)
232 --config convert.hg.tagsbranch=default (branch name)
233 tag revisions branch name
233 tag revisions branch name
234 --config convert.hg.usebranchnames=True (boolean)
234 --config convert.hg.usebranchnames=True (boolean)
235 preserve branch names
235 preserve branch names
236
236
237 """
237 """
238 return convcmd.convert(ui, src, dest, revmapfile, **opts)
238 return convcmd.convert(ui, src, dest, revmapfile, **opts)
239
239
240 def debugsvnlog(ui, **opts):
240 def debugsvnlog(ui, **opts):
241 return subversion.debugsvnlog(ui, **opts)
241 return subversion.debugsvnlog(ui, **opts)
242
242
243 def debugcvsps(ui, *args, **opts):
243 def debugcvsps(ui, *args, **opts):
244 '''create changeset information from CVS
244 '''create changeset information from CVS
245
245
246 This command is intended as a debugging tool for the CVS to
246 This command is intended as a debugging tool for the CVS to
247 Mercurial converter, and can be used as a direct replacement for
247 Mercurial converter, and can be used as a direct replacement for
248 cvsps.
248 cvsps.
249
249
250 Hg debugcvsps reads the CVS rlog for current directory (or any
250 Hg debugcvsps reads the CVS rlog for current directory (or any
251 named directory) in the CVS repository, and converts the log to a
251 named directory) in the CVS repository, and converts the log to a
252 series of changesets based on matching commit log entries and
252 series of changesets based on matching commit log entries and
253 dates.'''
253 dates.'''
254 return cvsps.debugcvsps(ui, *args, **opts)
254 return cvsps.debugcvsps(ui, *args, **opts)
255
255
256 commands.norepo += " convert debugsvnlog debugcvsps"
256 commands.norepo += " convert debugsvnlog debugcvsps"
257
257
258 cmdtable = {
258 cmdtable = {
259 "convert":
259 "convert":
260 (convert,
260 (convert,
261 [('A', 'authors', '', _('username mapping filename')),
261 [('A', 'authors', '',
262 ('d', 'dest-type', '', _('destination repository type')),
262 _('username mapping filename'), _('FILE')),
263 ('', 'filemap', '', _('remap file names using contents of file')),
263 ('d', 'dest-type', '',
264 ('r', 'rev', '', _('import up to target revision REV')),
264 _('destination repository type'), _('TYPE')),
265 ('s', 'source-type', '', _('source repository type')),
265 ('', 'filemap', '',
266 ('', 'splicemap', '', _('splice synthesized history into place')),
266 _('remap file names using contents of file'), _('FILE')),
267 ('', 'branchmap', '', _('change branch names while converting')),
267 ('r', 'rev', '',
268 _('import up to target revision REV'), _('REV')),
269 ('s', 'source-type', '',
270 _('source repository type'), _('TYPE')),
271 ('', 'splicemap', '',
272 _('splice synthesized history into place'), _('FILE')),
273 ('', 'branchmap', '',
274 _('change branch names while converting'), _('FILE')),
268 ('', 'branchsort', None, _('try to sort changesets by branches')),
275 ('', 'branchsort', None, _('try to sort changesets by branches')),
269 ('', 'datesort', None, _('try to sort changesets by date')),
276 ('', 'datesort', None, _('try to sort changesets by date')),
270 ('', 'sourcesort', None, _('preserve source changesets order'))],
277 ('', 'sourcesort', None, _('preserve source changesets order'))],
271 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]')),
278 _('hg convert [OPTION]... SOURCE [DEST [REVMAP]]')),
272 "debugsvnlog":
279 "debugsvnlog":
273 (debugsvnlog,
280 (debugsvnlog,
274 [],
281 [],
275 'hg debugsvnlog'),
282 'hg debugsvnlog'),
276 "debugcvsps":
283 "debugcvsps":
277 (debugcvsps,
284 (debugcvsps,
278 [
285 [
279 # Main options shared with cvsps-2.1
286 # Main options shared with cvsps-2.1
280 ('b', 'branches', [], _('only return changes on specified branches')),
287 ('b', 'branches', [], _('only return changes on specified branches')),
281 ('p', 'prefix', '', _('prefix to remove from file names')),
288 ('p', 'prefix', '', _('prefix to remove from file names')),
282 ('r', 'revisions', [],
289 ('r', 'revisions', [],
283 _('only return changes after or between specified tags')),
290 _('only return changes after or between specified tags')),
284 ('u', 'update-cache', None, _("update cvs log cache")),
291 ('u', 'update-cache', None, _("update cvs log cache")),
285 ('x', 'new-cache', None, _("create new cvs log cache")),
292 ('x', 'new-cache', None, _("create new cvs log cache")),
286 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
293 ('z', 'fuzz', 60, _('set commit time fuzz in seconds')),
287 ('', 'root', '', _('specify cvsroot')),
294 ('', 'root', '', _('specify cvsroot')),
288 # Options specific to builtin cvsps
295 # Options specific to builtin cvsps
289 ('', 'parents', '', _('show parent changesets')),
296 ('', 'parents', '', _('show parent changesets')),
290 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
297 ('', 'ancestors', '', _('show current changeset in ancestor branches')),
291 # Options that are ignored for compatibility with cvsps-2.1
298 # Options that are ignored for compatibility with cvsps-2.1
292 ('A', 'cvs-direct', None, _('ignored for compatibility')),
299 ('A', 'cvs-direct', None, _('ignored for compatibility')),
293 ],
300 ],
294 _('hg debugcvsps [OPTION]... [PATH]...')),
301 _('hg debugcvsps [OPTION]... [PATH]...')),
295 }
302 }
@@ -1,321 +1,325 b''
1 # extdiff.py - external diff program support for mercurial
1 # extdiff.py - external diff program support for mercurial
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to allow external programs to compare revisions
8 '''command to allow external programs to compare revisions
9
9
10 The extdiff Mercurial extension allows you to use external programs
10 The extdiff Mercurial extension allows you to use external programs
11 to compare revisions, or revision with working directory. The external
11 to compare revisions, or revision with working directory. The external
12 diff programs are called with a configurable set of options and two
12 diff programs are called with a configurable set of options and two
13 non-option arguments: paths to directories containing snapshots of
13 non-option arguments: paths to directories containing snapshots of
14 files to compare.
14 files to compare.
15
15
16 The extdiff extension also allows to configure new diff commands, so
16 The extdiff extension also allows to configure new diff commands, so
17 you do not need to type :hg:`extdiff -p kdiff3` always. ::
17 you do not need to type :hg:`extdiff -p kdiff3` always. ::
18
18
19 [extdiff]
19 [extdiff]
20 # add new command that runs GNU diff(1) in 'context diff' mode
20 # add new command that runs GNU diff(1) in 'context diff' mode
21 cdiff = gdiff -Nprc5
21 cdiff = gdiff -Nprc5
22 ## or the old way:
22 ## or the old way:
23 #cmd.cdiff = gdiff
23 #cmd.cdiff = gdiff
24 #opts.cdiff = -Nprc5
24 #opts.cdiff = -Nprc5
25
25
26 # add new command called vdiff, runs kdiff3
26 # add new command called vdiff, runs kdiff3
27 vdiff = kdiff3
27 vdiff = kdiff3
28
28
29 # add new command called meld, runs meld (no need to name twice)
29 # add new command called meld, runs meld (no need to name twice)
30 meld =
30 meld =
31
31
32 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
32 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
33 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
33 # (see http://www.vim.org/scripts/script.php?script_id=102) Non
34 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
34 # English user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
35 # your .vimrc
35 # your .vimrc
36 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
36 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
37
37
38 Tool arguments can include variables that are expanded at runtime::
38 Tool arguments can include variables that are expanded at runtime::
39
39
40 $parent1, $plabel1 - filename, descriptive label of first parent
40 $parent1, $plabel1 - filename, descriptive label of first parent
41 $child, $clabel - filename, descriptive label of child revision
41 $child, $clabel - filename, descriptive label of child revision
42 $parent2, $plabel2 - filename, descriptive label of second parent
42 $parent2, $plabel2 - filename, descriptive label of second parent
43 $parent is an alias for $parent1.
43 $parent is an alias for $parent1.
44
44
45 The extdiff extension will look in your [diff-tools] and [merge-tools]
45 The extdiff extension will look in your [diff-tools] and [merge-tools]
46 sections for diff tool arguments, when none are specified in [extdiff].
46 sections for diff tool arguments, when none are specified in [extdiff].
47
47
48 ::
48 ::
49
49
50 [extdiff]
50 [extdiff]
51 kdiff3 =
51 kdiff3 =
52
52
53 [diff-tools]
53 [diff-tools]
54 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
54 kdiff3.diffargs=--L1 '$plabel1' --L2 '$clabel' $parent $child
55
55
56 You can use -I/-X and list of file or directory names like normal
56 You can use -I/-X and list of file or directory names like normal
57 :hg:`diff` command. The extdiff extension makes snapshots of only
57 :hg:`diff` command. The extdiff extension makes snapshots of only
58 needed files, so running the external diff program will actually be
58 needed files, so running the external diff program will actually be
59 pretty fast (at least faster than having to compare the entire tree).
59 pretty fast (at least faster than having to compare the entire tree).
60 '''
60 '''
61
61
62 from mercurial.i18n import _
62 from mercurial.i18n import _
63 from mercurial.node import short, nullid
63 from mercurial.node import short, nullid
64 from mercurial import cmdutil, util, commands, encoding
64 from mercurial import cmdutil, util, commands, encoding
65 import os, shlex, shutil, tempfile, re
65 import os, shlex, shutil, tempfile, re
66
66
67 def snapshot(ui, repo, files, node, tmproot):
67 def snapshot(ui, repo, files, node, tmproot):
68 '''snapshot files as of some revision
68 '''snapshot files as of some revision
69 if not using snapshot, -I/-X does not work and recursive diff
69 if not using snapshot, -I/-X does not work and recursive diff
70 in tools like kdiff3 and meld displays too many files.'''
70 in tools like kdiff3 and meld displays too many files.'''
71 dirname = os.path.basename(repo.root)
71 dirname = os.path.basename(repo.root)
72 if dirname == "":
72 if dirname == "":
73 dirname = "root"
73 dirname = "root"
74 if node is not None:
74 if node is not None:
75 dirname = '%s.%s' % (dirname, short(node))
75 dirname = '%s.%s' % (dirname, short(node))
76 base = os.path.join(tmproot, dirname)
76 base = os.path.join(tmproot, dirname)
77 os.mkdir(base)
77 os.mkdir(base)
78 if node is not None:
78 if node is not None:
79 ui.note(_('making snapshot of %d files from rev %s\n') %
79 ui.note(_('making snapshot of %d files from rev %s\n') %
80 (len(files), short(node)))
80 (len(files), short(node)))
81 else:
81 else:
82 ui.note(_('making snapshot of %d files from working directory\n') %
82 ui.note(_('making snapshot of %d files from working directory\n') %
83 (len(files)))
83 (len(files)))
84 wopener = util.opener(base)
84 wopener = util.opener(base)
85 fns_and_mtime = []
85 fns_and_mtime = []
86 ctx = repo[node]
86 ctx = repo[node]
87 for fn in files:
87 for fn in files:
88 wfn = util.pconvert(fn)
88 wfn = util.pconvert(fn)
89 if not wfn in ctx:
89 if not wfn in ctx:
90 # File doesn't exist; could be a bogus modify
90 # File doesn't exist; could be a bogus modify
91 continue
91 continue
92 ui.note(' %s\n' % wfn)
92 ui.note(' %s\n' % wfn)
93 dest = os.path.join(base, wfn)
93 dest = os.path.join(base, wfn)
94 fctx = ctx[wfn]
94 fctx = ctx[wfn]
95 data = repo.wwritedata(wfn, fctx.data())
95 data = repo.wwritedata(wfn, fctx.data())
96 if 'l' in fctx.flags():
96 if 'l' in fctx.flags():
97 wopener.symlink(data, wfn)
97 wopener.symlink(data, wfn)
98 else:
98 else:
99 wopener(wfn, 'w').write(data)
99 wopener(wfn, 'w').write(data)
100 if 'x' in fctx.flags():
100 if 'x' in fctx.flags():
101 util.set_flags(dest, False, True)
101 util.set_flags(dest, False, True)
102 if node is None:
102 if node is None:
103 fns_and_mtime.append((dest, repo.wjoin(fn), os.path.getmtime(dest)))
103 fns_and_mtime.append((dest, repo.wjoin(fn), os.path.getmtime(dest)))
104 return dirname, fns_and_mtime
104 return dirname, fns_and_mtime
105
105
106 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
106 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
107 '''Do the actuall diff:
107 '''Do the actuall diff:
108
108
109 - copy to a temp structure if diffing 2 internal revisions
109 - copy to a temp structure if diffing 2 internal revisions
110 - copy to a temp structure if diffing working revision with
110 - copy to a temp structure if diffing working revision with
111 another one and more than 1 file is changed
111 another one and more than 1 file is changed
112 - just invoke the diff for a single file in the working dir
112 - just invoke the diff for a single file in the working dir
113 '''
113 '''
114
114
115 revs = opts.get('rev')
115 revs = opts.get('rev')
116 change = opts.get('change')
116 change = opts.get('change')
117 args = ' '.join(diffopts)
117 args = ' '.join(diffopts)
118 do3way = '$parent2' in args
118 do3way = '$parent2' in args
119
119
120 if revs and change:
120 if revs and change:
121 msg = _('cannot specify --rev and --change at the same time')
121 msg = _('cannot specify --rev and --change at the same time')
122 raise util.Abort(msg)
122 raise util.Abort(msg)
123 elif change:
123 elif change:
124 node2 = repo.lookup(change)
124 node2 = repo.lookup(change)
125 node1a, node1b = repo.changelog.parents(node2)
125 node1a, node1b = repo.changelog.parents(node2)
126 else:
126 else:
127 node1a, node2 = cmdutil.revpair(repo, revs)
127 node1a, node2 = cmdutil.revpair(repo, revs)
128 if not revs:
128 if not revs:
129 node1b = repo.dirstate.parents()[1]
129 node1b = repo.dirstate.parents()[1]
130 else:
130 else:
131 node1b = nullid
131 node1b = nullid
132
132
133 # Disable 3-way merge if there is only one parent
133 # Disable 3-way merge if there is only one parent
134 if do3way:
134 if do3way:
135 if node1b == nullid:
135 if node1b == nullid:
136 do3way = False
136 do3way = False
137
137
138 matcher = cmdutil.match(repo, pats, opts)
138 matcher = cmdutil.match(repo, pats, opts)
139 mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3])
139 mod_a, add_a, rem_a = map(set, repo.status(node1a, node2, matcher)[:3])
140 if do3way:
140 if do3way:
141 mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3])
141 mod_b, add_b, rem_b = map(set, repo.status(node1b, node2, matcher)[:3])
142 else:
142 else:
143 mod_b, add_b, rem_b = set(), set(), set()
143 mod_b, add_b, rem_b = set(), set(), set()
144 modadd = mod_a | add_a | mod_b | add_b
144 modadd = mod_a | add_a | mod_b | add_b
145 common = modadd | rem_a | rem_b
145 common = modadd | rem_a | rem_b
146 if not common:
146 if not common:
147 return 0
147 return 0
148
148
149 tmproot = tempfile.mkdtemp(prefix='extdiff.')
149 tmproot = tempfile.mkdtemp(prefix='extdiff.')
150 try:
150 try:
151 # Always make a copy of node1a (and node1b, if applicable)
151 # Always make a copy of node1a (and node1b, if applicable)
152 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
152 dir1a_files = mod_a | rem_a | ((mod_b | add_b) - add_a)
153 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0]
153 dir1a = snapshot(ui, repo, dir1a_files, node1a, tmproot)[0]
154 rev1a = '@%d' % repo[node1a].rev()
154 rev1a = '@%d' % repo[node1a].rev()
155 if do3way:
155 if do3way:
156 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
156 dir1b_files = mod_b | rem_b | ((mod_a | add_a) - add_b)
157 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0]
157 dir1b = snapshot(ui, repo, dir1b_files, node1b, tmproot)[0]
158 rev1b = '@%d' % repo[node1b].rev()
158 rev1b = '@%d' % repo[node1b].rev()
159 else:
159 else:
160 dir1b = None
160 dir1b = None
161 rev1b = ''
161 rev1b = ''
162
162
163 fns_and_mtime = []
163 fns_and_mtime = []
164
164
165 # If node2 in not the wc or there is >1 change, copy it
165 # If node2 in not the wc or there is >1 change, copy it
166 dir2root = ''
166 dir2root = ''
167 rev2 = ''
167 rev2 = ''
168 if node2:
168 if node2:
169 dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0]
169 dir2 = snapshot(ui, repo, modadd, node2, tmproot)[0]
170 rev2 = '@%d' % repo[node2].rev()
170 rev2 = '@%d' % repo[node2].rev()
171 elif len(common) > 1:
171 elif len(common) > 1:
172 #we only actually need to get the files to copy back to
172 #we only actually need to get the files to copy back to
173 #the working dir in this case (because the other cases
173 #the working dir in this case (because the other cases
174 #are: diffing 2 revisions or single file -- in which case
174 #are: diffing 2 revisions or single file -- in which case
175 #the file is already directly passed to the diff tool).
175 #the file is already directly passed to the diff tool).
176 dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot)
176 dir2, fns_and_mtime = snapshot(ui, repo, modadd, None, tmproot)
177 else:
177 else:
178 # This lets the diff tool open the changed file directly
178 # This lets the diff tool open the changed file directly
179 dir2 = ''
179 dir2 = ''
180 dir2root = repo.root
180 dir2root = repo.root
181
181
182 label1a = rev1a
182 label1a = rev1a
183 label1b = rev1b
183 label1b = rev1b
184 label2 = rev2
184 label2 = rev2
185
185
186 # If only one change, diff the files instead of the directories
186 # If only one change, diff the files instead of the directories
187 # Handle bogus modifies correctly by checking if the files exist
187 # Handle bogus modifies correctly by checking if the files exist
188 if len(common) == 1:
188 if len(common) == 1:
189 common_file = util.localpath(common.pop())
189 common_file = util.localpath(common.pop())
190 dir1a = os.path.join(dir1a, common_file)
190 dir1a = os.path.join(dir1a, common_file)
191 label1a = common_file + rev1a
191 label1a = common_file + rev1a
192 if not os.path.isfile(os.path.join(tmproot, dir1a)):
192 if not os.path.isfile(os.path.join(tmproot, dir1a)):
193 dir1a = os.devnull
193 dir1a = os.devnull
194 if do3way:
194 if do3way:
195 dir1b = os.path.join(dir1b, common_file)
195 dir1b = os.path.join(dir1b, common_file)
196 label1b = common_file + rev1b
196 label1b = common_file + rev1b
197 if not os.path.isfile(os.path.join(tmproot, dir1b)):
197 if not os.path.isfile(os.path.join(tmproot, dir1b)):
198 dir1b = os.devnull
198 dir1b = os.devnull
199 dir2 = os.path.join(dir2root, dir2, common_file)
199 dir2 = os.path.join(dir2root, dir2, common_file)
200 label2 = common_file + rev2
200 label2 = common_file + rev2
201
201
202 # Function to quote file/dir names in the argument string.
202 # Function to quote file/dir names in the argument string.
203 # When not operating in 3-way mode, an empty string is
203 # When not operating in 3-way mode, an empty string is
204 # returned for parent2
204 # returned for parent2
205 replace = dict(parent=dir1a, parent1=dir1a, parent2=dir1b,
205 replace = dict(parent=dir1a, parent1=dir1a, parent2=dir1b,
206 plabel1=label1a, plabel2=label1b,
206 plabel1=label1a, plabel2=label1b,
207 clabel=label2, child=dir2)
207 clabel=label2, child=dir2)
208 def quote(match):
208 def quote(match):
209 key = match.group()[1:]
209 key = match.group()[1:]
210 if not do3way and key == 'parent2':
210 if not do3way and key == 'parent2':
211 return ''
211 return ''
212 return util.shellquote(replace[key])
212 return util.shellquote(replace[key])
213
213
214 # Match parent2 first, so 'parent1?' will match both parent1 and parent
214 # Match parent2 first, so 'parent1?' will match both parent1 and parent
215 regex = '\$(parent2|parent1?|child|plabel1|plabel2|clabel)'
215 regex = '\$(parent2|parent1?|child|plabel1|plabel2|clabel)'
216 if not do3way and not re.search(regex, args):
216 if not do3way and not re.search(regex, args):
217 args += ' $parent1 $child'
217 args += ' $parent1 $child'
218 args = re.sub(regex, quote, args)
218 args = re.sub(regex, quote, args)
219 cmdline = util.shellquote(diffcmd) + ' ' + args
219 cmdline = util.shellquote(diffcmd) + ' ' + args
220
220
221 ui.debug('running %r in %s\n' % (cmdline, tmproot))
221 ui.debug('running %r in %s\n' % (cmdline, tmproot))
222 util.system(cmdline, cwd=tmproot)
222 util.system(cmdline, cwd=tmproot)
223
223
224 for copy_fn, working_fn, mtime in fns_and_mtime:
224 for copy_fn, working_fn, mtime in fns_and_mtime:
225 if os.path.getmtime(copy_fn) != mtime:
225 if os.path.getmtime(copy_fn) != mtime:
226 ui.debug('file changed while diffing. '
226 ui.debug('file changed while diffing. '
227 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
227 'Overwriting: %s (src: %s)\n' % (working_fn, copy_fn))
228 util.copyfile(copy_fn, working_fn)
228 util.copyfile(copy_fn, working_fn)
229
229
230 return 1
230 return 1
231 finally:
231 finally:
232 ui.note(_('cleaning up temp directory\n'))
232 ui.note(_('cleaning up temp directory\n'))
233 shutil.rmtree(tmproot)
233 shutil.rmtree(tmproot)
234
234
235 def extdiff(ui, repo, *pats, **opts):
235 def extdiff(ui, repo, *pats, **opts):
236 '''use external program to diff repository (or selected files)
236 '''use external program to diff repository (or selected files)
237
237
238 Show differences between revisions for the specified files, using
238 Show differences between revisions for the specified files, using
239 an external program. The default program used is diff, with
239 an external program. The default program used is diff, with
240 default options "-Npru".
240 default options "-Npru".
241
241
242 To select a different program, use the -p/--program option. The
242 To select a different program, use the -p/--program option. The
243 program will be passed the names of two directories to compare. To
243 program will be passed the names of two directories to compare. To
244 pass additional options to the program, use -o/--option. These
244 pass additional options to the program, use -o/--option. These
245 will be passed before the names of the directories to compare.
245 will be passed before the names of the directories to compare.
246
246
247 When two revision arguments are given, then changes are shown
247 When two revision arguments are given, then changes are shown
248 between those revisions. If only one revision is specified then
248 between those revisions. If only one revision is specified then
249 that revision is compared to the working directory, and, when no
249 that revision is compared to the working directory, and, when no
250 revisions are specified, the working directory files are compared
250 revisions are specified, the working directory files are compared
251 to its parent.'''
251 to its parent.'''
252 program = opts.get('program')
252 program = opts.get('program')
253 option = opts.get('option')
253 option = opts.get('option')
254 if not program:
254 if not program:
255 program = 'diff'
255 program = 'diff'
256 option = option or ['-Npru']
256 option = option or ['-Npru']
257 return dodiff(ui, repo, program, option, pats, opts)
257 return dodiff(ui, repo, program, option, pats, opts)
258
258
259 cmdtable = {
259 cmdtable = {
260 "extdiff":
260 "extdiff":
261 (extdiff,
261 (extdiff,
262 [('p', 'program', '', _('comparison program to run')),
262 [('p', 'program', '',
263 ('o', 'option', [], _('pass option to comparison program')),
263 _('comparison program to run'), _('CMD')),
264 ('r', 'rev', [], _('revision')),
264 ('o', 'option', [],
265 ('c', 'change', '', _('change made by revision')),
265 _('pass option to comparison program'), _('OPT')),
266 ('r', 'rev', [],
267 _('revision'), _('REV')),
268 ('c', 'change', '',
269 _('change made by revision'), _('REV')),
266 ] + commands.walkopts,
270 ] + commands.walkopts,
267 _('hg extdiff [OPT]... [FILE]...')),
271 _('hg extdiff [OPT]... [FILE]...')),
268 }
272 }
269
273
270 def uisetup(ui):
274 def uisetup(ui):
271 for cmd, path in ui.configitems('extdiff'):
275 for cmd, path in ui.configitems('extdiff'):
272 if cmd.startswith('cmd.'):
276 if cmd.startswith('cmd.'):
273 cmd = cmd[4:]
277 cmd = cmd[4:]
274 if not path:
278 if not path:
275 path = cmd
279 path = cmd
276 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
280 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
277 diffopts = diffopts and [diffopts] or []
281 diffopts = diffopts and [diffopts] or []
278 elif cmd.startswith('opts.'):
282 elif cmd.startswith('opts.'):
279 continue
283 continue
280 else:
284 else:
281 # command = path opts
285 # command = path opts
282 if path:
286 if path:
283 diffopts = shlex.split(path)
287 diffopts = shlex.split(path)
284 path = diffopts.pop(0)
288 path = diffopts.pop(0)
285 else:
289 else:
286 path, diffopts = cmd, []
290 path, diffopts = cmd, []
287 # look for diff arguments in [diff-tools] then [merge-tools]
291 # look for diff arguments in [diff-tools] then [merge-tools]
288 if diffopts == []:
292 if diffopts == []:
289 args = ui.config('diff-tools', cmd+'.diffargs') or \
293 args = ui.config('diff-tools', cmd+'.diffargs') or \
290 ui.config('merge-tools', cmd+'.diffargs')
294 ui.config('merge-tools', cmd+'.diffargs')
291 if args:
295 if args:
292 diffopts = shlex.split(args)
296 diffopts = shlex.split(args)
293 def save(cmd, path, diffopts):
297 def save(cmd, path, diffopts):
294 '''use closure to save diff command to use'''
298 '''use closure to save diff command to use'''
295 def mydiff(ui, repo, *pats, **opts):
299 def mydiff(ui, repo, *pats, **opts):
296 return dodiff(ui, repo, path, diffopts + opts['option'],
300 return dodiff(ui, repo, path, diffopts + opts['option'],
297 pats, opts)
301 pats, opts)
298 doc = _('''\
302 doc = _('''\
299 use %(path)s to diff repository (or selected files)
303 use %(path)s to diff repository (or selected files)
300
304
301 Show differences between revisions for the specified files, using
305 Show differences between revisions for the specified files, using
302 the %(path)s program.
306 the %(path)s program.
303
307
304 When two revision arguments are given, then changes are shown
308 When two revision arguments are given, then changes are shown
305 between those revisions. If only one revision is specified then
309 between those revisions. If only one revision is specified then
306 that revision is compared to the working directory, and, when no
310 that revision is compared to the working directory, and, when no
307 revisions are specified, the working directory files are compared
311 revisions are specified, the working directory files are compared
308 to its parent.\
312 to its parent.\
309 ''') % dict(path=util.uirepr(path))
313 ''') % dict(path=util.uirepr(path))
310
314
311 # We must translate the docstring right away since it is
315 # We must translate the docstring right away since it is
312 # used as a format string. The string will unfortunately
316 # used as a format string. The string will unfortunately
313 # be translated again in commands.helpcmd and this will
317 # be translated again in commands.helpcmd and this will
314 # fail when the docstring contains non-ASCII characters.
318 # fail when the docstring contains non-ASCII characters.
315 # Decoding the string to a Unicode string here (using the
319 # Decoding the string to a Unicode string here (using the
316 # right encoding) prevents that.
320 # right encoding) prevents that.
317 mydiff.__doc__ = doc.decode(encoding.encoding)
321 mydiff.__doc__ = doc.decode(encoding.encoding)
318 return mydiff
322 return mydiff
319 cmdtable[cmd] = (save(cmd, path, diffopts),
323 cmdtable[cmd] = (save(cmd, path, diffopts),
320 cmdtable['extdiff'][1][1:],
324 cmdtable['extdiff'][1][1:],
321 _('hg %s [OPTION]... [FILE]...') % cmd)
325 _('hg %s [OPTION]... [FILE]...') % cmd)
@@ -1,147 +1,148 b''
1 # fetch.py - pull and merge remote changes
1 # fetch.py - pull and merge remote changes
2 #
2 #
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''pull, update and merge in one command'''
8 '''pull, update and merge in one command'''
9
9
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11 from mercurial.node import nullid, short
11 from mercurial.node import nullid, short
12 from mercurial import commands, cmdutil, hg, util, url, error
12 from mercurial import commands, cmdutil, hg, util, url, error
13 from mercurial.lock import release
13 from mercurial.lock import release
14
14
15 def fetch(ui, repo, source='default', **opts):
15 def fetch(ui, repo, source='default', **opts):
16 '''pull changes from a remote repository, merge new changes if needed.
16 '''pull changes from a remote repository, merge new changes if needed.
17
17
18 This finds all changes from the repository at the specified path
18 This finds all changes from the repository at the specified path
19 or URL and adds them to the local repository.
19 or URL and adds them to the local repository.
20
20
21 If the pulled changes add a new branch head, the head is
21 If the pulled changes add a new branch head, the head is
22 automatically merged, and the result of the merge is committed.
22 automatically merged, and the result of the merge is committed.
23 Otherwise, the working directory is updated to include the new
23 Otherwise, the working directory is updated to include the new
24 changes.
24 changes.
25
25
26 When a merge occurs, the newly pulled changes are assumed to be
26 When a merge occurs, the newly pulled changes are assumed to be
27 "authoritative". The head of the new changes is used as the first
27 "authoritative". The head of the new changes is used as the first
28 parent, with local changes as the second. To switch the merge
28 parent, with local changes as the second. To switch the merge
29 order, use --switch-parent.
29 order, use --switch-parent.
30
30
31 See :hg:`help dates` for a list of formats valid for -d/--date.
31 See :hg:`help dates` for a list of formats valid for -d/--date.
32 '''
32 '''
33
33
34 date = opts.get('date')
34 date = opts.get('date')
35 if date:
35 if date:
36 opts['date'] = util.parsedate(date)
36 opts['date'] = util.parsedate(date)
37
37
38 parent, p2 = repo.dirstate.parents()
38 parent, p2 = repo.dirstate.parents()
39 branch = repo.dirstate.branch()
39 branch = repo.dirstate.branch()
40 branchnode = repo.branchtags().get(branch)
40 branchnode = repo.branchtags().get(branch)
41 if parent != branchnode:
41 if parent != branchnode:
42 raise util.Abort(_('working dir not at branch tip '
42 raise util.Abort(_('working dir not at branch tip '
43 '(use "hg update" to check out branch tip)'))
43 '(use "hg update" to check out branch tip)'))
44
44
45 if p2 != nullid:
45 if p2 != nullid:
46 raise util.Abort(_('outstanding uncommitted merge'))
46 raise util.Abort(_('outstanding uncommitted merge'))
47
47
48 wlock = lock = None
48 wlock = lock = None
49 try:
49 try:
50 wlock = repo.wlock()
50 wlock = repo.wlock()
51 lock = repo.lock()
51 lock = repo.lock()
52 mod, add, rem, del_ = repo.status()[:4]
52 mod, add, rem, del_ = repo.status()[:4]
53
53
54 if mod or add or rem:
54 if mod or add or rem:
55 raise util.Abort(_('outstanding uncommitted changes'))
55 raise util.Abort(_('outstanding uncommitted changes'))
56 if del_:
56 if del_:
57 raise util.Abort(_('working directory is missing some files'))
57 raise util.Abort(_('working directory is missing some files'))
58 bheads = repo.branchheads(branch)
58 bheads = repo.branchheads(branch)
59 bheads = [head for head in bheads if len(repo[head].children()) == 0]
59 bheads = [head for head in bheads if len(repo[head].children()) == 0]
60 if len(bheads) > 1:
60 if len(bheads) > 1:
61 raise util.Abort(_('multiple heads in this branch '
61 raise util.Abort(_('multiple heads in this branch '
62 '(use "hg heads ." and "hg merge" to merge)'))
62 '(use "hg heads ." and "hg merge" to merge)'))
63
63
64 other = hg.repository(hg.remoteui(repo, opts),
64 other = hg.repository(hg.remoteui(repo, opts),
65 ui.expandpath(source))
65 ui.expandpath(source))
66 ui.status(_('pulling from %s\n') %
66 ui.status(_('pulling from %s\n') %
67 url.hidepassword(ui.expandpath(source)))
67 url.hidepassword(ui.expandpath(source)))
68 revs = None
68 revs = None
69 if opts['rev']:
69 if opts['rev']:
70 try:
70 try:
71 revs = [other.lookup(rev) for rev in opts['rev']]
71 revs = [other.lookup(rev) for rev in opts['rev']]
72 except error.CapabilityError:
72 except error.CapabilityError:
73 err = _("Other repository doesn't support revision lookup, "
73 err = _("Other repository doesn't support revision lookup, "
74 "so a rev cannot be specified.")
74 "so a rev cannot be specified.")
75 raise util.Abort(err)
75 raise util.Abort(err)
76
76
77 # Are there any changes at all?
77 # Are there any changes at all?
78 modheads = repo.pull(other, heads=revs)
78 modheads = repo.pull(other, heads=revs)
79 if modheads == 0:
79 if modheads == 0:
80 return 0
80 return 0
81
81
82 # Is this a simple fast-forward along the current branch?
82 # Is this a simple fast-forward along the current branch?
83 newheads = repo.branchheads(branch)
83 newheads = repo.branchheads(branch)
84 newchildren = repo.changelog.nodesbetween([parent], newheads)[2]
84 newchildren = repo.changelog.nodesbetween([parent], newheads)[2]
85 if len(newheads) == 1:
85 if len(newheads) == 1:
86 if newchildren[0] != parent:
86 if newchildren[0] != parent:
87 return hg.clean(repo, newchildren[0])
87 return hg.clean(repo, newchildren[0])
88 else:
88 else:
89 return
89 return
90
90
91 # Are there more than one additional branch heads?
91 # Are there more than one additional branch heads?
92 newchildren = [n for n in newchildren if n != parent]
92 newchildren = [n for n in newchildren if n != parent]
93 newparent = parent
93 newparent = parent
94 if newchildren:
94 if newchildren:
95 newparent = newchildren[0]
95 newparent = newchildren[0]
96 hg.clean(repo, newparent)
96 hg.clean(repo, newparent)
97 newheads = [n for n in newheads if n != newparent]
97 newheads = [n for n in newheads if n != newparent]
98 if len(newheads) > 1:
98 if len(newheads) > 1:
99 ui.status(_('not merging with %d other new branch heads '
99 ui.status(_('not merging with %d other new branch heads '
100 '(use "hg heads ." and "hg merge" to merge them)\n') %
100 '(use "hg heads ." and "hg merge" to merge them)\n') %
101 (len(newheads) - 1))
101 (len(newheads) - 1))
102 return
102 return
103
103
104 # Otherwise, let's merge.
104 # Otherwise, let's merge.
105 err = False
105 err = False
106 if newheads:
106 if newheads:
107 # By default, we consider the repository we're pulling
107 # By default, we consider the repository we're pulling
108 # *from* as authoritative, so we merge our changes into
108 # *from* as authoritative, so we merge our changes into
109 # theirs.
109 # theirs.
110 if opts['switch_parent']:
110 if opts['switch_parent']:
111 firstparent, secondparent = newparent, newheads[0]
111 firstparent, secondparent = newparent, newheads[0]
112 else:
112 else:
113 firstparent, secondparent = newheads[0], newparent
113 firstparent, secondparent = newheads[0], newparent
114 ui.status(_('updating to %d:%s\n') %
114 ui.status(_('updating to %d:%s\n') %
115 (repo.changelog.rev(firstparent),
115 (repo.changelog.rev(firstparent),
116 short(firstparent)))
116 short(firstparent)))
117 hg.clean(repo, firstparent)
117 hg.clean(repo, firstparent)
118 ui.status(_('merging with %d:%s\n') %
118 ui.status(_('merging with %d:%s\n') %
119 (repo.changelog.rev(secondparent), short(secondparent)))
119 (repo.changelog.rev(secondparent), short(secondparent)))
120 err = hg.merge(repo, secondparent, remind=False)
120 err = hg.merge(repo, secondparent, remind=False)
121
121
122 if not err:
122 if not err:
123 # we don't translate commit messages
123 # we don't translate commit messages
124 message = (cmdutil.logmessage(opts) or
124 message = (cmdutil.logmessage(opts) or
125 ('Automated merge with %s' %
125 ('Automated merge with %s' %
126 url.removeauth(other.url())))
126 url.removeauth(other.url())))
127 editor = cmdutil.commiteditor
127 editor = cmdutil.commiteditor
128 if opts.get('force_editor') or opts.get('edit'):
128 if opts.get('force_editor') or opts.get('edit'):
129 editor = cmdutil.commitforceeditor
129 editor = cmdutil.commitforceeditor
130 n = repo.commit(message, opts['user'], opts['date'], editor=editor)
130 n = repo.commit(message, opts['user'], opts['date'], editor=editor)
131 ui.status(_('new changeset %d:%s merges remote changes '
131 ui.status(_('new changeset %d:%s merges remote changes '
132 'with local\n') % (repo.changelog.rev(n),
132 'with local\n') % (repo.changelog.rev(n),
133 short(n)))
133 short(n)))
134
134
135 finally:
135 finally:
136 release(lock, wlock)
136 release(lock, wlock)
137
137
138 cmdtable = {
138 cmdtable = {
139 'fetch':
139 'fetch':
140 (fetch,
140 (fetch,
141 [('r', 'rev', [], _('a specific revision you would like to pull')),
141 [('r', 'rev', [],
142 _('a specific revision you would like to pull'), _('REV')),
142 ('e', 'edit', None, _('edit commit message')),
143 ('e', 'edit', None, _('edit commit message')),
143 ('', 'force-editor', None, _('edit commit message (DEPRECATED)')),
144 ('', 'force-editor', None, _('edit commit message (DEPRECATED)')),
144 ('', 'switch-parent', None, _('switch parents when merging')),
145 ('', 'switch-parent', None, _('switch parents when merging')),
145 ] + commands.commitopts + commands.commitopts2 + commands.remoteopts,
146 ] + commands.commitopts + commands.commitopts2 + commands.remoteopts,
146 _('hg fetch [SOURCE]')),
147 _('hg fetch [SOURCE]')),
147 }
148 }
@@ -1,286 +1,288 b''
1 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
1 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 '''commands to sign and verify changesets'''
6 '''commands to sign and verify changesets'''
7
7
8 import os, tempfile, binascii
8 import os, tempfile, binascii
9 from mercurial import util, commands, match
9 from mercurial import util, commands, match
10 from mercurial import node as hgnode
10 from mercurial import node as hgnode
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12
12
13 class gpg(object):
13 class gpg(object):
14 def __init__(self, path, key=None):
14 def __init__(self, path, key=None):
15 self.path = path
15 self.path = path
16 self.key = (key and " --local-user \"%s\"" % key) or ""
16 self.key = (key and " --local-user \"%s\"" % key) or ""
17
17
18 def sign(self, data):
18 def sign(self, data):
19 gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key)
19 gpgcmd = "%s --sign --detach-sign%s" % (self.path, self.key)
20 return util.filter(data, gpgcmd)
20 return util.filter(data, gpgcmd)
21
21
22 def verify(self, data, sig):
22 def verify(self, data, sig):
23 """ returns of the good and bad signatures"""
23 """ returns of the good and bad signatures"""
24 sigfile = datafile = None
24 sigfile = datafile = None
25 try:
25 try:
26 # create temporary files
26 # create temporary files
27 fd, sigfile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".sig")
27 fd, sigfile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".sig")
28 fp = os.fdopen(fd, 'wb')
28 fp = os.fdopen(fd, 'wb')
29 fp.write(sig)
29 fp.write(sig)
30 fp.close()
30 fp.close()
31 fd, datafile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".txt")
31 fd, datafile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".txt")
32 fp = os.fdopen(fd, 'wb')
32 fp = os.fdopen(fd, 'wb')
33 fp.write(data)
33 fp.write(data)
34 fp.close()
34 fp.close()
35 gpgcmd = ("%s --logger-fd 1 --status-fd 1 --verify "
35 gpgcmd = ("%s --logger-fd 1 --status-fd 1 --verify "
36 "\"%s\" \"%s\"" % (self.path, sigfile, datafile))
36 "\"%s\" \"%s\"" % (self.path, sigfile, datafile))
37 ret = util.filter("", gpgcmd)
37 ret = util.filter("", gpgcmd)
38 finally:
38 finally:
39 for f in (sigfile, datafile):
39 for f in (sigfile, datafile):
40 try:
40 try:
41 if f:
41 if f:
42 os.unlink(f)
42 os.unlink(f)
43 except:
43 except:
44 pass
44 pass
45 keys = []
45 keys = []
46 key, fingerprint = None, None
46 key, fingerprint = None, None
47 err = ""
47 err = ""
48 for l in ret.splitlines():
48 for l in ret.splitlines():
49 # see DETAILS in the gnupg documentation
49 # see DETAILS in the gnupg documentation
50 # filter the logger output
50 # filter the logger output
51 if not l.startswith("[GNUPG:]"):
51 if not l.startswith("[GNUPG:]"):
52 continue
52 continue
53 l = l[9:]
53 l = l[9:]
54 if l.startswith("ERRSIG"):
54 if l.startswith("ERRSIG"):
55 err = _("error while verifying signature")
55 err = _("error while verifying signature")
56 break
56 break
57 elif l.startswith("VALIDSIG"):
57 elif l.startswith("VALIDSIG"):
58 # fingerprint of the primary key
58 # fingerprint of the primary key
59 fingerprint = l.split()[10]
59 fingerprint = l.split()[10]
60 elif (l.startswith("GOODSIG") or
60 elif (l.startswith("GOODSIG") or
61 l.startswith("EXPSIG") or
61 l.startswith("EXPSIG") or
62 l.startswith("EXPKEYSIG") or
62 l.startswith("EXPKEYSIG") or
63 l.startswith("BADSIG")):
63 l.startswith("BADSIG")):
64 if key is not None:
64 if key is not None:
65 keys.append(key + [fingerprint])
65 keys.append(key + [fingerprint])
66 key = l.split(" ", 2)
66 key = l.split(" ", 2)
67 fingerprint = None
67 fingerprint = None
68 if err:
68 if err:
69 return err, []
69 return err, []
70 if key is not None:
70 if key is not None:
71 keys.append(key + [fingerprint])
71 keys.append(key + [fingerprint])
72 return err, keys
72 return err, keys
73
73
74 def newgpg(ui, **opts):
74 def newgpg(ui, **opts):
75 """create a new gpg instance"""
75 """create a new gpg instance"""
76 gpgpath = ui.config("gpg", "cmd", "gpg")
76 gpgpath = ui.config("gpg", "cmd", "gpg")
77 gpgkey = opts.get('key')
77 gpgkey = opts.get('key')
78 if not gpgkey:
78 if not gpgkey:
79 gpgkey = ui.config("gpg", "key", None)
79 gpgkey = ui.config("gpg", "key", None)
80 return gpg(gpgpath, gpgkey)
80 return gpg(gpgpath, gpgkey)
81
81
82 def sigwalk(repo):
82 def sigwalk(repo):
83 """
83 """
84 walk over every sigs, yields a couple
84 walk over every sigs, yields a couple
85 ((node, version, sig), (filename, linenumber))
85 ((node, version, sig), (filename, linenumber))
86 """
86 """
87 def parsefile(fileiter, context):
87 def parsefile(fileiter, context):
88 ln = 1
88 ln = 1
89 for l in fileiter:
89 for l in fileiter:
90 if not l:
90 if not l:
91 continue
91 continue
92 yield (l.split(" ", 2), (context, ln))
92 yield (l.split(" ", 2), (context, ln))
93 ln += 1
93 ln += 1
94
94
95 # read the heads
95 # read the heads
96 fl = repo.file(".hgsigs")
96 fl = repo.file(".hgsigs")
97 for r in reversed(fl.heads()):
97 for r in reversed(fl.heads()):
98 fn = ".hgsigs|%s" % hgnode.short(r)
98 fn = ".hgsigs|%s" % hgnode.short(r)
99 for item in parsefile(fl.read(r).splitlines(), fn):
99 for item in parsefile(fl.read(r).splitlines(), fn):
100 yield item
100 yield item
101 try:
101 try:
102 # read local signatures
102 # read local signatures
103 fn = "localsigs"
103 fn = "localsigs"
104 for item in parsefile(repo.opener(fn), fn):
104 for item in parsefile(repo.opener(fn), fn):
105 yield item
105 yield item
106 except IOError:
106 except IOError:
107 pass
107 pass
108
108
109 def getkeys(ui, repo, mygpg, sigdata, context):
109 def getkeys(ui, repo, mygpg, sigdata, context):
110 """get the keys who signed a data"""
110 """get the keys who signed a data"""
111 fn, ln = context
111 fn, ln = context
112 node, version, sig = sigdata
112 node, version, sig = sigdata
113 prefix = "%s:%d" % (fn, ln)
113 prefix = "%s:%d" % (fn, ln)
114 node = hgnode.bin(node)
114 node = hgnode.bin(node)
115
115
116 data = node2txt(repo, node, version)
116 data = node2txt(repo, node, version)
117 sig = binascii.a2b_base64(sig)
117 sig = binascii.a2b_base64(sig)
118 err, keys = mygpg.verify(data, sig)
118 err, keys = mygpg.verify(data, sig)
119 if err:
119 if err:
120 ui.warn("%s:%d %s\n" % (fn, ln , err))
120 ui.warn("%s:%d %s\n" % (fn, ln , err))
121 return None
121 return None
122
122
123 validkeys = []
123 validkeys = []
124 # warn for expired key and/or sigs
124 # warn for expired key and/or sigs
125 for key in keys:
125 for key in keys:
126 if key[0] == "BADSIG":
126 if key[0] == "BADSIG":
127 ui.write(_("%s Bad signature from \"%s\"\n") % (prefix, key[2]))
127 ui.write(_("%s Bad signature from \"%s\"\n") % (prefix, key[2]))
128 continue
128 continue
129 if key[0] == "EXPSIG":
129 if key[0] == "EXPSIG":
130 ui.write(_("%s Note: Signature has expired"
130 ui.write(_("%s Note: Signature has expired"
131 " (signed by: \"%s\")\n") % (prefix, key[2]))
131 " (signed by: \"%s\")\n") % (prefix, key[2]))
132 elif key[0] == "EXPKEYSIG":
132 elif key[0] == "EXPKEYSIG":
133 ui.write(_("%s Note: This key has expired"
133 ui.write(_("%s Note: This key has expired"
134 " (signed by: \"%s\")\n") % (prefix, key[2]))
134 " (signed by: \"%s\")\n") % (prefix, key[2]))
135 validkeys.append((key[1], key[2], key[3]))
135 validkeys.append((key[1], key[2], key[3]))
136 return validkeys
136 return validkeys
137
137
138 def sigs(ui, repo):
138 def sigs(ui, repo):
139 """list signed changesets"""
139 """list signed changesets"""
140 mygpg = newgpg(ui)
140 mygpg = newgpg(ui)
141 revs = {}
141 revs = {}
142
142
143 for data, context in sigwalk(repo):
143 for data, context in sigwalk(repo):
144 node, version, sig = data
144 node, version, sig = data
145 fn, ln = context
145 fn, ln = context
146 try:
146 try:
147 n = repo.lookup(node)
147 n = repo.lookup(node)
148 except KeyError:
148 except KeyError:
149 ui.warn(_("%s:%d node does not exist\n") % (fn, ln))
149 ui.warn(_("%s:%d node does not exist\n") % (fn, ln))
150 continue
150 continue
151 r = repo.changelog.rev(n)
151 r = repo.changelog.rev(n)
152 keys = getkeys(ui, repo, mygpg, data, context)
152 keys = getkeys(ui, repo, mygpg, data, context)
153 if not keys:
153 if not keys:
154 continue
154 continue
155 revs.setdefault(r, [])
155 revs.setdefault(r, [])
156 revs[r].extend(keys)
156 revs[r].extend(keys)
157 for rev in sorted(revs, reverse=True):
157 for rev in sorted(revs, reverse=True):
158 for k in revs[rev]:
158 for k in revs[rev]:
159 r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev)))
159 r = "%5d:%s" % (rev, hgnode.hex(repo.changelog.node(rev)))
160 ui.write("%-30s %s\n" % (keystr(ui, k), r))
160 ui.write("%-30s %s\n" % (keystr(ui, k), r))
161
161
162 def check(ui, repo, rev):
162 def check(ui, repo, rev):
163 """verify all the signatures there may be for a particular revision"""
163 """verify all the signatures there may be for a particular revision"""
164 mygpg = newgpg(ui)
164 mygpg = newgpg(ui)
165 rev = repo.lookup(rev)
165 rev = repo.lookup(rev)
166 hexrev = hgnode.hex(rev)
166 hexrev = hgnode.hex(rev)
167 keys = []
167 keys = []
168
168
169 for data, context in sigwalk(repo):
169 for data, context in sigwalk(repo):
170 node, version, sig = data
170 node, version, sig = data
171 if node == hexrev:
171 if node == hexrev:
172 k = getkeys(ui, repo, mygpg, data, context)
172 k = getkeys(ui, repo, mygpg, data, context)
173 if k:
173 if k:
174 keys.extend(k)
174 keys.extend(k)
175
175
176 if not keys:
176 if not keys:
177 ui.write(_("No valid signature for %s\n") % hgnode.short(rev))
177 ui.write(_("No valid signature for %s\n") % hgnode.short(rev))
178 return
178 return
179
179
180 # print summary
180 # print summary
181 ui.write("%s is signed by:\n" % hgnode.short(rev))
181 ui.write("%s is signed by:\n" % hgnode.short(rev))
182 for key in keys:
182 for key in keys:
183 ui.write(" %s\n" % keystr(ui, key))
183 ui.write(" %s\n" % keystr(ui, key))
184
184
185 def keystr(ui, key):
185 def keystr(ui, key):
186 """associate a string to a key (username, comment)"""
186 """associate a string to a key (username, comment)"""
187 keyid, user, fingerprint = key
187 keyid, user, fingerprint = key
188 comment = ui.config("gpg", fingerprint, None)
188 comment = ui.config("gpg", fingerprint, None)
189 if comment:
189 if comment:
190 return "%s (%s)" % (user, comment)
190 return "%s (%s)" % (user, comment)
191 else:
191 else:
192 return user
192 return user
193
193
194 def sign(ui, repo, *revs, **opts):
194 def sign(ui, repo, *revs, **opts):
195 """add a signature for the current or given revision
195 """add a signature for the current or given revision
196
196
197 If no revision is given, the parent of the working directory is used,
197 If no revision is given, the parent of the working directory is used,
198 or tip if no revision is checked out.
198 or tip if no revision is checked out.
199
199
200 See :hg:`help dates` for a list of formats valid for -d/--date.
200 See :hg:`help dates` for a list of formats valid for -d/--date.
201 """
201 """
202
202
203 mygpg = newgpg(ui, **opts)
203 mygpg = newgpg(ui, **opts)
204 sigver = "0"
204 sigver = "0"
205 sigmessage = ""
205 sigmessage = ""
206
206
207 date = opts.get('date')
207 date = opts.get('date')
208 if date:
208 if date:
209 opts['date'] = util.parsedate(date)
209 opts['date'] = util.parsedate(date)
210
210
211 if revs:
211 if revs:
212 nodes = [repo.lookup(n) for n in revs]
212 nodes = [repo.lookup(n) for n in revs]
213 else:
213 else:
214 nodes = [node for node in repo.dirstate.parents()
214 nodes = [node for node in repo.dirstate.parents()
215 if node != hgnode.nullid]
215 if node != hgnode.nullid]
216 if len(nodes) > 1:
216 if len(nodes) > 1:
217 raise util.Abort(_('uncommitted merge - please provide a '
217 raise util.Abort(_('uncommitted merge - please provide a '
218 'specific revision'))
218 'specific revision'))
219 if not nodes:
219 if not nodes:
220 nodes = [repo.changelog.tip()]
220 nodes = [repo.changelog.tip()]
221
221
222 for n in nodes:
222 for n in nodes:
223 hexnode = hgnode.hex(n)
223 hexnode = hgnode.hex(n)
224 ui.write(_("Signing %d:%s\n") % (repo.changelog.rev(n),
224 ui.write(_("Signing %d:%s\n") % (repo.changelog.rev(n),
225 hgnode.short(n)))
225 hgnode.short(n)))
226 # build data
226 # build data
227 data = node2txt(repo, n, sigver)
227 data = node2txt(repo, n, sigver)
228 sig = mygpg.sign(data)
228 sig = mygpg.sign(data)
229 if not sig:
229 if not sig:
230 raise util.Abort(_("Error while signing"))
230 raise util.Abort(_("Error while signing"))
231 sig = binascii.b2a_base64(sig)
231 sig = binascii.b2a_base64(sig)
232 sig = sig.replace("\n", "")
232 sig = sig.replace("\n", "")
233 sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
233 sigmessage += "%s %s %s\n" % (hexnode, sigver, sig)
234
234
235 # write it
235 # write it
236 if opts['local']:
236 if opts['local']:
237 repo.opener("localsigs", "ab").write(sigmessage)
237 repo.opener("localsigs", "ab").write(sigmessage)
238 return
238 return
239
239
240 msigs = match.exact(repo.root, '', ['.hgsigs'])
240 msigs = match.exact(repo.root, '', ['.hgsigs'])
241 s = repo.status(match=msigs, unknown=True, ignored=True)[:6]
241 s = repo.status(match=msigs, unknown=True, ignored=True)[:6]
242 if util.any(s) and not opts["force"]:
242 if util.any(s) and not opts["force"]:
243 raise util.Abort(_("working copy of .hgsigs is changed "
243 raise util.Abort(_("working copy of .hgsigs is changed "
244 "(please commit .hgsigs manually "
244 "(please commit .hgsigs manually "
245 "or use --force)"))
245 "or use --force)"))
246
246
247 repo.wfile(".hgsigs", "ab").write(sigmessage)
247 repo.wfile(".hgsigs", "ab").write(sigmessage)
248
248
249 if '.hgsigs' not in repo.dirstate:
249 if '.hgsigs' not in repo.dirstate:
250 repo[None].add([".hgsigs"])
250 repo[None].add([".hgsigs"])
251
251
252 if opts["no_commit"]:
252 if opts["no_commit"]:
253 return
253 return
254
254
255 message = opts['message']
255 message = opts['message']
256 if not message:
256 if not message:
257 # we don't translate commit messages
257 # we don't translate commit messages
258 message = "\n".join(["Added signature for changeset %s"
258 message = "\n".join(["Added signature for changeset %s"
259 % hgnode.short(n)
259 % hgnode.short(n)
260 for n in nodes])
260 for n in nodes])
261 try:
261 try:
262 repo.commit(message, opts['user'], opts['date'], match=msigs)
262 repo.commit(message, opts['user'], opts['date'], match=msigs)
263 except ValueError, inst:
263 except ValueError, inst:
264 raise util.Abort(str(inst))
264 raise util.Abort(str(inst))
265
265
266 def node2txt(repo, node, ver):
266 def node2txt(repo, node, ver):
267 """map a manifest into some text"""
267 """map a manifest into some text"""
268 if ver == "0":
268 if ver == "0":
269 return "%s\n" % hgnode.hex(node)
269 return "%s\n" % hgnode.hex(node)
270 else:
270 else:
271 raise util.Abort(_("unknown signature version"))
271 raise util.Abort(_("unknown signature version"))
272
272
273 cmdtable = {
273 cmdtable = {
274 "sign":
274 "sign":
275 (sign,
275 (sign,
276 [('l', 'local', None, _('make the signature local')),
276 [('l', 'local', None, _('make the signature local')),
277 ('f', 'force', None, _('sign even if the sigfile is modified')),
277 ('f', 'force', None, _('sign even if the sigfile is modified')),
278 ('', 'no-commit', None, _('do not commit the sigfile after signing')),
278 ('', 'no-commit', None, _('do not commit the sigfile after signing')),
279 ('k', 'key', '', _('the key id to sign with')),
279 ('k', 'key', '',
280 ('m', 'message', '', _('commit message')),
280 _('the key id to sign with'), _('ID')),
281 ('m', 'message', '',
282 _('commit message'), _('TEXT')),
281 ] + commands.commitopts2,
283 ] + commands.commitopts2,
282 _('hg sign [OPTION]... [REVISION]...')),
284 _('hg sign [OPTION]... [REVISION]...')),
283 "sigcheck": (check, [], _('hg sigcheck REVISION')),
285 "sigcheck": (check, [], _('hg sigcheck REVISION')),
284 "sigs": (sigs, [], _('hg sigs')),
286 "sigs": (sigs, [], _('hg sigs')),
285 }
287 }
286
288
@@ -1,378 +1,380 b''
1 # ASCII graph log extension for Mercurial
1 # ASCII graph log extension for Mercurial
2 #
2 #
3 # Copyright 2007 Joel Rosdahl <joel@rosdahl.net>
3 # Copyright 2007 Joel Rosdahl <joel@rosdahl.net>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to view revision graphs from a shell
8 '''command to view revision graphs from a shell
9
9
10 This extension adds a --graph option to the incoming, outgoing and log
10 This extension adds a --graph option to the incoming, outgoing and log
11 commands. When this options is given, an ASCII representation of the
11 commands. When this options is given, an ASCII representation of the
12 revision graph is also shown.
12 revision graph is also shown.
13 '''
13 '''
14
14
15 import os
15 import os
16 from mercurial.cmdutil import revrange, show_changeset
16 from mercurial.cmdutil import revrange, show_changeset
17 from mercurial.commands import templateopts
17 from mercurial.commands import templateopts
18 from mercurial.i18n import _
18 from mercurial.i18n import _
19 from mercurial.node import nullrev
19 from mercurial.node import nullrev
20 from mercurial import bundlerepo, changegroup, cmdutil, commands, extensions
20 from mercurial import bundlerepo, changegroup, cmdutil, commands, extensions
21 from mercurial import hg, url, util, graphmod, discovery
21 from mercurial import hg, url, util, graphmod, discovery
22
22
23 ASCIIDATA = 'ASC'
23 ASCIIDATA = 'ASC'
24
24
25 def asciiedges(seen, rev, parents):
25 def asciiedges(seen, rev, parents):
26 """adds edge info to changelog DAG walk suitable for ascii()"""
26 """adds edge info to changelog DAG walk suitable for ascii()"""
27 if rev not in seen:
27 if rev not in seen:
28 seen.append(rev)
28 seen.append(rev)
29 nodeidx = seen.index(rev)
29 nodeidx = seen.index(rev)
30
30
31 knownparents = []
31 knownparents = []
32 newparents = []
32 newparents = []
33 for parent in parents:
33 for parent in parents:
34 if parent in seen:
34 if parent in seen:
35 knownparents.append(parent)
35 knownparents.append(parent)
36 else:
36 else:
37 newparents.append(parent)
37 newparents.append(parent)
38
38
39 ncols = len(seen)
39 ncols = len(seen)
40 seen[nodeidx:nodeidx + 1] = newparents
40 seen[nodeidx:nodeidx + 1] = newparents
41 edges = [(nodeidx, seen.index(p)) for p in knownparents]
41 edges = [(nodeidx, seen.index(p)) for p in knownparents]
42
42
43 if len(newparents) > 0:
43 if len(newparents) > 0:
44 edges.append((nodeidx, nodeidx))
44 edges.append((nodeidx, nodeidx))
45 if len(newparents) > 1:
45 if len(newparents) > 1:
46 edges.append((nodeidx, nodeidx + 1))
46 edges.append((nodeidx, nodeidx + 1))
47
47
48 nmorecols = len(seen) - ncols
48 nmorecols = len(seen) - ncols
49 return nodeidx, edges, ncols, nmorecols
49 return nodeidx, edges, ncols, nmorecols
50
50
51 def fix_long_right_edges(edges):
51 def fix_long_right_edges(edges):
52 for (i, (start, end)) in enumerate(edges):
52 for (i, (start, end)) in enumerate(edges):
53 if end > start:
53 if end > start:
54 edges[i] = (start, end + 1)
54 edges[i] = (start, end + 1)
55
55
56 def get_nodeline_edges_tail(
56 def get_nodeline_edges_tail(
57 node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail):
57 node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail):
58 if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0:
58 if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0:
59 # Still going in the same non-vertical direction.
59 # Still going in the same non-vertical direction.
60 if n_columns_diff == -1:
60 if n_columns_diff == -1:
61 start = max(node_index + 1, p_node_index)
61 start = max(node_index + 1, p_node_index)
62 tail = ["|", " "] * (start - node_index - 1)
62 tail = ["|", " "] * (start - node_index - 1)
63 tail.extend(["/", " "] * (n_columns - start))
63 tail.extend(["/", " "] * (n_columns - start))
64 return tail
64 return tail
65 else:
65 else:
66 return ["\\", " "] * (n_columns - node_index - 1)
66 return ["\\", " "] * (n_columns - node_index - 1)
67 else:
67 else:
68 return ["|", " "] * (n_columns - node_index - 1)
68 return ["|", " "] * (n_columns - node_index - 1)
69
69
70 def draw_edges(edges, nodeline, interline):
70 def draw_edges(edges, nodeline, interline):
71 for (start, end) in edges:
71 for (start, end) in edges:
72 if start == end + 1:
72 if start == end + 1:
73 interline[2 * end + 1] = "/"
73 interline[2 * end + 1] = "/"
74 elif start == end - 1:
74 elif start == end - 1:
75 interline[2 * start + 1] = "\\"
75 interline[2 * start + 1] = "\\"
76 elif start == end:
76 elif start == end:
77 interline[2 * start] = "|"
77 interline[2 * start] = "|"
78 else:
78 else:
79 nodeline[2 * end] = "+"
79 nodeline[2 * end] = "+"
80 if start > end:
80 if start > end:
81 (start, end) = (end, start)
81 (start, end) = (end, start)
82 for i in range(2 * start + 1, 2 * end):
82 for i in range(2 * start + 1, 2 * end):
83 if nodeline[i] != "+":
83 if nodeline[i] != "+":
84 nodeline[i] = "-"
84 nodeline[i] = "-"
85
85
86 def get_padding_line(ni, n_columns, edges):
86 def get_padding_line(ni, n_columns, edges):
87 line = []
87 line = []
88 line.extend(["|", " "] * ni)
88 line.extend(["|", " "] * ni)
89 if (ni, ni - 1) in edges or (ni, ni) in edges:
89 if (ni, ni - 1) in edges or (ni, ni) in edges:
90 # (ni, ni - 1) (ni, ni)
90 # (ni, ni - 1) (ni, ni)
91 # | | | | | | | |
91 # | | | | | | | |
92 # +---o | | o---+
92 # +---o | | o---+
93 # | | c | | c | |
93 # | | c | | c | |
94 # | |/ / | |/ /
94 # | |/ / | |/ /
95 # | | | | | |
95 # | | | | | |
96 c = "|"
96 c = "|"
97 else:
97 else:
98 c = " "
98 c = " "
99 line.extend([c, " "])
99 line.extend([c, " "])
100 line.extend(["|", " "] * (n_columns - ni - 1))
100 line.extend(["|", " "] * (n_columns - ni - 1))
101 return line
101 return line
102
102
103 def asciistate():
103 def asciistate():
104 """returns the initial value for the "state" argument to ascii()"""
104 """returns the initial value for the "state" argument to ascii()"""
105 return [0, 0]
105 return [0, 0]
106
106
107 def ascii(ui, state, type, char, text, coldata):
107 def ascii(ui, state, type, char, text, coldata):
108 """prints an ASCII graph of the DAG
108 """prints an ASCII graph of the DAG
109
109
110 takes the following arguments (one call per node in the graph):
110 takes the following arguments (one call per node in the graph):
111
111
112 - ui to write to
112 - ui to write to
113 - Somewhere to keep the needed state in (init to asciistate())
113 - Somewhere to keep the needed state in (init to asciistate())
114 - Column of the current node in the set of ongoing edges.
114 - Column of the current node in the set of ongoing edges.
115 - Type indicator of node data == ASCIIDATA.
115 - Type indicator of node data == ASCIIDATA.
116 - Payload: (char, lines):
116 - Payload: (char, lines):
117 - Character to use as node's symbol.
117 - Character to use as node's symbol.
118 - List of lines to display as the node's text.
118 - List of lines to display as the node's text.
119 - Edges; a list of (col, next_col) indicating the edges between
119 - Edges; a list of (col, next_col) indicating the edges between
120 the current node and its parents.
120 the current node and its parents.
121 - Number of columns (ongoing edges) in the current revision.
121 - Number of columns (ongoing edges) in the current revision.
122 - The difference between the number of columns (ongoing edges)
122 - The difference between the number of columns (ongoing edges)
123 in the next revision and the number of columns (ongoing edges)
123 in the next revision and the number of columns (ongoing edges)
124 in the current revision. That is: -1 means one column removed;
124 in the current revision. That is: -1 means one column removed;
125 0 means no columns added or removed; 1 means one column added.
125 0 means no columns added or removed; 1 means one column added.
126 """
126 """
127
127
128 idx, edges, ncols, coldiff = coldata
128 idx, edges, ncols, coldiff = coldata
129 assert -2 < coldiff < 2
129 assert -2 < coldiff < 2
130 if coldiff == -1:
130 if coldiff == -1:
131 # Transform
131 # Transform
132 #
132 #
133 # | | | | | |
133 # | | | | | |
134 # o | | into o---+
134 # o | | into o---+
135 # |X / |/ /
135 # |X / |/ /
136 # | | | |
136 # | | | |
137 fix_long_right_edges(edges)
137 fix_long_right_edges(edges)
138
138
139 # add_padding_line says whether to rewrite
139 # add_padding_line says whether to rewrite
140 #
140 #
141 # | | | | | | | |
141 # | | | | | | | |
142 # | o---+ into | o---+
142 # | o---+ into | o---+
143 # | / / | | | # <--- padding line
143 # | / / | | | # <--- padding line
144 # o | | | / /
144 # o | | | / /
145 # o | |
145 # o | |
146 add_padding_line = (len(text) > 2 and coldiff == -1 and
146 add_padding_line = (len(text) > 2 and coldiff == -1 and
147 [x for (x, y) in edges if x + 1 < y])
147 [x for (x, y) in edges if x + 1 < y])
148
148
149 # fix_nodeline_tail says whether to rewrite
149 # fix_nodeline_tail says whether to rewrite
150 #
150 #
151 # | | o | | | | o | |
151 # | | o | | | | o | |
152 # | | |/ / | | |/ /
152 # | | |/ / | | |/ /
153 # | o | | into | o / / # <--- fixed nodeline tail
153 # | o | | into | o / / # <--- fixed nodeline tail
154 # | |/ / | |/ /
154 # | |/ / | |/ /
155 # o | | o | |
155 # o | | o | |
156 fix_nodeline_tail = len(text) <= 2 and not add_padding_line
156 fix_nodeline_tail = len(text) <= 2 and not add_padding_line
157
157
158 # nodeline is the line containing the node character (typically o)
158 # nodeline is the line containing the node character (typically o)
159 nodeline = ["|", " "] * idx
159 nodeline = ["|", " "] * idx
160 nodeline.extend([char, " "])
160 nodeline.extend([char, " "])
161
161
162 nodeline.extend(
162 nodeline.extend(
163 get_nodeline_edges_tail(idx, state[1], ncols, coldiff,
163 get_nodeline_edges_tail(idx, state[1], ncols, coldiff,
164 state[0], fix_nodeline_tail))
164 state[0], fix_nodeline_tail))
165
165
166 # shift_interline is the line containing the non-vertical
166 # shift_interline is the line containing the non-vertical
167 # edges between this entry and the next
167 # edges between this entry and the next
168 shift_interline = ["|", " "] * idx
168 shift_interline = ["|", " "] * idx
169 if coldiff == -1:
169 if coldiff == -1:
170 n_spaces = 1
170 n_spaces = 1
171 edge_ch = "/"
171 edge_ch = "/"
172 elif coldiff == 0:
172 elif coldiff == 0:
173 n_spaces = 2
173 n_spaces = 2
174 edge_ch = "|"
174 edge_ch = "|"
175 else:
175 else:
176 n_spaces = 3
176 n_spaces = 3
177 edge_ch = "\\"
177 edge_ch = "\\"
178 shift_interline.extend(n_spaces * [" "])
178 shift_interline.extend(n_spaces * [" "])
179 shift_interline.extend([edge_ch, " "] * (ncols - idx - 1))
179 shift_interline.extend([edge_ch, " "] * (ncols - idx - 1))
180
180
181 # draw edges from the current node to its parents
181 # draw edges from the current node to its parents
182 draw_edges(edges, nodeline, shift_interline)
182 draw_edges(edges, nodeline, shift_interline)
183
183
184 # lines is the list of all graph lines to print
184 # lines is the list of all graph lines to print
185 lines = [nodeline]
185 lines = [nodeline]
186 if add_padding_line:
186 if add_padding_line:
187 lines.append(get_padding_line(idx, ncols, edges))
187 lines.append(get_padding_line(idx, ncols, edges))
188 lines.append(shift_interline)
188 lines.append(shift_interline)
189
189
190 # make sure that there are as many graph lines as there are
190 # make sure that there are as many graph lines as there are
191 # log strings
191 # log strings
192 while len(text) < len(lines):
192 while len(text) < len(lines):
193 text.append("")
193 text.append("")
194 if len(lines) < len(text):
194 if len(lines) < len(text):
195 extra_interline = ["|", " "] * (ncols + coldiff)
195 extra_interline = ["|", " "] * (ncols + coldiff)
196 while len(lines) < len(text):
196 while len(lines) < len(text):
197 lines.append(extra_interline)
197 lines.append(extra_interline)
198
198
199 # print lines
199 # print lines
200 indentation_level = max(ncols, ncols + coldiff)
200 indentation_level = max(ncols, ncols + coldiff)
201 for (line, logstr) in zip(lines, text):
201 for (line, logstr) in zip(lines, text):
202 ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr)
202 ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr)
203 ui.write(ln.rstrip() + '\n')
203 ui.write(ln.rstrip() + '\n')
204
204
205 # ... and start over
205 # ... and start over
206 state[0] = coldiff
206 state[0] = coldiff
207 state[1] = idx
207 state[1] = idx
208
208
209 def get_revs(repo, rev_opt):
209 def get_revs(repo, rev_opt):
210 if rev_opt:
210 if rev_opt:
211 revs = revrange(repo, rev_opt)
211 revs = revrange(repo, rev_opt)
212 return (max(revs), min(revs))
212 return (max(revs), min(revs))
213 else:
213 else:
214 return (len(repo) - 1, 0)
214 return (len(repo) - 1, 0)
215
215
216 def check_unsupported_flags(opts):
216 def check_unsupported_flags(opts):
217 for op in ["follow", "follow_first", "date", "copies", "keyword", "remove",
217 for op in ["follow", "follow_first", "date", "copies", "keyword", "remove",
218 "only_merges", "user", "only_branch", "prune", "newest_first",
218 "only_merges", "user", "only_branch", "prune", "newest_first",
219 "no_merges", "include", "exclude"]:
219 "no_merges", "include", "exclude"]:
220 if op in opts and opts[op]:
220 if op in opts and opts[op]:
221 raise util.Abort(_("--graph option is incompatible with --%s")
221 raise util.Abort(_("--graph option is incompatible with --%s")
222 % op.replace("_", "-"))
222 % op.replace("_", "-"))
223
223
224 def generate(ui, dag, displayer, showparents, edgefn):
224 def generate(ui, dag, displayer, showparents, edgefn):
225 seen, state = [], asciistate()
225 seen, state = [], asciistate()
226 for rev, type, ctx, parents in dag:
226 for rev, type, ctx, parents in dag:
227 char = ctx.node() in showparents and '@' or 'o'
227 char = ctx.node() in showparents and '@' or 'o'
228 displayer.show(ctx)
228 displayer.show(ctx)
229 lines = displayer.hunk.pop(rev).split('\n')[:-1]
229 lines = displayer.hunk.pop(rev).split('\n')[:-1]
230 ascii(ui, state, type, char, lines, edgefn(seen, rev, parents))
230 ascii(ui, state, type, char, lines, edgefn(seen, rev, parents))
231
231
232 def graphlog(ui, repo, path=None, **opts):
232 def graphlog(ui, repo, path=None, **opts):
233 """show revision history alongside an ASCII revision graph
233 """show revision history alongside an ASCII revision graph
234
234
235 Print a revision history alongside a revision graph drawn with
235 Print a revision history alongside a revision graph drawn with
236 ASCII characters.
236 ASCII characters.
237
237
238 Nodes printed as an @ character are parents of the working
238 Nodes printed as an @ character are parents of the working
239 directory.
239 directory.
240 """
240 """
241
241
242 check_unsupported_flags(opts)
242 check_unsupported_flags(opts)
243 limit = cmdutil.loglimit(opts)
243 limit = cmdutil.loglimit(opts)
244 start, stop = get_revs(repo, opts["rev"])
244 start, stop = get_revs(repo, opts["rev"])
245 if start == nullrev:
245 if start == nullrev:
246 return
246 return
247
247
248 if path:
248 if path:
249 path = util.canonpath(repo.root, os.getcwd(), path)
249 path = util.canonpath(repo.root, os.getcwd(), path)
250 if path: # could be reset in canonpath
250 if path: # could be reset in canonpath
251 revdag = graphmod.filerevs(repo, path, start, stop, limit)
251 revdag = graphmod.filerevs(repo, path, start, stop, limit)
252 else:
252 else:
253 if limit is not None:
253 if limit is not None:
254 stop = max(stop, start - limit + 1)
254 stop = max(stop, start - limit + 1)
255 revdag = graphmod.revisions(repo, start, stop)
255 revdag = graphmod.revisions(repo, start, stop)
256
256
257 displayer = show_changeset(ui, repo, opts, buffered=True)
257 displayer = show_changeset(ui, repo, opts, buffered=True)
258 showparents = [ctx.node() for ctx in repo[None].parents()]
258 showparents = [ctx.node() for ctx in repo[None].parents()]
259 generate(ui, revdag, displayer, showparents, asciiedges)
259 generate(ui, revdag, displayer, showparents, asciiedges)
260
260
261 def graphrevs(repo, nodes, opts):
261 def graphrevs(repo, nodes, opts):
262 limit = cmdutil.loglimit(opts)
262 limit = cmdutil.loglimit(opts)
263 nodes.reverse()
263 nodes.reverse()
264 if limit is not None:
264 if limit is not None:
265 nodes = nodes[:limit]
265 nodes = nodes[:limit]
266 return graphmod.nodes(repo, nodes)
266 return graphmod.nodes(repo, nodes)
267
267
268 def goutgoing(ui, repo, dest=None, **opts):
268 def goutgoing(ui, repo, dest=None, **opts):
269 """show the outgoing changesets alongside an ASCII revision graph
269 """show the outgoing changesets alongside an ASCII revision graph
270
270
271 Print the outgoing changesets alongside a revision graph drawn with
271 Print the outgoing changesets alongside a revision graph drawn with
272 ASCII characters.
272 ASCII characters.
273
273
274 Nodes printed as an @ character are parents of the working
274 Nodes printed as an @ character are parents of the working
275 directory.
275 directory.
276 """
276 """
277
277
278 check_unsupported_flags(opts)
278 check_unsupported_flags(opts)
279 dest = ui.expandpath(dest or 'default-push', dest or 'default')
279 dest = ui.expandpath(dest or 'default-push', dest or 'default')
280 dest, branches = hg.parseurl(dest, opts.get('branch'))
280 dest, branches = hg.parseurl(dest, opts.get('branch'))
281 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
281 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
282 other = hg.repository(hg.remoteui(ui, opts), dest)
282 other = hg.repository(hg.remoteui(ui, opts), dest)
283 if revs:
283 if revs:
284 revs = [repo.lookup(rev) for rev in revs]
284 revs = [repo.lookup(rev) for rev in revs]
285 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
285 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
286 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
286 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
287 if not o:
287 if not o:
288 ui.status(_("no changes found\n"))
288 ui.status(_("no changes found\n"))
289 return
289 return
290
290
291 o = repo.changelog.nodesbetween(o, revs)[0]
291 o = repo.changelog.nodesbetween(o, revs)[0]
292 revdag = graphrevs(repo, o, opts)
292 revdag = graphrevs(repo, o, opts)
293 displayer = show_changeset(ui, repo, opts, buffered=True)
293 displayer = show_changeset(ui, repo, opts, buffered=True)
294 showparents = [ctx.node() for ctx in repo[None].parents()]
294 showparents = [ctx.node() for ctx in repo[None].parents()]
295 generate(ui, revdag, displayer, showparents, asciiedges)
295 generate(ui, revdag, displayer, showparents, asciiedges)
296
296
297 def gincoming(ui, repo, source="default", **opts):
297 def gincoming(ui, repo, source="default", **opts):
298 """show the incoming changesets alongside an ASCII revision graph
298 """show the incoming changesets alongside an ASCII revision graph
299
299
300 Print the incoming changesets alongside a revision graph drawn with
300 Print the incoming changesets alongside a revision graph drawn with
301 ASCII characters.
301 ASCII characters.
302
302
303 Nodes printed as an @ character are parents of the working
303 Nodes printed as an @ character are parents of the working
304 directory.
304 directory.
305 """
305 """
306
306
307 check_unsupported_flags(opts)
307 check_unsupported_flags(opts)
308 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
308 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
309 other = hg.repository(hg.remoteui(repo, opts), source)
309 other = hg.repository(hg.remoteui(repo, opts), source)
310 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
310 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
311 ui.status(_('comparing with %s\n') % url.hidepassword(source))
311 ui.status(_('comparing with %s\n') % url.hidepassword(source))
312 if revs:
312 if revs:
313 revs = [other.lookup(rev) for rev in revs]
313 revs = [other.lookup(rev) for rev in revs]
314 incoming = discovery.findincoming(repo, other, heads=revs,
314 incoming = discovery.findincoming(repo, other, heads=revs,
315 force=opts["force"])
315 force=opts["force"])
316 if not incoming:
316 if not incoming:
317 try:
317 try:
318 os.unlink(opts["bundle"])
318 os.unlink(opts["bundle"])
319 except:
319 except:
320 pass
320 pass
321 ui.status(_("no changes found\n"))
321 ui.status(_("no changes found\n"))
322 return
322 return
323
323
324 cleanup = None
324 cleanup = None
325 try:
325 try:
326
326
327 fname = opts["bundle"]
327 fname = opts["bundle"]
328 if fname or not other.local():
328 if fname or not other.local():
329 # create a bundle (uncompressed if other repo is not local)
329 # create a bundle (uncompressed if other repo is not local)
330 if revs is None:
330 if revs is None:
331 cg = other.changegroup(incoming, "incoming")
331 cg = other.changegroup(incoming, "incoming")
332 else:
332 else:
333 cg = other.changegroupsubset(incoming, revs, 'incoming')
333 cg = other.changegroupsubset(incoming, revs, 'incoming')
334 bundletype = other.local() and "HG10BZ" or "HG10UN"
334 bundletype = other.local() and "HG10BZ" or "HG10UN"
335 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
335 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
336 # keep written bundle?
336 # keep written bundle?
337 if opts["bundle"]:
337 if opts["bundle"]:
338 cleanup = None
338 cleanup = None
339 if not other.local():
339 if not other.local():
340 # use the created uncompressed bundlerepo
340 # use the created uncompressed bundlerepo
341 other = bundlerepo.bundlerepository(ui, repo.root, fname)
341 other = bundlerepo.bundlerepository(ui, repo.root, fname)
342
342
343 chlist = other.changelog.nodesbetween(incoming, revs)[0]
343 chlist = other.changelog.nodesbetween(incoming, revs)[0]
344 revdag = graphrevs(other, chlist, opts)
344 revdag = graphrevs(other, chlist, opts)
345 displayer = show_changeset(ui, other, opts, buffered=True)
345 displayer = show_changeset(ui, other, opts, buffered=True)
346 showparents = [ctx.node() for ctx in repo[None].parents()]
346 showparents = [ctx.node() for ctx in repo[None].parents()]
347 generate(ui, revdag, displayer, showparents, asciiedges)
347 generate(ui, revdag, displayer, showparents, asciiedges)
348
348
349 finally:
349 finally:
350 if hasattr(other, 'close'):
350 if hasattr(other, 'close'):
351 other.close()
351 other.close()
352 if cleanup:
352 if cleanup:
353 os.unlink(cleanup)
353 os.unlink(cleanup)
354
354
355 def uisetup(ui):
355 def uisetup(ui):
356 '''Initialize the extension.'''
356 '''Initialize the extension.'''
357 _wrapcmd(ui, 'log', commands.table, graphlog)
357 _wrapcmd(ui, 'log', commands.table, graphlog)
358 _wrapcmd(ui, 'incoming', commands.table, gincoming)
358 _wrapcmd(ui, 'incoming', commands.table, gincoming)
359 _wrapcmd(ui, 'outgoing', commands.table, goutgoing)
359 _wrapcmd(ui, 'outgoing', commands.table, goutgoing)
360
360
361 def _wrapcmd(ui, cmd, table, wrapfn):
361 def _wrapcmd(ui, cmd, table, wrapfn):
362 '''wrap the command'''
362 '''wrap the command'''
363 def graph(orig, *args, **kwargs):
363 def graph(orig, *args, **kwargs):
364 if kwargs['graph']:
364 if kwargs['graph']:
365 return wrapfn(*args, **kwargs)
365 return wrapfn(*args, **kwargs)
366 return orig(*args, **kwargs)
366 return orig(*args, **kwargs)
367 entry = extensions.wrapcommand(table, cmd, graph)
367 entry = extensions.wrapcommand(table, cmd, graph)
368 entry[1].append(('G', 'graph', None, _("show the revision DAG")))
368 entry[1].append(('G', 'graph', None, _("show the revision DAG")))
369
369
370 cmdtable = {
370 cmdtable = {
371 "glog":
371 "glog":
372 (graphlog,
372 (graphlog,
373 [('l', 'limit', '', _('limit number of changes displayed')),
373 [('l', 'limit', '',
374 _('limit number of changes displayed'), _('NUM')),
374 ('p', 'patch', False, _('show patch')),
375 ('p', 'patch', False, _('show patch')),
375 ('r', 'rev', [], _('show the specified revision or range')),
376 ('r', 'rev', [],
377 _('show the specified revision or range'), _('REV')),
376 ] + templateopts,
378 ] + templateopts,
377 _('hg glog [OPTION]... [FILE]')),
379 _('hg glog [OPTION]... [FILE]')),
378 }
380 }
@@ -1,347 +1,348 b''
1 # Minimal support for git commands on an hg repository
1 # Minimal support for git commands on an hg repository
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''browse the repository in a graphical way
8 '''browse the repository in a graphical way
9
9
10 The hgk extension allows browsing the history of a repository in a
10 The hgk extension allows browsing the history of a repository in a
11 graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is not
11 graphical way. It requires Tcl/Tk version 8.4 or later. (Tcl/Tk is not
12 distributed with Mercurial.)
12 distributed with Mercurial.)
13
13
14 hgk consists of two parts: a Tcl script that does the displaying and
14 hgk consists of two parts: a Tcl script that does the displaying and
15 querying of information, and an extension to Mercurial named hgk.py,
15 querying of information, and an extension to Mercurial named hgk.py,
16 which provides hooks for hgk to get information. hgk can be found in
16 which provides hooks for hgk to get information. hgk can be found in
17 the contrib directory, and the extension is shipped in the hgext
17 the contrib directory, and the extension is shipped in the hgext
18 repository, and needs to be enabled.
18 repository, and needs to be enabled.
19
19
20 The :hg:`view` command will launch the hgk Tcl script. For this command
20 The :hg:`view` command will launch the hgk Tcl script. For this command
21 to work, hgk must be in your search path. Alternately, you can specify
21 to work, hgk must be in your search path. Alternately, you can specify
22 the path to hgk in your .hgrc file::
22 the path to hgk in your .hgrc file::
23
23
24 [hgk]
24 [hgk]
25 path=/location/of/hgk
25 path=/location/of/hgk
26
26
27 hgk can make use of the extdiff extension to visualize revisions.
27 hgk can make use of the extdiff extension to visualize revisions.
28 Assuming you had already configured extdiff vdiff command, just add::
28 Assuming you had already configured extdiff vdiff command, just add::
29
29
30 [hgk]
30 [hgk]
31 vdiff=vdiff
31 vdiff=vdiff
32
32
33 Revisions context menu will now display additional entries to fire
33 Revisions context menu will now display additional entries to fire
34 vdiff on hovered and selected revisions.
34 vdiff on hovered and selected revisions.
35 '''
35 '''
36
36
37 import os
37 import os
38 from mercurial import commands, util, patch, revlog, cmdutil
38 from mercurial import commands, util, patch, revlog, cmdutil
39 from mercurial.node import nullid, nullrev, short
39 from mercurial.node import nullid, nullrev, short
40 from mercurial.i18n import _
40 from mercurial.i18n import _
41
41
42 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
42 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
43 """diff trees from two commits"""
43 """diff trees from two commits"""
44 def __difftree(repo, node1, node2, files=[]):
44 def __difftree(repo, node1, node2, files=[]):
45 assert node2 is not None
45 assert node2 is not None
46 mmap = repo[node1].manifest()
46 mmap = repo[node1].manifest()
47 mmap2 = repo[node2].manifest()
47 mmap2 = repo[node2].manifest()
48 m = cmdutil.match(repo, files)
48 m = cmdutil.match(repo, files)
49 modified, added, removed = repo.status(node1, node2, m)[:3]
49 modified, added, removed = repo.status(node1, node2, m)[:3]
50 empty = short(nullid)
50 empty = short(nullid)
51
51
52 for f in modified:
52 for f in modified:
53 # TODO get file permissions
53 # TODO get file permissions
54 ui.write(":100664 100664 %s %s M\t%s\t%s\n" %
54 ui.write(":100664 100664 %s %s M\t%s\t%s\n" %
55 (short(mmap[f]), short(mmap2[f]), f, f))
55 (short(mmap[f]), short(mmap2[f]), f, f))
56 for f in added:
56 for f in added:
57 ui.write(":000000 100664 %s %s N\t%s\t%s\n" %
57 ui.write(":000000 100664 %s %s N\t%s\t%s\n" %
58 (empty, short(mmap2[f]), f, f))
58 (empty, short(mmap2[f]), f, f))
59 for f in removed:
59 for f in removed:
60 ui.write(":100664 000000 %s %s D\t%s\t%s\n" %
60 ui.write(":100664 000000 %s %s D\t%s\t%s\n" %
61 (short(mmap[f]), empty, f, f))
61 (short(mmap[f]), empty, f, f))
62 ##
62 ##
63
63
64 while True:
64 while True:
65 if opts['stdin']:
65 if opts['stdin']:
66 try:
66 try:
67 line = raw_input().split(' ')
67 line = raw_input().split(' ')
68 node1 = line[0]
68 node1 = line[0]
69 if len(line) > 1:
69 if len(line) > 1:
70 node2 = line[1]
70 node2 = line[1]
71 else:
71 else:
72 node2 = None
72 node2 = None
73 except EOFError:
73 except EOFError:
74 break
74 break
75 node1 = repo.lookup(node1)
75 node1 = repo.lookup(node1)
76 if node2:
76 if node2:
77 node2 = repo.lookup(node2)
77 node2 = repo.lookup(node2)
78 else:
78 else:
79 node2 = node1
79 node2 = node1
80 node1 = repo.changelog.parents(node1)[0]
80 node1 = repo.changelog.parents(node1)[0]
81 if opts['patch']:
81 if opts['patch']:
82 if opts['pretty']:
82 if opts['pretty']:
83 catcommit(ui, repo, node2, "")
83 catcommit(ui, repo, node2, "")
84 m = cmdutil.match(repo, files)
84 m = cmdutil.match(repo, files)
85 chunks = patch.diff(repo, node1, node2, match=m,
85 chunks = patch.diff(repo, node1, node2, match=m,
86 opts=patch.diffopts(ui, {'git': True}))
86 opts=patch.diffopts(ui, {'git': True}))
87 for chunk in chunks:
87 for chunk in chunks:
88 ui.write(chunk)
88 ui.write(chunk)
89 else:
89 else:
90 __difftree(repo, node1, node2, files=files)
90 __difftree(repo, node1, node2, files=files)
91 if not opts['stdin']:
91 if not opts['stdin']:
92 break
92 break
93
93
94 def catcommit(ui, repo, n, prefix, ctx=None):
94 def catcommit(ui, repo, n, prefix, ctx=None):
95 nlprefix = '\n' + prefix
95 nlprefix = '\n' + prefix
96 if ctx is None:
96 if ctx is None:
97 ctx = repo[n]
97 ctx = repo[n]
98 ui.write("tree %s\n" % short(ctx.changeset()[0])) # use ctx.node() instead ??
98 ui.write("tree %s\n" % short(ctx.changeset()[0])) # use ctx.node() instead ??
99 for p in ctx.parents():
99 for p in ctx.parents():
100 ui.write("parent %s\n" % p)
100 ui.write("parent %s\n" % p)
101
101
102 date = ctx.date()
102 date = ctx.date()
103 description = ctx.description().replace("\0", "")
103 description = ctx.description().replace("\0", "")
104 lines = description.splitlines()
104 lines = description.splitlines()
105 if lines and lines[-1].startswith('committer:'):
105 if lines and lines[-1].startswith('committer:'):
106 committer = lines[-1].split(': ')[1].rstrip()
106 committer = lines[-1].split(': ')[1].rstrip()
107 else:
107 else:
108 committer = ctx.user()
108 committer = ctx.user()
109
109
110 ui.write("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1]))
110 ui.write("author %s %s %s\n" % (ctx.user(), int(date[0]), date[1]))
111 ui.write("committer %s %s %s\n" % (committer, int(date[0]), date[1]))
111 ui.write("committer %s %s %s\n" % (committer, int(date[0]), date[1]))
112 ui.write("revision %d\n" % ctx.rev())
112 ui.write("revision %d\n" % ctx.rev())
113 ui.write("branch %s\n\n" % ctx.branch())
113 ui.write("branch %s\n\n" % ctx.branch())
114
114
115 if prefix != "":
115 if prefix != "":
116 ui.write("%s%s\n" % (prefix, description.replace('\n', nlprefix).strip()))
116 ui.write("%s%s\n" % (prefix, description.replace('\n', nlprefix).strip()))
117 else:
117 else:
118 ui.write(description + "\n")
118 ui.write(description + "\n")
119 if prefix:
119 if prefix:
120 ui.write('\0')
120 ui.write('\0')
121
121
122 def base(ui, repo, node1, node2):
122 def base(ui, repo, node1, node2):
123 """output common ancestor information"""
123 """output common ancestor information"""
124 node1 = repo.lookup(node1)
124 node1 = repo.lookup(node1)
125 node2 = repo.lookup(node2)
125 node2 = repo.lookup(node2)
126 n = repo.changelog.ancestor(node1, node2)
126 n = repo.changelog.ancestor(node1, node2)
127 ui.write(short(n) + "\n")
127 ui.write(short(n) + "\n")
128
128
129 def catfile(ui, repo, type=None, r=None, **opts):
129 def catfile(ui, repo, type=None, r=None, **opts):
130 """cat a specific revision"""
130 """cat a specific revision"""
131 # in stdin mode, every line except the commit is prefixed with two
131 # in stdin mode, every line except the commit is prefixed with two
132 # spaces. This way the our caller can find the commit without magic
132 # spaces. This way the our caller can find the commit without magic
133 # strings
133 # strings
134 #
134 #
135 prefix = ""
135 prefix = ""
136 if opts['stdin']:
136 if opts['stdin']:
137 try:
137 try:
138 (type, r) = raw_input().split(' ')
138 (type, r) = raw_input().split(' ')
139 prefix = " "
139 prefix = " "
140 except EOFError:
140 except EOFError:
141 return
141 return
142
142
143 else:
143 else:
144 if not type or not r:
144 if not type or not r:
145 ui.warn(_("cat-file: type or revision not supplied\n"))
145 ui.warn(_("cat-file: type or revision not supplied\n"))
146 commands.help_(ui, 'cat-file')
146 commands.help_(ui, 'cat-file')
147
147
148 while r:
148 while r:
149 if type != "commit":
149 if type != "commit":
150 ui.warn(_("aborting hg cat-file only understands commits\n"))
150 ui.warn(_("aborting hg cat-file only understands commits\n"))
151 return 1
151 return 1
152 n = repo.lookup(r)
152 n = repo.lookup(r)
153 catcommit(ui, repo, n, prefix)
153 catcommit(ui, repo, n, prefix)
154 if opts['stdin']:
154 if opts['stdin']:
155 try:
155 try:
156 (type, r) = raw_input().split(' ')
156 (type, r) = raw_input().split(' ')
157 except EOFError:
157 except EOFError:
158 break
158 break
159 else:
159 else:
160 break
160 break
161
161
162 # git rev-tree is a confusing thing. You can supply a number of
162 # git rev-tree is a confusing thing. You can supply a number of
163 # commit sha1s on the command line, and it walks the commit history
163 # commit sha1s on the command line, and it walks the commit history
164 # telling you which commits are reachable from the supplied ones via
164 # telling you which commits are reachable from the supplied ones via
165 # a bitmask based on arg position.
165 # a bitmask based on arg position.
166 # you can specify a commit to stop at by starting the sha1 with ^
166 # you can specify a commit to stop at by starting the sha1 with ^
167 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
167 def revtree(ui, args, repo, full="tree", maxnr=0, parents=False):
168 def chlogwalk():
168 def chlogwalk():
169 count = len(repo)
169 count = len(repo)
170 i = count
170 i = count
171 l = [0] * 100
171 l = [0] * 100
172 chunk = 100
172 chunk = 100
173 while True:
173 while True:
174 if chunk > i:
174 if chunk > i:
175 chunk = i
175 chunk = i
176 i = 0
176 i = 0
177 else:
177 else:
178 i -= chunk
178 i -= chunk
179
179
180 for x in xrange(chunk):
180 for x in xrange(chunk):
181 if i + x >= count:
181 if i + x >= count:
182 l[chunk - x:] = [0] * (chunk - x)
182 l[chunk - x:] = [0] * (chunk - x)
183 break
183 break
184 if full != None:
184 if full != None:
185 l[x] = repo[i + x]
185 l[x] = repo[i + x]
186 l[x].changeset() # force reading
186 l[x].changeset() # force reading
187 else:
187 else:
188 l[x] = 1
188 l[x] = 1
189 for x in xrange(chunk - 1, -1, -1):
189 for x in xrange(chunk - 1, -1, -1):
190 if l[x] != 0:
190 if l[x] != 0:
191 yield (i + x, full != None and l[x] or None)
191 yield (i + x, full != None and l[x] or None)
192 if i == 0:
192 if i == 0:
193 break
193 break
194
194
195 # calculate and return the reachability bitmask for sha
195 # calculate and return the reachability bitmask for sha
196 def is_reachable(ar, reachable, sha):
196 def is_reachable(ar, reachable, sha):
197 if len(ar) == 0:
197 if len(ar) == 0:
198 return 1
198 return 1
199 mask = 0
199 mask = 0
200 for i in xrange(len(ar)):
200 for i in xrange(len(ar)):
201 if sha in reachable[i]:
201 if sha in reachable[i]:
202 mask |= 1 << i
202 mask |= 1 << i
203
203
204 return mask
204 return mask
205
205
206 reachable = []
206 reachable = []
207 stop_sha1 = []
207 stop_sha1 = []
208 want_sha1 = []
208 want_sha1 = []
209 count = 0
209 count = 0
210
210
211 # figure out which commits they are asking for and which ones they
211 # figure out which commits they are asking for and which ones they
212 # want us to stop on
212 # want us to stop on
213 for i, arg in enumerate(args):
213 for i, arg in enumerate(args):
214 if arg.startswith('^'):
214 if arg.startswith('^'):
215 s = repo.lookup(arg[1:])
215 s = repo.lookup(arg[1:])
216 stop_sha1.append(s)
216 stop_sha1.append(s)
217 want_sha1.append(s)
217 want_sha1.append(s)
218 elif arg != 'HEAD':
218 elif arg != 'HEAD':
219 want_sha1.append(repo.lookup(arg))
219 want_sha1.append(repo.lookup(arg))
220
220
221 # calculate the graph for the supplied commits
221 # calculate the graph for the supplied commits
222 for i, n in enumerate(want_sha1):
222 for i, n in enumerate(want_sha1):
223 reachable.append(set())
223 reachable.append(set())
224 visit = [n]
224 visit = [n]
225 reachable[i].add(n)
225 reachable[i].add(n)
226 while visit:
226 while visit:
227 n = visit.pop(0)
227 n = visit.pop(0)
228 if n in stop_sha1:
228 if n in stop_sha1:
229 continue
229 continue
230 for p in repo.changelog.parents(n):
230 for p in repo.changelog.parents(n):
231 if p not in reachable[i]:
231 if p not in reachable[i]:
232 reachable[i].add(p)
232 reachable[i].add(p)
233 visit.append(p)
233 visit.append(p)
234 if p in stop_sha1:
234 if p in stop_sha1:
235 continue
235 continue
236
236
237 # walk the repository looking for commits that are in our
237 # walk the repository looking for commits that are in our
238 # reachability graph
238 # reachability graph
239 for i, ctx in chlogwalk():
239 for i, ctx in chlogwalk():
240 n = repo.changelog.node(i)
240 n = repo.changelog.node(i)
241 mask = is_reachable(want_sha1, reachable, n)
241 mask = is_reachable(want_sha1, reachable, n)
242 if mask:
242 if mask:
243 parentstr = ""
243 parentstr = ""
244 if parents:
244 if parents:
245 pp = repo.changelog.parents(n)
245 pp = repo.changelog.parents(n)
246 if pp[0] != nullid:
246 if pp[0] != nullid:
247 parentstr += " " + short(pp[0])
247 parentstr += " " + short(pp[0])
248 if pp[1] != nullid:
248 if pp[1] != nullid:
249 parentstr += " " + short(pp[1])
249 parentstr += " " + short(pp[1])
250 if not full:
250 if not full:
251 ui.write("%s%s\n" % (short(n), parentstr))
251 ui.write("%s%s\n" % (short(n), parentstr))
252 elif full == "commit":
252 elif full == "commit":
253 ui.write("%s%s\n" % (short(n), parentstr))
253 ui.write("%s%s\n" % (short(n), parentstr))
254 catcommit(ui, repo, n, ' ', ctx)
254 catcommit(ui, repo, n, ' ', ctx)
255 else:
255 else:
256 (p1, p2) = repo.changelog.parents(n)
256 (p1, p2) = repo.changelog.parents(n)
257 (h, h1, h2) = map(short, (n, p1, p2))
257 (h, h1, h2) = map(short, (n, p1, p2))
258 (i1, i2) = map(repo.changelog.rev, (p1, p2))
258 (i1, i2) = map(repo.changelog.rev, (p1, p2))
259
259
260 date = ctx.date()[0]
260 date = ctx.date()[0]
261 ui.write("%s %s:%s" % (date, h, mask))
261 ui.write("%s %s:%s" % (date, h, mask))
262 mask = is_reachable(want_sha1, reachable, p1)
262 mask = is_reachable(want_sha1, reachable, p1)
263 if i1 != nullrev and mask > 0:
263 if i1 != nullrev and mask > 0:
264 ui.write("%s:%s " % (h1, mask)),
264 ui.write("%s:%s " % (h1, mask)),
265 mask = is_reachable(want_sha1, reachable, p2)
265 mask = is_reachable(want_sha1, reachable, p2)
266 if i2 != nullrev and mask > 0:
266 if i2 != nullrev and mask > 0:
267 ui.write("%s:%s " % (h2, mask))
267 ui.write("%s:%s " % (h2, mask))
268 ui.write("\n")
268 ui.write("\n")
269 if maxnr and count >= maxnr:
269 if maxnr and count >= maxnr:
270 break
270 break
271 count += 1
271 count += 1
272
272
273 def revparse(ui, repo, *revs, **opts):
273 def revparse(ui, repo, *revs, **opts):
274 """parse given revisions"""
274 """parse given revisions"""
275 def revstr(rev):
275 def revstr(rev):
276 if rev == 'HEAD':
276 if rev == 'HEAD':
277 rev = 'tip'
277 rev = 'tip'
278 return revlog.hex(repo.lookup(rev))
278 return revlog.hex(repo.lookup(rev))
279
279
280 for r in revs:
280 for r in revs:
281 revrange = r.split(':', 1)
281 revrange = r.split(':', 1)
282 ui.write('%s\n' % revstr(revrange[0]))
282 ui.write('%s\n' % revstr(revrange[0]))
283 if len(revrange) == 2:
283 if len(revrange) == 2:
284 ui.write('^%s\n' % revstr(revrange[1]))
284 ui.write('^%s\n' % revstr(revrange[1]))
285
285
286 # git rev-list tries to order things by date, and has the ability to stop
286 # git rev-list tries to order things by date, and has the ability to stop
287 # at a given commit without walking the whole repo. TODO add the stop
287 # at a given commit without walking the whole repo. TODO add the stop
288 # parameter
288 # parameter
289 def revlist(ui, repo, *revs, **opts):
289 def revlist(ui, repo, *revs, **opts):
290 """print revisions"""
290 """print revisions"""
291 if opts['header']:
291 if opts['header']:
292 full = "commit"
292 full = "commit"
293 else:
293 else:
294 full = None
294 full = None
295 copy = [x for x in revs]
295 copy = [x for x in revs]
296 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
296 revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
297
297
298 def config(ui, repo, **opts):
298 def config(ui, repo, **opts):
299 """print extension options"""
299 """print extension options"""
300 def writeopt(name, value):
300 def writeopt(name, value):
301 ui.write('k=%s\nv=%s\n' % (name, value))
301 ui.write('k=%s\nv=%s\n' % (name, value))
302
302
303 writeopt('vdiff', ui.config('hgk', 'vdiff', ''))
303 writeopt('vdiff', ui.config('hgk', 'vdiff', ''))
304
304
305
305
306 def view(ui, repo, *etc, **opts):
306 def view(ui, repo, *etc, **opts):
307 "start interactive history viewer"
307 "start interactive history viewer"
308 os.chdir(repo.root)
308 os.chdir(repo.root)
309 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
309 optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
310 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
310 cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
311 ui.debug("running %s\n" % cmd)
311 ui.debug("running %s\n" % cmd)
312 util.system(cmd)
312 util.system(cmd)
313
313
314 cmdtable = {
314 cmdtable = {
315 "^view":
315 "^view":
316 (view,
316 (view,
317 [('l', 'limit', '', _('limit number of changes displayed'))],
317 [('l', 'limit', '',
318 _('limit number of changes displayed'), _('NUM'))],
318 _('hg view [-l LIMIT] [REVRANGE]')),
319 _('hg view [-l LIMIT] [REVRANGE]')),
319 "debug-diff-tree":
320 "debug-diff-tree":
320 (difftree,
321 (difftree,
321 [('p', 'patch', None, _('generate patch')),
322 [('p', 'patch', None, _('generate patch')),
322 ('r', 'recursive', None, _('recursive')),
323 ('r', 'recursive', None, _('recursive')),
323 ('P', 'pretty', None, _('pretty')),
324 ('P', 'pretty', None, _('pretty')),
324 ('s', 'stdin', None, _('stdin')),
325 ('s', 'stdin', None, _('stdin')),
325 ('C', 'copy', None, _('detect copies')),
326 ('C', 'copy', None, _('detect copies')),
326 ('S', 'search', "", _('search'))],
327 ('S', 'search', "", _('search'))],
327 _('hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...')),
328 _('hg git-diff-tree [OPTION]... NODE1 NODE2 [FILE]...')),
328 "debug-cat-file":
329 "debug-cat-file":
329 (catfile,
330 (catfile,
330 [('s', 'stdin', None, _('stdin'))],
331 [('s', 'stdin', None, _('stdin'))],
331 _('hg debug-cat-file [OPTION]... TYPE FILE')),
332 _('hg debug-cat-file [OPTION]... TYPE FILE')),
332 "debug-config":
333 "debug-config":
333 (config, [], _('hg debug-config')),
334 (config, [], _('hg debug-config')),
334 "debug-merge-base":
335 "debug-merge-base":
335 (base, [], _('hg debug-merge-base REV REV')),
336 (base, [], _('hg debug-merge-base REV REV')),
336 "debug-rev-parse":
337 "debug-rev-parse":
337 (revparse,
338 (revparse,
338 [('', 'default', '', _('ignored'))],
339 [('', 'default', '', _('ignored'))],
339 _('hg debug-rev-parse REV')),
340 _('hg debug-rev-parse REV')),
340 "debug-rev-list":
341 "debug-rev-list":
341 (revlist,
342 (revlist,
342 [('H', 'header', None, _('header')),
343 [('H', 'header', None, _('header')),
343 ('t', 'topo-order', None, _('topo-order')),
344 ('t', 'topo-order', None, _('topo-order')),
344 ('p', 'parents', None, _('parents')),
345 ('p', 'parents', None, _('parents')),
345 ('n', 'max-count', 0, _('max-count'))],
346 ('n', 'max-count', 0, _('max-count'))],
346 _('hg debug-rev-list [OPTION]... REV...')),
347 _('hg debug-rev-list [OPTION]... REV...')),
347 }
348 }
@@ -1,86 +1,89 b''
1 # __init__.py - inotify-based status acceleration for Linux
1 # __init__.py - inotify-based status acceleration for Linux
2 #
2 #
3 # Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
3 # Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
4 # Copyright 2007, 2008 Brendan Cully <brendan@kublai.com>
4 # Copyright 2007, 2008 Brendan Cully <brendan@kublai.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''accelerate status report using Linux's inotify service'''
9 '''accelerate status report using Linux's inotify service'''
10
10
11 # todo: socket permissions
11 # todo: socket permissions
12
12
13 from mercurial.i18n import _
13 from mercurial.i18n import _
14 import server
14 import server
15 from client import client, QueryFailed
15 from client import client, QueryFailed
16
16
17 def serve(ui, repo, **opts):
17 def serve(ui, repo, **opts):
18 '''start an inotify server for this repository'''
18 '''start an inotify server for this repository'''
19 server.start(ui, repo.dirstate, repo.root, opts)
19 server.start(ui, repo.dirstate, repo.root, opts)
20
20
21 def debuginotify(ui, repo, **opts):
21 def debuginotify(ui, repo, **opts):
22 '''debugging information for inotify extension
22 '''debugging information for inotify extension
23
23
24 Prints the list of directories being watched by the inotify server.
24 Prints the list of directories being watched by the inotify server.
25 '''
25 '''
26 cli = client(ui, repo)
26 cli = client(ui, repo)
27 response = cli.debugquery()
27 response = cli.debugquery()
28
28
29 ui.write(_('directories being watched:\n'))
29 ui.write(_('directories being watched:\n'))
30 for path in response:
30 for path in response:
31 ui.write((' %s/\n') % path)
31 ui.write((' %s/\n') % path)
32
32
33 def reposetup(ui, repo):
33 def reposetup(ui, repo):
34 if not hasattr(repo, 'dirstate'):
34 if not hasattr(repo, 'dirstate'):
35 return
35 return
36
36
37 class inotifydirstate(repo.dirstate.__class__):
37 class inotifydirstate(repo.dirstate.__class__):
38
38
39 # We'll set this to false after an unsuccessful attempt so that
39 # We'll set this to false after an unsuccessful attempt so that
40 # next calls of status() within the same instance don't try again
40 # next calls of status() within the same instance don't try again
41 # to start an inotify server if it won't start.
41 # to start an inotify server if it won't start.
42 _inotifyon = True
42 _inotifyon = True
43
43
44 def status(self, match, subrepos, ignored, clean, unknown):
44 def status(self, match, subrepos, ignored, clean, unknown):
45 files = match.files()
45 files = match.files()
46 if '.' in files:
46 if '.' in files:
47 files = []
47 files = []
48 if self._inotifyon and not ignored and not subrepos and not self._dirty:
48 if self._inotifyon and not ignored and not subrepos and not self._dirty:
49 cli = client(ui, repo)
49 cli = client(ui, repo)
50 try:
50 try:
51 result = cli.statusquery(files, match, False,
51 result = cli.statusquery(files, match, False,
52 clean, unknown)
52 clean, unknown)
53 except QueryFailed, instr:
53 except QueryFailed, instr:
54 ui.debug(str(instr))
54 ui.debug(str(instr))
55 # don't retry within the same hg instance
55 # don't retry within the same hg instance
56 inotifydirstate._inotifyon = False
56 inotifydirstate._inotifyon = False
57 pass
57 pass
58 else:
58 else:
59 if ui.config('inotify', 'debug'):
59 if ui.config('inotify', 'debug'):
60 r2 = super(inotifydirstate, self).status(
60 r2 = super(inotifydirstate, self).status(
61 match, [], False, clean, unknown)
61 match, [], False, clean, unknown)
62 for c, a, b in zip('LMARDUIC', result, r2):
62 for c, a, b in zip('LMARDUIC', result, r2):
63 for f in a:
63 for f in a:
64 if f not in b:
64 if f not in b:
65 ui.warn('*** inotify: %s +%s\n' % (c, f))
65 ui.warn('*** inotify: %s +%s\n' % (c, f))
66 for f in b:
66 for f in b:
67 if f not in a:
67 if f not in a:
68 ui.warn('*** inotify: %s -%s\n' % (c, f))
68 ui.warn('*** inotify: %s -%s\n' % (c, f))
69 result = r2
69 result = r2
70 return result
70 return result
71 return super(inotifydirstate, self).status(
71 return super(inotifydirstate, self).status(
72 match, subrepos, ignored, clean, unknown)
72 match, subrepos, ignored, clean, unknown)
73
73
74 repo.dirstate.__class__ = inotifydirstate
74 repo.dirstate.__class__ = inotifydirstate
75
75
76 cmdtable = {
76 cmdtable = {
77 'debuginotify':
77 'debuginotify':
78 (debuginotify, [], ('hg debuginotify')),
78 (debuginotify, [], ('hg debuginotify')),
79 '^inserve':
79 '^inserve':
80 (serve,
80 (serve,
81 [('d', 'daemon', None, _('run server in background')),
81 [('d', 'daemon', None, _('run server in background')),
82 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
82 ('', 'daemon-pipefds', '',
83 ('t', 'idle-timeout', '', _('minutes to sit idle before exiting')),
83 _('used internally by daemon mode'), _('NUM')),
84 ('', 'pid-file', '', _('name of file to write process ID to'))],
84 ('t', 'idle-timeout', '',
85 _('minutes to sit idle before exiting'), _('NUM')),
86 ('', 'pid-file', '',
87 _('name of file to write process ID to'), _('FILE'))],
85 _('hg inserve [OPTION]...')),
88 _('hg inserve [OPTION]...')),
86 }
89 }
@@ -1,575 +1,576 b''
1 # keyword.py - $Keyword$ expansion for Mercurial
1 # keyword.py - $Keyword$ expansion for Mercurial
2 #
2 #
3 # Copyright 2007-2010 Christian Ebert <blacktrash@gmx.net>
3 # Copyright 2007-2010 Christian Ebert <blacktrash@gmx.net>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 #
7 #
8 # $Id$
8 # $Id$
9 #
9 #
10 # Keyword expansion hack against the grain of a DSCM
10 # Keyword expansion hack against the grain of a DSCM
11 #
11 #
12 # There are many good reasons why this is not needed in a distributed
12 # There are many good reasons why this is not needed in a distributed
13 # SCM, still it may be useful in very small projects based on single
13 # SCM, still it may be useful in very small projects based on single
14 # files (like LaTeX packages), that are mostly addressed to an
14 # files (like LaTeX packages), that are mostly addressed to an
15 # audience not running a version control system.
15 # audience not running a version control system.
16 #
16 #
17 # For in-depth discussion refer to
17 # For in-depth discussion refer to
18 # <http://mercurial.selenic.com/wiki/KeywordPlan>.
18 # <http://mercurial.selenic.com/wiki/KeywordPlan>.
19 #
19 #
20 # Keyword expansion is based on Mercurial's changeset template mappings.
20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 #
21 #
22 # Binary files are not touched.
22 # Binary files are not touched.
23 #
23 #
24 # Files to act upon/ignore are specified in the [keyword] section.
24 # Files to act upon/ignore are specified in the [keyword] section.
25 # Customized keyword template mappings in the [keywordmaps] section.
25 # Customized keyword template mappings in the [keywordmaps] section.
26 #
26 #
27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
28
28
29 '''expand keywords in tracked files
29 '''expand keywords in tracked files
30
30
31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
32 tracked text files selected by your configuration.
32 tracked text files selected by your configuration.
33
33
34 Keywords are only expanded in local repositories and not stored in the
34 Keywords are only expanded in local repositories and not stored in the
35 change history. The mechanism can be regarded as a convenience for the
35 change history. The mechanism can be regarded as a convenience for the
36 current user or for archive distribution.
36 current user or for archive distribution.
37
37
38 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
38 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
39 sections of hgrc files.
39 sections of hgrc files.
40
40
41 Example::
41 Example::
42
42
43 [keyword]
43 [keyword]
44 # expand keywords in every python file except those matching "x*"
44 # expand keywords in every python file except those matching "x*"
45 **.py =
45 **.py =
46 x* = ignore
46 x* = ignore
47
47
48 [keywordset]
48 [keywordset]
49 # prefer svn- over cvs-like default keywordmaps
49 # prefer svn- over cvs-like default keywordmaps
50 svn = True
50 svn = True
51
51
52 NOTE: the more specific you are in your filename patterns the less you
52 NOTE: the more specific you are in your filename patterns the less you
53 lose speed in huge repositories.
53 lose speed in huge repositories.
54
54
55 For [keywordmaps] template mapping and expansion demonstration and
55 For [keywordmaps] template mapping and expansion demonstration and
56 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
56 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
57 available templates and filters.
57 available templates and filters.
58
58
59 Three additional date template filters are provided::
59 Three additional date template filters are provided::
60
60
61 utcdate "2006/09/18 15:13:13"
61 utcdate "2006/09/18 15:13:13"
62 svnutcdate "2006-09-18 15:13:13Z"
62 svnutcdate "2006-09-18 15:13:13Z"
63 svnisodate "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
63 svnisodate "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
64
64
65 The default template mappings (view with :hg:`kwdemo -d`) can be
65 The default template mappings (view with :hg:`kwdemo -d`) can be
66 replaced with customized keywords and templates. Again, run
66 replaced with customized keywords and templates. Again, run
67 :hg:`kwdemo` to control the results of your config changes.
67 :hg:`kwdemo` to control the results of your config changes.
68
68
69 Before changing/disabling active keywords, run :hg:`kwshrink` to avoid
69 Before changing/disabling active keywords, run :hg:`kwshrink` to avoid
70 the risk of inadvertently storing expanded keywords in the change
70 the risk of inadvertently storing expanded keywords in the change
71 history.
71 history.
72
72
73 To force expansion after enabling it, or a configuration change, run
73 To force expansion after enabling it, or a configuration change, run
74 :hg:`kwexpand`.
74 :hg:`kwexpand`.
75
75
76 Expansions spanning more than one line and incremental expansions,
76 Expansions spanning more than one line and incremental expansions,
77 like CVS' $Log$, are not supported. A keyword template map "Log =
77 like CVS' $Log$, are not supported. A keyword template map "Log =
78 {desc}" expands to the first line of the changeset description.
78 {desc}" expands to the first line of the changeset description.
79 '''
79 '''
80
80
81 from mercurial import commands, cmdutil, dispatch, filelog, revlog, extensions
81 from mercurial import commands, cmdutil, dispatch, filelog, revlog, extensions
82 from mercurial import patch, localrepo, templater, templatefilters, util, match
82 from mercurial import patch, localrepo, templater, templatefilters, util, match
83 from mercurial.hgweb import webcommands
83 from mercurial.hgweb import webcommands
84 from mercurial.i18n import _
84 from mercurial.i18n import _
85 import re, shutil, tempfile
85 import re, shutil, tempfile
86
86
87 commands.optionalrepo += ' kwdemo'
87 commands.optionalrepo += ' kwdemo'
88
88
89 # hg commands that do not act on keywords
89 # hg commands that do not act on keywords
90 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
90 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
91 ' log outgoing push rename rollback tip verify'
91 ' log outgoing push rename rollback tip verify'
92 ' convert email glog')
92 ' convert email glog')
93
93
94 # hg commands that trigger expansion only when writing to working dir,
94 # hg commands that trigger expansion only when writing to working dir,
95 # not when reading filelog, and unexpand when reading from working dir
95 # not when reading filelog, and unexpand when reading from working dir
96 restricted = 'merge record qrecord resolve transplant'
96 restricted = 'merge record qrecord resolve transplant'
97
97
98 # commands using dorecord
98 # commands using dorecord
99 recordcommands = 'record qrecord'
99 recordcommands = 'record qrecord'
100 # names of extensions using dorecord
100 # names of extensions using dorecord
101 recordextensions = 'record'
101 recordextensions = 'record'
102
102
103 # date like in cvs' $Date
103 # date like in cvs' $Date
104 utcdate = lambda x: util.datestr((x[0], 0), '%Y/%m/%d %H:%M:%S')
104 utcdate = lambda x: util.datestr((x[0], 0), '%Y/%m/%d %H:%M:%S')
105 # date like in svn's $Date
105 # date like in svn's $Date
106 svnisodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
106 svnisodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
107 # date like in svn's $Id
107 # date like in svn's $Id
108 svnutcdate = lambda x: util.datestr((x[0], 0), '%Y-%m-%d %H:%M:%SZ')
108 svnutcdate = lambda x: util.datestr((x[0], 0), '%Y-%m-%d %H:%M:%SZ')
109
109
110 # make keyword tools accessible
110 # make keyword tools accessible
111 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
111 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
112
112
113
113
114 def _defaultkwmaps(ui):
114 def _defaultkwmaps(ui):
115 '''Returns default keywordmaps according to keywordset configuration.'''
115 '''Returns default keywordmaps according to keywordset configuration.'''
116 templates = {
116 templates = {
117 'Revision': '{node|short}',
117 'Revision': '{node|short}',
118 'Author': '{author|user}',
118 'Author': '{author|user}',
119 }
119 }
120 kwsets = ({
120 kwsets = ({
121 'Date': '{date|utcdate}',
121 'Date': '{date|utcdate}',
122 'RCSfile': '{file|basename},v',
122 'RCSfile': '{file|basename},v',
123 'RCSFile': '{file|basename},v', # kept for backwards compatibility
123 'RCSFile': '{file|basename},v', # kept for backwards compatibility
124 # with hg-keyword
124 # with hg-keyword
125 'Source': '{root}/{file},v',
125 'Source': '{root}/{file},v',
126 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
126 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
127 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
127 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
128 }, {
128 }, {
129 'Date': '{date|svnisodate}',
129 'Date': '{date|svnisodate}',
130 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
130 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
131 'LastChangedRevision': '{node|short}',
131 'LastChangedRevision': '{node|short}',
132 'LastChangedBy': '{author|user}',
132 'LastChangedBy': '{author|user}',
133 'LastChangedDate': '{date|svnisodate}',
133 'LastChangedDate': '{date|svnisodate}',
134 })
134 })
135 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
135 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
136 return templates
136 return templates
137
137
138 class kwtemplater(object):
138 class kwtemplater(object):
139 '''
139 '''
140 Sets up keyword templates, corresponding keyword regex, and
140 Sets up keyword templates, corresponding keyword regex, and
141 provides keyword substitution functions.
141 provides keyword substitution functions.
142 '''
142 '''
143
143
144 def __init__(self, ui, repo):
144 def __init__(self, ui, repo):
145 self.ui = ui
145 self.ui = ui
146 self.repo = repo
146 self.repo = repo
147 self.match = match.match(repo.root, '', [],
147 self.match = match.match(repo.root, '', [],
148 kwtools['inc'], kwtools['exc'])
148 kwtools['inc'], kwtools['exc'])
149 self.restrict = kwtools['hgcmd'] in restricted.split()
149 self.restrict = kwtools['hgcmd'] in restricted.split()
150 self.record = kwtools['hgcmd'] in recordcommands.split()
150 self.record = kwtools['hgcmd'] in recordcommands.split()
151
151
152 kwmaps = self.ui.configitems('keywordmaps')
152 kwmaps = self.ui.configitems('keywordmaps')
153 if kwmaps: # override default templates
153 if kwmaps: # override default templates
154 self.templates = dict((k, templater.parsestring(v, False))
154 self.templates = dict((k, templater.parsestring(v, False))
155 for k, v in kwmaps)
155 for k, v in kwmaps)
156 else:
156 else:
157 self.templates = _defaultkwmaps(self.ui)
157 self.templates = _defaultkwmaps(self.ui)
158 escaped = map(re.escape, self.templates.keys())
158 escaped = map(re.escape, self.templates.keys())
159 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
159 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
160 self.re_kw = re.compile(kwpat)
160 self.re_kw = re.compile(kwpat)
161
161
162 templatefilters.filters['utcdate'] = utcdate
162 templatefilters.filters['utcdate'] = utcdate
163 templatefilters.filters['svnisodate'] = svnisodate
163 templatefilters.filters['svnisodate'] = svnisodate
164 templatefilters.filters['svnutcdate'] = svnutcdate
164 templatefilters.filters['svnutcdate'] = svnutcdate
165
165
166 def substitute(self, data, path, ctx, subfunc):
166 def substitute(self, data, path, ctx, subfunc):
167 '''Replaces keywords in data with expanded template.'''
167 '''Replaces keywords in data with expanded template.'''
168 def kwsub(mobj):
168 def kwsub(mobj):
169 kw = mobj.group(1)
169 kw = mobj.group(1)
170 ct = cmdutil.changeset_templater(self.ui, self.repo,
170 ct = cmdutil.changeset_templater(self.ui, self.repo,
171 False, None, '', False)
171 False, None, '', False)
172 ct.use_template(self.templates[kw])
172 ct.use_template(self.templates[kw])
173 self.ui.pushbuffer()
173 self.ui.pushbuffer()
174 ct.show(ctx, root=self.repo.root, file=path)
174 ct.show(ctx, root=self.repo.root, file=path)
175 ekw = templatefilters.firstline(self.ui.popbuffer())
175 ekw = templatefilters.firstline(self.ui.popbuffer())
176 return '$%s: %s $' % (kw, ekw)
176 return '$%s: %s $' % (kw, ekw)
177 return subfunc(kwsub, data)
177 return subfunc(kwsub, data)
178
178
179 def expand(self, path, node, data):
179 def expand(self, path, node, data):
180 '''Returns data with keywords expanded.'''
180 '''Returns data with keywords expanded.'''
181 if not self.restrict and self.match(path) and not util.binary(data):
181 if not self.restrict and self.match(path) and not util.binary(data):
182 ctx = self.repo.filectx(path, fileid=node).changectx()
182 ctx = self.repo.filectx(path, fileid=node).changectx()
183 return self.substitute(data, path, ctx, self.re_kw.sub)
183 return self.substitute(data, path, ctx, self.re_kw.sub)
184 return data
184 return data
185
185
186 def iskwfile(self, path, flagfunc):
186 def iskwfile(self, path, flagfunc):
187 '''Returns true if path matches [keyword] pattern
187 '''Returns true if path matches [keyword] pattern
188 and is not a symbolic link.
188 and is not a symbolic link.
189 Caveat: localrepository._link fails on Windows.'''
189 Caveat: localrepository._link fails on Windows.'''
190 return self.match(path) and not 'l' in flagfunc(path)
190 return self.match(path) and not 'l' in flagfunc(path)
191
191
192 def overwrite(self, ctx, candidates, iswctx, expand):
192 def overwrite(self, ctx, candidates, iswctx, expand):
193 '''Overwrites selected files expanding/shrinking keywords.'''
193 '''Overwrites selected files expanding/shrinking keywords.'''
194 mf = ctx.manifest()
194 mf = ctx.manifest()
195 if self.record:
195 if self.record:
196 candidates = [f for f in ctx.files() if f in mf]
196 candidates = [f for f in ctx.files() if f in mf]
197 candidates = [f for f in candidates if self.iskwfile(f, ctx.flags)]
197 candidates = [f for f in candidates if self.iskwfile(f, ctx.flags)]
198 if candidates:
198 if candidates:
199 self.restrict = True # do not expand when reading
199 self.restrict = True # do not expand when reading
200 msg = (expand and _('overwriting %s expanding keywords\n')
200 msg = (expand and _('overwriting %s expanding keywords\n')
201 or _('overwriting %s shrinking keywords\n'))
201 or _('overwriting %s shrinking keywords\n'))
202 for f in candidates:
202 for f in candidates:
203 if not self.record:
203 if not self.record:
204 data = self.repo.file(f).read(mf[f])
204 data = self.repo.file(f).read(mf[f])
205 else:
205 else:
206 data = self.repo.wread(f)
206 data = self.repo.wread(f)
207 if util.binary(data):
207 if util.binary(data):
208 continue
208 continue
209 if expand:
209 if expand:
210 if iswctx:
210 if iswctx:
211 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
211 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
212 data, found = self.substitute(data, f, ctx,
212 data, found = self.substitute(data, f, ctx,
213 self.re_kw.subn)
213 self.re_kw.subn)
214 else:
214 else:
215 found = self.re_kw.search(data)
215 found = self.re_kw.search(data)
216 if found:
216 if found:
217 self.ui.note(msg % f)
217 self.ui.note(msg % f)
218 self.repo.wwrite(f, data, mf.flags(f))
218 self.repo.wwrite(f, data, mf.flags(f))
219 if iswctx:
219 if iswctx:
220 self.repo.dirstate.normal(f)
220 self.repo.dirstate.normal(f)
221 elif self.record:
221 elif self.record:
222 self.repo.dirstate.normallookup(f)
222 self.repo.dirstate.normallookup(f)
223 self.restrict = False
223 self.restrict = False
224
224
225 def shrinktext(self, text):
225 def shrinktext(self, text):
226 '''Unconditionally removes all keyword substitutions from text.'''
226 '''Unconditionally removes all keyword substitutions from text.'''
227 return self.re_kw.sub(r'$\1$', text)
227 return self.re_kw.sub(r'$\1$', text)
228
228
229 def shrink(self, fname, text):
229 def shrink(self, fname, text):
230 '''Returns text with all keyword substitutions removed.'''
230 '''Returns text with all keyword substitutions removed.'''
231 if self.match(fname) and not util.binary(text):
231 if self.match(fname) and not util.binary(text):
232 return self.shrinktext(text)
232 return self.shrinktext(text)
233 return text
233 return text
234
234
235 def shrinklines(self, fname, lines):
235 def shrinklines(self, fname, lines):
236 '''Returns lines with keyword substitutions removed.'''
236 '''Returns lines with keyword substitutions removed.'''
237 if self.match(fname):
237 if self.match(fname):
238 text = ''.join(lines)
238 text = ''.join(lines)
239 if not util.binary(text):
239 if not util.binary(text):
240 return self.shrinktext(text).splitlines(True)
240 return self.shrinktext(text).splitlines(True)
241 return lines
241 return lines
242
242
243 def wread(self, fname, data):
243 def wread(self, fname, data):
244 '''If in restricted mode returns data read from wdir with
244 '''If in restricted mode returns data read from wdir with
245 keyword substitutions removed.'''
245 keyword substitutions removed.'''
246 return self.restrict and self.shrink(fname, data) or data
246 return self.restrict and self.shrink(fname, data) or data
247
247
248 class kwfilelog(filelog.filelog):
248 class kwfilelog(filelog.filelog):
249 '''
249 '''
250 Subclass of filelog to hook into its read, add, cmp methods.
250 Subclass of filelog to hook into its read, add, cmp methods.
251 Keywords are "stored" unexpanded, and processed on reading.
251 Keywords are "stored" unexpanded, and processed on reading.
252 '''
252 '''
253 def __init__(self, opener, kwt, path):
253 def __init__(self, opener, kwt, path):
254 super(kwfilelog, self).__init__(opener, path)
254 super(kwfilelog, self).__init__(opener, path)
255 self.kwt = kwt
255 self.kwt = kwt
256 self.path = path
256 self.path = path
257
257
258 def read(self, node):
258 def read(self, node):
259 '''Expands keywords when reading filelog.'''
259 '''Expands keywords when reading filelog.'''
260 data = super(kwfilelog, self).read(node)
260 data = super(kwfilelog, self).read(node)
261 return self.kwt.expand(self.path, node, data)
261 return self.kwt.expand(self.path, node, data)
262
262
263 def add(self, text, meta, tr, link, p1=None, p2=None):
263 def add(self, text, meta, tr, link, p1=None, p2=None):
264 '''Removes keyword substitutions when adding to filelog.'''
264 '''Removes keyword substitutions when adding to filelog.'''
265 text = self.kwt.shrink(self.path, text)
265 text = self.kwt.shrink(self.path, text)
266 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
266 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
267
267
268 def cmp(self, node, text):
268 def cmp(self, node, text):
269 '''Removes keyword substitutions for comparison.'''
269 '''Removes keyword substitutions for comparison.'''
270 text = self.kwt.shrink(self.path, text)
270 text = self.kwt.shrink(self.path, text)
271 if self.renamed(node):
271 if self.renamed(node):
272 t2 = super(kwfilelog, self).read(node)
272 t2 = super(kwfilelog, self).read(node)
273 return t2 != text
273 return t2 != text
274 return revlog.revlog.cmp(self, node, text)
274 return revlog.revlog.cmp(self, node, text)
275
275
276 def _status(ui, repo, kwt, *pats, **opts):
276 def _status(ui, repo, kwt, *pats, **opts):
277 '''Bails out if [keyword] configuration is not active.
277 '''Bails out if [keyword] configuration is not active.
278 Returns status of working directory.'''
278 Returns status of working directory.'''
279 if kwt:
279 if kwt:
280 return repo.status(match=cmdutil.match(repo, pats, opts), clean=True,
280 return repo.status(match=cmdutil.match(repo, pats, opts), clean=True,
281 unknown=opts.get('unknown') or opts.get('all'))
281 unknown=opts.get('unknown') or opts.get('all'))
282 if ui.configitems('keyword'):
282 if ui.configitems('keyword'):
283 raise util.Abort(_('[keyword] patterns cannot match'))
283 raise util.Abort(_('[keyword] patterns cannot match'))
284 raise util.Abort(_('no [keyword] patterns configured'))
284 raise util.Abort(_('no [keyword] patterns configured'))
285
285
286 def _kwfwrite(ui, repo, expand, *pats, **opts):
286 def _kwfwrite(ui, repo, expand, *pats, **opts):
287 '''Selects files and passes them to kwtemplater.overwrite.'''
287 '''Selects files and passes them to kwtemplater.overwrite.'''
288 wctx = repo[None]
288 wctx = repo[None]
289 if len(wctx.parents()) > 1:
289 if len(wctx.parents()) > 1:
290 raise util.Abort(_('outstanding uncommitted merge'))
290 raise util.Abort(_('outstanding uncommitted merge'))
291 kwt = kwtools['templater']
291 kwt = kwtools['templater']
292 wlock = repo.wlock()
292 wlock = repo.wlock()
293 try:
293 try:
294 status = _status(ui, repo, kwt, *pats, **opts)
294 status = _status(ui, repo, kwt, *pats, **opts)
295 modified, added, removed, deleted, unknown, ignored, clean = status
295 modified, added, removed, deleted, unknown, ignored, clean = status
296 if modified or added or removed or deleted:
296 if modified or added or removed or deleted:
297 raise util.Abort(_('outstanding uncommitted changes'))
297 raise util.Abort(_('outstanding uncommitted changes'))
298 kwt.overwrite(wctx, clean, True, expand)
298 kwt.overwrite(wctx, clean, True, expand)
299 finally:
299 finally:
300 wlock.release()
300 wlock.release()
301
301
302 def demo(ui, repo, *args, **opts):
302 def demo(ui, repo, *args, **opts):
303 '''print [keywordmaps] configuration and an expansion example
303 '''print [keywordmaps] configuration and an expansion example
304
304
305 Show current, custom, or default keyword template maps and their
305 Show current, custom, or default keyword template maps and their
306 expansions.
306 expansions.
307
307
308 Extend the current configuration by specifying maps as arguments
308 Extend the current configuration by specifying maps as arguments
309 and using -f/--rcfile to source an external hgrc file.
309 and using -f/--rcfile to source an external hgrc file.
310
310
311 Use -d/--default to disable current configuration.
311 Use -d/--default to disable current configuration.
312
312
313 See :hg:`help templates` for information on templates and filters.
313 See :hg:`help templates` for information on templates and filters.
314 '''
314 '''
315 def demoitems(section, items):
315 def demoitems(section, items):
316 ui.write('[%s]\n' % section)
316 ui.write('[%s]\n' % section)
317 for k, v in sorted(items):
317 for k, v in sorted(items):
318 ui.write('%s = %s\n' % (k, v))
318 ui.write('%s = %s\n' % (k, v))
319
319
320 fn = 'demo.txt'
320 fn = 'demo.txt'
321 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
321 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
322 ui.note(_('creating temporary repository at %s\n') % tmpdir)
322 ui.note(_('creating temporary repository at %s\n') % tmpdir)
323 repo = localrepo.localrepository(ui, tmpdir, True)
323 repo = localrepo.localrepository(ui, tmpdir, True)
324 ui.setconfig('keyword', fn, '')
324 ui.setconfig('keyword', fn, '')
325
325
326 uikwmaps = ui.configitems('keywordmaps')
326 uikwmaps = ui.configitems('keywordmaps')
327 if args or opts.get('rcfile'):
327 if args or opts.get('rcfile'):
328 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
328 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
329 if uikwmaps:
329 if uikwmaps:
330 ui.status(_('\textending current template maps\n'))
330 ui.status(_('\textending current template maps\n'))
331 if opts.get('default') or not uikwmaps:
331 if opts.get('default') or not uikwmaps:
332 ui.status(_('\toverriding default template maps\n'))
332 ui.status(_('\toverriding default template maps\n'))
333 if opts.get('rcfile'):
333 if opts.get('rcfile'):
334 ui.readconfig(opts.get('rcfile'))
334 ui.readconfig(opts.get('rcfile'))
335 if args:
335 if args:
336 # simulate hgrc parsing
336 # simulate hgrc parsing
337 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
337 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
338 fp = repo.opener('hgrc', 'w')
338 fp = repo.opener('hgrc', 'w')
339 fp.writelines(rcmaps)
339 fp.writelines(rcmaps)
340 fp.close()
340 fp.close()
341 ui.readconfig(repo.join('hgrc'))
341 ui.readconfig(repo.join('hgrc'))
342 kwmaps = dict(ui.configitems('keywordmaps'))
342 kwmaps = dict(ui.configitems('keywordmaps'))
343 elif opts.get('default'):
343 elif opts.get('default'):
344 ui.status(_('\n\tconfiguration using default keyword template maps\n'))
344 ui.status(_('\n\tconfiguration using default keyword template maps\n'))
345 kwmaps = _defaultkwmaps(ui)
345 kwmaps = _defaultkwmaps(ui)
346 if uikwmaps:
346 if uikwmaps:
347 ui.status(_('\tdisabling current template maps\n'))
347 ui.status(_('\tdisabling current template maps\n'))
348 for k, v in kwmaps.iteritems():
348 for k, v in kwmaps.iteritems():
349 ui.setconfig('keywordmaps', k, v)
349 ui.setconfig('keywordmaps', k, v)
350 else:
350 else:
351 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
351 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
352 kwmaps = dict(uikwmaps) or _defaultkwmaps(ui)
352 kwmaps = dict(uikwmaps) or _defaultkwmaps(ui)
353
353
354 uisetup(ui)
354 uisetup(ui)
355 reposetup(ui, repo)
355 reposetup(ui, repo)
356 ui.write('[extensions]\nkeyword =\n')
356 ui.write('[extensions]\nkeyword =\n')
357 demoitems('keyword', ui.configitems('keyword'))
357 demoitems('keyword', ui.configitems('keyword'))
358 demoitems('keywordmaps', kwmaps.iteritems())
358 demoitems('keywordmaps', kwmaps.iteritems())
359 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
359 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
360 repo.wopener(fn, 'w').write(keywords)
360 repo.wopener(fn, 'w').write(keywords)
361 repo[None].add([fn])
361 repo[None].add([fn])
362 ui.note(_('\nkeywords written to %s:\n') % fn)
362 ui.note(_('\nkeywords written to %s:\n') % fn)
363 ui.note(keywords)
363 ui.note(keywords)
364 repo.dirstate.setbranch('demobranch')
364 repo.dirstate.setbranch('demobranch')
365 for name, cmd in ui.configitems('hooks'):
365 for name, cmd in ui.configitems('hooks'):
366 if name.split('.', 1)[0].find('commit') > -1:
366 if name.split('.', 1)[0].find('commit') > -1:
367 repo.ui.setconfig('hooks', name, '')
367 repo.ui.setconfig('hooks', name, '')
368 msg = _('hg keyword configuration and expansion example')
368 msg = _('hg keyword configuration and expansion example')
369 ui.note("hg ci -m '%s'\n" % msg)
369 ui.note("hg ci -m '%s'\n" % msg)
370 repo.commit(text=msg)
370 repo.commit(text=msg)
371 ui.status(_('\n\tkeywords expanded\n'))
371 ui.status(_('\n\tkeywords expanded\n'))
372 ui.write(repo.wread(fn))
372 ui.write(repo.wread(fn))
373 shutil.rmtree(tmpdir, ignore_errors=True)
373 shutil.rmtree(tmpdir, ignore_errors=True)
374
374
375 def expand(ui, repo, *pats, **opts):
375 def expand(ui, repo, *pats, **opts):
376 '''expand keywords in the working directory
376 '''expand keywords in the working directory
377
377
378 Run after (re)enabling keyword expansion.
378 Run after (re)enabling keyword expansion.
379
379
380 kwexpand refuses to run if given files contain local changes.
380 kwexpand refuses to run if given files contain local changes.
381 '''
381 '''
382 # 3rd argument sets expansion to True
382 # 3rd argument sets expansion to True
383 _kwfwrite(ui, repo, True, *pats, **opts)
383 _kwfwrite(ui, repo, True, *pats, **opts)
384
384
385 def files(ui, repo, *pats, **opts):
385 def files(ui, repo, *pats, **opts):
386 '''show files configured for keyword expansion
386 '''show files configured for keyword expansion
387
387
388 List which files in the working directory are matched by the
388 List which files in the working directory are matched by the
389 [keyword] configuration patterns.
389 [keyword] configuration patterns.
390
390
391 Useful to prevent inadvertent keyword expansion and to speed up
391 Useful to prevent inadvertent keyword expansion and to speed up
392 execution by including only files that are actual candidates for
392 execution by including only files that are actual candidates for
393 expansion.
393 expansion.
394
394
395 See :hg:`help keyword` on how to construct patterns both for
395 See :hg:`help keyword` on how to construct patterns both for
396 inclusion and exclusion of files.
396 inclusion and exclusion of files.
397
397
398 With -A/--all and -v/--verbose the codes used to show the status
398 With -A/--all and -v/--verbose the codes used to show the status
399 of files are::
399 of files are::
400
400
401 K = keyword expansion candidate
401 K = keyword expansion candidate
402 k = keyword expansion candidate (not tracked)
402 k = keyword expansion candidate (not tracked)
403 I = ignored
403 I = ignored
404 i = ignored (not tracked)
404 i = ignored (not tracked)
405 '''
405 '''
406 kwt = kwtools['templater']
406 kwt = kwtools['templater']
407 status = _status(ui, repo, kwt, *pats, **opts)
407 status = _status(ui, repo, kwt, *pats, **opts)
408 cwd = pats and repo.getcwd() or ''
408 cwd = pats and repo.getcwd() or ''
409 modified, added, removed, deleted, unknown, ignored, clean = status
409 modified, added, removed, deleted, unknown, ignored, clean = status
410 files = []
410 files = []
411 if not opts.get('unknown') or opts.get('all'):
411 if not opts.get('unknown') or opts.get('all'):
412 files = sorted(modified + added + clean)
412 files = sorted(modified + added + clean)
413 wctx = repo[None]
413 wctx = repo[None]
414 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
414 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
415 kwunknown = [f for f in unknown if kwt.iskwfile(f, wctx.flags)]
415 kwunknown = [f for f in unknown if kwt.iskwfile(f, wctx.flags)]
416 if not opts.get('ignore') or opts.get('all'):
416 if not opts.get('ignore') or opts.get('all'):
417 showfiles = kwfiles, kwunknown
417 showfiles = kwfiles, kwunknown
418 else:
418 else:
419 showfiles = [], []
419 showfiles = [], []
420 if opts.get('all') or opts.get('ignore'):
420 if opts.get('all') or opts.get('ignore'):
421 showfiles += ([f for f in files if f not in kwfiles],
421 showfiles += ([f for f in files if f not in kwfiles],
422 [f for f in unknown if f not in kwunknown])
422 [f for f in unknown if f not in kwunknown])
423 for char, filenames in zip('KkIi', showfiles):
423 for char, filenames in zip('KkIi', showfiles):
424 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
424 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
425 for f in filenames:
425 for f in filenames:
426 ui.write(fmt % repo.pathto(f, cwd))
426 ui.write(fmt % repo.pathto(f, cwd))
427
427
428 def shrink(ui, repo, *pats, **opts):
428 def shrink(ui, repo, *pats, **opts):
429 '''revert expanded keywords in the working directory
429 '''revert expanded keywords in the working directory
430
430
431 Run before changing/disabling active keywords or if you experience
431 Run before changing/disabling active keywords or if you experience
432 problems with :hg:`import` or :hg:`merge`.
432 problems with :hg:`import` or :hg:`merge`.
433
433
434 kwshrink refuses to run if given files contain local changes.
434 kwshrink refuses to run if given files contain local changes.
435 '''
435 '''
436 # 3rd argument sets expansion to False
436 # 3rd argument sets expansion to False
437 _kwfwrite(ui, repo, False, *pats, **opts)
437 _kwfwrite(ui, repo, False, *pats, **opts)
438
438
439
439
440 def uisetup(ui):
440 def uisetup(ui):
441 '''Collects [keyword] config in kwtools.
441 '''Collects [keyword] config in kwtools.
442 Monkeypatches dispatch._parse if needed.'''
442 Monkeypatches dispatch._parse if needed.'''
443
443
444 for pat, opt in ui.configitems('keyword'):
444 for pat, opt in ui.configitems('keyword'):
445 if opt != 'ignore':
445 if opt != 'ignore':
446 kwtools['inc'].append(pat)
446 kwtools['inc'].append(pat)
447 else:
447 else:
448 kwtools['exc'].append(pat)
448 kwtools['exc'].append(pat)
449
449
450 if kwtools['inc']:
450 if kwtools['inc']:
451 def kwdispatch_parse(orig, ui, args):
451 def kwdispatch_parse(orig, ui, args):
452 '''Monkeypatch dispatch._parse to obtain running hg command.'''
452 '''Monkeypatch dispatch._parse to obtain running hg command.'''
453 cmd, func, args, options, cmdoptions = orig(ui, args)
453 cmd, func, args, options, cmdoptions = orig(ui, args)
454 kwtools['hgcmd'] = cmd
454 kwtools['hgcmd'] = cmd
455 return cmd, func, args, options, cmdoptions
455 return cmd, func, args, options, cmdoptions
456
456
457 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
457 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
458
458
459 def reposetup(ui, repo):
459 def reposetup(ui, repo):
460 '''Sets up repo as kwrepo for keyword substitution.
460 '''Sets up repo as kwrepo for keyword substitution.
461 Overrides file method to return kwfilelog instead of filelog
461 Overrides file method to return kwfilelog instead of filelog
462 if file matches user configuration.
462 if file matches user configuration.
463 Wraps commit to overwrite configured files with updated
463 Wraps commit to overwrite configured files with updated
464 keyword substitutions.
464 keyword substitutions.
465 Monkeypatches patch and webcommands.'''
465 Monkeypatches patch and webcommands.'''
466
466
467 try:
467 try:
468 if (not repo.local() or not kwtools['inc']
468 if (not repo.local() or not kwtools['inc']
469 or kwtools['hgcmd'] in nokwcommands.split()
469 or kwtools['hgcmd'] in nokwcommands.split()
470 or '.hg' in util.splitpath(repo.root)
470 or '.hg' in util.splitpath(repo.root)
471 or repo._url.startswith('bundle:')):
471 or repo._url.startswith('bundle:')):
472 return
472 return
473 except AttributeError:
473 except AttributeError:
474 pass
474 pass
475
475
476 kwtools['templater'] = kwt = kwtemplater(ui, repo)
476 kwtools['templater'] = kwt = kwtemplater(ui, repo)
477
477
478 class kwrepo(repo.__class__):
478 class kwrepo(repo.__class__):
479 def file(self, f):
479 def file(self, f):
480 if f[0] == '/':
480 if f[0] == '/':
481 f = f[1:]
481 f = f[1:]
482 return kwfilelog(self.sopener, kwt, f)
482 return kwfilelog(self.sopener, kwt, f)
483
483
484 def wread(self, filename):
484 def wread(self, filename):
485 data = super(kwrepo, self).wread(filename)
485 data = super(kwrepo, self).wread(filename)
486 return kwt.wread(filename, data)
486 return kwt.wread(filename, data)
487
487
488 def commit(self, *args, **opts):
488 def commit(self, *args, **opts):
489 # use custom commitctx for user commands
489 # use custom commitctx for user commands
490 # other extensions can still wrap repo.commitctx directly
490 # other extensions can still wrap repo.commitctx directly
491 self.commitctx = self.kwcommitctx
491 self.commitctx = self.kwcommitctx
492 try:
492 try:
493 return super(kwrepo, self).commit(*args, **opts)
493 return super(kwrepo, self).commit(*args, **opts)
494 finally:
494 finally:
495 del self.commitctx
495 del self.commitctx
496
496
497 def kwcommitctx(self, ctx, error=False):
497 def kwcommitctx(self, ctx, error=False):
498 n = super(kwrepo, self).commitctx(ctx, error)
498 n = super(kwrepo, self).commitctx(ctx, error)
499 # no lock needed, only called from repo.commit() which already locks
499 # no lock needed, only called from repo.commit() which already locks
500 if not kwt.record:
500 if not kwt.record:
501 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
501 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
502 False, True)
502 False, True)
503 return n
503 return n
504
504
505 # monkeypatches
505 # monkeypatches
506 def kwpatchfile_init(orig, self, ui, fname, opener,
506 def kwpatchfile_init(orig, self, ui, fname, opener,
507 missing=False, eolmode=None):
507 missing=False, eolmode=None):
508 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
508 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
509 rejects or conflicts due to expanded keywords in working dir.'''
509 rejects or conflicts due to expanded keywords in working dir.'''
510 orig(self, ui, fname, opener, missing, eolmode)
510 orig(self, ui, fname, opener, missing, eolmode)
511 # shrink keywords read from working dir
511 # shrink keywords read from working dir
512 self.lines = kwt.shrinklines(self.fname, self.lines)
512 self.lines = kwt.shrinklines(self.fname, self.lines)
513
513
514 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
514 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
515 opts=None):
515 opts=None):
516 '''Monkeypatch patch.diff to avoid expansion except when
516 '''Monkeypatch patch.diff to avoid expansion except when
517 comparing against working dir.'''
517 comparing against working dir.'''
518 if node2 is not None:
518 if node2 is not None:
519 kwt.match = util.never
519 kwt.match = util.never
520 elif node1 is not None and node1 != repo['.'].node():
520 elif node1 is not None and node1 != repo['.'].node():
521 kwt.restrict = True
521 kwt.restrict = True
522 return orig(repo, node1, node2, match, changes, opts)
522 return orig(repo, node1, node2, match, changes, opts)
523
523
524 def kwweb_skip(orig, web, req, tmpl):
524 def kwweb_skip(orig, web, req, tmpl):
525 '''Wraps webcommands.x turning off keyword expansion.'''
525 '''Wraps webcommands.x turning off keyword expansion.'''
526 kwt.match = util.never
526 kwt.match = util.never
527 return orig(web, req, tmpl)
527 return orig(web, req, tmpl)
528
528
529 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
529 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
530 '''Wraps record.dorecord expanding keywords after recording.'''
530 '''Wraps record.dorecord expanding keywords after recording.'''
531 wlock = repo.wlock()
531 wlock = repo.wlock()
532 try:
532 try:
533 # record returns 0 even when nothing has changed
533 # record returns 0 even when nothing has changed
534 # therefore compare nodes before and after
534 # therefore compare nodes before and after
535 ctx = repo['.']
535 ctx = repo['.']
536 ret = orig(ui, repo, commitfunc, *pats, **opts)
536 ret = orig(ui, repo, commitfunc, *pats, **opts)
537 recordctx = repo['.']
537 recordctx = repo['.']
538 if ctx != recordctx:
538 if ctx != recordctx:
539 kwt.overwrite(recordctx, None, False, True)
539 kwt.overwrite(recordctx, None, False, True)
540 return ret
540 return ret
541 finally:
541 finally:
542 wlock.release()
542 wlock.release()
543
543
544 repo.__class__ = kwrepo
544 repo.__class__ = kwrepo
545
545
546 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
546 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
547 if not kwt.restrict:
547 if not kwt.restrict:
548 extensions.wrapfunction(patch, 'diff', kw_diff)
548 extensions.wrapfunction(patch, 'diff', kw_diff)
549 for c in 'annotate changeset rev filediff diff'.split():
549 for c in 'annotate changeset rev filediff diff'.split():
550 extensions.wrapfunction(webcommands, c, kwweb_skip)
550 extensions.wrapfunction(webcommands, c, kwweb_skip)
551 for name in recordextensions.split():
551 for name in recordextensions.split():
552 try:
552 try:
553 record = extensions.find(name)
553 record = extensions.find(name)
554 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
554 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
555 except KeyError:
555 except KeyError:
556 pass
556 pass
557
557
558 cmdtable = {
558 cmdtable = {
559 'kwdemo':
559 'kwdemo':
560 (demo,
560 (demo,
561 [('d', 'default', None, _('show default keyword template maps')),
561 [('d', 'default', None, _('show default keyword template maps')),
562 ('f', 'rcfile', '', _('read maps from rcfile'))],
562 ('f', 'rcfile', '',
563 _('read maps from rcfile'), _('FILE'))],
563 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
564 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
564 'kwexpand': (expand, commands.walkopts,
565 'kwexpand': (expand, commands.walkopts,
565 _('hg kwexpand [OPTION]... [FILE]...')),
566 _('hg kwexpand [OPTION]... [FILE]...')),
566 'kwfiles':
567 'kwfiles':
567 (files,
568 (files,
568 [('A', 'all', None, _('show keyword status flags of all files')),
569 [('A', 'all', None, _('show keyword status flags of all files')),
569 ('i', 'ignore', None, _('show files excluded from expansion')),
570 ('i', 'ignore', None, _('show files excluded from expansion')),
570 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
571 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
571 ] + commands.walkopts,
572 ] + commands.walkopts,
572 _('hg kwfiles [OPTION]... [FILE]...')),
573 _('hg kwfiles [OPTION]... [FILE]...')),
573 'kwshrink': (shrink, commands.walkopts,
574 'kwshrink': (shrink, commands.walkopts,
574 _('hg kwshrink [OPTION]... [FILE]...')),
575 _('hg kwshrink [OPTION]... [FILE]...')),
575 }
576 }
@@ -1,2987 +1,2996 b''
1 # mq.py - patch queues for mercurial
1 # mq.py - patch queues for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
3 # Copyright 2005, 2006 Chris Mason <mason@suse.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''manage a stack of patches
8 '''manage a stack of patches
9
9
10 This extension lets you work with a stack of patches in a Mercurial
10 This extension lets you work with a stack of patches in a Mercurial
11 repository. It manages two stacks of patches - all known patches, and
11 repository. It manages two stacks of patches - all known patches, and
12 applied patches (subset of known patches).
12 applied patches (subset of known patches).
13
13
14 Known patches are represented as patch files in the .hg/patches
14 Known patches are represented as patch files in the .hg/patches
15 directory. Applied patches are both patch files and changesets.
15 directory. Applied patches are both patch files and changesets.
16
16
17 Common tasks (use :hg:`help command` for more details)::
17 Common tasks (use :hg:`help command` for more details)::
18
18
19 create new patch qnew
19 create new patch qnew
20 import existing patch qimport
20 import existing patch qimport
21
21
22 print patch series qseries
22 print patch series qseries
23 print applied patches qapplied
23 print applied patches qapplied
24
24
25 add known patch to applied stack qpush
25 add known patch to applied stack qpush
26 remove patch from applied stack qpop
26 remove patch from applied stack qpop
27 refresh contents of top applied patch qrefresh
27 refresh contents of top applied patch qrefresh
28
28
29 By default, mq will automatically use git patches when required to
29 By default, mq will automatically use git patches when required to
30 avoid losing file mode changes, copy records, binary files or empty
30 avoid losing file mode changes, copy records, binary files or empty
31 files creations or deletions. This behaviour can be configured with::
31 files creations or deletions. This behaviour can be configured with::
32
32
33 [mq]
33 [mq]
34 git = auto/keep/yes/no
34 git = auto/keep/yes/no
35
35
36 If set to 'keep', mq will obey the [diff] section configuration while
36 If set to 'keep', mq will obey the [diff] section configuration while
37 preserving existing git patches upon qrefresh. If set to 'yes' or
37 preserving existing git patches upon qrefresh. If set to 'yes' or
38 'no', mq will override the [diff] section and always generate git or
38 'no', mq will override the [diff] section and always generate git or
39 regular patches, possibly losing data in the second case.
39 regular patches, possibly losing data in the second case.
40
40
41 You will by default be managing a patch queue named "patches". You can
41 You will by default be managing a patch queue named "patches". You can
42 create other, independent patch queues with the :hg:`qqueue` command.
42 create other, independent patch queues with the :hg:`qqueue` command.
43 '''
43 '''
44
44
45 from mercurial.i18n import _
45 from mercurial.i18n import _
46 from mercurial.node import bin, hex, short, nullid, nullrev
46 from mercurial.node import bin, hex, short, nullid, nullrev
47 from mercurial.lock import release
47 from mercurial.lock import release
48 from mercurial import commands, cmdutil, hg, patch, util
48 from mercurial import commands, cmdutil, hg, patch, util
49 from mercurial import repair, extensions, url, error
49 from mercurial import repair, extensions, url, error
50 import os, sys, re, errno
50 import os, sys, re, errno
51
51
52 commands.norepo += " qclone"
52 commands.norepo += " qclone"
53
53
54 # Patch names looks like unix-file names.
54 # Patch names looks like unix-file names.
55 # They must be joinable with queue directory and result in the patch path.
55 # They must be joinable with queue directory and result in the patch path.
56 normname = util.normpath
56 normname = util.normpath
57
57
58 class statusentry(object):
58 class statusentry(object):
59 def __init__(self, node, name):
59 def __init__(self, node, name):
60 self.node, self.name = node, name
60 self.node, self.name = node, name
61
61
62 def __str__(self):
62 def __str__(self):
63 return hex(self.node) + ':' + self.name
63 return hex(self.node) + ':' + self.name
64
64
65 class patchheader(object):
65 class patchheader(object):
66 def __init__(self, pf, plainmode=False):
66 def __init__(self, pf, plainmode=False):
67 def eatdiff(lines):
67 def eatdiff(lines):
68 while lines:
68 while lines:
69 l = lines[-1]
69 l = lines[-1]
70 if (l.startswith("diff -") or
70 if (l.startswith("diff -") or
71 l.startswith("Index:") or
71 l.startswith("Index:") or
72 l.startswith("===========")):
72 l.startswith("===========")):
73 del lines[-1]
73 del lines[-1]
74 else:
74 else:
75 break
75 break
76 def eatempty(lines):
76 def eatempty(lines):
77 while lines:
77 while lines:
78 if not lines[-1].strip():
78 if not lines[-1].strip():
79 del lines[-1]
79 del lines[-1]
80 else:
80 else:
81 break
81 break
82
82
83 message = []
83 message = []
84 comments = []
84 comments = []
85 user = None
85 user = None
86 date = None
86 date = None
87 parent = None
87 parent = None
88 format = None
88 format = None
89 subject = None
89 subject = None
90 diffstart = 0
90 diffstart = 0
91
91
92 for line in file(pf):
92 for line in file(pf):
93 line = line.rstrip()
93 line = line.rstrip()
94 if (line.startswith('diff --git')
94 if (line.startswith('diff --git')
95 or (diffstart and line.startswith('+++ '))):
95 or (diffstart and line.startswith('+++ '))):
96 diffstart = 2
96 diffstart = 2
97 break
97 break
98 diffstart = 0 # reset
98 diffstart = 0 # reset
99 if line.startswith("--- "):
99 if line.startswith("--- "):
100 diffstart = 1
100 diffstart = 1
101 continue
101 continue
102 elif format == "hgpatch":
102 elif format == "hgpatch":
103 # parse values when importing the result of an hg export
103 # parse values when importing the result of an hg export
104 if line.startswith("# User "):
104 if line.startswith("# User "):
105 user = line[7:]
105 user = line[7:]
106 elif line.startswith("# Date "):
106 elif line.startswith("# Date "):
107 date = line[7:]
107 date = line[7:]
108 elif line.startswith("# Parent "):
108 elif line.startswith("# Parent "):
109 parent = line[9:]
109 parent = line[9:]
110 elif not line.startswith("# ") and line:
110 elif not line.startswith("# ") and line:
111 message.append(line)
111 message.append(line)
112 format = None
112 format = None
113 elif line == '# HG changeset patch':
113 elif line == '# HG changeset patch':
114 message = []
114 message = []
115 format = "hgpatch"
115 format = "hgpatch"
116 elif (format != "tagdone" and (line.startswith("Subject: ") or
116 elif (format != "tagdone" and (line.startswith("Subject: ") or
117 line.startswith("subject: "))):
117 line.startswith("subject: "))):
118 subject = line[9:]
118 subject = line[9:]
119 format = "tag"
119 format = "tag"
120 elif (format != "tagdone" and (line.startswith("From: ") or
120 elif (format != "tagdone" and (line.startswith("From: ") or
121 line.startswith("from: "))):
121 line.startswith("from: "))):
122 user = line[6:]
122 user = line[6:]
123 format = "tag"
123 format = "tag"
124 elif (format != "tagdone" and (line.startswith("Date: ") or
124 elif (format != "tagdone" and (line.startswith("Date: ") or
125 line.startswith("date: "))):
125 line.startswith("date: "))):
126 date = line[6:]
126 date = line[6:]
127 format = "tag"
127 format = "tag"
128 elif format == "tag" and line == "":
128 elif format == "tag" and line == "":
129 # when looking for tags (subject: from: etc) they
129 # when looking for tags (subject: from: etc) they
130 # end once you find a blank line in the source
130 # end once you find a blank line in the source
131 format = "tagdone"
131 format = "tagdone"
132 elif message or line:
132 elif message or line:
133 message.append(line)
133 message.append(line)
134 comments.append(line)
134 comments.append(line)
135
135
136 eatdiff(message)
136 eatdiff(message)
137 eatdiff(comments)
137 eatdiff(comments)
138 eatempty(message)
138 eatempty(message)
139 eatempty(comments)
139 eatempty(comments)
140
140
141 # make sure message isn't empty
141 # make sure message isn't empty
142 if format and format.startswith("tag") and subject:
142 if format and format.startswith("tag") and subject:
143 message.insert(0, "")
143 message.insert(0, "")
144 message.insert(0, subject)
144 message.insert(0, subject)
145
145
146 self.message = message
146 self.message = message
147 self.comments = comments
147 self.comments = comments
148 self.user = user
148 self.user = user
149 self.date = date
149 self.date = date
150 self.parent = parent
150 self.parent = parent
151 self.haspatch = diffstart > 1
151 self.haspatch = diffstart > 1
152 self.plainmode = plainmode
152 self.plainmode = plainmode
153
153
154 def setuser(self, user):
154 def setuser(self, user):
155 if not self.updateheader(['From: ', '# User '], user):
155 if not self.updateheader(['From: ', '# User '], user):
156 try:
156 try:
157 patchheaderat = self.comments.index('# HG changeset patch')
157 patchheaderat = self.comments.index('# HG changeset patch')
158 self.comments.insert(patchheaderat + 1, '# User ' + user)
158 self.comments.insert(patchheaderat + 1, '# User ' + user)
159 except ValueError:
159 except ValueError:
160 if self.plainmode or self._hasheader(['Date: ']):
160 if self.plainmode or self._hasheader(['Date: ']):
161 self.comments = ['From: ' + user] + self.comments
161 self.comments = ['From: ' + user] + self.comments
162 else:
162 else:
163 tmp = ['# HG changeset patch', '# User ' + user, '']
163 tmp = ['# HG changeset patch', '# User ' + user, '']
164 self.comments = tmp + self.comments
164 self.comments = tmp + self.comments
165 self.user = user
165 self.user = user
166
166
167 def setdate(self, date):
167 def setdate(self, date):
168 if not self.updateheader(['Date: ', '# Date '], date):
168 if not self.updateheader(['Date: ', '# Date '], date):
169 try:
169 try:
170 patchheaderat = self.comments.index('# HG changeset patch')
170 patchheaderat = self.comments.index('# HG changeset patch')
171 self.comments.insert(patchheaderat + 1, '# Date ' + date)
171 self.comments.insert(patchheaderat + 1, '# Date ' + date)
172 except ValueError:
172 except ValueError:
173 if self.plainmode or self._hasheader(['From: ']):
173 if self.plainmode or self._hasheader(['From: ']):
174 self.comments = ['Date: ' + date] + self.comments
174 self.comments = ['Date: ' + date] + self.comments
175 else:
175 else:
176 tmp = ['# HG changeset patch', '# Date ' + date, '']
176 tmp = ['# HG changeset patch', '# Date ' + date, '']
177 self.comments = tmp + self.comments
177 self.comments = tmp + self.comments
178 self.date = date
178 self.date = date
179
179
180 def setparent(self, parent):
180 def setparent(self, parent):
181 if not self.updateheader(['# Parent '], parent):
181 if not self.updateheader(['# Parent '], parent):
182 try:
182 try:
183 patchheaderat = self.comments.index('# HG changeset patch')
183 patchheaderat = self.comments.index('# HG changeset patch')
184 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
184 self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
185 except ValueError:
185 except ValueError:
186 pass
186 pass
187 self.parent = parent
187 self.parent = parent
188
188
189 def setmessage(self, message):
189 def setmessage(self, message):
190 if self.comments:
190 if self.comments:
191 self._delmsg()
191 self._delmsg()
192 self.message = [message]
192 self.message = [message]
193 self.comments += self.message
193 self.comments += self.message
194
194
195 def updateheader(self, prefixes, new):
195 def updateheader(self, prefixes, new):
196 '''Update all references to a field in the patch header.
196 '''Update all references to a field in the patch header.
197 Return whether the field is present.'''
197 Return whether the field is present.'''
198 res = False
198 res = False
199 for prefix in prefixes:
199 for prefix in prefixes:
200 for i in xrange(len(self.comments)):
200 for i in xrange(len(self.comments)):
201 if self.comments[i].startswith(prefix):
201 if self.comments[i].startswith(prefix):
202 self.comments[i] = prefix + new
202 self.comments[i] = prefix + new
203 res = True
203 res = True
204 break
204 break
205 return res
205 return res
206
206
207 def _hasheader(self, prefixes):
207 def _hasheader(self, prefixes):
208 '''Check if a header starts with any of the given prefixes.'''
208 '''Check if a header starts with any of the given prefixes.'''
209 for prefix in prefixes:
209 for prefix in prefixes:
210 for comment in self.comments:
210 for comment in self.comments:
211 if comment.startswith(prefix):
211 if comment.startswith(prefix):
212 return True
212 return True
213 return False
213 return False
214
214
215 def __str__(self):
215 def __str__(self):
216 if not self.comments:
216 if not self.comments:
217 return ''
217 return ''
218 return '\n'.join(self.comments) + '\n\n'
218 return '\n'.join(self.comments) + '\n\n'
219
219
220 def _delmsg(self):
220 def _delmsg(self):
221 '''Remove existing message, keeping the rest of the comments fields.
221 '''Remove existing message, keeping the rest of the comments fields.
222 If comments contains 'subject: ', message will prepend
222 If comments contains 'subject: ', message will prepend
223 the field and a blank line.'''
223 the field and a blank line.'''
224 if self.message:
224 if self.message:
225 subj = 'subject: ' + self.message[0].lower()
225 subj = 'subject: ' + self.message[0].lower()
226 for i in xrange(len(self.comments)):
226 for i in xrange(len(self.comments)):
227 if subj == self.comments[i].lower():
227 if subj == self.comments[i].lower():
228 del self.comments[i]
228 del self.comments[i]
229 self.message = self.message[2:]
229 self.message = self.message[2:]
230 break
230 break
231 ci = 0
231 ci = 0
232 for mi in self.message:
232 for mi in self.message:
233 while mi != self.comments[ci]:
233 while mi != self.comments[ci]:
234 ci += 1
234 ci += 1
235 del self.comments[ci]
235 del self.comments[ci]
236
236
237 class queue(object):
237 class queue(object):
238 def __init__(self, ui, path, patchdir=None):
238 def __init__(self, ui, path, patchdir=None):
239 self.basepath = path
239 self.basepath = path
240 try:
240 try:
241 fh = open(os.path.join(path, 'patches.queue'))
241 fh = open(os.path.join(path, 'patches.queue'))
242 cur = fh.read().rstrip()
242 cur = fh.read().rstrip()
243 if not cur:
243 if not cur:
244 curpath = os.path.join(path, 'patches')
244 curpath = os.path.join(path, 'patches')
245 else:
245 else:
246 curpath = os.path.join(path, 'patches-' + cur)
246 curpath = os.path.join(path, 'patches-' + cur)
247 except IOError:
247 except IOError:
248 curpath = os.path.join(path, 'patches')
248 curpath = os.path.join(path, 'patches')
249 self.path = patchdir or curpath
249 self.path = patchdir or curpath
250 self.opener = util.opener(self.path)
250 self.opener = util.opener(self.path)
251 self.ui = ui
251 self.ui = ui
252 self.applied_dirty = 0
252 self.applied_dirty = 0
253 self.series_dirty = 0
253 self.series_dirty = 0
254 self.series_path = "series"
254 self.series_path = "series"
255 self.status_path = "status"
255 self.status_path = "status"
256 self.guards_path = "guards"
256 self.guards_path = "guards"
257 self.active_guards = None
257 self.active_guards = None
258 self.guards_dirty = False
258 self.guards_dirty = False
259 # Handle mq.git as a bool with extended values
259 # Handle mq.git as a bool with extended values
260 try:
260 try:
261 gitmode = ui.configbool('mq', 'git', None)
261 gitmode = ui.configbool('mq', 'git', None)
262 if gitmode is None:
262 if gitmode is None:
263 raise error.ConfigError()
263 raise error.ConfigError()
264 self.gitmode = gitmode and 'yes' or 'no'
264 self.gitmode = gitmode and 'yes' or 'no'
265 except error.ConfigError:
265 except error.ConfigError:
266 self.gitmode = ui.config('mq', 'git', 'auto').lower()
266 self.gitmode = ui.config('mq', 'git', 'auto').lower()
267 self.plainmode = ui.configbool('mq', 'plain', False)
267 self.plainmode = ui.configbool('mq', 'plain', False)
268
268
269 @util.propertycache
269 @util.propertycache
270 def applied(self):
270 def applied(self):
271 if os.path.exists(self.join(self.status_path)):
271 if os.path.exists(self.join(self.status_path)):
272 def parse(l):
272 def parse(l):
273 n, name = l.split(':', 1)
273 n, name = l.split(':', 1)
274 return statusentry(bin(n), name)
274 return statusentry(bin(n), name)
275 lines = self.opener(self.status_path).read().splitlines()
275 lines = self.opener(self.status_path).read().splitlines()
276 return [parse(l) for l in lines]
276 return [parse(l) for l in lines]
277 return []
277 return []
278
278
279 @util.propertycache
279 @util.propertycache
280 def full_series(self):
280 def full_series(self):
281 if os.path.exists(self.join(self.series_path)):
281 if os.path.exists(self.join(self.series_path)):
282 return self.opener(self.series_path).read().splitlines()
282 return self.opener(self.series_path).read().splitlines()
283 return []
283 return []
284
284
285 @util.propertycache
285 @util.propertycache
286 def series(self):
286 def series(self):
287 self.parse_series()
287 self.parse_series()
288 return self.series
288 return self.series
289
289
290 @util.propertycache
290 @util.propertycache
291 def series_guards(self):
291 def series_guards(self):
292 self.parse_series()
292 self.parse_series()
293 return self.series_guards
293 return self.series_guards
294
294
295 def invalidate(self):
295 def invalidate(self):
296 for a in 'applied full_series series series_guards'.split():
296 for a in 'applied full_series series series_guards'.split():
297 if a in self.__dict__:
297 if a in self.__dict__:
298 delattr(self, a)
298 delattr(self, a)
299 self.applied_dirty = 0
299 self.applied_dirty = 0
300 self.series_dirty = 0
300 self.series_dirty = 0
301 self.guards_dirty = False
301 self.guards_dirty = False
302 self.active_guards = None
302 self.active_guards = None
303
303
304 def diffopts(self, opts={}, patchfn=None):
304 def diffopts(self, opts={}, patchfn=None):
305 diffopts = patch.diffopts(self.ui, opts)
305 diffopts = patch.diffopts(self.ui, opts)
306 if self.gitmode == 'auto':
306 if self.gitmode == 'auto':
307 diffopts.upgrade = True
307 diffopts.upgrade = True
308 elif self.gitmode == 'keep':
308 elif self.gitmode == 'keep':
309 pass
309 pass
310 elif self.gitmode in ('yes', 'no'):
310 elif self.gitmode in ('yes', 'no'):
311 diffopts.git = self.gitmode == 'yes'
311 diffopts.git = self.gitmode == 'yes'
312 else:
312 else:
313 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
313 raise util.Abort(_('mq.git option can be auto/keep/yes/no'
314 ' got %s') % self.gitmode)
314 ' got %s') % self.gitmode)
315 if patchfn:
315 if patchfn:
316 diffopts = self.patchopts(diffopts, patchfn)
316 diffopts = self.patchopts(diffopts, patchfn)
317 return diffopts
317 return diffopts
318
318
319 def patchopts(self, diffopts, *patches):
319 def patchopts(self, diffopts, *patches):
320 """Return a copy of input diff options with git set to true if
320 """Return a copy of input diff options with git set to true if
321 referenced patch is a git patch and should be preserved as such.
321 referenced patch is a git patch and should be preserved as such.
322 """
322 """
323 diffopts = diffopts.copy()
323 diffopts = diffopts.copy()
324 if not diffopts.git and self.gitmode == 'keep':
324 if not diffopts.git and self.gitmode == 'keep':
325 for patchfn in patches:
325 for patchfn in patches:
326 patchf = self.opener(patchfn, 'r')
326 patchf = self.opener(patchfn, 'r')
327 # if the patch was a git patch, refresh it as a git patch
327 # if the patch was a git patch, refresh it as a git patch
328 for line in patchf:
328 for line in patchf:
329 if line.startswith('diff --git'):
329 if line.startswith('diff --git'):
330 diffopts.git = True
330 diffopts.git = True
331 break
331 break
332 patchf.close()
332 patchf.close()
333 return diffopts
333 return diffopts
334
334
335 def join(self, *p):
335 def join(self, *p):
336 return os.path.join(self.path, *p)
336 return os.path.join(self.path, *p)
337
337
338 def find_series(self, patch):
338 def find_series(self, patch):
339 def matchpatch(l):
339 def matchpatch(l):
340 l = l.split('#', 1)[0]
340 l = l.split('#', 1)[0]
341 return l.strip() == patch
341 return l.strip() == patch
342 for index, l in enumerate(self.full_series):
342 for index, l in enumerate(self.full_series):
343 if matchpatch(l):
343 if matchpatch(l):
344 return index
344 return index
345 return None
345 return None
346
346
347 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
347 guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
348
348
349 def parse_series(self):
349 def parse_series(self):
350 self.series = []
350 self.series = []
351 self.series_guards = []
351 self.series_guards = []
352 for l in self.full_series:
352 for l in self.full_series:
353 h = l.find('#')
353 h = l.find('#')
354 if h == -1:
354 if h == -1:
355 patch = l
355 patch = l
356 comment = ''
356 comment = ''
357 elif h == 0:
357 elif h == 0:
358 continue
358 continue
359 else:
359 else:
360 patch = l[:h]
360 patch = l[:h]
361 comment = l[h:]
361 comment = l[h:]
362 patch = patch.strip()
362 patch = patch.strip()
363 if patch:
363 if patch:
364 if patch in self.series:
364 if patch in self.series:
365 raise util.Abort(_('%s appears more than once in %s') %
365 raise util.Abort(_('%s appears more than once in %s') %
366 (patch, self.join(self.series_path)))
366 (patch, self.join(self.series_path)))
367 self.series.append(patch)
367 self.series.append(patch)
368 self.series_guards.append(self.guard_re.findall(comment))
368 self.series_guards.append(self.guard_re.findall(comment))
369
369
370 def check_guard(self, guard):
370 def check_guard(self, guard):
371 if not guard:
371 if not guard:
372 return _('guard cannot be an empty string')
372 return _('guard cannot be an empty string')
373 bad_chars = '# \t\r\n\f'
373 bad_chars = '# \t\r\n\f'
374 first = guard[0]
374 first = guard[0]
375 if first in '-+':
375 if first in '-+':
376 return (_('guard %r starts with invalid character: %r') %
376 return (_('guard %r starts with invalid character: %r') %
377 (guard, first))
377 (guard, first))
378 for c in bad_chars:
378 for c in bad_chars:
379 if c in guard:
379 if c in guard:
380 return _('invalid character in guard %r: %r') % (guard, c)
380 return _('invalid character in guard %r: %r') % (guard, c)
381
381
382 def set_active(self, guards):
382 def set_active(self, guards):
383 for guard in guards:
383 for guard in guards:
384 bad = self.check_guard(guard)
384 bad = self.check_guard(guard)
385 if bad:
385 if bad:
386 raise util.Abort(bad)
386 raise util.Abort(bad)
387 guards = sorted(set(guards))
387 guards = sorted(set(guards))
388 self.ui.debug('active guards: %s\n' % ' '.join(guards))
388 self.ui.debug('active guards: %s\n' % ' '.join(guards))
389 self.active_guards = guards
389 self.active_guards = guards
390 self.guards_dirty = True
390 self.guards_dirty = True
391
391
392 def active(self):
392 def active(self):
393 if self.active_guards is None:
393 if self.active_guards is None:
394 self.active_guards = []
394 self.active_guards = []
395 try:
395 try:
396 guards = self.opener(self.guards_path).read().split()
396 guards = self.opener(self.guards_path).read().split()
397 except IOError, err:
397 except IOError, err:
398 if err.errno != errno.ENOENT:
398 if err.errno != errno.ENOENT:
399 raise
399 raise
400 guards = []
400 guards = []
401 for i, guard in enumerate(guards):
401 for i, guard in enumerate(guards):
402 bad = self.check_guard(guard)
402 bad = self.check_guard(guard)
403 if bad:
403 if bad:
404 self.ui.warn('%s:%d: %s\n' %
404 self.ui.warn('%s:%d: %s\n' %
405 (self.join(self.guards_path), i + 1, bad))
405 (self.join(self.guards_path), i + 1, bad))
406 else:
406 else:
407 self.active_guards.append(guard)
407 self.active_guards.append(guard)
408 return self.active_guards
408 return self.active_guards
409
409
410 def set_guards(self, idx, guards):
410 def set_guards(self, idx, guards):
411 for g in guards:
411 for g in guards:
412 if len(g) < 2:
412 if len(g) < 2:
413 raise util.Abort(_('guard %r too short') % g)
413 raise util.Abort(_('guard %r too short') % g)
414 if g[0] not in '-+':
414 if g[0] not in '-+':
415 raise util.Abort(_('guard %r starts with invalid char') % g)
415 raise util.Abort(_('guard %r starts with invalid char') % g)
416 bad = self.check_guard(g[1:])
416 bad = self.check_guard(g[1:])
417 if bad:
417 if bad:
418 raise util.Abort(bad)
418 raise util.Abort(bad)
419 drop = self.guard_re.sub('', self.full_series[idx])
419 drop = self.guard_re.sub('', self.full_series[idx])
420 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
420 self.full_series[idx] = drop + ''.join([' #' + g for g in guards])
421 self.parse_series()
421 self.parse_series()
422 self.series_dirty = True
422 self.series_dirty = True
423
423
424 def pushable(self, idx):
424 def pushable(self, idx):
425 if isinstance(idx, str):
425 if isinstance(idx, str):
426 idx = self.series.index(idx)
426 idx = self.series.index(idx)
427 patchguards = self.series_guards[idx]
427 patchguards = self.series_guards[idx]
428 if not patchguards:
428 if not patchguards:
429 return True, None
429 return True, None
430 guards = self.active()
430 guards = self.active()
431 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
431 exactneg = [g for g in patchguards if g[0] == '-' and g[1:] in guards]
432 if exactneg:
432 if exactneg:
433 return False, exactneg[0]
433 return False, exactneg[0]
434 pos = [g for g in patchguards if g[0] == '+']
434 pos = [g for g in patchguards if g[0] == '+']
435 exactpos = [g for g in pos if g[1:] in guards]
435 exactpos = [g for g in pos if g[1:] in guards]
436 if pos:
436 if pos:
437 if exactpos:
437 if exactpos:
438 return True, exactpos[0]
438 return True, exactpos[0]
439 return False, pos
439 return False, pos
440 return True, ''
440 return True, ''
441
441
442 def explain_pushable(self, idx, all_patches=False):
442 def explain_pushable(self, idx, all_patches=False):
443 write = all_patches and self.ui.write or self.ui.warn
443 write = all_patches and self.ui.write or self.ui.warn
444 if all_patches or self.ui.verbose:
444 if all_patches or self.ui.verbose:
445 if isinstance(idx, str):
445 if isinstance(idx, str):
446 idx = self.series.index(idx)
446 idx = self.series.index(idx)
447 pushable, why = self.pushable(idx)
447 pushable, why = self.pushable(idx)
448 if all_patches and pushable:
448 if all_patches and pushable:
449 if why is None:
449 if why is None:
450 write(_('allowing %s - no guards in effect\n') %
450 write(_('allowing %s - no guards in effect\n') %
451 self.series[idx])
451 self.series[idx])
452 else:
452 else:
453 if not why:
453 if not why:
454 write(_('allowing %s - no matching negative guards\n') %
454 write(_('allowing %s - no matching negative guards\n') %
455 self.series[idx])
455 self.series[idx])
456 else:
456 else:
457 write(_('allowing %s - guarded by %r\n') %
457 write(_('allowing %s - guarded by %r\n') %
458 (self.series[idx], why))
458 (self.series[idx], why))
459 if not pushable:
459 if not pushable:
460 if why:
460 if why:
461 write(_('skipping %s - guarded by %r\n') %
461 write(_('skipping %s - guarded by %r\n') %
462 (self.series[idx], why))
462 (self.series[idx], why))
463 else:
463 else:
464 write(_('skipping %s - no matching guards\n') %
464 write(_('skipping %s - no matching guards\n') %
465 self.series[idx])
465 self.series[idx])
466
466
467 def save_dirty(self):
467 def save_dirty(self):
468 def write_list(items, path):
468 def write_list(items, path):
469 fp = self.opener(path, 'w')
469 fp = self.opener(path, 'w')
470 for i in items:
470 for i in items:
471 fp.write("%s\n" % i)
471 fp.write("%s\n" % i)
472 fp.close()
472 fp.close()
473 if self.applied_dirty:
473 if self.applied_dirty:
474 write_list(map(str, self.applied), self.status_path)
474 write_list(map(str, self.applied), self.status_path)
475 if self.series_dirty:
475 if self.series_dirty:
476 write_list(self.full_series, self.series_path)
476 write_list(self.full_series, self.series_path)
477 if self.guards_dirty:
477 if self.guards_dirty:
478 write_list(self.active_guards, self.guards_path)
478 write_list(self.active_guards, self.guards_path)
479
479
480 def removeundo(self, repo):
480 def removeundo(self, repo):
481 undo = repo.sjoin('undo')
481 undo = repo.sjoin('undo')
482 if not os.path.exists(undo):
482 if not os.path.exists(undo):
483 return
483 return
484 try:
484 try:
485 os.unlink(undo)
485 os.unlink(undo)
486 except OSError, inst:
486 except OSError, inst:
487 self.ui.warn(_('error removing undo: %s\n') % str(inst))
487 self.ui.warn(_('error removing undo: %s\n') % str(inst))
488
488
489 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
489 def printdiff(self, repo, diffopts, node1, node2=None, files=None,
490 fp=None, changes=None, opts={}):
490 fp=None, changes=None, opts={}):
491 stat = opts.get('stat')
491 stat = opts.get('stat')
492 m = cmdutil.match(repo, files, opts)
492 m = cmdutil.match(repo, files, opts)
493 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
493 cmdutil.diffordiffstat(self.ui, repo, diffopts, node1, node2, m,
494 changes, stat, fp)
494 changes, stat, fp)
495
495
496 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
496 def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
497 # first try just applying the patch
497 # first try just applying the patch
498 (err, n) = self.apply(repo, [patch], update_status=False,
498 (err, n) = self.apply(repo, [patch], update_status=False,
499 strict=True, merge=rev)
499 strict=True, merge=rev)
500
500
501 if err == 0:
501 if err == 0:
502 return (err, n)
502 return (err, n)
503
503
504 if n is None:
504 if n is None:
505 raise util.Abort(_("apply failed for patch %s") % patch)
505 raise util.Abort(_("apply failed for patch %s") % patch)
506
506
507 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
507 self.ui.warn(_("patch didn't work out, merging %s\n") % patch)
508
508
509 # apply failed, strip away that rev and merge.
509 # apply failed, strip away that rev and merge.
510 hg.clean(repo, head)
510 hg.clean(repo, head)
511 self.strip(repo, n, update=False, backup='strip')
511 self.strip(repo, n, update=False, backup='strip')
512
512
513 ctx = repo[rev]
513 ctx = repo[rev]
514 ret = hg.merge(repo, rev)
514 ret = hg.merge(repo, rev)
515 if ret:
515 if ret:
516 raise util.Abort(_("update returned %d") % ret)
516 raise util.Abort(_("update returned %d") % ret)
517 n = repo.commit(ctx.description(), ctx.user(), force=True)
517 n = repo.commit(ctx.description(), ctx.user(), force=True)
518 if n is None:
518 if n is None:
519 raise util.Abort(_("repo commit failed"))
519 raise util.Abort(_("repo commit failed"))
520 try:
520 try:
521 ph = patchheader(mergeq.join(patch), self.plainmode)
521 ph = patchheader(mergeq.join(patch), self.plainmode)
522 except:
522 except:
523 raise util.Abort(_("unable to read %s") % patch)
523 raise util.Abort(_("unable to read %s") % patch)
524
524
525 diffopts = self.patchopts(diffopts, patch)
525 diffopts = self.patchopts(diffopts, patch)
526 patchf = self.opener(patch, "w")
526 patchf = self.opener(patch, "w")
527 comments = str(ph)
527 comments = str(ph)
528 if comments:
528 if comments:
529 patchf.write(comments)
529 patchf.write(comments)
530 self.printdiff(repo, diffopts, head, n, fp=patchf)
530 self.printdiff(repo, diffopts, head, n, fp=patchf)
531 patchf.close()
531 patchf.close()
532 self.removeundo(repo)
532 self.removeundo(repo)
533 return (0, n)
533 return (0, n)
534
534
535 def qparents(self, repo, rev=None):
535 def qparents(self, repo, rev=None):
536 if rev is None:
536 if rev is None:
537 (p1, p2) = repo.dirstate.parents()
537 (p1, p2) = repo.dirstate.parents()
538 if p2 == nullid:
538 if p2 == nullid:
539 return p1
539 return p1
540 if not self.applied:
540 if not self.applied:
541 return None
541 return None
542 return self.applied[-1].node
542 return self.applied[-1].node
543 p1, p2 = repo.changelog.parents(rev)
543 p1, p2 = repo.changelog.parents(rev)
544 if p2 != nullid and p2 in [x.node for x in self.applied]:
544 if p2 != nullid and p2 in [x.node for x in self.applied]:
545 return p2
545 return p2
546 return p1
546 return p1
547
547
548 def mergepatch(self, repo, mergeq, series, diffopts):
548 def mergepatch(self, repo, mergeq, series, diffopts):
549 if not self.applied:
549 if not self.applied:
550 # each of the patches merged in will have two parents. This
550 # each of the patches merged in will have two parents. This
551 # can confuse the qrefresh, qdiff, and strip code because it
551 # can confuse the qrefresh, qdiff, and strip code because it
552 # needs to know which parent is actually in the patch queue.
552 # needs to know which parent is actually in the patch queue.
553 # so, we insert a merge marker with only one parent. This way
553 # so, we insert a merge marker with only one parent. This way
554 # the first patch in the queue is never a merge patch
554 # the first patch in the queue is never a merge patch
555 #
555 #
556 pname = ".hg.patches.merge.marker"
556 pname = ".hg.patches.merge.marker"
557 n = repo.commit('[mq]: merge marker', force=True)
557 n = repo.commit('[mq]: merge marker', force=True)
558 self.removeundo(repo)
558 self.removeundo(repo)
559 self.applied.append(statusentry(n, pname))
559 self.applied.append(statusentry(n, pname))
560 self.applied_dirty = 1
560 self.applied_dirty = 1
561
561
562 head = self.qparents(repo)
562 head = self.qparents(repo)
563
563
564 for patch in series:
564 for patch in series:
565 patch = mergeq.lookup(patch, strict=True)
565 patch = mergeq.lookup(patch, strict=True)
566 if not patch:
566 if not patch:
567 self.ui.warn(_("patch %s does not exist\n") % patch)
567 self.ui.warn(_("patch %s does not exist\n") % patch)
568 return (1, None)
568 return (1, None)
569 pushable, reason = self.pushable(patch)
569 pushable, reason = self.pushable(patch)
570 if not pushable:
570 if not pushable:
571 self.explain_pushable(patch, all_patches=True)
571 self.explain_pushable(patch, all_patches=True)
572 continue
572 continue
573 info = mergeq.isapplied(patch)
573 info = mergeq.isapplied(patch)
574 if not info:
574 if not info:
575 self.ui.warn(_("patch %s is not applied\n") % patch)
575 self.ui.warn(_("patch %s is not applied\n") % patch)
576 return (1, None)
576 return (1, None)
577 rev = info[1]
577 rev = info[1]
578 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
578 err, head = self.mergeone(repo, mergeq, head, patch, rev, diffopts)
579 if head:
579 if head:
580 self.applied.append(statusentry(head, patch))
580 self.applied.append(statusentry(head, patch))
581 self.applied_dirty = 1
581 self.applied_dirty = 1
582 if err:
582 if err:
583 return (err, head)
583 return (err, head)
584 self.save_dirty()
584 self.save_dirty()
585 return (0, head)
585 return (0, head)
586
586
587 def patch(self, repo, patchfile):
587 def patch(self, repo, patchfile):
588 '''Apply patchfile to the working directory.
588 '''Apply patchfile to the working directory.
589 patchfile: name of patch file'''
589 patchfile: name of patch file'''
590 files = {}
590 files = {}
591 try:
591 try:
592 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
592 fuzz = patch.patch(patchfile, self.ui, strip=1, cwd=repo.root,
593 files=files, eolmode=None)
593 files=files, eolmode=None)
594 except Exception, inst:
594 except Exception, inst:
595 self.ui.note(str(inst) + '\n')
595 self.ui.note(str(inst) + '\n')
596 if not self.ui.verbose:
596 if not self.ui.verbose:
597 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
597 self.ui.warn(_("patch failed, unable to continue (try -v)\n"))
598 return (False, files, False)
598 return (False, files, False)
599
599
600 return (True, files, fuzz)
600 return (True, files, fuzz)
601
601
602 def apply(self, repo, series, list=False, update_status=True,
602 def apply(self, repo, series, list=False, update_status=True,
603 strict=False, patchdir=None, merge=None, all_files=None):
603 strict=False, patchdir=None, merge=None, all_files=None):
604 wlock = lock = tr = None
604 wlock = lock = tr = None
605 try:
605 try:
606 wlock = repo.wlock()
606 wlock = repo.wlock()
607 lock = repo.lock()
607 lock = repo.lock()
608 tr = repo.transaction("qpush")
608 tr = repo.transaction("qpush")
609 try:
609 try:
610 ret = self._apply(repo, series, list, update_status,
610 ret = self._apply(repo, series, list, update_status,
611 strict, patchdir, merge, all_files=all_files)
611 strict, patchdir, merge, all_files=all_files)
612 tr.close()
612 tr.close()
613 self.save_dirty()
613 self.save_dirty()
614 return ret
614 return ret
615 except:
615 except:
616 try:
616 try:
617 tr.abort()
617 tr.abort()
618 finally:
618 finally:
619 repo.invalidate()
619 repo.invalidate()
620 repo.dirstate.invalidate()
620 repo.dirstate.invalidate()
621 raise
621 raise
622 finally:
622 finally:
623 release(tr, lock, wlock)
623 release(tr, lock, wlock)
624 self.removeundo(repo)
624 self.removeundo(repo)
625
625
626 def _apply(self, repo, series, list=False, update_status=True,
626 def _apply(self, repo, series, list=False, update_status=True,
627 strict=False, patchdir=None, merge=None, all_files=None):
627 strict=False, patchdir=None, merge=None, all_files=None):
628 '''returns (error, hash)
628 '''returns (error, hash)
629 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
629 error = 1 for unable to read, 2 for patch failed, 3 for patch fuzz'''
630 # TODO unify with commands.py
630 # TODO unify with commands.py
631 if not patchdir:
631 if not patchdir:
632 patchdir = self.path
632 patchdir = self.path
633 err = 0
633 err = 0
634 n = None
634 n = None
635 for patchname in series:
635 for patchname in series:
636 pushable, reason = self.pushable(patchname)
636 pushable, reason = self.pushable(patchname)
637 if not pushable:
637 if not pushable:
638 self.explain_pushable(patchname, all_patches=True)
638 self.explain_pushable(patchname, all_patches=True)
639 continue
639 continue
640 self.ui.status(_("applying %s\n") % patchname)
640 self.ui.status(_("applying %s\n") % patchname)
641 pf = os.path.join(patchdir, patchname)
641 pf = os.path.join(patchdir, patchname)
642
642
643 try:
643 try:
644 ph = patchheader(self.join(patchname), self.plainmode)
644 ph = patchheader(self.join(patchname), self.plainmode)
645 except:
645 except:
646 self.ui.warn(_("unable to read %s\n") % patchname)
646 self.ui.warn(_("unable to read %s\n") % patchname)
647 err = 1
647 err = 1
648 break
648 break
649
649
650 message = ph.message
650 message = ph.message
651 if not message:
651 if not message:
652 message = "imported patch %s\n" % patchname
652 message = "imported patch %s\n" % patchname
653 else:
653 else:
654 if list:
654 if list:
655 message.append("\nimported patch %s" % patchname)
655 message.append("\nimported patch %s" % patchname)
656 message = '\n'.join(message)
656 message = '\n'.join(message)
657
657
658 if ph.haspatch:
658 if ph.haspatch:
659 (patcherr, files, fuzz) = self.patch(repo, pf)
659 (patcherr, files, fuzz) = self.patch(repo, pf)
660 if all_files is not None:
660 if all_files is not None:
661 all_files.update(files)
661 all_files.update(files)
662 patcherr = not patcherr
662 patcherr = not patcherr
663 else:
663 else:
664 self.ui.warn(_("patch %s is empty\n") % patchname)
664 self.ui.warn(_("patch %s is empty\n") % patchname)
665 patcherr, files, fuzz = 0, [], 0
665 patcherr, files, fuzz = 0, [], 0
666
666
667 if merge and files:
667 if merge and files:
668 # Mark as removed/merged and update dirstate parent info
668 # Mark as removed/merged and update dirstate parent info
669 removed = []
669 removed = []
670 merged = []
670 merged = []
671 for f in files:
671 for f in files:
672 if os.path.exists(repo.wjoin(f)):
672 if os.path.exists(repo.wjoin(f)):
673 merged.append(f)
673 merged.append(f)
674 else:
674 else:
675 removed.append(f)
675 removed.append(f)
676 for f in removed:
676 for f in removed:
677 repo.dirstate.remove(f)
677 repo.dirstate.remove(f)
678 for f in merged:
678 for f in merged:
679 repo.dirstate.merge(f)
679 repo.dirstate.merge(f)
680 p1, p2 = repo.dirstate.parents()
680 p1, p2 = repo.dirstate.parents()
681 repo.dirstate.setparents(p1, merge)
681 repo.dirstate.setparents(p1, merge)
682
682
683 files = patch.updatedir(self.ui, repo, files)
683 files = patch.updatedir(self.ui, repo, files)
684 match = cmdutil.matchfiles(repo, files or [])
684 match = cmdutil.matchfiles(repo, files or [])
685 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
685 n = repo.commit(message, ph.user, ph.date, match=match, force=True)
686
686
687 if n is None:
687 if n is None:
688 raise util.Abort(_("repo commit failed"))
688 raise util.Abort(_("repo commit failed"))
689
689
690 if update_status:
690 if update_status:
691 self.applied.append(statusentry(n, patchname))
691 self.applied.append(statusentry(n, patchname))
692
692
693 if patcherr:
693 if patcherr:
694 self.ui.warn(_("patch failed, rejects left in working dir\n"))
694 self.ui.warn(_("patch failed, rejects left in working dir\n"))
695 err = 2
695 err = 2
696 break
696 break
697
697
698 if fuzz and strict:
698 if fuzz and strict:
699 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
699 self.ui.warn(_("fuzz found when applying patch, stopping\n"))
700 err = 3
700 err = 3
701 break
701 break
702 return (err, n)
702 return (err, n)
703
703
704 def _cleanup(self, patches, numrevs, keep=False):
704 def _cleanup(self, patches, numrevs, keep=False):
705 if not keep:
705 if not keep:
706 r = self.qrepo()
706 r = self.qrepo()
707 if r:
707 if r:
708 r[None].remove(patches, True)
708 r[None].remove(patches, True)
709 else:
709 else:
710 for p in patches:
710 for p in patches:
711 os.unlink(self.join(p))
711 os.unlink(self.join(p))
712
712
713 if numrevs:
713 if numrevs:
714 del self.applied[:numrevs]
714 del self.applied[:numrevs]
715 self.applied_dirty = 1
715 self.applied_dirty = 1
716
716
717 for i in sorted([self.find_series(p) for p in patches], reverse=True):
717 for i in sorted([self.find_series(p) for p in patches], reverse=True):
718 del self.full_series[i]
718 del self.full_series[i]
719 self.parse_series()
719 self.parse_series()
720 self.series_dirty = 1
720 self.series_dirty = 1
721
721
722 def _revpatches(self, repo, revs):
722 def _revpatches(self, repo, revs):
723 firstrev = repo[self.applied[0].node].rev()
723 firstrev = repo[self.applied[0].node].rev()
724 patches = []
724 patches = []
725 for i, rev in enumerate(revs):
725 for i, rev in enumerate(revs):
726
726
727 if rev < firstrev:
727 if rev < firstrev:
728 raise util.Abort(_('revision %d is not managed') % rev)
728 raise util.Abort(_('revision %d is not managed') % rev)
729
729
730 ctx = repo[rev]
730 ctx = repo[rev]
731 base = self.applied[i].node
731 base = self.applied[i].node
732 if ctx.node() != base:
732 if ctx.node() != base:
733 msg = _('cannot delete revision %d above applied patches')
733 msg = _('cannot delete revision %d above applied patches')
734 raise util.Abort(msg % rev)
734 raise util.Abort(msg % rev)
735
735
736 patch = self.applied[i].name
736 patch = self.applied[i].name
737 for fmt in ('[mq]: %s', 'imported patch %s'):
737 for fmt in ('[mq]: %s', 'imported patch %s'):
738 if ctx.description() == fmt % patch:
738 if ctx.description() == fmt % patch:
739 msg = _('patch %s finalized without changeset message\n')
739 msg = _('patch %s finalized without changeset message\n')
740 repo.ui.status(msg % patch)
740 repo.ui.status(msg % patch)
741 break
741 break
742
742
743 patches.append(patch)
743 patches.append(patch)
744 return patches
744 return patches
745
745
746 def finish(self, repo, revs):
746 def finish(self, repo, revs):
747 patches = self._revpatches(repo, sorted(revs))
747 patches = self._revpatches(repo, sorted(revs))
748 self._cleanup(patches, len(patches))
748 self._cleanup(patches, len(patches))
749
749
750 def delete(self, repo, patches, opts):
750 def delete(self, repo, patches, opts):
751 if not patches and not opts.get('rev'):
751 if not patches and not opts.get('rev'):
752 raise util.Abort(_('qdelete requires at least one revision or '
752 raise util.Abort(_('qdelete requires at least one revision or '
753 'patch name'))
753 'patch name'))
754
754
755 for patch in patches:
755 for patch in patches:
756 patch = self.lookup(patch, strict=True)
756 patch = self.lookup(patch, strict=True)
757 info = self.isapplied(patch)
757 info = self.isapplied(patch)
758 if info:
758 if info:
759 raise util.Abort(_("cannot delete applied patch %s") % patch)
759 raise util.Abort(_("cannot delete applied patch %s") % patch)
760 if patch not in self.series:
760 if patch not in self.series:
761 raise util.Abort(_("patch %s not in series file") % patch)
761 raise util.Abort(_("patch %s not in series file") % patch)
762
762
763 patches = list(patches)
763 patches = list(patches)
764 numrevs = 0
764 numrevs = 0
765 if opts.get('rev'):
765 if opts.get('rev'):
766 if not self.applied:
766 if not self.applied:
767 raise util.Abort(_('no patches applied'))
767 raise util.Abort(_('no patches applied'))
768 revs = cmdutil.revrange(repo, opts['rev'])
768 revs = cmdutil.revrange(repo, opts['rev'])
769 if len(revs) > 1 and revs[0] > revs[1]:
769 if len(revs) > 1 and revs[0] > revs[1]:
770 revs.reverse()
770 revs.reverse()
771 revpatches = self._revpatches(repo, revs)
771 revpatches = self._revpatches(repo, revs)
772 patches += revpatches
772 patches += revpatches
773 numrevs = len(revpatches)
773 numrevs = len(revpatches)
774
774
775 self._cleanup(patches, numrevs, opts.get('keep'))
775 self._cleanup(patches, numrevs, opts.get('keep'))
776
776
777 def check_toppatch(self, repo):
777 def check_toppatch(self, repo):
778 if self.applied:
778 if self.applied:
779 top = self.applied[-1].node
779 top = self.applied[-1].node
780 patch = self.applied[-1].name
780 patch = self.applied[-1].name
781 pp = repo.dirstate.parents()
781 pp = repo.dirstate.parents()
782 if top not in pp:
782 if top not in pp:
783 raise util.Abort(_("working directory revision is not qtip"))
783 raise util.Abort(_("working directory revision is not qtip"))
784 return top, patch
784 return top, patch
785 return None, None
785 return None, None
786
786
787 def check_localchanges(self, repo, force=False, refresh=True):
787 def check_localchanges(self, repo, force=False, refresh=True):
788 m, a, r, d = repo.status()[:4]
788 m, a, r, d = repo.status()[:4]
789 if (m or a or r or d) and not force:
789 if (m or a or r or d) and not force:
790 if refresh:
790 if refresh:
791 raise util.Abort(_("local changes found, refresh first"))
791 raise util.Abort(_("local changes found, refresh first"))
792 else:
792 else:
793 raise util.Abort(_("local changes found"))
793 raise util.Abort(_("local changes found"))
794 return m, a, r, d
794 return m, a, r, d
795
795
796 _reserved = ('series', 'status', 'guards')
796 _reserved = ('series', 'status', 'guards')
797 def check_reserved_name(self, name):
797 def check_reserved_name(self, name):
798 if (name in self._reserved or name.startswith('.hg')
798 if (name in self._reserved or name.startswith('.hg')
799 or name.startswith('.mq') or '#' in name or ':' in name):
799 or name.startswith('.mq') or '#' in name or ':' in name):
800 raise util.Abort(_('"%s" cannot be used as the name of a patch')
800 raise util.Abort(_('"%s" cannot be used as the name of a patch')
801 % name)
801 % name)
802
802
803 def new(self, repo, patchfn, *pats, **opts):
803 def new(self, repo, patchfn, *pats, **opts):
804 """options:
804 """options:
805 msg: a string or a no-argument function returning a string
805 msg: a string or a no-argument function returning a string
806 """
806 """
807 msg = opts.get('msg')
807 msg = opts.get('msg')
808 user = opts.get('user')
808 user = opts.get('user')
809 date = opts.get('date')
809 date = opts.get('date')
810 if date:
810 if date:
811 date = util.parsedate(date)
811 date = util.parsedate(date)
812 diffopts = self.diffopts({'git': opts.get('git')})
812 diffopts = self.diffopts({'git': opts.get('git')})
813 self.check_reserved_name(patchfn)
813 self.check_reserved_name(patchfn)
814 if os.path.exists(self.join(patchfn)):
814 if os.path.exists(self.join(patchfn)):
815 raise util.Abort(_('patch "%s" already exists') % patchfn)
815 raise util.Abort(_('patch "%s" already exists') % patchfn)
816 if opts.get('include') or opts.get('exclude') or pats:
816 if opts.get('include') or opts.get('exclude') or pats:
817 match = cmdutil.match(repo, pats, opts)
817 match = cmdutil.match(repo, pats, opts)
818 # detect missing files in pats
818 # detect missing files in pats
819 def badfn(f, msg):
819 def badfn(f, msg):
820 raise util.Abort('%s: %s' % (f, msg))
820 raise util.Abort('%s: %s' % (f, msg))
821 match.bad = badfn
821 match.bad = badfn
822 m, a, r, d = repo.status(match=match)[:4]
822 m, a, r, d = repo.status(match=match)[:4]
823 else:
823 else:
824 m, a, r, d = self.check_localchanges(repo, force=True)
824 m, a, r, d = self.check_localchanges(repo, force=True)
825 match = cmdutil.matchfiles(repo, m + a + r)
825 match = cmdutil.matchfiles(repo, m + a + r)
826 if len(repo[None].parents()) > 1:
826 if len(repo[None].parents()) > 1:
827 raise util.Abort(_('cannot manage merge changesets'))
827 raise util.Abort(_('cannot manage merge changesets'))
828 commitfiles = m + a + r
828 commitfiles = m + a + r
829 self.check_toppatch(repo)
829 self.check_toppatch(repo)
830 insert = self.full_series_end()
830 insert = self.full_series_end()
831 wlock = repo.wlock()
831 wlock = repo.wlock()
832 try:
832 try:
833 # if patch file write fails, abort early
833 # if patch file write fails, abort early
834 p = self.opener(patchfn, "w")
834 p = self.opener(patchfn, "w")
835 try:
835 try:
836 if self.plainmode:
836 if self.plainmode:
837 if user:
837 if user:
838 p.write("From: " + user + "\n")
838 p.write("From: " + user + "\n")
839 if not date:
839 if not date:
840 p.write("\n")
840 p.write("\n")
841 if date:
841 if date:
842 p.write("Date: %d %d\n\n" % date)
842 p.write("Date: %d %d\n\n" % date)
843 else:
843 else:
844 p.write("# HG changeset patch\n")
844 p.write("# HG changeset patch\n")
845 p.write("# Parent "
845 p.write("# Parent "
846 + hex(repo[None].parents()[0].node()) + "\n")
846 + hex(repo[None].parents()[0].node()) + "\n")
847 if user:
847 if user:
848 p.write("# User " + user + "\n")
848 p.write("# User " + user + "\n")
849 if date:
849 if date:
850 p.write("# Date %s %s\n\n" % date)
850 p.write("# Date %s %s\n\n" % date)
851 if hasattr(msg, '__call__'):
851 if hasattr(msg, '__call__'):
852 msg = msg()
852 msg = msg()
853 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
853 commitmsg = msg and msg or ("[mq]: %s" % patchfn)
854 n = repo.commit(commitmsg, user, date, match=match, force=True)
854 n = repo.commit(commitmsg, user, date, match=match, force=True)
855 if n is None:
855 if n is None:
856 raise util.Abort(_("repo commit failed"))
856 raise util.Abort(_("repo commit failed"))
857 try:
857 try:
858 self.full_series[insert:insert] = [patchfn]
858 self.full_series[insert:insert] = [patchfn]
859 self.applied.append(statusentry(n, patchfn))
859 self.applied.append(statusentry(n, patchfn))
860 self.parse_series()
860 self.parse_series()
861 self.series_dirty = 1
861 self.series_dirty = 1
862 self.applied_dirty = 1
862 self.applied_dirty = 1
863 if msg:
863 if msg:
864 msg = msg + "\n\n"
864 msg = msg + "\n\n"
865 p.write(msg)
865 p.write(msg)
866 if commitfiles:
866 if commitfiles:
867 parent = self.qparents(repo, n)
867 parent = self.qparents(repo, n)
868 chunks = patch.diff(repo, node1=parent, node2=n,
868 chunks = patch.diff(repo, node1=parent, node2=n,
869 match=match, opts=diffopts)
869 match=match, opts=diffopts)
870 for chunk in chunks:
870 for chunk in chunks:
871 p.write(chunk)
871 p.write(chunk)
872 p.close()
872 p.close()
873 wlock.release()
873 wlock.release()
874 wlock = None
874 wlock = None
875 r = self.qrepo()
875 r = self.qrepo()
876 if r:
876 if r:
877 r[None].add([patchfn])
877 r[None].add([patchfn])
878 except:
878 except:
879 repo.rollback()
879 repo.rollback()
880 raise
880 raise
881 except Exception:
881 except Exception:
882 patchpath = self.join(patchfn)
882 patchpath = self.join(patchfn)
883 try:
883 try:
884 os.unlink(patchpath)
884 os.unlink(patchpath)
885 except:
885 except:
886 self.ui.warn(_('error unlinking %s\n') % patchpath)
886 self.ui.warn(_('error unlinking %s\n') % patchpath)
887 raise
887 raise
888 self.removeundo(repo)
888 self.removeundo(repo)
889 finally:
889 finally:
890 release(wlock)
890 release(wlock)
891
891
892 def strip(self, repo, rev, update=True, backup="all", force=None):
892 def strip(self, repo, rev, update=True, backup="all", force=None):
893 wlock = lock = None
893 wlock = lock = None
894 try:
894 try:
895 wlock = repo.wlock()
895 wlock = repo.wlock()
896 lock = repo.lock()
896 lock = repo.lock()
897
897
898 if update:
898 if update:
899 self.check_localchanges(repo, force=force, refresh=False)
899 self.check_localchanges(repo, force=force, refresh=False)
900 urev = self.qparents(repo, rev)
900 urev = self.qparents(repo, rev)
901 hg.clean(repo, urev)
901 hg.clean(repo, urev)
902 repo.dirstate.write()
902 repo.dirstate.write()
903
903
904 self.removeundo(repo)
904 self.removeundo(repo)
905 repair.strip(self.ui, repo, rev, backup)
905 repair.strip(self.ui, repo, rev, backup)
906 # strip may have unbundled a set of backed up revisions after
906 # strip may have unbundled a set of backed up revisions after
907 # the actual strip
907 # the actual strip
908 self.removeundo(repo)
908 self.removeundo(repo)
909 finally:
909 finally:
910 release(lock, wlock)
910 release(lock, wlock)
911
911
912 def isapplied(self, patch):
912 def isapplied(self, patch):
913 """returns (index, rev, patch)"""
913 """returns (index, rev, patch)"""
914 for i, a in enumerate(self.applied):
914 for i, a in enumerate(self.applied):
915 if a.name == patch:
915 if a.name == patch:
916 return (i, a.node, a.name)
916 return (i, a.node, a.name)
917 return None
917 return None
918
918
919 # if the exact patch name does not exist, we try a few
919 # if the exact patch name does not exist, we try a few
920 # variations. If strict is passed, we try only #1
920 # variations. If strict is passed, we try only #1
921 #
921 #
922 # 1) a number to indicate an offset in the series file
922 # 1) a number to indicate an offset in the series file
923 # 2) a unique substring of the patch name was given
923 # 2) a unique substring of the patch name was given
924 # 3) patchname[-+]num to indicate an offset in the series file
924 # 3) patchname[-+]num to indicate an offset in the series file
925 def lookup(self, patch, strict=False):
925 def lookup(self, patch, strict=False):
926 patch = patch and str(patch)
926 patch = patch and str(patch)
927
927
928 def partial_name(s):
928 def partial_name(s):
929 if s in self.series:
929 if s in self.series:
930 return s
930 return s
931 matches = [x for x in self.series if s in x]
931 matches = [x for x in self.series if s in x]
932 if len(matches) > 1:
932 if len(matches) > 1:
933 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
933 self.ui.warn(_('patch name "%s" is ambiguous:\n') % s)
934 for m in matches:
934 for m in matches:
935 self.ui.warn(' %s\n' % m)
935 self.ui.warn(' %s\n' % m)
936 return None
936 return None
937 if matches:
937 if matches:
938 return matches[0]
938 return matches[0]
939 if self.series and self.applied:
939 if self.series and self.applied:
940 if s == 'qtip':
940 if s == 'qtip':
941 return self.series[self.series_end(True)-1]
941 return self.series[self.series_end(True)-1]
942 if s == 'qbase':
942 if s == 'qbase':
943 return self.series[0]
943 return self.series[0]
944 return None
944 return None
945
945
946 if patch is None:
946 if patch is None:
947 return None
947 return None
948 if patch in self.series:
948 if patch in self.series:
949 return patch
949 return patch
950
950
951 if not os.path.isfile(self.join(patch)):
951 if not os.path.isfile(self.join(patch)):
952 try:
952 try:
953 sno = int(patch)
953 sno = int(patch)
954 except (ValueError, OverflowError):
954 except (ValueError, OverflowError):
955 pass
955 pass
956 else:
956 else:
957 if -len(self.series) <= sno < len(self.series):
957 if -len(self.series) <= sno < len(self.series):
958 return self.series[sno]
958 return self.series[sno]
959
959
960 if not strict:
960 if not strict:
961 res = partial_name(patch)
961 res = partial_name(patch)
962 if res:
962 if res:
963 return res
963 return res
964 minus = patch.rfind('-')
964 minus = patch.rfind('-')
965 if minus >= 0:
965 if minus >= 0:
966 res = partial_name(patch[:minus])
966 res = partial_name(patch[:minus])
967 if res:
967 if res:
968 i = self.series.index(res)
968 i = self.series.index(res)
969 try:
969 try:
970 off = int(patch[minus + 1:] or 1)
970 off = int(patch[minus + 1:] or 1)
971 except (ValueError, OverflowError):
971 except (ValueError, OverflowError):
972 pass
972 pass
973 else:
973 else:
974 if i - off >= 0:
974 if i - off >= 0:
975 return self.series[i - off]
975 return self.series[i - off]
976 plus = patch.rfind('+')
976 plus = patch.rfind('+')
977 if plus >= 0:
977 if plus >= 0:
978 res = partial_name(patch[:plus])
978 res = partial_name(patch[:plus])
979 if res:
979 if res:
980 i = self.series.index(res)
980 i = self.series.index(res)
981 try:
981 try:
982 off = int(patch[plus + 1:] or 1)
982 off = int(patch[plus + 1:] or 1)
983 except (ValueError, OverflowError):
983 except (ValueError, OverflowError):
984 pass
984 pass
985 else:
985 else:
986 if i + off < len(self.series):
986 if i + off < len(self.series):
987 return self.series[i + off]
987 return self.series[i + off]
988 raise util.Abort(_("patch %s not in series") % patch)
988 raise util.Abort(_("patch %s not in series") % patch)
989
989
990 def push(self, repo, patch=None, force=False, list=False,
990 def push(self, repo, patch=None, force=False, list=False,
991 mergeq=None, all=False, move=False):
991 mergeq=None, all=False, move=False):
992 diffopts = self.diffopts()
992 diffopts = self.diffopts()
993 wlock = repo.wlock()
993 wlock = repo.wlock()
994 try:
994 try:
995 heads = []
995 heads = []
996 for b, ls in repo.branchmap().iteritems():
996 for b, ls in repo.branchmap().iteritems():
997 heads += ls
997 heads += ls
998 if not heads:
998 if not heads:
999 heads = [nullid]
999 heads = [nullid]
1000 if repo.dirstate.parents()[0] not in heads:
1000 if repo.dirstate.parents()[0] not in heads:
1001 self.ui.status(_("(working directory not at a head)\n"))
1001 self.ui.status(_("(working directory not at a head)\n"))
1002
1002
1003 if not self.series:
1003 if not self.series:
1004 self.ui.warn(_('no patches in series\n'))
1004 self.ui.warn(_('no patches in series\n'))
1005 return 0
1005 return 0
1006
1006
1007 patch = self.lookup(patch)
1007 patch = self.lookup(patch)
1008 # Suppose our series file is: A B C and the current 'top'
1008 # Suppose our series file is: A B C and the current 'top'
1009 # patch is B. qpush C should be performed (moving forward)
1009 # patch is B. qpush C should be performed (moving forward)
1010 # qpush B is a NOP (no change) qpush A is an error (can't
1010 # qpush B is a NOP (no change) qpush A is an error (can't
1011 # go backwards with qpush)
1011 # go backwards with qpush)
1012 if patch:
1012 if patch:
1013 info = self.isapplied(patch)
1013 info = self.isapplied(patch)
1014 if info:
1014 if info:
1015 if info[0] < len(self.applied) - 1:
1015 if info[0] < len(self.applied) - 1:
1016 raise util.Abort(
1016 raise util.Abort(
1017 _("cannot push to a previous patch: %s") % patch)
1017 _("cannot push to a previous patch: %s") % patch)
1018 self.ui.warn(
1018 self.ui.warn(
1019 _('qpush: %s is already at the top\n') % patch)
1019 _('qpush: %s is already at the top\n') % patch)
1020 return
1020 return
1021 pushable, reason = self.pushable(patch)
1021 pushable, reason = self.pushable(patch)
1022 if not pushable:
1022 if not pushable:
1023 if reason:
1023 if reason:
1024 reason = _('guarded by %r') % reason
1024 reason = _('guarded by %r') % reason
1025 else:
1025 else:
1026 reason = _('no matching guards')
1026 reason = _('no matching guards')
1027 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1027 self.ui.warn(_("cannot push '%s' - %s\n") % (patch, reason))
1028 return 1
1028 return 1
1029 elif all:
1029 elif all:
1030 patch = self.series[-1]
1030 patch = self.series[-1]
1031 if self.isapplied(patch):
1031 if self.isapplied(patch):
1032 self.ui.warn(_('all patches are currently applied\n'))
1032 self.ui.warn(_('all patches are currently applied\n'))
1033 return 0
1033 return 0
1034
1034
1035 # Following the above example, starting at 'top' of B:
1035 # Following the above example, starting at 'top' of B:
1036 # qpush should be performed (pushes C), but a subsequent
1036 # qpush should be performed (pushes C), but a subsequent
1037 # qpush without an argument is an error (nothing to
1037 # qpush without an argument is an error (nothing to
1038 # apply). This allows a loop of "...while hg qpush..." to
1038 # apply). This allows a loop of "...while hg qpush..." to
1039 # work as it detects an error when done
1039 # work as it detects an error when done
1040 start = self.series_end()
1040 start = self.series_end()
1041 if start == len(self.series):
1041 if start == len(self.series):
1042 self.ui.warn(_('patch series already fully applied\n'))
1042 self.ui.warn(_('patch series already fully applied\n'))
1043 return 1
1043 return 1
1044 if not force:
1044 if not force:
1045 self.check_localchanges(repo)
1045 self.check_localchanges(repo)
1046
1046
1047 if move:
1047 if move:
1048 try:
1048 try:
1049 del self.full_series[self.full_series.index(patch, start)]
1049 del self.full_series[self.full_series.index(patch, start)]
1050 except ValueError:
1050 except ValueError:
1051 raise util.Abort(_("patch '%s' not found") % patch)
1051 raise util.Abort(_("patch '%s' not found") % patch)
1052 self.full_series.insert(start, patch)
1052 self.full_series.insert(start, patch)
1053 self.parse_series()
1053 self.parse_series()
1054 self.series_dirty = 1
1054 self.series_dirty = 1
1055
1055
1056 self.applied_dirty = 1
1056 self.applied_dirty = 1
1057 if start > 0:
1057 if start > 0:
1058 self.check_toppatch(repo)
1058 self.check_toppatch(repo)
1059 if not patch:
1059 if not patch:
1060 patch = self.series[start]
1060 patch = self.series[start]
1061 end = start + 1
1061 end = start + 1
1062 else:
1062 else:
1063 end = self.series.index(patch, start) + 1
1063 end = self.series.index(patch, start) + 1
1064
1064
1065 s = self.series[start:end]
1065 s = self.series[start:end]
1066 all_files = set()
1066 all_files = set()
1067 try:
1067 try:
1068 if mergeq:
1068 if mergeq:
1069 ret = self.mergepatch(repo, mergeq, s, diffopts)
1069 ret = self.mergepatch(repo, mergeq, s, diffopts)
1070 else:
1070 else:
1071 ret = self.apply(repo, s, list, all_files=all_files)
1071 ret = self.apply(repo, s, list, all_files=all_files)
1072 except:
1072 except:
1073 self.ui.warn(_('cleaning up working directory...'))
1073 self.ui.warn(_('cleaning up working directory...'))
1074 node = repo.dirstate.parents()[0]
1074 node = repo.dirstate.parents()[0]
1075 hg.revert(repo, node, None)
1075 hg.revert(repo, node, None)
1076 # only remove unknown files that we know we touched or
1076 # only remove unknown files that we know we touched or
1077 # created while patching
1077 # created while patching
1078 for f in all_files:
1078 for f in all_files:
1079 if f not in repo.dirstate:
1079 if f not in repo.dirstate:
1080 try:
1080 try:
1081 util.unlink(repo.wjoin(f))
1081 util.unlink(repo.wjoin(f))
1082 except OSError, inst:
1082 except OSError, inst:
1083 if inst.errno != errno.ENOENT:
1083 if inst.errno != errno.ENOENT:
1084 raise
1084 raise
1085 self.ui.warn(_('done\n'))
1085 self.ui.warn(_('done\n'))
1086 raise
1086 raise
1087
1087
1088 if not self.applied:
1088 if not self.applied:
1089 return ret[0]
1089 return ret[0]
1090 top = self.applied[-1].name
1090 top = self.applied[-1].name
1091 if ret[0] and ret[0] > 1:
1091 if ret[0] and ret[0] > 1:
1092 msg = _("errors during apply, please fix and refresh %s\n")
1092 msg = _("errors during apply, please fix and refresh %s\n")
1093 self.ui.write(msg % top)
1093 self.ui.write(msg % top)
1094 else:
1094 else:
1095 self.ui.write(_("now at: %s\n") % top)
1095 self.ui.write(_("now at: %s\n") % top)
1096 return ret[0]
1096 return ret[0]
1097
1097
1098 finally:
1098 finally:
1099 wlock.release()
1099 wlock.release()
1100
1100
1101 def pop(self, repo, patch=None, force=False, update=True, all=False):
1101 def pop(self, repo, patch=None, force=False, update=True, all=False):
1102 wlock = repo.wlock()
1102 wlock = repo.wlock()
1103 try:
1103 try:
1104 if patch:
1104 if patch:
1105 # index, rev, patch
1105 # index, rev, patch
1106 info = self.isapplied(patch)
1106 info = self.isapplied(patch)
1107 if not info:
1107 if not info:
1108 patch = self.lookup(patch)
1108 patch = self.lookup(patch)
1109 info = self.isapplied(patch)
1109 info = self.isapplied(patch)
1110 if not info:
1110 if not info:
1111 raise util.Abort(_("patch %s is not applied") % patch)
1111 raise util.Abort(_("patch %s is not applied") % patch)
1112
1112
1113 if not self.applied:
1113 if not self.applied:
1114 # Allow qpop -a to work repeatedly,
1114 # Allow qpop -a to work repeatedly,
1115 # but not qpop without an argument
1115 # but not qpop without an argument
1116 self.ui.warn(_("no patches applied\n"))
1116 self.ui.warn(_("no patches applied\n"))
1117 return not all
1117 return not all
1118
1118
1119 if all:
1119 if all:
1120 start = 0
1120 start = 0
1121 elif patch:
1121 elif patch:
1122 start = info[0] + 1
1122 start = info[0] + 1
1123 else:
1123 else:
1124 start = len(self.applied) - 1
1124 start = len(self.applied) - 1
1125
1125
1126 if start >= len(self.applied):
1126 if start >= len(self.applied):
1127 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1127 self.ui.warn(_("qpop: %s is already at the top\n") % patch)
1128 return
1128 return
1129
1129
1130 if not update:
1130 if not update:
1131 parents = repo.dirstate.parents()
1131 parents = repo.dirstate.parents()
1132 rr = [x.node for x in self.applied]
1132 rr = [x.node for x in self.applied]
1133 for p in parents:
1133 for p in parents:
1134 if p in rr:
1134 if p in rr:
1135 self.ui.warn(_("qpop: forcing dirstate update\n"))
1135 self.ui.warn(_("qpop: forcing dirstate update\n"))
1136 update = True
1136 update = True
1137 else:
1137 else:
1138 parents = [p.node() for p in repo[None].parents()]
1138 parents = [p.node() for p in repo[None].parents()]
1139 needupdate = False
1139 needupdate = False
1140 for entry in self.applied[start:]:
1140 for entry in self.applied[start:]:
1141 if entry.node in parents:
1141 if entry.node in parents:
1142 needupdate = True
1142 needupdate = True
1143 break
1143 break
1144 update = needupdate
1144 update = needupdate
1145
1145
1146 if not force and update:
1146 if not force and update:
1147 self.check_localchanges(repo)
1147 self.check_localchanges(repo)
1148
1148
1149 self.applied_dirty = 1
1149 self.applied_dirty = 1
1150 end = len(self.applied)
1150 end = len(self.applied)
1151 rev = self.applied[start].node
1151 rev = self.applied[start].node
1152 if update:
1152 if update:
1153 top = self.check_toppatch(repo)[0]
1153 top = self.check_toppatch(repo)[0]
1154
1154
1155 try:
1155 try:
1156 heads = repo.changelog.heads(rev)
1156 heads = repo.changelog.heads(rev)
1157 except error.LookupError:
1157 except error.LookupError:
1158 node = short(rev)
1158 node = short(rev)
1159 raise util.Abort(_('trying to pop unknown node %s') % node)
1159 raise util.Abort(_('trying to pop unknown node %s') % node)
1160
1160
1161 if heads != [self.applied[-1].node]:
1161 if heads != [self.applied[-1].node]:
1162 raise util.Abort(_("popping would remove a revision not "
1162 raise util.Abort(_("popping would remove a revision not "
1163 "managed by this patch queue"))
1163 "managed by this patch queue"))
1164
1164
1165 # we know there are no local changes, so we can make a simplified
1165 # we know there are no local changes, so we can make a simplified
1166 # form of hg.update.
1166 # form of hg.update.
1167 if update:
1167 if update:
1168 qp = self.qparents(repo, rev)
1168 qp = self.qparents(repo, rev)
1169 ctx = repo[qp]
1169 ctx = repo[qp]
1170 m, a, r, d = repo.status(qp, top)[:4]
1170 m, a, r, d = repo.status(qp, top)[:4]
1171 if d:
1171 if d:
1172 raise util.Abort(_("deletions found between repo revs"))
1172 raise util.Abort(_("deletions found between repo revs"))
1173 for f in a:
1173 for f in a:
1174 try:
1174 try:
1175 util.unlink(repo.wjoin(f))
1175 util.unlink(repo.wjoin(f))
1176 except OSError, e:
1176 except OSError, e:
1177 if e.errno != errno.ENOENT:
1177 if e.errno != errno.ENOENT:
1178 raise
1178 raise
1179 repo.dirstate.forget(f)
1179 repo.dirstate.forget(f)
1180 for f in m + r:
1180 for f in m + r:
1181 fctx = ctx[f]
1181 fctx = ctx[f]
1182 repo.wwrite(f, fctx.data(), fctx.flags())
1182 repo.wwrite(f, fctx.data(), fctx.flags())
1183 repo.dirstate.normal(f)
1183 repo.dirstate.normal(f)
1184 repo.dirstate.setparents(qp, nullid)
1184 repo.dirstate.setparents(qp, nullid)
1185 for patch in reversed(self.applied[start:end]):
1185 for patch in reversed(self.applied[start:end]):
1186 self.ui.status(_("popping %s\n") % patch.name)
1186 self.ui.status(_("popping %s\n") % patch.name)
1187 del self.applied[start:end]
1187 del self.applied[start:end]
1188 self.strip(repo, rev, update=False, backup='strip')
1188 self.strip(repo, rev, update=False, backup='strip')
1189 if self.applied:
1189 if self.applied:
1190 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1190 self.ui.write(_("now at: %s\n") % self.applied[-1].name)
1191 else:
1191 else:
1192 self.ui.write(_("patch queue now empty\n"))
1192 self.ui.write(_("patch queue now empty\n"))
1193 finally:
1193 finally:
1194 wlock.release()
1194 wlock.release()
1195
1195
1196 def diff(self, repo, pats, opts):
1196 def diff(self, repo, pats, opts):
1197 top, patch = self.check_toppatch(repo)
1197 top, patch = self.check_toppatch(repo)
1198 if not top:
1198 if not top:
1199 self.ui.write(_("no patches applied\n"))
1199 self.ui.write(_("no patches applied\n"))
1200 return
1200 return
1201 qp = self.qparents(repo, top)
1201 qp = self.qparents(repo, top)
1202 if opts.get('reverse'):
1202 if opts.get('reverse'):
1203 node1, node2 = None, qp
1203 node1, node2 = None, qp
1204 else:
1204 else:
1205 node1, node2 = qp, None
1205 node1, node2 = qp, None
1206 diffopts = self.diffopts(opts, patch)
1206 diffopts = self.diffopts(opts, patch)
1207 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1207 self.printdiff(repo, diffopts, node1, node2, files=pats, opts=opts)
1208
1208
1209 def refresh(self, repo, pats=None, **opts):
1209 def refresh(self, repo, pats=None, **opts):
1210 if not self.applied:
1210 if not self.applied:
1211 self.ui.write(_("no patches applied\n"))
1211 self.ui.write(_("no patches applied\n"))
1212 return 1
1212 return 1
1213 msg = opts.get('msg', '').rstrip()
1213 msg = opts.get('msg', '').rstrip()
1214 newuser = opts.get('user')
1214 newuser = opts.get('user')
1215 newdate = opts.get('date')
1215 newdate = opts.get('date')
1216 if newdate:
1216 if newdate:
1217 newdate = '%d %d' % util.parsedate(newdate)
1217 newdate = '%d %d' % util.parsedate(newdate)
1218 wlock = repo.wlock()
1218 wlock = repo.wlock()
1219
1219
1220 try:
1220 try:
1221 self.check_toppatch(repo)
1221 self.check_toppatch(repo)
1222 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1222 (top, patchfn) = (self.applied[-1].node, self.applied[-1].name)
1223 if repo.changelog.heads(top) != [top]:
1223 if repo.changelog.heads(top) != [top]:
1224 raise util.Abort(_("cannot refresh a revision with children"))
1224 raise util.Abort(_("cannot refresh a revision with children"))
1225
1225
1226 cparents = repo.changelog.parents(top)
1226 cparents = repo.changelog.parents(top)
1227 patchparent = self.qparents(repo, top)
1227 patchparent = self.qparents(repo, top)
1228 ph = patchheader(self.join(patchfn), self.plainmode)
1228 ph = patchheader(self.join(patchfn), self.plainmode)
1229 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1229 diffopts = self.diffopts({'git': opts.get('git')}, patchfn)
1230 if msg:
1230 if msg:
1231 ph.setmessage(msg)
1231 ph.setmessage(msg)
1232 if newuser:
1232 if newuser:
1233 ph.setuser(newuser)
1233 ph.setuser(newuser)
1234 if newdate:
1234 if newdate:
1235 ph.setdate(newdate)
1235 ph.setdate(newdate)
1236 ph.setparent(hex(patchparent))
1236 ph.setparent(hex(patchparent))
1237
1237
1238 # only commit new patch when write is complete
1238 # only commit new patch when write is complete
1239 patchf = self.opener(patchfn, 'w', atomictemp=True)
1239 patchf = self.opener(patchfn, 'w', atomictemp=True)
1240
1240
1241 comments = str(ph)
1241 comments = str(ph)
1242 if comments:
1242 if comments:
1243 patchf.write(comments)
1243 patchf.write(comments)
1244
1244
1245 # update the dirstate in place, strip off the qtip commit
1245 # update the dirstate in place, strip off the qtip commit
1246 # and then commit.
1246 # and then commit.
1247 #
1247 #
1248 # this should really read:
1248 # this should really read:
1249 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1249 # mm, dd, aa, aa2 = repo.status(tip, patchparent)[:4]
1250 # but we do it backwards to take advantage of manifest/chlog
1250 # but we do it backwards to take advantage of manifest/chlog
1251 # caching against the next repo.status call
1251 # caching against the next repo.status call
1252 mm, aa, dd, aa2 = repo.status(patchparent, top)[:4]
1252 mm, aa, dd, aa2 = repo.status(patchparent, top)[:4]
1253 changes = repo.changelog.read(top)
1253 changes = repo.changelog.read(top)
1254 man = repo.manifest.read(changes[0])
1254 man = repo.manifest.read(changes[0])
1255 aaa = aa[:]
1255 aaa = aa[:]
1256 matchfn = cmdutil.match(repo, pats, opts)
1256 matchfn = cmdutil.match(repo, pats, opts)
1257 # in short mode, we only diff the files included in the
1257 # in short mode, we only diff the files included in the
1258 # patch already plus specified files
1258 # patch already plus specified files
1259 if opts.get('short'):
1259 if opts.get('short'):
1260 # if amending a patch, we start with existing
1260 # if amending a patch, we start with existing
1261 # files plus specified files - unfiltered
1261 # files plus specified files - unfiltered
1262 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1262 match = cmdutil.matchfiles(repo, mm + aa + dd + matchfn.files())
1263 # filter with inc/exl options
1263 # filter with inc/exl options
1264 matchfn = cmdutil.match(repo, opts=opts)
1264 matchfn = cmdutil.match(repo, opts=opts)
1265 else:
1265 else:
1266 match = cmdutil.matchall(repo)
1266 match = cmdutil.matchall(repo)
1267 m, a, r, d = repo.status(match=match)[:4]
1267 m, a, r, d = repo.status(match=match)[:4]
1268
1268
1269 # we might end up with files that were added between
1269 # we might end up with files that were added between
1270 # qtip and the dirstate parent, but then changed in the
1270 # qtip and the dirstate parent, but then changed in the
1271 # local dirstate. in this case, we want them to only
1271 # local dirstate. in this case, we want them to only
1272 # show up in the added section
1272 # show up in the added section
1273 for x in m:
1273 for x in m:
1274 if x not in aa:
1274 if x not in aa:
1275 mm.append(x)
1275 mm.append(x)
1276 # we might end up with files added by the local dirstate that
1276 # we might end up with files added by the local dirstate that
1277 # were deleted by the patch. In this case, they should only
1277 # were deleted by the patch. In this case, they should only
1278 # show up in the changed section.
1278 # show up in the changed section.
1279 for x in a:
1279 for x in a:
1280 if x in dd:
1280 if x in dd:
1281 del dd[dd.index(x)]
1281 del dd[dd.index(x)]
1282 mm.append(x)
1282 mm.append(x)
1283 else:
1283 else:
1284 aa.append(x)
1284 aa.append(x)
1285 # make sure any files deleted in the local dirstate
1285 # make sure any files deleted in the local dirstate
1286 # are not in the add or change column of the patch
1286 # are not in the add or change column of the patch
1287 forget = []
1287 forget = []
1288 for x in d + r:
1288 for x in d + r:
1289 if x in aa:
1289 if x in aa:
1290 del aa[aa.index(x)]
1290 del aa[aa.index(x)]
1291 forget.append(x)
1291 forget.append(x)
1292 continue
1292 continue
1293 elif x in mm:
1293 elif x in mm:
1294 del mm[mm.index(x)]
1294 del mm[mm.index(x)]
1295 dd.append(x)
1295 dd.append(x)
1296
1296
1297 m = list(set(mm))
1297 m = list(set(mm))
1298 r = list(set(dd))
1298 r = list(set(dd))
1299 a = list(set(aa))
1299 a = list(set(aa))
1300 c = [filter(matchfn, l) for l in (m, a, r)]
1300 c = [filter(matchfn, l) for l in (m, a, r)]
1301 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1301 match = cmdutil.matchfiles(repo, set(c[0] + c[1] + c[2]))
1302 chunks = patch.diff(repo, patchparent, match=match,
1302 chunks = patch.diff(repo, patchparent, match=match,
1303 changes=c, opts=diffopts)
1303 changes=c, opts=diffopts)
1304 for chunk in chunks:
1304 for chunk in chunks:
1305 patchf.write(chunk)
1305 patchf.write(chunk)
1306
1306
1307 try:
1307 try:
1308 if diffopts.git or diffopts.upgrade:
1308 if diffopts.git or diffopts.upgrade:
1309 copies = {}
1309 copies = {}
1310 for dst in a:
1310 for dst in a:
1311 src = repo.dirstate.copied(dst)
1311 src = repo.dirstate.copied(dst)
1312 # during qfold, the source file for copies may
1312 # during qfold, the source file for copies may
1313 # be removed. Treat this as a simple add.
1313 # be removed. Treat this as a simple add.
1314 if src is not None and src in repo.dirstate:
1314 if src is not None and src in repo.dirstate:
1315 copies.setdefault(src, []).append(dst)
1315 copies.setdefault(src, []).append(dst)
1316 repo.dirstate.add(dst)
1316 repo.dirstate.add(dst)
1317 # remember the copies between patchparent and qtip
1317 # remember the copies between patchparent and qtip
1318 for dst in aaa:
1318 for dst in aaa:
1319 f = repo.file(dst)
1319 f = repo.file(dst)
1320 src = f.renamed(man[dst])
1320 src = f.renamed(man[dst])
1321 if src:
1321 if src:
1322 copies.setdefault(src[0], []).extend(
1322 copies.setdefault(src[0], []).extend(
1323 copies.get(dst, []))
1323 copies.get(dst, []))
1324 if dst in a:
1324 if dst in a:
1325 copies[src[0]].append(dst)
1325 copies[src[0]].append(dst)
1326 # we can't copy a file created by the patch itself
1326 # we can't copy a file created by the patch itself
1327 if dst in copies:
1327 if dst in copies:
1328 del copies[dst]
1328 del copies[dst]
1329 for src, dsts in copies.iteritems():
1329 for src, dsts in copies.iteritems():
1330 for dst in dsts:
1330 for dst in dsts:
1331 repo.dirstate.copy(src, dst)
1331 repo.dirstate.copy(src, dst)
1332 else:
1332 else:
1333 for dst in a:
1333 for dst in a:
1334 repo.dirstate.add(dst)
1334 repo.dirstate.add(dst)
1335 # Drop useless copy information
1335 # Drop useless copy information
1336 for f in list(repo.dirstate.copies()):
1336 for f in list(repo.dirstate.copies()):
1337 repo.dirstate.copy(None, f)
1337 repo.dirstate.copy(None, f)
1338 for f in r:
1338 for f in r:
1339 repo.dirstate.remove(f)
1339 repo.dirstate.remove(f)
1340 # if the patch excludes a modified file, mark that
1340 # if the patch excludes a modified file, mark that
1341 # file with mtime=0 so status can see it.
1341 # file with mtime=0 so status can see it.
1342 mm = []
1342 mm = []
1343 for i in xrange(len(m)-1, -1, -1):
1343 for i in xrange(len(m)-1, -1, -1):
1344 if not matchfn(m[i]):
1344 if not matchfn(m[i]):
1345 mm.append(m[i])
1345 mm.append(m[i])
1346 del m[i]
1346 del m[i]
1347 for f in m:
1347 for f in m:
1348 repo.dirstate.normal(f)
1348 repo.dirstate.normal(f)
1349 for f in mm:
1349 for f in mm:
1350 repo.dirstate.normallookup(f)
1350 repo.dirstate.normallookup(f)
1351 for f in forget:
1351 for f in forget:
1352 repo.dirstate.forget(f)
1352 repo.dirstate.forget(f)
1353
1353
1354 if not msg:
1354 if not msg:
1355 if not ph.message:
1355 if not ph.message:
1356 message = "[mq]: %s\n" % patchfn
1356 message = "[mq]: %s\n" % patchfn
1357 else:
1357 else:
1358 message = "\n".join(ph.message)
1358 message = "\n".join(ph.message)
1359 else:
1359 else:
1360 message = msg
1360 message = msg
1361
1361
1362 user = ph.user or changes[1]
1362 user = ph.user or changes[1]
1363
1363
1364 # assumes strip can roll itself back if interrupted
1364 # assumes strip can roll itself back if interrupted
1365 repo.dirstate.setparents(*cparents)
1365 repo.dirstate.setparents(*cparents)
1366 self.applied.pop()
1366 self.applied.pop()
1367 self.applied_dirty = 1
1367 self.applied_dirty = 1
1368 self.strip(repo, top, update=False,
1368 self.strip(repo, top, update=False,
1369 backup='strip')
1369 backup='strip')
1370 except:
1370 except:
1371 repo.dirstate.invalidate()
1371 repo.dirstate.invalidate()
1372 raise
1372 raise
1373
1373
1374 try:
1374 try:
1375 # might be nice to attempt to roll back strip after this
1375 # might be nice to attempt to roll back strip after this
1376 patchf.rename()
1376 patchf.rename()
1377 n = repo.commit(message, user, ph.date, match=match,
1377 n = repo.commit(message, user, ph.date, match=match,
1378 force=True)
1378 force=True)
1379 self.applied.append(statusentry(n, patchfn))
1379 self.applied.append(statusentry(n, patchfn))
1380 except:
1380 except:
1381 ctx = repo[cparents[0]]
1381 ctx = repo[cparents[0]]
1382 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1382 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
1383 self.save_dirty()
1383 self.save_dirty()
1384 self.ui.warn(_('refresh interrupted while patch was popped! '
1384 self.ui.warn(_('refresh interrupted while patch was popped! '
1385 '(revert --all, qpush to recover)\n'))
1385 '(revert --all, qpush to recover)\n'))
1386 raise
1386 raise
1387 finally:
1387 finally:
1388 wlock.release()
1388 wlock.release()
1389 self.removeundo(repo)
1389 self.removeundo(repo)
1390
1390
1391 def init(self, repo, create=False):
1391 def init(self, repo, create=False):
1392 if not create and os.path.isdir(self.path):
1392 if not create and os.path.isdir(self.path):
1393 raise util.Abort(_("patch queue directory already exists"))
1393 raise util.Abort(_("patch queue directory already exists"))
1394 try:
1394 try:
1395 os.mkdir(self.path)
1395 os.mkdir(self.path)
1396 except OSError, inst:
1396 except OSError, inst:
1397 if inst.errno != errno.EEXIST or not create:
1397 if inst.errno != errno.EEXIST or not create:
1398 raise
1398 raise
1399 if create:
1399 if create:
1400 return self.qrepo(create=True)
1400 return self.qrepo(create=True)
1401
1401
1402 def unapplied(self, repo, patch=None):
1402 def unapplied(self, repo, patch=None):
1403 if patch and patch not in self.series:
1403 if patch and patch not in self.series:
1404 raise util.Abort(_("patch %s is not in series file") % patch)
1404 raise util.Abort(_("patch %s is not in series file") % patch)
1405 if not patch:
1405 if not patch:
1406 start = self.series_end()
1406 start = self.series_end()
1407 else:
1407 else:
1408 start = self.series.index(patch) + 1
1408 start = self.series.index(patch) + 1
1409 unapplied = []
1409 unapplied = []
1410 for i in xrange(start, len(self.series)):
1410 for i in xrange(start, len(self.series)):
1411 pushable, reason = self.pushable(i)
1411 pushable, reason = self.pushable(i)
1412 if pushable:
1412 if pushable:
1413 unapplied.append((i, self.series[i]))
1413 unapplied.append((i, self.series[i]))
1414 self.explain_pushable(i)
1414 self.explain_pushable(i)
1415 return unapplied
1415 return unapplied
1416
1416
1417 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1417 def qseries(self, repo, missing=None, start=0, length=None, status=None,
1418 summary=False):
1418 summary=False):
1419 def displayname(pfx, patchname, state):
1419 def displayname(pfx, patchname, state):
1420 if pfx:
1420 if pfx:
1421 self.ui.write(pfx)
1421 self.ui.write(pfx)
1422 if summary:
1422 if summary:
1423 ph = patchheader(self.join(patchname), self.plainmode)
1423 ph = patchheader(self.join(patchname), self.plainmode)
1424 msg = ph.message and ph.message[0] or ''
1424 msg = ph.message and ph.message[0] or ''
1425 if not self.ui.plain():
1425 if not self.ui.plain():
1426 width = util.termwidth() - len(pfx) - len(patchname) - 2
1426 width = util.termwidth() - len(pfx) - len(patchname) - 2
1427 if width > 0:
1427 if width > 0:
1428 msg = util.ellipsis(msg, width)
1428 msg = util.ellipsis(msg, width)
1429 else:
1429 else:
1430 msg = ''
1430 msg = ''
1431 self.ui.write(patchname, label='qseries.' + state)
1431 self.ui.write(patchname, label='qseries.' + state)
1432 self.ui.write(': ')
1432 self.ui.write(': ')
1433 self.ui.write(msg, label='qseries.message.' + state)
1433 self.ui.write(msg, label='qseries.message.' + state)
1434 else:
1434 else:
1435 self.ui.write(patchname, label='qseries.' + state)
1435 self.ui.write(patchname, label='qseries.' + state)
1436 self.ui.write('\n')
1436 self.ui.write('\n')
1437
1437
1438 applied = set([p.name for p in self.applied])
1438 applied = set([p.name for p in self.applied])
1439 if length is None:
1439 if length is None:
1440 length = len(self.series) - start
1440 length = len(self.series) - start
1441 if not missing:
1441 if not missing:
1442 if self.ui.verbose:
1442 if self.ui.verbose:
1443 idxwidth = len(str(start + length - 1))
1443 idxwidth = len(str(start + length - 1))
1444 for i in xrange(start, start + length):
1444 for i in xrange(start, start + length):
1445 patch = self.series[i]
1445 patch = self.series[i]
1446 if patch in applied:
1446 if patch in applied:
1447 char, state = 'A', 'applied'
1447 char, state = 'A', 'applied'
1448 elif self.pushable(i)[0]:
1448 elif self.pushable(i)[0]:
1449 char, state = 'U', 'unapplied'
1449 char, state = 'U', 'unapplied'
1450 else:
1450 else:
1451 char, state = 'G', 'guarded'
1451 char, state = 'G', 'guarded'
1452 pfx = ''
1452 pfx = ''
1453 if self.ui.verbose:
1453 if self.ui.verbose:
1454 pfx = '%*d %s ' % (idxwidth, i, char)
1454 pfx = '%*d %s ' % (idxwidth, i, char)
1455 elif status and status != char:
1455 elif status and status != char:
1456 continue
1456 continue
1457 displayname(pfx, patch, state)
1457 displayname(pfx, patch, state)
1458 else:
1458 else:
1459 msng_list = []
1459 msng_list = []
1460 for root, dirs, files in os.walk(self.path):
1460 for root, dirs, files in os.walk(self.path):
1461 d = root[len(self.path) + 1:]
1461 d = root[len(self.path) + 1:]
1462 for f in files:
1462 for f in files:
1463 fl = os.path.join(d, f)
1463 fl = os.path.join(d, f)
1464 if (fl not in self.series and
1464 if (fl not in self.series and
1465 fl not in (self.status_path, self.series_path,
1465 fl not in (self.status_path, self.series_path,
1466 self.guards_path)
1466 self.guards_path)
1467 and not fl.startswith('.')):
1467 and not fl.startswith('.')):
1468 msng_list.append(fl)
1468 msng_list.append(fl)
1469 for x in sorted(msng_list):
1469 for x in sorted(msng_list):
1470 pfx = self.ui.verbose and ('D ') or ''
1470 pfx = self.ui.verbose and ('D ') or ''
1471 displayname(pfx, x, 'missing')
1471 displayname(pfx, x, 'missing')
1472
1472
1473 def issaveline(self, l):
1473 def issaveline(self, l):
1474 if l.name == '.hg.patches.save.line':
1474 if l.name == '.hg.patches.save.line':
1475 return True
1475 return True
1476
1476
1477 def qrepo(self, create=False):
1477 def qrepo(self, create=False):
1478 if create or os.path.isdir(self.join(".hg")):
1478 if create or os.path.isdir(self.join(".hg")):
1479 return hg.repository(self.ui, path=self.path, create=create)
1479 return hg.repository(self.ui, path=self.path, create=create)
1480
1480
1481 def restore(self, repo, rev, delete=None, qupdate=None):
1481 def restore(self, repo, rev, delete=None, qupdate=None):
1482 desc = repo[rev].description().strip()
1482 desc = repo[rev].description().strip()
1483 lines = desc.splitlines()
1483 lines = desc.splitlines()
1484 i = 0
1484 i = 0
1485 datastart = None
1485 datastart = None
1486 series = []
1486 series = []
1487 applied = []
1487 applied = []
1488 qpp = None
1488 qpp = None
1489 for i, line in enumerate(lines):
1489 for i, line in enumerate(lines):
1490 if line == 'Patch Data:':
1490 if line == 'Patch Data:':
1491 datastart = i + 1
1491 datastart = i + 1
1492 elif line.startswith('Dirstate:'):
1492 elif line.startswith('Dirstate:'):
1493 l = line.rstrip()
1493 l = line.rstrip()
1494 l = l[10:].split(' ')
1494 l = l[10:].split(' ')
1495 qpp = [bin(x) for x in l]
1495 qpp = [bin(x) for x in l]
1496 elif datastart != None:
1496 elif datastart != None:
1497 l = line.rstrip()
1497 l = line.rstrip()
1498 n, name = l.split(':', 1)
1498 n, name = l.split(':', 1)
1499 if n:
1499 if n:
1500 applied.append(statusentry(bin(n), name))
1500 applied.append(statusentry(bin(n), name))
1501 else:
1501 else:
1502 series.append(l)
1502 series.append(l)
1503 if datastart is None:
1503 if datastart is None:
1504 self.ui.warn(_("No saved patch data found\n"))
1504 self.ui.warn(_("No saved patch data found\n"))
1505 return 1
1505 return 1
1506 self.ui.warn(_("restoring status: %s\n") % lines[0])
1506 self.ui.warn(_("restoring status: %s\n") % lines[0])
1507 self.full_series = series
1507 self.full_series = series
1508 self.applied = applied
1508 self.applied = applied
1509 self.parse_series()
1509 self.parse_series()
1510 self.series_dirty = 1
1510 self.series_dirty = 1
1511 self.applied_dirty = 1
1511 self.applied_dirty = 1
1512 heads = repo.changelog.heads()
1512 heads = repo.changelog.heads()
1513 if delete:
1513 if delete:
1514 if rev not in heads:
1514 if rev not in heads:
1515 self.ui.warn(_("save entry has children, leaving it alone\n"))
1515 self.ui.warn(_("save entry has children, leaving it alone\n"))
1516 else:
1516 else:
1517 self.ui.warn(_("removing save entry %s\n") % short(rev))
1517 self.ui.warn(_("removing save entry %s\n") % short(rev))
1518 pp = repo.dirstate.parents()
1518 pp = repo.dirstate.parents()
1519 if rev in pp:
1519 if rev in pp:
1520 update = True
1520 update = True
1521 else:
1521 else:
1522 update = False
1522 update = False
1523 self.strip(repo, rev, update=update, backup='strip')
1523 self.strip(repo, rev, update=update, backup='strip')
1524 if qpp:
1524 if qpp:
1525 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1525 self.ui.warn(_("saved queue repository parents: %s %s\n") %
1526 (short(qpp[0]), short(qpp[1])))
1526 (short(qpp[0]), short(qpp[1])))
1527 if qupdate:
1527 if qupdate:
1528 self.ui.status(_("queue directory updating\n"))
1528 self.ui.status(_("queue directory updating\n"))
1529 r = self.qrepo()
1529 r = self.qrepo()
1530 if not r:
1530 if not r:
1531 self.ui.warn(_("Unable to load queue repository\n"))
1531 self.ui.warn(_("Unable to load queue repository\n"))
1532 return 1
1532 return 1
1533 hg.clean(r, qpp[0])
1533 hg.clean(r, qpp[0])
1534
1534
1535 def save(self, repo, msg=None):
1535 def save(self, repo, msg=None):
1536 if not self.applied:
1536 if not self.applied:
1537 self.ui.warn(_("save: no patches applied, exiting\n"))
1537 self.ui.warn(_("save: no patches applied, exiting\n"))
1538 return 1
1538 return 1
1539 if self.issaveline(self.applied[-1]):
1539 if self.issaveline(self.applied[-1]):
1540 self.ui.warn(_("status is already saved\n"))
1540 self.ui.warn(_("status is already saved\n"))
1541 return 1
1541 return 1
1542
1542
1543 if not msg:
1543 if not msg:
1544 msg = _("hg patches saved state")
1544 msg = _("hg patches saved state")
1545 else:
1545 else:
1546 msg = "hg patches: " + msg.rstrip('\r\n')
1546 msg = "hg patches: " + msg.rstrip('\r\n')
1547 r = self.qrepo()
1547 r = self.qrepo()
1548 if r:
1548 if r:
1549 pp = r.dirstate.parents()
1549 pp = r.dirstate.parents()
1550 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1550 msg += "\nDirstate: %s %s" % (hex(pp[0]), hex(pp[1]))
1551 msg += "\n\nPatch Data:\n"
1551 msg += "\n\nPatch Data:\n"
1552 msg += ''.join('%s\n' % x for x in self.applied)
1552 msg += ''.join('%s\n' % x for x in self.applied)
1553 msg += ''.join(':%s\n' % x for x in self.full_series)
1553 msg += ''.join(':%s\n' % x for x in self.full_series)
1554 n = repo.commit(msg, force=True)
1554 n = repo.commit(msg, force=True)
1555 if not n:
1555 if not n:
1556 self.ui.warn(_("repo commit failed\n"))
1556 self.ui.warn(_("repo commit failed\n"))
1557 return 1
1557 return 1
1558 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1558 self.applied.append(statusentry(n, '.hg.patches.save.line'))
1559 self.applied_dirty = 1
1559 self.applied_dirty = 1
1560 self.removeundo(repo)
1560 self.removeundo(repo)
1561
1561
1562 def full_series_end(self):
1562 def full_series_end(self):
1563 if self.applied:
1563 if self.applied:
1564 p = self.applied[-1].name
1564 p = self.applied[-1].name
1565 end = self.find_series(p)
1565 end = self.find_series(p)
1566 if end is None:
1566 if end is None:
1567 return len(self.full_series)
1567 return len(self.full_series)
1568 return end + 1
1568 return end + 1
1569 return 0
1569 return 0
1570
1570
1571 def series_end(self, all_patches=False):
1571 def series_end(self, all_patches=False):
1572 """If all_patches is False, return the index of the next pushable patch
1572 """If all_patches is False, return the index of the next pushable patch
1573 in the series, or the series length. If all_patches is True, return the
1573 in the series, or the series length. If all_patches is True, return the
1574 index of the first patch past the last applied one.
1574 index of the first patch past the last applied one.
1575 """
1575 """
1576 end = 0
1576 end = 0
1577 def next(start):
1577 def next(start):
1578 if all_patches or start >= len(self.series):
1578 if all_patches or start >= len(self.series):
1579 return start
1579 return start
1580 for i in xrange(start, len(self.series)):
1580 for i in xrange(start, len(self.series)):
1581 p, reason = self.pushable(i)
1581 p, reason = self.pushable(i)
1582 if p:
1582 if p:
1583 break
1583 break
1584 self.explain_pushable(i)
1584 self.explain_pushable(i)
1585 return i
1585 return i
1586 if self.applied:
1586 if self.applied:
1587 p = self.applied[-1].name
1587 p = self.applied[-1].name
1588 try:
1588 try:
1589 end = self.series.index(p)
1589 end = self.series.index(p)
1590 except ValueError:
1590 except ValueError:
1591 return 0
1591 return 0
1592 return next(end + 1)
1592 return next(end + 1)
1593 return next(end)
1593 return next(end)
1594
1594
1595 def appliedname(self, index):
1595 def appliedname(self, index):
1596 pname = self.applied[index].name
1596 pname = self.applied[index].name
1597 if not self.ui.verbose:
1597 if not self.ui.verbose:
1598 p = pname
1598 p = pname
1599 else:
1599 else:
1600 p = str(self.series.index(pname)) + " " + pname
1600 p = str(self.series.index(pname)) + " " + pname
1601 return p
1601 return p
1602
1602
1603 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1603 def qimport(self, repo, files, patchname=None, rev=None, existing=None,
1604 force=None, git=False):
1604 force=None, git=False):
1605 def checkseries(patchname):
1605 def checkseries(patchname):
1606 if patchname in self.series:
1606 if patchname in self.series:
1607 raise util.Abort(_('patch %s is already in the series file')
1607 raise util.Abort(_('patch %s is already in the series file')
1608 % patchname)
1608 % patchname)
1609 def checkfile(patchname):
1609 def checkfile(patchname):
1610 if not force and os.path.exists(self.join(patchname)):
1610 if not force and os.path.exists(self.join(patchname)):
1611 raise util.Abort(_('patch "%s" already exists')
1611 raise util.Abort(_('patch "%s" already exists')
1612 % patchname)
1612 % patchname)
1613
1613
1614 if rev:
1614 if rev:
1615 if files:
1615 if files:
1616 raise util.Abort(_('option "-r" not valid when importing '
1616 raise util.Abort(_('option "-r" not valid when importing '
1617 'files'))
1617 'files'))
1618 rev = cmdutil.revrange(repo, rev)
1618 rev = cmdutil.revrange(repo, rev)
1619 rev.sort(reverse=True)
1619 rev.sort(reverse=True)
1620 if (len(files) > 1 or len(rev) > 1) and patchname:
1620 if (len(files) > 1 or len(rev) > 1) and patchname:
1621 raise util.Abort(_('option "-n" not valid when importing multiple '
1621 raise util.Abort(_('option "-n" not valid when importing multiple '
1622 'patches'))
1622 'patches'))
1623 added = []
1623 added = []
1624 if rev:
1624 if rev:
1625 # If mq patches are applied, we can only import revisions
1625 # If mq patches are applied, we can only import revisions
1626 # that form a linear path to qbase.
1626 # that form a linear path to qbase.
1627 # Otherwise, they should form a linear path to a head.
1627 # Otherwise, they should form a linear path to a head.
1628 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1628 heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
1629 if len(heads) > 1:
1629 if len(heads) > 1:
1630 raise util.Abort(_('revision %d is the root of more than one '
1630 raise util.Abort(_('revision %d is the root of more than one '
1631 'branch') % rev[-1])
1631 'branch') % rev[-1])
1632 if self.applied:
1632 if self.applied:
1633 base = repo.changelog.node(rev[0])
1633 base = repo.changelog.node(rev[0])
1634 if base in [n.node for n in self.applied]:
1634 if base in [n.node for n in self.applied]:
1635 raise util.Abort(_('revision %d is already managed')
1635 raise util.Abort(_('revision %d is already managed')
1636 % rev[0])
1636 % rev[0])
1637 if heads != [self.applied[-1].node]:
1637 if heads != [self.applied[-1].node]:
1638 raise util.Abort(_('revision %d is not the parent of '
1638 raise util.Abort(_('revision %d is not the parent of '
1639 'the queue') % rev[0])
1639 'the queue') % rev[0])
1640 base = repo.changelog.rev(self.applied[0].node)
1640 base = repo.changelog.rev(self.applied[0].node)
1641 lastparent = repo.changelog.parentrevs(base)[0]
1641 lastparent = repo.changelog.parentrevs(base)[0]
1642 else:
1642 else:
1643 if heads != [repo.changelog.node(rev[0])]:
1643 if heads != [repo.changelog.node(rev[0])]:
1644 raise util.Abort(_('revision %d has unmanaged children')
1644 raise util.Abort(_('revision %d has unmanaged children')
1645 % rev[0])
1645 % rev[0])
1646 lastparent = None
1646 lastparent = None
1647
1647
1648 diffopts = self.diffopts({'git': git})
1648 diffopts = self.diffopts({'git': git})
1649 for r in rev:
1649 for r in rev:
1650 p1, p2 = repo.changelog.parentrevs(r)
1650 p1, p2 = repo.changelog.parentrevs(r)
1651 n = repo.changelog.node(r)
1651 n = repo.changelog.node(r)
1652 if p2 != nullrev:
1652 if p2 != nullrev:
1653 raise util.Abort(_('cannot import merge revision %d') % r)
1653 raise util.Abort(_('cannot import merge revision %d') % r)
1654 if lastparent and lastparent != r:
1654 if lastparent and lastparent != r:
1655 raise util.Abort(_('revision %d is not the parent of %d')
1655 raise util.Abort(_('revision %d is not the parent of %d')
1656 % (r, lastparent))
1656 % (r, lastparent))
1657 lastparent = p1
1657 lastparent = p1
1658
1658
1659 if not patchname:
1659 if not patchname:
1660 patchname = normname('%d.diff' % r)
1660 patchname = normname('%d.diff' % r)
1661 self.check_reserved_name(patchname)
1661 self.check_reserved_name(patchname)
1662 checkseries(patchname)
1662 checkseries(patchname)
1663 checkfile(patchname)
1663 checkfile(patchname)
1664 self.full_series.insert(0, patchname)
1664 self.full_series.insert(0, patchname)
1665
1665
1666 patchf = self.opener(patchname, "w")
1666 patchf = self.opener(patchname, "w")
1667 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1667 cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
1668 patchf.close()
1668 patchf.close()
1669
1669
1670 se = statusentry(n, patchname)
1670 se = statusentry(n, patchname)
1671 self.applied.insert(0, se)
1671 self.applied.insert(0, se)
1672
1672
1673 added.append(patchname)
1673 added.append(patchname)
1674 patchname = None
1674 patchname = None
1675 self.parse_series()
1675 self.parse_series()
1676 self.applied_dirty = 1
1676 self.applied_dirty = 1
1677
1677
1678 for i, filename in enumerate(files):
1678 for i, filename in enumerate(files):
1679 if existing:
1679 if existing:
1680 if filename == '-':
1680 if filename == '-':
1681 raise util.Abort(_('-e is incompatible with import from -'))
1681 raise util.Abort(_('-e is incompatible with import from -'))
1682 if not patchname:
1682 if not patchname:
1683 patchname = normname(filename)
1683 patchname = normname(filename)
1684 self.check_reserved_name(patchname)
1684 self.check_reserved_name(patchname)
1685 if not os.path.isfile(self.join(patchname)):
1685 if not os.path.isfile(self.join(patchname)):
1686 raise util.Abort(_("patch %s does not exist") % patchname)
1686 raise util.Abort(_("patch %s does not exist") % patchname)
1687 else:
1687 else:
1688 try:
1688 try:
1689 if filename == '-':
1689 if filename == '-':
1690 if not patchname:
1690 if not patchname:
1691 raise util.Abort(
1691 raise util.Abort(
1692 _('need --name to import a patch from -'))
1692 _('need --name to import a patch from -'))
1693 text = sys.stdin.read()
1693 text = sys.stdin.read()
1694 else:
1694 else:
1695 text = url.open(self.ui, filename).read()
1695 text = url.open(self.ui, filename).read()
1696 except (OSError, IOError):
1696 except (OSError, IOError):
1697 raise util.Abort(_("unable to read %s") % filename)
1697 raise util.Abort(_("unable to read %s") % filename)
1698 if not patchname:
1698 if not patchname:
1699 patchname = normname(os.path.basename(filename))
1699 patchname = normname(os.path.basename(filename))
1700 self.check_reserved_name(patchname)
1700 self.check_reserved_name(patchname)
1701 checkfile(patchname)
1701 checkfile(patchname)
1702 patchf = self.opener(patchname, "w")
1702 patchf = self.opener(patchname, "w")
1703 patchf.write(text)
1703 patchf.write(text)
1704 if not force:
1704 if not force:
1705 checkseries(patchname)
1705 checkseries(patchname)
1706 if patchname not in self.series:
1706 if patchname not in self.series:
1707 index = self.full_series_end() + i
1707 index = self.full_series_end() + i
1708 self.full_series[index:index] = [patchname]
1708 self.full_series[index:index] = [patchname]
1709 self.parse_series()
1709 self.parse_series()
1710 self.ui.warn(_("adding %s to series file\n") % patchname)
1710 self.ui.warn(_("adding %s to series file\n") % patchname)
1711 added.append(patchname)
1711 added.append(patchname)
1712 patchname = None
1712 patchname = None
1713 self.series_dirty = 1
1713 self.series_dirty = 1
1714 qrepo = self.qrepo()
1714 qrepo = self.qrepo()
1715 if qrepo:
1715 if qrepo:
1716 qrepo[None].add(added)
1716 qrepo[None].add(added)
1717
1717
1718 def delete(ui, repo, *patches, **opts):
1718 def delete(ui, repo, *patches, **opts):
1719 """remove patches from queue
1719 """remove patches from queue
1720
1720
1721 The patches must not be applied, and at least one patch is required. With
1721 The patches must not be applied, and at least one patch is required. With
1722 -k/--keep, the patch files are preserved in the patch directory.
1722 -k/--keep, the patch files are preserved in the patch directory.
1723
1723
1724 To stop managing a patch and move it into permanent history,
1724 To stop managing a patch and move it into permanent history,
1725 use the :hg:`qfinish` command."""
1725 use the :hg:`qfinish` command."""
1726 q = repo.mq
1726 q = repo.mq
1727 q.delete(repo, patches, opts)
1727 q.delete(repo, patches, opts)
1728 q.save_dirty()
1728 q.save_dirty()
1729 return 0
1729 return 0
1730
1730
1731 def applied(ui, repo, patch=None, **opts):
1731 def applied(ui, repo, patch=None, **opts):
1732 """print the patches already applied"""
1732 """print the patches already applied"""
1733
1733
1734 q = repo.mq
1734 q = repo.mq
1735 l = len(q.applied)
1735 l = len(q.applied)
1736
1736
1737 if patch:
1737 if patch:
1738 if patch not in q.series:
1738 if patch not in q.series:
1739 raise util.Abort(_("patch %s is not in series file") % patch)
1739 raise util.Abort(_("patch %s is not in series file") % patch)
1740 end = q.series.index(patch) + 1
1740 end = q.series.index(patch) + 1
1741 else:
1741 else:
1742 end = q.series_end(True)
1742 end = q.series_end(True)
1743
1743
1744 if opts.get('last') and not end:
1744 if opts.get('last') and not end:
1745 ui.write(_("no patches applied\n"))
1745 ui.write(_("no patches applied\n"))
1746 return 1
1746 return 1
1747 elif opts.get('last') and end == 1:
1747 elif opts.get('last') and end == 1:
1748 ui.write(_("only one patch applied\n"))
1748 ui.write(_("only one patch applied\n"))
1749 return 1
1749 return 1
1750 elif opts.get('last'):
1750 elif opts.get('last'):
1751 start = end - 2
1751 start = end - 2
1752 end = 1
1752 end = 1
1753 else:
1753 else:
1754 start = 0
1754 start = 0
1755
1755
1756 return q.qseries(repo, length=end, start=start, status='A',
1756 return q.qseries(repo, length=end, start=start, status='A',
1757 summary=opts.get('summary'))
1757 summary=opts.get('summary'))
1758
1758
1759 def unapplied(ui, repo, patch=None, **opts):
1759 def unapplied(ui, repo, patch=None, **opts):
1760 """print the patches not yet applied"""
1760 """print the patches not yet applied"""
1761
1761
1762 q = repo.mq
1762 q = repo.mq
1763 if patch:
1763 if patch:
1764 if patch not in q.series:
1764 if patch not in q.series:
1765 raise util.Abort(_("patch %s is not in series file") % patch)
1765 raise util.Abort(_("patch %s is not in series file") % patch)
1766 start = q.series.index(patch) + 1
1766 start = q.series.index(patch) + 1
1767 else:
1767 else:
1768 start = q.series_end(True)
1768 start = q.series_end(True)
1769
1769
1770 if start == len(q.series) and opts.get('first'):
1770 if start == len(q.series) and opts.get('first'):
1771 ui.write(_("all patches applied\n"))
1771 ui.write(_("all patches applied\n"))
1772 return 1
1772 return 1
1773
1773
1774 length = opts.get('first') and 1 or None
1774 length = opts.get('first') and 1 or None
1775 return q.qseries(repo, start=start, length=length, status='U',
1775 return q.qseries(repo, start=start, length=length, status='U',
1776 summary=opts.get('summary'))
1776 summary=opts.get('summary'))
1777
1777
1778 def qimport(ui, repo, *filename, **opts):
1778 def qimport(ui, repo, *filename, **opts):
1779 """import a patch
1779 """import a patch
1780
1780
1781 The patch is inserted into the series after the last applied
1781 The patch is inserted into the series after the last applied
1782 patch. If no patches have been applied, qimport prepends the patch
1782 patch. If no patches have been applied, qimport prepends the patch
1783 to the series.
1783 to the series.
1784
1784
1785 The patch will have the same name as its source file unless you
1785 The patch will have the same name as its source file unless you
1786 give it a new one with -n/--name.
1786 give it a new one with -n/--name.
1787
1787
1788 You can register an existing patch inside the patch directory with
1788 You can register an existing patch inside the patch directory with
1789 the -e/--existing flag.
1789 the -e/--existing flag.
1790
1790
1791 With -f/--force, an existing patch of the same name will be
1791 With -f/--force, an existing patch of the same name will be
1792 overwritten.
1792 overwritten.
1793
1793
1794 An existing changeset may be placed under mq control with -r/--rev
1794 An existing changeset may be placed under mq control with -r/--rev
1795 (e.g. qimport --rev tip -n patch will place tip under mq control).
1795 (e.g. qimport --rev tip -n patch will place tip under mq control).
1796 With -g/--git, patches imported with --rev will use the git diff
1796 With -g/--git, patches imported with --rev will use the git diff
1797 format. See the diffs help topic for information on why this is
1797 format. See the diffs help topic for information on why this is
1798 important for preserving rename/copy information and permission
1798 important for preserving rename/copy information and permission
1799 changes.
1799 changes.
1800
1800
1801 To import a patch from standard input, pass - as the patch file.
1801 To import a patch from standard input, pass - as the patch file.
1802 When importing from standard input, a patch name must be specified
1802 When importing from standard input, a patch name must be specified
1803 using the --name flag.
1803 using the --name flag.
1804 """
1804 """
1805 q = repo.mq
1805 q = repo.mq
1806 q.qimport(repo, filename, patchname=opts['name'],
1806 q.qimport(repo, filename, patchname=opts['name'],
1807 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1807 existing=opts['existing'], force=opts['force'], rev=opts['rev'],
1808 git=opts['git'])
1808 git=opts['git'])
1809 q.save_dirty()
1809 q.save_dirty()
1810
1810
1811 if opts.get('push') and not opts.get('rev'):
1811 if opts.get('push') and not opts.get('rev'):
1812 return q.push(repo, None)
1812 return q.push(repo, None)
1813 return 0
1813 return 0
1814
1814
1815 def qinit(ui, repo, create):
1815 def qinit(ui, repo, create):
1816 """initialize a new queue repository
1816 """initialize a new queue repository
1817
1817
1818 This command also creates a series file for ordering patches, and
1818 This command also creates a series file for ordering patches, and
1819 an mq-specific .hgignore file in the queue repository, to exclude
1819 an mq-specific .hgignore file in the queue repository, to exclude
1820 the status and guards files (these contain mostly transient state)."""
1820 the status and guards files (these contain mostly transient state)."""
1821 q = repo.mq
1821 q = repo.mq
1822 r = q.init(repo, create)
1822 r = q.init(repo, create)
1823 q.save_dirty()
1823 q.save_dirty()
1824 if r:
1824 if r:
1825 if not os.path.exists(r.wjoin('.hgignore')):
1825 if not os.path.exists(r.wjoin('.hgignore')):
1826 fp = r.wopener('.hgignore', 'w')
1826 fp = r.wopener('.hgignore', 'w')
1827 fp.write('^\\.hg\n')
1827 fp.write('^\\.hg\n')
1828 fp.write('^\\.mq\n')
1828 fp.write('^\\.mq\n')
1829 fp.write('syntax: glob\n')
1829 fp.write('syntax: glob\n')
1830 fp.write('status\n')
1830 fp.write('status\n')
1831 fp.write('guards\n')
1831 fp.write('guards\n')
1832 fp.close()
1832 fp.close()
1833 if not os.path.exists(r.wjoin('series')):
1833 if not os.path.exists(r.wjoin('series')):
1834 r.wopener('series', 'w').close()
1834 r.wopener('series', 'w').close()
1835 r[None].add(['.hgignore', 'series'])
1835 r[None].add(['.hgignore', 'series'])
1836 commands.add(ui, r)
1836 commands.add(ui, r)
1837 return 0
1837 return 0
1838
1838
1839 def init(ui, repo, **opts):
1839 def init(ui, repo, **opts):
1840 """init a new queue repository (DEPRECATED)
1840 """init a new queue repository (DEPRECATED)
1841
1841
1842 The queue repository is unversioned by default. If
1842 The queue repository is unversioned by default. If
1843 -c/--create-repo is specified, qinit will create a separate nested
1843 -c/--create-repo is specified, qinit will create a separate nested
1844 repository for patches (qinit -c may also be run later to convert
1844 repository for patches (qinit -c may also be run later to convert
1845 an unversioned patch repository into a versioned one). You can use
1845 an unversioned patch repository into a versioned one). You can use
1846 qcommit to commit changes to this queue repository.
1846 qcommit to commit changes to this queue repository.
1847
1847
1848 This command is deprecated. Without -c, it's implied by other relevant
1848 This command is deprecated. Without -c, it's implied by other relevant
1849 commands. With -c, use :hg:`init --mq` instead."""
1849 commands. With -c, use :hg:`init --mq` instead."""
1850 return qinit(ui, repo, create=opts['create_repo'])
1850 return qinit(ui, repo, create=opts['create_repo'])
1851
1851
1852 def clone(ui, source, dest=None, **opts):
1852 def clone(ui, source, dest=None, **opts):
1853 '''clone main and patch repository at same time
1853 '''clone main and patch repository at same time
1854
1854
1855 If source is local, destination will have no patches applied. If
1855 If source is local, destination will have no patches applied. If
1856 source is remote, this command can not check if patches are
1856 source is remote, this command can not check if patches are
1857 applied in source, so cannot guarantee that patches are not
1857 applied in source, so cannot guarantee that patches are not
1858 applied in destination. If you clone remote repository, be sure
1858 applied in destination. If you clone remote repository, be sure
1859 before that it has no patches applied.
1859 before that it has no patches applied.
1860
1860
1861 Source patch repository is looked for in <src>/.hg/patches by
1861 Source patch repository is looked for in <src>/.hg/patches by
1862 default. Use -p <url> to change.
1862 default. Use -p <url> to change.
1863
1863
1864 The patch directory must be a nested Mercurial repository, as
1864 The patch directory must be a nested Mercurial repository, as
1865 would be created by :hg:`init --mq`.
1865 would be created by :hg:`init --mq`.
1866 '''
1866 '''
1867 def patchdir(repo):
1867 def patchdir(repo):
1868 url = repo.url()
1868 url = repo.url()
1869 if url.endswith('/'):
1869 if url.endswith('/'):
1870 url = url[:-1]
1870 url = url[:-1]
1871 return url + '/.hg/patches'
1871 return url + '/.hg/patches'
1872 if dest is None:
1872 if dest is None:
1873 dest = hg.defaultdest(source)
1873 dest = hg.defaultdest(source)
1874 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
1874 sr = hg.repository(hg.remoteui(ui, opts), ui.expandpath(source))
1875 if opts['patches']:
1875 if opts['patches']:
1876 patchespath = ui.expandpath(opts['patches'])
1876 patchespath = ui.expandpath(opts['patches'])
1877 else:
1877 else:
1878 patchespath = patchdir(sr)
1878 patchespath = patchdir(sr)
1879 try:
1879 try:
1880 hg.repository(ui, patchespath)
1880 hg.repository(ui, patchespath)
1881 except error.RepoError:
1881 except error.RepoError:
1882 raise util.Abort(_('versioned patch repository not found'
1882 raise util.Abort(_('versioned patch repository not found'
1883 ' (see init --mq)'))
1883 ' (see init --mq)'))
1884 qbase, destrev = None, None
1884 qbase, destrev = None, None
1885 if sr.local():
1885 if sr.local():
1886 if sr.mq.applied:
1886 if sr.mq.applied:
1887 qbase = sr.mq.applied[0].node
1887 qbase = sr.mq.applied[0].node
1888 if not hg.islocal(dest):
1888 if not hg.islocal(dest):
1889 heads = set(sr.heads())
1889 heads = set(sr.heads())
1890 destrev = list(heads.difference(sr.heads(qbase)))
1890 destrev = list(heads.difference(sr.heads(qbase)))
1891 destrev.append(sr.changelog.parents(qbase)[0])
1891 destrev.append(sr.changelog.parents(qbase)[0])
1892 elif sr.capable('lookup'):
1892 elif sr.capable('lookup'):
1893 try:
1893 try:
1894 qbase = sr.lookup('qbase')
1894 qbase = sr.lookup('qbase')
1895 except error.RepoError:
1895 except error.RepoError:
1896 pass
1896 pass
1897 ui.note(_('cloning main repository\n'))
1897 ui.note(_('cloning main repository\n'))
1898 sr, dr = hg.clone(ui, sr.url(), dest,
1898 sr, dr = hg.clone(ui, sr.url(), dest,
1899 pull=opts['pull'],
1899 pull=opts['pull'],
1900 rev=destrev,
1900 rev=destrev,
1901 update=False,
1901 update=False,
1902 stream=opts['uncompressed'])
1902 stream=opts['uncompressed'])
1903 ui.note(_('cloning patch repository\n'))
1903 ui.note(_('cloning patch repository\n'))
1904 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1904 hg.clone(ui, opts['patches'] or patchdir(sr), patchdir(dr),
1905 pull=opts['pull'], update=not opts['noupdate'],
1905 pull=opts['pull'], update=not opts['noupdate'],
1906 stream=opts['uncompressed'])
1906 stream=opts['uncompressed'])
1907 if dr.local():
1907 if dr.local():
1908 if qbase:
1908 if qbase:
1909 ui.note(_('stripping applied patches from destination '
1909 ui.note(_('stripping applied patches from destination '
1910 'repository\n'))
1910 'repository\n'))
1911 dr.mq.strip(dr, qbase, update=False, backup=None)
1911 dr.mq.strip(dr, qbase, update=False, backup=None)
1912 if not opts['noupdate']:
1912 if not opts['noupdate']:
1913 ui.note(_('updating destination repository\n'))
1913 ui.note(_('updating destination repository\n'))
1914 hg.update(dr, dr.changelog.tip())
1914 hg.update(dr, dr.changelog.tip())
1915
1915
1916 def commit(ui, repo, *pats, **opts):
1916 def commit(ui, repo, *pats, **opts):
1917 """commit changes in the queue repository (DEPRECATED)
1917 """commit changes in the queue repository (DEPRECATED)
1918
1918
1919 This command is deprecated; use :hg:`commit --mq` instead."""
1919 This command is deprecated; use :hg:`commit --mq` instead."""
1920 q = repo.mq
1920 q = repo.mq
1921 r = q.qrepo()
1921 r = q.qrepo()
1922 if not r:
1922 if not r:
1923 raise util.Abort('no queue repository')
1923 raise util.Abort('no queue repository')
1924 commands.commit(r.ui, r, *pats, **opts)
1924 commands.commit(r.ui, r, *pats, **opts)
1925
1925
1926 def series(ui, repo, **opts):
1926 def series(ui, repo, **opts):
1927 """print the entire series file"""
1927 """print the entire series file"""
1928 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1928 repo.mq.qseries(repo, missing=opts['missing'], summary=opts['summary'])
1929 return 0
1929 return 0
1930
1930
1931 def top(ui, repo, **opts):
1931 def top(ui, repo, **opts):
1932 """print the name of the current patch"""
1932 """print the name of the current patch"""
1933 q = repo.mq
1933 q = repo.mq
1934 t = q.applied and q.series_end(True) or 0
1934 t = q.applied and q.series_end(True) or 0
1935 if t:
1935 if t:
1936 return q.qseries(repo, start=t - 1, length=1, status='A',
1936 return q.qseries(repo, start=t - 1, length=1, status='A',
1937 summary=opts.get('summary'))
1937 summary=opts.get('summary'))
1938 else:
1938 else:
1939 ui.write(_("no patches applied\n"))
1939 ui.write(_("no patches applied\n"))
1940 return 1
1940 return 1
1941
1941
1942 def next(ui, repo, **opts):
1942 def next(ui, repo, **opts):
1943 """print the name of the next patch"""
1943 """print the name of the next patch"""
1944 q = repo.mq
1944 q = repo.mq
1945 end = q.series_end()
1945 end = q.series_end()
1946 if end == len(q.series):
1946 if end == len(q.series):
1947 ui.write(_("all patches applied\n"))
1947 ui.write(_("all patches applied\n"))
1948 return 1
1948 return 1
1949 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1949 return q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
1950
1950
1951 def prev(ui, repo, **opts):
1951 def prev(ui, repo, **opts):
1952 """print the name of the previous patch"""
1952 """print the name of the previous patch"""
1953 q = repo.mq
1953 q = repo.mq
1954 l = len(q.applied)
1954 l = len(q.applied)
1955 if l == 1:
1955 if l == 1:
1956 ui.write(_("only one patch applied\n"))
1956 ui.write(_("only one patch applied\n"))
1957 return 1
1957 return 1
1958 if not l:
1958 if not l:
1959 ui.write(_("no patches applied\n"))
1959 ui.write(_("no patches applied\n"))
1960 return 1
1960 return 1
1961 return q.qseries(repo, start=l - 2, length=1, status='A',
1961 return q.qseries(repo, start=l - 2, length=1, status='A',
1962 summary=opts.get('summary'))
1962 summary=opts.get('summary'))
1963
1963
1964 def setupheaderopts(ui, opts):
1964 def setupheaderopts(ui, opts):
1965 if not opts.get('user') and opts.get('currentuser'):
1965 if not opts.get('user') and opts.get('currentuser'):
1966 opts['user'] = ui.username()
1966 opts['user'] = ui.username()
1967 if not opts.get('date') and opts.get('currentdate'):
1967 if not opts.get('date') and opts.get('currentdate'):
1968 opts['date'] = "%d %d" % util.makedate()
1968 opts['date'] = "%d %d" % util.makedate()
1969
1969
1970 def new(ui, repo, patch, *args, **opts):
1970 def new(ui, repo, patch, *args, **opts):
1971 """create a new patch
1971 """create a new patch
1972
1972
1973 qnew creates a new patch on top of the currently-applied patch (if
1973 qnew creates a new patch on top of the currently-applied patch (if
1974 any). The patch will be initialized with any outstanding changes
1974 any). The patch will be initialized with any outstanding changes
1975 in the working directory. You may also use -I/--include,
1975 in the working directory. You may also use -I/--include,
1976 -X/--exclude, and/or a list of files after the patch name to add
1976 -X/--exclude, and/or a list of files after the patch name to add
1977 only changes to matching files to the new patch, leaving the rest
1977 only changes to matching files to the new patch, leaving the rest
1978 as uncommitted modifications.
1978 as uncommitted modifications.
1979
1979
1980 -u/--user and -d/--date can be used to set the (given) user and
1980 -u/--user and -d/--date can be used to set the (given) user and
1981 date, respectively. -U/--currentuser and -D/--currentdate set user
1981 date, respectively. -U/--currentuser and -D/--currentdate set user
1982 to current user and date to current date.
1982 to current user and date to current date.
1983
1983
1984 -e/--edit, -m/--message or -l/--logfile set the patch header as
1984 -e/--edit, -m/--message or -l/--logfile set the patch header as
1985 well as the commit message. If none is specified, the header is
1985 well as the commit message. If none is specified, the header is
1986 empty and the commit message is '[mq]: PATCH'.
1986 empty and the commit message is '[mq]: PATCH'.
1987
1987
1988 Use the -g/--git option to keep the patch in the git extended diff
1988 Use the -g/--git option to keep the patch in the git extended diff
1989 format. Read the diffs help topic for more information on why this
1989 format. Read the diffs help topic for more information on why this
1990 is important for preserving permission changes and copy/rename
1990 is important for preserving permission changes and copy/rename
1991 information.
1991 information.
1992 """
1992 """
1993 msg = cmdutil.logmessage(opts)
1993 msg = cmdutil.logmessage(opts)
1994 def getmsg():
1994 def getmsg():
1995 return ui.edit(msg, ui.username())
1995 return ui.edit(msg, ui.username())
1996 q = repo.mq
1996 q = repo.mq
1997 opts['msg'] = msg
1997 opts['msg'] = msg
1998 if opts.get('edit'):
1998 if opts.get('edit'):
1999 opts['msg'] = getmsg
1999 opts['msg'] = getmsg
2000 else:
2000 else:
2001 opts['msg'] = msg
2001 opts['msg'] = msg
2002 setupheaderopts(ui, opts)
2002 setupheaderopts(ui, opts)
2003 q.new(repo, patch, *args, **opts)
2003 q.new(repo, patch, *args, **opts)
2004 q.save_dirty()
2004 q.save_dirty()
2005 return 0
2005 return 0
2006
2006
2007 def refresh(ui, repo, *pats, **opts):
2007 def refresh(ui, repo, *pats, **opts):
2008 """update the current patch
2008 """update the current patch
2009
2009
2010 If any file patterns are provided, the refreshed patch will
2010 If any file patterns are provided, the refreshed patch will
2011 contain only the modifications that match those patterns; the
2011 contain only the modifications that match those patterns; the
2012 remaining modifications will remain in the working directory.
2012 remaining modifications will remain in the working directory.
2013
2013
2014 If -s/--short is specified, files currently included in the patch
2014 If -s/--short is specified, files currently included in the patch
2015 will be refreshed just like matched files and remain in the patch.
2015 will be refreshed just like matched files and remain in the patch.
2016
2016
2017 hg add/remove/copy/rename work as usual, though you might want to
2017 hg add/remove/copy/rename work as usual, though you might want to
2018 use git-style patches (-g/--git or [diff] git=1) to track copies
2018 use git-style patches (-g/--git or [diff] git=1) to track copies
2019 and renames. See the diffs help topic for more information on the
2019 and renames. See the diffs help topic for more information on the
2020 git diff format.
2020 git diff format.
2021 """
2021 """
2022 q = repo.mq
2022 q = repo.mq
2023 message = cmdutil.logmessage(opts)
2023 message = cmdutil.logmessage(opts)
2024 if opts['edit']:
2024 if opts['edit']:
2025 if not q.applied:
2025 if not q.applied:
2026 ui.write(_("no patches applied\n"))
2026 ui.write(_("no patches applied\n"))
2027 return 1
2027 return 1
2028 if message:
2028 if message:
2029 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2029 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2030 patch = q.applied[-1].name
2030 patch = q.applied[-1].name
2031 ph = patchheader(q.join(patch), q.plainmode)
2031 ph = patchheader(q.join(patch), q.plainmode)
2032 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2032 message = ui.edit('\n'.join(ph.message), ph.user or ui.username())
2033 setupheaderopts(ui, opts)
2033 setupheaderopts(ui, opts)
2034 ret = q.refresh(repo, pats, msg=message, **opts)
2034 ret = q.refresh(repo, pats, msg=message, **opts)
2035 q.save_dirty()
2035 q.save_dirty()
2036 return ret
2036 return ret
2037
2037
2038 def diff(ui, repo, *pats, **opts):
2038 def diff(ui, repo, *pats, **opts):
2039 """diff of the current patch and subsequent modifications
2039 """diff of the current patch and subsequent modifications
2040
2040
2041 Shows a diff which includes the current patch as well as any
2041 Shows a diff which includes the current patch as well as any
2042 changes which have been made in the working directory since the
2042 changes which have been made in the working directory since the
2043 last refresh (thus showing what the current patch would become
2043 last refresh (thus showing what the current patch would become
2044 after a qrefresh).
2044 after a qrefresh).
2045
2045
2046 Use :hg:`diff` if you only want to see the changes made since the
2046 Use :hg:`diff` if you only want to see the changes made since the
2047 last qrefresh, or :hg:`export qtip` if you want to see changes
2047 last qrefresh, or :hg:`export qtip` if you want to see changes
2048 made by the current patch without including changes made since the
2048 made by the current patch without including changes made since the
2049 qrefresh.
2049 qrefresh.
2050 """
2050 """
2051 repo.mq.diff(repo, pats, opts)
2051 repo.mq.diff(repo, pats, opts)
2052 return 0
2052 return 0
2053
2053
2054 def fold(ui, repo, *files, **opts):
2054 def fold(ui, repo, *files, **opts):
2055 """fold the named patches into the current patch
2055 """fold the named patches into the current patch
2056
2056
2057 Patches must not yet be applied. Each patch will be successively
2057 Patches must not yet be applied. Each patch will be successively
2058 applied to the current patch in the order given. If all the
2058 applied to the current patch in the order given. If all the
2059 patches apply successfully, the current patch will be refreshed
2059 patches apply successfully, the current patch will be refreshed
2060 with the new cumulative patch, and the folded patches will be
2060 with the new cumulative patch, and the folded patches will be
2061 deleted. With -k/--keep, the folded patch files will not be
2061 deleted. With -k/--keep, the folded patch files will not be
2062 removed afterwards.
2062 removed afterwards.
2063
2063
2064 The header for each folded patch will be concatenated with the
2064 The header for each folded patch will be concatenated with the
2065 current patch header, separated by a line of '* * *'."""
2065 current patch header, separated by a line of '* * *'."""
2066
2066
2067 q = repo.mq
2067 q = repo.mq
2068
2068
2069 if not files:
2069 if not files:
2070 raise util.Abort(_('qfold requires at least one patch name'))
2070 raise util.Abort(_('qfold requires at least one patch name'))
2071 if not q.check_toppatch(repo)[0]:
2071 if not q.check_toppatch(repo)[0]:
2072 raise util.Abort(_('No patches applied'))
2072 raise util.Abort(_('No patches applied'))
2073 q.check_localchanges(repo)
2073 q.check_localchanges(repo)
2074
2074
2075 message = cmdutil.logmessage(opts)
2075 message = cmdutil.logmessage(opts)
2076 if opts['edit']:
2076 if opts['edit']:
2077 if message:
2077 if message:
2078 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2078 raise util.Abort(_('option "-e" incompatible with "-m" or "-l"'))
2079
2079
2080 parent = q.lookup('qtip')
2080 parent = q.lookup('qtip')
2081 patches = []
2081 patches = []
2082 messages = []
2082 messages = []
2083 for f in files:
2083 for f in files:
2084 p = q.lookup(f)
2084 p = q.lookup(f)
2085 if p in patches or p == parent:
2085 if p in patches or p == parent:
2086 ui.warn(_('Skipping already folded patch %s') % p)
2086 ui.warn(_('Skipping already folded patch %s') % p)
2087 if q.isapplied(p):
2087 if q.isapplied(p):
2088 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2088 raise util.Abort(_('qfold cannot fold already applied patch %s') % p)
2089 patches.append(p)
2089 patches.append(p)
2090
2090
2091 for p in patches:
2091 for p in patches:
2092 if not message:
2092 if not message:
2093 ph = patchheader(q.join(p), q.plainmode)
2093 ph = patchheader(q.join(p), q.plainmode)
2094 if ph.message:
2094 if ph.message:
2095 messages.append(ph.message)
2095 messages.append(ph.message)
2096 pf = q.join(p)
2096 pf = q.join(p)
2097 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2097 (patchsuccess, files, fuzz) = q.patch(repo, pf)
2098 if not patchsuccess:
2098 if not patchsuccess:
2099 raise util.Abort(_('Error folding patch %s') % p)
2099 raise util.Abort(_('Error folding patch %s') % p)
2100 patch.updatedir(ui, repo, files)
2100 patch.updatedir(ui, repo, files)
2101
2101
2102 if not message:
2102 if not message:
2103 ph = patchheader(q.join(parent), q.plainmode)
2103 ph = patchheader(q.join(parent), q.plainmode)
2104 message, user = ph.message, ph.user
2104 message, user = ph.message, ph.user
2105 for msg in messages:
2105 for msg in messages:
2106 message.append('* * *')
2106 message.append('* * *')
2107 message.extend(msg)
2107 message.extend(msg)
2108 message = '\n'.join(message)
2108 message = '\n'.join(message)
2109
2109
2110 if opts['edit']:
2110 if opts['edit']:
2111 message = ui.edit(message, user or ui.username())
2111 message = ui.edit(message, user or ui.username())
2112
2112
2113 diffopts = q.patchopts(q.diffopts(), *patches)
2113 diffopts = q.patchopts(q.diffopts(), *patches)
2114 q.refresh(repo, msg=message, git=diffopts.git)
2114 q.refresh(repo, msg=message, git=diffopts.git)
2115 q.delete(repo, patches, opts)
2115 q.delete(repo, patches, opts)
2116 q.save_dirty()
2116 q.save_dirty()
2117
2117
2118 def goto(ui, repo, patch, **opts):
2118 def goto(ui, repo, patch, **opts):
2119 '''push or pop patches until named patch is at top of stack'''
2119 '''push or pop patches until named patch is at top of stack'''
2120 q = repo.mq
2120 q = repo.mq
2121 patch = q.lookup(patch)
2121 patch = q.lookup(patch)
2122 if q.isapplied(patch):
2122 if q.isapplied(patch):
2123 ret = q.pop(repo, patch, force=opts['force'])
2123 ret = q.pop(repo, patch, force=opts['force'])
2124 else:
2124 else:
2125 ret = q.push(repo, patch, force=opts['force'])
2125 ret = q.push(repo, patch, force=opts['force'])
2126 q.save_dirty()
2126 q.save_dirty()
2127 return ret
2127 return ret
2128
2128
2129 def guard(ui, repo, *args, **opts):
2129 def guard(ui, repo, *args, **opts):
2130 '''set or print guards for a patch
2130 '''set or print guards for a patch
2131
2131
2132 Guards control whether a patch can be pushed. A patch with no
2132 Guards control whether a patch can be pushed. A patch with no
2133 guards is always pushed. A patch with a positive guard ("+foo") is
2133 guards is always pushed. A patch with a positive guard ("+foo") is
2134 pushed only if the :hg:`qselect` command has activated it. A patch with
2134 pushed only if the :hg:`qselect` command has activated it. A patch with
2135 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2135 a negative guard ("-foo") is never pushed if the :hg:`qselect` command
2136 has activated it.
2136 has activated it.
2137
2137
2138 With no arguments, print the currently active guards.
2138 With no arguments, print the currently active guards.
2139 With arguments, set guards for the named patch.
2139 With arguments, set guards for the named patch.
2140 NOTE: Specifying negative guards now requires '--'.
2140 NOTE: Specifying negative guards now requires '--'.
2141
2141
2142 To set guards on another patch::
2142 To set guards on another patch::
2143
2143
2144 hg qguard other.patch -- +2.6.17 -stable
2144 hg qguard other.patch -- +2.6.17 -stable
2145 '''
2145 '''
2146 def status(idx):
2146 def status(idx):
2147 guards = q.series_guards[idx] or ['unguarded']
2147 guards = q.series_guards[idx] or ['unguarded']
2148 ui.write('%s: ' % ui.label(q.series[idx], 'qguard.patch'))
2148 ui.write('%s: ' % ui.label(q.series[idx], 'qguard.patch'))
2149 for i, guard in enumerate(guards):
2149 for i, guard in enumerate(guards):
2150 if guard.startswith('+'):
2150 if guard.startswith('+'):
2151 ui.write(guard, label='qguard.positive')
2151 ui.write(guard, label='qguard.positive')
2152 elif guard.startswith('-'):
2152 elif guard.startswith('-'):
2153 ui.write(guard, label='qguard.negative')
2153 ui.write(guard, label='qguard.negative')
2154 else:
2154 else:
2155 ui.write(guard, label='qguard.unguarded')
2155 ui.write(guard, label='qguard.unguarded')
2156 if i != len(guards) - 1:
2156 if i != len(guards) - 1:
2157 ui.write(' ')
2157 ui.write(' ')
2158 ui.write('\n')
2158 ui.write('\n')
2159 q = repo.mq
2159 q = repo.mq
2160 patch = None
2160 patch = None
2161 args = list(args)
2161 args = list(args)
2162 if opts['list']:
2162 if opts['list']:
2163 if args or opts['none']:
2163 if args or opts['none']:
2164 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2164 raise util.Abort(_('cannot mix -l/--list with options or arguments'))
2165 for i in xrange(len(q.series)):
2165 for i in xrange(len(q.series)):
2166 status(i)
2166 status(i)
2167 return
2167 return
2168 if not args or args[0][0:1] in '-+':
2168 if not args or args[0][0:1] in '-+':
2169 if not q.applied:
2169 if not q.applied:
2170 raise util.Abort(_('no patches applied'))
2170 raise util.Abort(_('no patches applied'))
2171 patch = q.applied[-1].name
2171 patch = q.applied[-1].name
2172 if patch is None and args[0][0:1] not in '-+':
2172 if patch is None and args[0][0:1] not in '-+':
2173 patch = args.pop(0)
2173 patch = args.pop(0)
2174 if patch is None:
2174 if patch is None:
2175 raise util.Abort(_('no patch to work with'))
2175 raise util.Abort(_('no patch to work with'))
2176 if args or opts['none']:
2176 if args or opts['none']:
2177 idx = q.find_series(patch)
2177 idx = q.find_series(patch)
2178 if idx is None:
2178 if idx is None:
2179 raise util.Abort(_('no patch named %s') % patch)
2179 raise util.Abort(_('no patch named %s') % patch)
2180 q.set_guards(idx, args)
2180 q.set_guards(idx, args)
2181 q.save_dirty()
2181 q.save_dirty()
2182 else:
2182 else:
2183 status(q.series.index(q.lookup(patch)))
2183 status(q.series.index(q.lookup(patch)))
2184
2184
2185 def header(ui, repo, patch=None):
2185 def header(ui, repo, patch=None):
2186 """print the header of the topmost or specified patch"""
2186 """print the header of the topmost or specified patch"""
2187 q = repo.mq
2187 q = repo.mq
2188
2188
2189 if patch:
2189 if patch:
2190 patch = q.lookup(patch)
2190 patch = q.lookup(patch)
2191 else:
2191 else:
2192 if not q.applied:
2192 if not q.applied:
2193 ui.write(_('no patches applied\n'))
2193 ui.write(_('no patches applied\n'))
2194 return 1
2194 return 1
2195 patch = q.lookup('qtip')
2195 patch = q.lookup('qtip')
2196 ph = patchheader(q.join(patch), q.plainmode)
2196 ph = patchheader(q.join(patch), q.plainmode)
2197
2197
2198 ui.write('\n'.join(ph.message) + '\n')
2198 ui.write('\n'.join(ph.message) + '\n')
2199
2199
2200 def lastsavename(path):
2200 def lastsavename(path):
2201 (directory, base) = os.path.split(path)
2201 (directory, base) = os.path.split(path)
2202 names = os.listdir(directory)
2202 names = os.listdir(directory)
2203 namere = re.compile("%s.([0-9]+)" % base)
2203 namere = re.compile("%s.([0-9]+)" % base)
2204 maxindex = None
2204 maxindex = None
2205 maxname = None
2205 maxname = None
2206 for f in names:
2206 for f in names:
2207 m = namere.match(f)
2207 m = namere.match(f)
2208 if m:
2208 if m:
2209 index = int(m.group(1))
2209 index = int(m.group(1))
2210 if maxindex is None or index > maxindex:
2210 if maxindex is None or index > maxindex:
2211 maxindex = index
2211 maxindex = index
2212 maxname = f
2212 maxname = f
2213 if maxname:
2213 if maxname:
2214 return (os.path.join(directory, maxname), maxindex)
2214 return (os.path.join(directory, maxname), maxindex)
2215 return (None, None)
2215 return (None, None)
2216
2216
2217 def savename(path):
2217 def savename(path):
2218 (last, index) = lastsavename(path)
2218 (last, index) = lastsavename(path)
2219 if last is None:
2219 if last is None:
2220 index = 0
2220 index = 0
2221 newpath = path + ".%d" % (index + 1)
2221 newpath = path + ".%d" % (index + 1)
2222 return newpath
2222 return newpath
2223
2223
2224 def push(ui, repo, patch=None, **opts):
2224 def push(ui, repo, patch=None, **opts):
2225 """push the next patch onto the stack
2225 """push the next patch onto the stack
2226
2226
2227 When -f/--force is applied, all local changes in patched files
2227 When -f/--force is applied, all local changes in patched files
2228 will be lost.
2228 will be lost.
2229 """
2229 """
2230 q = repo.mq
2230 q = repo.mq
2231 mergeq = None
2231 mergeq = None
2232
2232
2233 if opts['merge']:
2233 if opts['merge']:
2234 if opts['name']:
2234 if opts['name']:
2235 newpath = repo.join(opts['name'])
2235 newpath = repo.join(opts['name'])
2236 else:
2236 else:
2237 newpath, i = lastsavename(q.path)
2237 newpath, i = lastsavename(q.path)
2238 if not newpath:
2238 if not newpath:
2239 ui.warn(_("no saved queues found, please use -n\n"))
2239 ui.warn(_("no saved queues found, please use -n\n"))
2240 return 1
2240 return 1
2241 mergeq = queue(ui, repo.join(""), newpath)
2241 mergeq = queue(ui, repo.join(""), newpath)
2242 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2242 ui.warn(_("merging with queue at: %s\n") % mergeq.path)
2243 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2243 ret = q.push(repo, patch, force=opts['force'], list=opts['list'],
2244 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'))
2244 mergeq=mergeq, all=opts.get('all'), move=opts.get('move'))
2245 return ret
2245 return ret
2246
2246
2247 def pop(ui, repo, patch=None, **opts):
2247 def pop(ui, repo, patch=None, **opts):
2248 """pop the current patch off the stack
2248 """pop the current patch off the stack
2249
2249
2250 By default, pops off the top of the patch stack. If given a patch
2250 By default, pops off the top of the patch stack. If given a patch
2251 name, keeps popping off patches until the named patch is at the
2251 name, keeps popping off patches until the named patch is at the
2252 top of the stack.
2252 top of the stack.
2253 """
2253 """
2254 localupdate = True
2254 localupdate = True
2255 if opts['name']:
2255 if opts['name']:
2256 q = queue(ui, repo.join(""), repo.join(opts['name']))
2256 q = queue(ui, repo.join(""), repo.join(opts['name']))
2257 ui.warn(_('using patch queue: %s\n') % q.path)
2257 ui.warn(_('using patch queue: %s\n') % q.path)
2258 localupdate = False
2258 localupdate = False
2259 else:
2259 else:
2260 q = repo.mq
2260 q = repo.mq
2261 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2261 ret = q.pop(repo, patch, force=opts['force'], update=localupdate,
2262 all=opts['all'])
2262 all=opts['all'])
2263 q.save_dirty()
2263 q.save_dirty()
2264 return ret
2264 return ret
2265
2265
2266 def rename(ui, repo, patch, name=None, **opts):
2266 def rename(ui, repo, patch, name=None, **opts):
2267 """rename a patch
2267 """rename a patch
2268
2268
2269 With one argument, renames the current patch to PATCH1.
2269 With one argument, renames the current patch to PATCH1.
2270 With two arguments, renames PATCH1 to PATCH2."""
2270 With two arguments, renames PATCH1 to PATCH2."""
2271
2271
2272 q = repo.mq
2272 q = repo.mq
2273
2273
2274 if not name:
2274 if not name:
2275 name = patch
2275 name = patch
2276 patch = None
2276 patch = None
2277
2277
2278 if patch:
2278 if patch:
2279 patch = q.lookup(patch)
2279 patch = q.lookup(patch)
2280 else:
2280 else:
2281 if not q.applied:
2281 if not q.applied:
2282 ui.write(_('no patches applied\n'))
2282 ui.write(_('no patches applied\n'))
2283 return
2283 return
2284 patch = q.lookup('qtip')
2284 patch = q.lookup('qtip')
2285 absdest = q.join(name)
2285 absdest = q.join(name)
2286 if os.path.isdir(absdest):
2286 if os.path.isdir(absdest):
2287 name = normname(os.path.join(name, os.path.basename(patch)))
2287 name = normname(os.path.join(name, os.path.basename(patch)))
2288 absdest = q.join(name)
2288 absdest = q.join(name)
2289 if os.path.exists(absdest):
2289 if os.path.exists(absdest):
2290 raise util.Abort(_('%s already exists') % absdest)
2290 raise util.Abort(_('%s already exists') % absdest)
2291
2291
2292 if name in q.series:
2292 if name in q.series:
2293 raise util.Abort(
2293 raise util.Abort(
2294 _('A patch named %s already exists in the series file') % name)
2294 _('A patch named %s already exists in the series file') % name)
2295
2295
2296 ui.note(_('renaming %s to %s\n') % (patch, name))
2296 ui.note(_('renaming %s to %s\n') % (patch, name))
2297 i = q.find_series(patch)
2297 i = q.find_series(patch)
2298 guards = q.guard_re.findall(q.full_series[i])
2298 guards = q.guard_re.findall(q.full_series[i])
2299 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2299 q.full_series[i] = name + ''.join([' #' + g for g in guards])
2300 q.parse_series()
2300 q.parse_series()
2301 q.series_dirty = 1
2301 q.series_dirty = 1
2302
2302
2303 info = q.isapplied(patch)
2303 info = q.isapplied(patch)
2304 if info:
2304 if info:
2305 q.applied[info[0]] = statusentry(info[1], name)
2305 q.applied[info[0]] = statusentry(info[1], name)
2306 q.applied_dirty = 1
2306 q.applied_dirty = 1
2307
2307
2308 util.rename(q.join(patch), absdest)
2308 util.rename(q.join(patch), absdest)
2309 r = q.qrepo()
2309 r = q.qrepo()
2310 if r:
2310 if r:
2311 wctx = r[None]
2311 wctx = r[None]
2312 wlock = r.wlock()
2312 wlock = r.wlock()
2313 try:
2313 try:
2314 if r.dirstate[patch] == 'a':
2314 if r.dirstate[patch] == 'a':
2315 r.dirstate.forget(patch)
2315 r.dirstate.forget(patch)
2316 r.dirstate.add(name)
2316 r.dirstate.add(name)
2317 else:
2317 else:
2318 if r.dirstate[name] == 'r':
2318 if r.dirstate[name] == 'r':
2319 wctx.undelete([name])
2319 wctx.undelete([name])
2320 wctx.copy(patch, name)
2320 wctx.copy(patch, name)
2321 wctx.remove([patch], False)
2321 wctx.remove([patch], False)
2322 finally:
2322 finally:
2323 wlock.release()
2323 wlock.release()
2324
2324
2325 q.save_dirty()
2325 q.save_dirty()
2326
2326
2327 def restore(ui, repo, rev, **opts):
2327 def restore(ui, repo, rev, **opts):
2328 """restore the queue state saved by a revision (DEPRECATED)
2328 """restore the queue state saved by a revision (DEPRECATED)
2329
2329
2330 This command is deprecated, use rebase --mq instead."""
2330 This command is deprecated, use rebase --mq instead."""
2331 rev = repo.lookup(rev)
2331 rev = repo.lookup(rev)
2332 q = repo.mq
2332 q = repo.mq
2333 q.restore(repo, rev, delete=opts['delete'],
2333 q.restore(repo, rev, delete=opts['delete'],
2334 qupdate=opts['update'])
2334 qupdate=opts['update'])
2335 q.save_dirty()
2335 q.save_dirty()
2336 return 0
2336 return 0
2337
2337
2338 def save(ui, repo, **opts):
2338 def save(ui, repo, **opts):
2339 """save current queue state (DEPRECATED)
2339 """save current queue state (DEPRECATED)
2340
2340
2341 This command is deprecated, use rebase --mq instead."""
2341 This command is deprecated, use rebase --mq instead."""
2342 q = repo.mq
2342 q = repo.mq
2343 message = cmdutil.logmessage(opts)
2343 message = cmdutil.logmessage(opts)
2344 ret = q.save(repo, msg=message)
2344 ret = q.save(repo, msg=message)
2345 if ret:
2345 if ret:
2346 return ret
2346 return ret
2347 q.save_dirty()
2347 q.save_dirty()
2348 if opts['copy']:
2348 if opts['copy']:
2349 path = q.path
2349 path = q.path
2350 if opts['name']:
2350 if opts['name']:
2351 newpath = os.path.join(q.basepath, opts['name'])
2351 newpath = os.path.join(q.basepath, opts['name'])
2352 if os.path.exists(newpath):
2352 if os.path.exists(newpath):
2353 if not os.path.isdir(newpath):
2353 if not os.path.isdir(newpath):
2354 raise util.Abort(_('destination %s exists and is not '
2354 raise util.Abort(_('destination %s exists and is not '
2355 'a directory') % newpath)
2355 'a directory') % newpath)
2356 if not opts['force']:
2356 if not opts['force']:
2357 raise util.Abort(_('destination %s exists, '
2357 raise util.Abort(_('destination %s exists, '
2358 'use -f to force') % newpath)
2358 'use -f to force') % newpath)
2359 else:
2359 else:
2360 newpath = savename(path)
2360 newpath = savename(path)
2361 ui.warn(_("copy %s to %s\n") % (path, newpath))
2361 ui.warn(_("copy %s to %s\n") % (path, newpath))
2362 util.copyfiles(path, newpath)
2362 util.copyfiles(path, newpath)
2363 if opts['empty']:
2363 if opts['empty']:
2364 try:
2364 try:
2365 os.unlink(q.join(q.status_path))
2365 os.unlink(q.join(q.status_path))
2366 except:
2366 except:
2367 pass
2367 pass
2368 return 0
2368 return 0
2369
2369
2370 def strip(ui, repo, rev, **opts):
2370 def strip(ui, repo, rev, **opts):
2371 """strip a changeset and all its descendants from the repository
2371 """strip a changeset and all its descendants from the repository
2372
2372
2373 The strip command removes all changesets whose local revision
2373 The strip command removes all changesets whose local revision
2374 number is greater than or equal to REV, and then restores any
2374 number is greater than or equal to REV, and then restores any
2375 changesets that are not descendants of REV. If the working
2375 changesets that are not descendants of REV. If the working
2376 directory has uncommitted changes, the operation is aborted unless
2376 directory has uncommitted changes, the operation is aborted unless
2377 the --force flag is supplied.
2377 the --force flag is supplied.
2378
2378
2379 If a parent of the working directory is stripped, then the working
2379 If a parent of the working directory is stripped, then the working
2380 directory will automatically be updated to the most recent
2380 directory will automatically be updated to the most recent
2381 available ancestor of the stripped parent after the operation
2381 available ancestor of the stripped parent after the operation
2382 completes.
2382 completes.
2383
2383
2384 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2384 Any stripped changesets are stored in ``.hg/strip-backup`` as a
2385 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2385 bundle (see :hg:`help bundle` and :hg:`help unbundle`). They can
2386 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2386 be restored by running :hg:`unbundle .hg/strip-backup/BUNDLE`,
2387 where BUNDLE is the bundle file created by the strip. Note that
2387 where BUNDLE is the bundle file created by the strip. Note that
2388 the local revision numbers will in general be different after the
2388 the local revision numbers will in general be different after the
2389 restore.
2389 restore.
2390
2390
2391 Use the --nobackup option to discard the backup bundle once the
2391 Use the --nobackup option to discard the backup bundle once the
2392 operation completes.
2392 operation completes.
2393 """
2393 """
2394 backup = 'all'
2394 backup = 'all'
2395 if opts['backup']:
2395 if opts['backup']:
2396 backup = 'strip'
2396 backup = 'strip'
2397 elif opts['nobackup']:
2397 elif opts['nobackup']:
2398 backup = 'none'
2398 backup = 'none'
2399
2399
2400 rev = repo.lookup(rev)
2400 rev = repo.lookup(rev)
2401 p = repo.dirstate.parents()
2401 p = repo.dirstate.parents()
2402 cl = repo.changelog
2402 cl = repo.changelog
2403 update = True
2403 update = True
2404 if p[0] == nullid:
2404 if p[0] == nullid:
2405 update = False
2405 update = False
2406 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2406 elif p[1] == nullid and rev != cl.ancestor(p[0], rev):
2407 update = False
2407 update = False
2408 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2408 elif rev not in (cl.ancestor(p[0], rev), cl.ancestor(p[1], rev)):
2409 update = False
2409 update = False
2410
2410
2411 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2411 repo.mq.strip(repo, rev, backup=backup, update=update, force=opts['force'])
2412 return 0
2412 return 0
2413
2413
2414 def select(ui, repo, *args, **opts):
2414 def select(ui, repo, *args, **opts):
2415 '''set or print guarded patches to push
2415 '''set or print guarded patches to push
2416
2416
2417 Use the :hg:`qguard` command to set or print guards on patch, then use
2417 Use the :hg:`qguard` command to set or print guards on patch, then use
2418 qselect to tell mq which guards to use. A patch will be pushed if
2418 qselect to tell mq which guards to use. A patch will be pushed if
2419 it has no guards or any positive guards match the currently
2419 it has no guards or any positive guards match the currently
2420 selected guard, but will not be pushed if any negative guards
2420 selected guard, but will not be pushed if any negative guards
2421 match the current guard. For example::
2421 match the current guard. For example::
2422
2422
2423 qguard foo.patch -stable (negative guard)
2423 qguard foo.patch -stable (negative guard)
2424 qguard bar.patch +stable (positive guard)
2424 qguard bar.patch +stable (positive guard)
2425 qselect stable
2425 qselect stable
2426
2426
2427 This activates the "stable" guard. mq will skip foo.patch (because
2427 This activates the "stable" guard. mq will skip foo.patch (because
2428 it has a negative match) but push bar.patch (because it has a
2428 it has a negative match) but push bar.patch (because it has a
2429 positive match).
2429 positive match).
2430
2430
2431 With no arguments, prints the currently active guards.
2431 With no arguments, prints the currently active guards.
2432 With one argument, sets the active guard.
2432 With one argument, sets the active guard.
2433
2433
2434 Use -n/--none to deactivate guards (no other arguments needed).
2434 Use -n/--none to deactivate guards (no other arguments needed).
2435 When no guards are active, patches with positive guards are
2435 When no guards are active, patches with positive guards are
2436 skipped and patches with negative guards are pushed.
2436 skipped and patches with negative guards are pushed.
2437
2437
2438 qselect can change the guards on applied patches. It does not pop
2438 qselect can change the guards on applied patches. It does not pop
2439 guarded patches by default. Use --pop to pop back to the last
2439 guarded patches by default. Use --pop to pop back to the last
2440 applied patch that is not guarded. Use --reapply (which implies
2440 applied patch that is not guarded. Use --reapply (which implies
2441 --pop) to push back to the current patch afterwards, but skip
2441 --pop) to push back to the current patch afterwards, but skip
2442 guarded patches.
2442 guarded patches.
2443
2443
2444 Use -s/--series to print a list of all guards in the series file
2444 Use -s/--series to print a list of all guards in the series file
2445 (no other arguments needed). Use -v for more information.'''
2445 (no other arguments needed). Use -v for more information.'''
2446
2446
2447 q = repo.mq
2447 q = repo.mq
2448 guards = q.active()
2448 guards = q.active()
2449 if args or opts['none']:
2449 if args or opts['none']:
2450 old_unapplied = q.unapplied(repo)
2450 old_unapplied = q.unapplied(repo)
2451 old_guarded = [i for i in xrange(len(q.applied)) if
2451 old_guarded = [i for i in xrange(len(q.applied)) if
2452 not q.pushable(i)[0]]
2452 not q.pushable(i)[0]]
2453 q.set_active(args)
2453 q.set_active(args)
2454 q.save_dirty()
2454 q.save_dirty()
2455 if not args:
2455 if not args:
2456 ui.status(_('guards deactivated\n'))
2456 ui.status(_('guards deactivated\n'))
2457 if not opts['pop'] and not opts['reapply']:
2457 if not opts['pop'] and not opts['reapply']:
2458 unapplied = q.unapplied(repo)
2458 unapplied = q.unapplied(repo)
2459 guarded = [i for i in xrange(len(q.applied))
2459 guarded = [i for i in xrange(len(q.applied))
2460 if not q.pushable(i)[0]]
2460 if not q.pushable(i)[0]]
2461 if len(unapplied) != len(old_unapplied):
2461 if len(unapplied) != len(old_unapplied):
2462 ui.status(_('number of unguarded, unapplied patches has '
2462 ui.status(_('number of unguarded, unapplied patches has '
2463 'changed from %d to %d\n') %
2463 'changed from %d to %d\n') %
2464 (len(old_unapplied), len(unapplied)))
2464 (len(old_unapplied), len(unapplied)))
2465 if len(guarded) != len(old_guarded):
2465 if len(guarded) != len(old_guarded):
2466 ui.status(_('number of guarded, applied patches has changed '
2466 ui.status(_('number of guarded, applied patches has changed '
2467 'from %d to %d\n') %
2467 'from %d to %d\n') %
2468 (len(old_guarded), len(guarded)))
2468 (len(old_guarded), len(guarded)))
2469 elif opts['series']:
2469 elif opts['series']:
2470 guards = {}
2470 guards = {}
2471 noguards = 0
2471 noguards = 0
2472 for gs in q.series_guards:
2472 for gs in q.series_guards:
2473 if not gs:
2473 if not gs:
2474 noguards += 1
2474 noguards += 1
2475 for g in gs:
2475 for g in gs:
2476 guards.setdefault(g, 0)
2476 guards.setdefault(g, 0)
2477 guards[g] += 1
2477 guards[g] += 1
2478 if ui.verbose:
2478 if ui.verbose:
2479 guards['NONE'] = noguards
2479 guards['NONE'] = noguards
2480 guards = guards.items()
2480 guards = guards.items()
2481 guards.sort(key=lambda x: x[0][1:])
2481 guards.sort(key=lambda x: x[0][1:])
2482 if guards:
2482 if guards:
2483 ui.note(_('guards in series file:\n'))
2483 ui.note(_('guards in series file:\n'))
2484 for guard, count in guards:
2484 for guard, count in guards:
2485 ui.note('%2d ' % count)
2485 ui.note('%2d ' % count)
2486 ui.write(guard, '\n')
2486 ui.write(guard, '\n')
2487 else:
2487 else:
2488 ui.note(_('no guards in series file\n'))
2488 ui.note(_('no guards in series file\n'))
2489 else:
2489 else:
2490 if guards:
2490 if guards:
2491 ui.note(_('active guards:\n'))
2491 ui.note(_('active guards:\n'))
2492 for g in guards:
2492 for g in guards:
2493 ui.write(g, '\n')
2493 ui.write(g, '\n')
2494 else:
2494 else:
2495 ui.write(_('no active guards\n'))
2495 ui.write(_('no active guards\n'))
2496 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2496 reapply = opts['reapply'] and q.applied and q.appliedname(-1)
2497 popped = False
2497 popped = False
2498 if opts['pop'] or opts['reapply']:
2498 if opts['pop'] or opts['reapply']:
2499 for i in xrange(len(q.applied)):
2499 for i in xrange(len(q.applied)):
2500 pushable, reason = q.pushable(i)
2500 pushable, reason = q.pushable(i)
2501 if not pushable:
2501 if not pushable:
2502 ui.status(_('popping guarded patches\n'))
2502 ui.status(_('popping guarded patches\n'))
2503 popped = True
2503 popped = True
2504 if i == 0:
2504 if i == 0:
2505 q.pop(repo, all=True)
2505 q.pop(repo, all=True)
2506 else:
2506 else:
2507 q.pop(repo, i - 1)
2507 q.pop(repo, i - 1)
2508 break
2508 break
2509 if popped:
2509 if popped:
2510 try:
2510 try:
2511 if reapply:
2511 if reapply:
2512 ui.status(_('reapplying unguarded patches\n'))
2512 ui.status(_('reapplying unguarded patches\n'))
2513 q.push(repo, reapply)
2513 q.push(repo, reapply)
2514 finally:
2514 finally:
2515 q.save_dirty()
2515 q.save_dirty()
2516
2516
2517 def finish(ui, repo, *revrange, **opts):
2517 def finish(ui, repo, *revrange, **opts):
2518 """move applied patches into repository history
2518 """move applied patches into repository history
2519
2519
2520 Finishes the specified revisions (corresponding to applied
2520 Finishes the specified revisions (corresponding to applied
2521 patches) by moving them out of mq control into regular repository
2521 patches) by moving them out of mq control into regular repository
2522 history.
2522 history.
2523
2523
2524 Accepts a revision range or the -a/--applied option. If --applied
2524 Accepts a revision range or the -a/--applied option. If --applied
2525 is specified, all applied mq revisions are removed from mq
2525 is specified, all applied mq revisions are removed from mq
2526 control. Otherwise, the given revisions must be at the base of the
2526 control. Otherwise, the given revisions must be at the base of the
2527 stack of applied patches.
2527 stack of applied patches.
2528
2528
2529 This can be especially useful if your changes have been applied to
2529 This can be especially useful if your changes have been applied to
2530 an upstream repository, or if you are about to push your changes
2530 an upstream repository, or if you are about to push your changes
2531 to upstream.
2531 to upstream.
2532 """
2532 """
2533 if not opts['applied'] and not revrange:
2533 if not opts['applied'] and not revrange:
2534 raise util.Abort(_('no revisions specified'))
2534 raise util.Abort(_('no revisions specified'))
2535 elif opts['applied']:
2535 elif opts['applied']:
2536 revrange = ('qbase:qtip',) + revrange
2536 revrange = ('qbase:qtip',) + revrange
2537
2537
2538 q = repo.mq
2538 q = repo.mq
2539 if not q.applied:
2539 if not q.applied:
2540 ui.status(_('no patches applied\n'))
2540 ui.status(_('no patches applied\n'))
2541 return 0
2541 return 0
2542
2542
2543 revs = cmdutil.revrange(repo, revrange)
2543 revs = cmdutil.revrange(repo, revrange)
2544 q.finish(repo, revs)
2544 q.finish(repo, revs)
2545 q.save_dirty()
2545 q.save_dirty()
2546 return 0
2546 return 0
2547
2547
2548 def qqueue(ui, repo, name=None, **opts):
2548 def qqueue(ui, repo, name=None, **opts):
2549 '''manage multiple patch queues
2549 '''manage multiple patch queues
2550
2550
2551 Supports switching between different patch queues, as well as creating
2551 Supports switching between different patch queues, as well as creating
2552 new patch queues and deleting existing ones.
2552 new patch queues and deleting existing ones.
2553
2553
2554 Omitting a queue name or specifying -l/--list will show you the registered
2554 Omitting a queue name or specifying -l/--list will show you the registered
2555 queues - by default the "normal" patches queue is registered. The currently
2555 queues - by default the "normal" patches queue is registered. The currently
2556 active queue will be marked with "(active)".
2556 active queue will be marked with "(active)".
2557
2557
2558 To create a new queue, use -c/--create. The queue is automatically made
2558 To create a new queue, use -c/--create. The queue is automatically made
2559 active, except in the case where there are applied patches from the
2559 active, except in the case where there are applied patches from the
2560 currently active queue in the repository. Then the queue will only be
2560 currently active queue in the repository. Then the queue will only be
2561 created and switching will fail.
2561 created and switching will fail.
2562
2562
2563 To delete an existing queue, use --delete. You cannot delete the currently
2563 To delete an existing queue, use --delete. You cannot delete the currently
2564 active queue.
2564 active queue.
2565 '''
2565 '''
2566
2566
2567 q = repo.mq
2567 q = repo.mq
2568
2568
2569 _defaultqueue = 'patches'
2569 _defaultqueue = 'patches'
2570 _allqueues = 'patches.queues'
2570 _allqueues = 'patches.queues'
2571 _activequeue = 'patches.queue'
2571 _activequeue = 'patches.queue'
2572
2572
2573 def _getcurrent():
2573 def _getcurrent():
2574 cur = os.path.basename(q.path)
2574 cur = os.path.basename(q.path)
2575 if cur.startswith('patches-'):
2575 if cur.startswith('patches-'):
2576 cur = cur[8:]
2576 cur = cur[8:]
2577 return cur
2577 return cur
2578
2578
2579 def _noqueues():
2579 def _noqueues():
2580 try:
2580 try:
2581 fh = repo.opener(_allqueues, 'r')
2581 fh = repo.opener(_allqueues, 'r')
2582 fh.close()
2582 fh.close()
2583 except IOError:
2583 except IOError:
2584 return True
2584 return True
2585
2585
2586 return False
2586 return False
2587
2587
2588 def _getqueues():
2588 def _getqueues():
2589 current = _getcurrent()
2589 current = _getcurrent()
2590
2590
2591 try:
2591 try:
2592 fh = repo.opener(_allqueues, 'r')
2592 fh = repo.opener(_allqueues, 'r')
2593 queues = [queue.strip() for queue in fh if queue.strip()]
2593 queues = [queue.strip() for queue in fh if queue.strip()]
2594 if current not in queues:
2594 if current not in queues:
2595 queues.append(current)
2595 queues.append(current)
2596 except IOError:
2596 except IOError:
2597 queues = [_defaultqueue]
2597 queues = [_defaultqueue]
2598
2598
2599 return sorted(queues)
2599 return sorted(queues)
2600
2600
2601 def _setactive(name):
2601 def _setactive(name):
2602 if q.applied:
2602 if q.applied:
2603 raise util.Abort(_('patches applied - cannot set new queue active'))
2603 raise util.Abort(_('patches applied - cannot set new queue active'))
2604
2604
2605 fh = repo.opener(_activequeue, 'w')
2605 fh = repo.opener(_activequeue, 'w')
2606 if name != 'patches':
2606 if name != 'patches':
2607 fh.write(name)
2607 fh.write(name)
2608 fh.close()
2608 fh.close()
2609
2609
2610 def _addqueue(name):
2610 def _addqueue(name):
2611 fh = repo.opener(_allqueues, 'a')
2611 fh = repo.opener(_allqueues, 'a')
2612 fh.write('%s\n' % (name,))
2612 fh.write('%s\n' % (name,))
2613 fh.close()
2613 fh.close()
2614
2614
2615 def _validname(name):
2615 def _validname(name):
2616 for n in name:
2616 for n in name:
2617 if n in ':\\/.':
2617 if n in ':\\/.':
2618 return False
2618 return False
2619 return True
2619 return True
2620
2620
2621 if not name or opts.get('list'):
2621 if not name or opts.get('list'):
2622 current = _getcurrent()
2622 current = _getcurrent()
2623 for queue in _getqueues():
2623 for queue in _getqueues():
2624 ui.write('%s' % (queue,))
2624 ui.write('%s' % (queue,))
2625 if queue == current:
2625 if queue == current:
2626 ui.write(_(' (active)\n'))
2626 ui.write(_(' (active)\n'))
2627 else:
2627 else:
2628 ui.write('\n')
2628 ui.write('\n')
2629 return
2629 return
2630
2630
2631 if not _validname(name):
2631 if not _validname(name):
2632 raise util.Abort(
2632 raise util.Abort(
2633 _('invalid queue name, may not contain the characters ":\\/."'))
2633 _('invalid queue name, may not contain the characters ":\\/."'))
2634
2634
2635 existing = _getqueues()
2635 existing = _getqueues()
2636
2636
2637 if opts.get('create'):
2637 if opts.get('create'):
2638 if name in existing:
2638 if name in existing:
2639 raise util.Abort(_('queue "%s" already exists') % name)
2639 raise util.Abort(_('queue "%s" already exists') % name)
2640 if _noqueues():
2640 if _noqueues():
2641 _addqueue(_defaultqueue)
2641 _addqueue(_defaultqueue)
2642 _addqueue(name)
2642 _addqueue(name)
2643 _setactive(name)
2643 _setactive(name)
2644 elif opts.get('delete'):
2644 elif opts.get('delete'):
2645 if name not in existing:
2645 if name not in existing:
2646 raise util.Abort(_('cannot delete queue that does not exist'))
2646 raise util.Abort(_('cannot delete queue that does not exist'))
2647
2647
2648 current = _getcurrent()
2648 current = _getcurrent()
2649
2649
2650 if name == current:
2650 if name == current:
2651 raise util.Abort(_('cannot delete currently active queue'))
2651 raise util.Abort(_('cannot delete currently active queue'))
2652
2652
2653 fh = repo.opener('patches.queues.new', 'w')
2653 fh = repo.opener('patches.queues.new', 'w')
2654 for queue in existing:
2654 for queue in existing:
2655 if queue == name:
2655 if queue == name:
2656 continue
2656 continue
2657 fh.write('%s\n' % (queue,))
2657 fh.write('%s\n' % (queue,))
2658 fh.close()
2658 fh.close()
2659 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2659 util.rename(repo.join('patches.queues.new'), repo.join(_allqueues))
2660 else:
2660 else:
2661 if name not in existing:
2661 if name not in existing:
2662 raise util.Abort(_('use --create to create a new queue'))
2662 raise util.Abort(_('use --create to create a new queue'))
2663 _setactive(name)
2663 _setactive(name)
2664
2664
2665 def reposetup(ui, repo):
2665 def reposetup(ui, repo):
2666 class mqrepo(repo.__class__):
2666 class mqrepo(repo.__class__):
2667 @util.propertycache
2667 @util.propertycache
2668 def mq(self):
2668 def mq(self):
2669 return queue(self.ui, self.join(""))
2669 return queue(self.ui, self.join(""))
2670
2670
2671 def abort_if_wdir_patched(self, errmsg, force=False):
2671 def abort_if_wdir_patched(self, errmsg, force=False):
2672 if self.mq.applied and not force:
2672 if self.mq.applied and not force:
2673 parent = self.dirstate.parents()[0]
2673 parent = self.dirstate.parents()[0]
2674 if parent in [s.node for s in self.mq.applied]:
2674 if parent in [s.node for s in self.mq.applied]:
2675 raise util.Abort(errmsg)
2675 raise util.Abort(errmsg)
2676
2676
2677 def commit(self, text="", user=None, date=None, match=None,
2677 def commit(self, text="", user=None, date=None, match=None,
2678 force=False, editor=False, extra={}):
2678 force=False, editor=False, extra={}):
2679 self.abort_if_wdir_patched(
2679 self.abort_if_wdir_patched(
2680 _('cannot commit over an applied mq patch'),
2680 _('cannot commit over an applied mq patch'),
2681 force)
2681 force)
2682
2682
2683 return super(mqrepo, self).commit(text, user, date, match, force,
2683 return super(mqrepo, self).commit(text, user, date, match, force,
2684 editor, extra)
2684 editor, extra)
2685
2685
2686 def push(self, remote, force=False, revs=None, newbranch=False):
2686 def push(self, remote, force=False, revs=None, newbranch=False):
2687 if self.mq.applied and not force and not revs:
2687 if self.mq.applied and not force and not revs:
2688 raise util.Abort(_('source has mq patches applied'))
2688 raise util.Abort(_('source has mq patches applied'))
2689 return super(mqrepo, self).push(remote, force, revs, newbranch)
2689 return super(mqrepo, self).push(remote, force, revs, newbranch)
2690
2690
2691 def _findtags(self):
2691 def _findtags(self):
2692 '''augment tags from base class with patch tags'''
2692 '''augment tags from base class with patch tags'''
2693 result = super(mqrepo, self)._findtags()
2693 result = super(mqrepo, self)._findtags()
2694
2694
2695 q = self.mq
2695 q = self.mq
2696 if not q.applied:
2696 if not q.applied:
2697 return result
2697 return result
2698
2698
2699 mqtags = [(patch.node, patch.name) for patch in q.applied]
2699 mqtags = [(patch.node, patch.name) for patch in q.applied]
2700
2700
2701 if mqtags[-1][0] not in self.changelog.nodemap:
2701 if mqtags[-1][0] not in self.changelog.nodemap:
2702 self.ui.warn(_('mq status file refers to unknown node %s\n')
2702 self.ui.warn(_('mq status file refers to unknown node %s\n')
2703 % short(mqtags[-1][0]))
2703 % short(mqtags[-1][0]))
2704 return result
2704 return result
2705
2705
2706 mqtags.append((mqtags[-1][0], 'qtip'))
2706 mqtags.append((mqtags[-1][0], 'qtip'))
2707 mqtags.append((mqtags[0][0], 'qbase'))
2707 mqtags.append((mqtags[0][0], 'qbase'))
2708 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2708 mqtags.append((self.changelog.parents(mqtags[0][0])[0], 'qparent'))
2709 tags = result[0]
2709 tags = result[0]
2710 for patch in mqtags:
2710 for patch in mqtags:
2711 if patch[1] in tags:
2711 if patch[1] in tags:
2712 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2712 self.ui.warn(_('Tag %s overrides mq patch of the same name\n')
2713 % patch[1])
2713 % patch[1])
2714 else:
2714 else:
2715 tags[patch[1]] = patch[0]
2715 tags[patch[1]] = patch[0]
2716
2716
2717 return result
2717 return result
2718
2718
2719 def _branchtags(self, partial, lrev):
2719 def _branchtags(self, partial, lrev):
2720 q = self.mq
2720 q = self.mq
2721 if not q.applied:
2721 if not q.applied:
2722 return super(mqrepo, self)._branchtags(partial, lrev)
2722 return super(mqrepo, self)._branchtags(partial, lrev)
2723
2723
2724 cl = self.changelog
2724 cl = self.changelog
2725 qbasenode = q.applied[0].node
2725 qbasenode = q.applied[0].node
2726 if qbasenode not in cl.nodemap:
2726 if qbasenode not in cl.nodemap:
2727 self.ui.warn(_('mq status file refers to unknown node %s\n')
2727 self.ui.warn(_('mq status file refers to unknown node %s\n')
2728 % short(qbasenode))
2728 % short(qbasenode))
2729 return super(mqrepo, self)._branchtags(partial, lrev)
2729 return super(mqrepo, self)._branchtags(partial, lrev)
2730
2730
2731 qbase = cl.rev(qbasenode)
2731 qbase = cl.rev(qbasenode)
2732 start = lrev + 1
2732 start = lrev + 1
2733 if start < qbase:
2733 if start < qbase:
2734 # update the cache (excluding the patches) and save it
2734 # update the cache (excluding the patches) and save it
2735 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
2735 ctxgen = (self[r] for r in xrange(lrev + 1, qbase))
2736 self._updatebranchcache(partial, ctxgen)
2736 self._updatebranchcache(partial, ctxgen)
2737 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
2737 self._writebranchcache(partial, cl.node(qbase - 1), qbase - 1)
2738 start = qbase
2738 start = qbase
2739 # if start = qbase, the cache is as updated as it should be.
2739 # if start = qbase, the cache is as updated as it should be.
2740 # if start > qbase, the cache includes (part of) the patches.
2740 # if start > qbase, the cache includes (part of) the patches.
2741 # we might as well use it, but we won't save it.
2741 # we might as well use it, but we won't save it.
2742
2742
2743 # update the cache up to the tip
2743 # update the cache up to the tip
2744 ctxgen = (self[r] for r in xrange(start, len(cl)))
2744 ctxgen = (self[r] for r in xrange(start, len(cl)))
2745 self._updatebranchcache(partial, ctxgen)
2745 self._updatebranchcache(partial, ctxgen)
2746
2746
2747 return partial
2747 return partial
2748
2748
2749 if repo.local():
2749 if repo.local():
2750 repo.__class__ = mqrepo
2750 repo.__class__ = mqrepo
2751
2751
2752 def mqimport(orig, ui, repo, *args, **kwargs):
2752 def mqimport(orig, ui, repo, *args, **kwargs):
2753 if (hasattr(repo, 'abort_if_wdir_patched')
2753 if (hasattr(repo, 'abort_if_wdir_patched')
2754 and not kwargs.get('no_commit', False)):
2754 and not kwargs.get('no_commit', False)):
2755 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2755 repo.abort_if_wdir_patched(_('cannot import over an applied patch'),
2756 kwargs.get('force'))
2756 kwargs.get('force'))
2757 return orig(ui, repo, *args, **kwargs)
2757 return orig(ui, repo, *args, **kwargs)
2758
2758
2759 def mqinit(orig, ui, *args, **kwargs):
2759 def mqinit(orig, ui, *args, **kwargs):
2760 mq = kwargs.pop('mq', None)
2760 mq = kwargs.pop('mq', None)
2761
2761
2762 if not mq:
2762 if not mq:
2763 return orig(ui, *args, **kwargs)
2763 return orig(ui, *args, **kwargs)
2764
2764
2765 if args:
2765 if args:
2766 repopath = args[0]
2766 repopath = args[0]
2767 if not hg.islocal(repopath):
2767 if not hg.islocal(repopath):
2768 raise util.Abort(_('only a local queue repository '
2768 raise util.Abort(_('only a local queue repository '
2769 'may be initialized'))
2769 'may be initialized'))
2770 else:
2770 else:
2771 repopath = cmdutil.findrepo(os.getcwd())
2771 repopath = cmdutil.findrepo(os.getcwd())
2772 if not repopath:
2772 if not repopath:
2773 raise util.Abort(_('There is no Mercurial repository here '
2773 raise util.Abort(_('There is no Mercurial repository here '
2774 '(.hg not found)'))
2774 '(.hg not found)'))
2775 repo = hg.repository(ui, repopath)
2775 repo = hg.repository(ui, repopath)
2776 return qinit(ui, repo, True)
2776 return qinit(ui, repo, True)
2777
2777
2778 def mqcommand(orig, ui, repo, *args, **kwargs):
2778 def mqcommand(orig, ui, repo, *args, **kwargs):
2779 """Add --mq option to operate on patch repository instead of main"""
2779 """Add --mq option to operate on patch repository instead of main"""
2780
2780
2781 # some commands do not like getting unknown options
2781 # some commands do not like getting unknown options
2782 mq = kwargs.pop('mq', None)
2782 mq = kwargs.pop('mq', None)
2783
2783
2784 if not mq:
2784 if not mq:
2785 return orig(ui, repo, *args, **kwargs)
2785 return orig(ui, repo, *args, **kwargs)
2786
2786
2787 q = repo.mq
2787 q = repo.mq
2788 r = q.qrepo()
2788 r = q.qrepo()
2789 if not r:
2789 if not r:
2790 raise util.Abort(_('no queue repository'))
2790 raise util.Abort(_('no queue repository'))
2791 return orig(r.ui, r, *args, **kwargs)
2791 return orig(r.ui, r, *args, **kwargs)
2792
2792
2793 def summary(orig, ui, repo, *args, **kwargs):
2793 def summary(orig, ui, repo, *args, **kwargs):
2794 r = orig(ui, repo, *args, **kwargs)
2794 r = orig(ui, repo, *args, **kwargs)
2795 q = repo.mq
2795 q = repo.mq
2796 m = []
2796 m = []
2797 a, u = len(q.applied), len(q.unapplied(repo))
2797 a, u = len(q.applied), len(q.unapplied(repo))
2798 if a:
2798 if a:
2799 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
2799 m.append(ui.label(_("%d applied"), 'qseries.applied') % a)
2800 if u:
2800 if u:
2801 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
2801 m.append(ui.label(_("%d unapplied"), 'qseries.unapplied') % u)
2802 if m:
2802 if m:
2803 ui.write("mq: %s\n" % ', '.join(m))
2803 ui.write("mq: %s\n" % ', '.join(m))
2804 else:
2804 else:
2805 ui.note(_("mq: (empty queue)\n"))
2805 ui.note(_("mq: (empty queue)\n"))
2806 return r
2806 return r
2807
2807
2808 def uisetup(ui):
2808 def uisetup(ui):
2809 mqopt = [('', 'mq', None, _("operate on patch repository"))]
2809 mqopt = [('', 'mq', None, _("operate on patch repository"))]
2810
2810
2811 extensions.wrapcommand(commands.table, 'import', mqimport)
2811 extensions.wrapcommand(commands.table, 'import', mqimport)
2812 extensions.wrapcommand(commands.table, 'summary', summary)
2812 extensions.wrapcommand(commands.table, 'summary', summary)
2813
2813
2814 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
2814 entry = extensions.wrapcommand(commands.table, 'init', mqinit)
2815 entry[1].extend(mqopt)
2815 entry[1].extend(mqopt)
2816
2816
2817 norepo = commands.norepo.split(" ")
2817 norepo = commands.norepo.split(" ")
2818 for cmd in commands.table.keys():
2818 for cmd in commands.table.keys():
2819 cmd = cmdutil.parsealiases(cmd)[0]
2819 cmd = cmdutil.parsealiases(cmd)[0]
2820 if cmd in norepo:
2820 if cmd in norepo:
2821 continue
2821 continue
2822 entry = extensions.wrapcommand(commands.table, cmd, mqcommand)
2822 entry = extensions.wrapcommand(commands.table, cmd, mqcommand)
2823 entry[1].extend(mqopt)
2823 entry[1].extend(mqopt)
2824
2824
2825 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2825 seriesopts = [('s', 'summary', None, _('print first line of patch header'))]
2826
2826
2827 cmdtable = {
2827 cmdtable = {
2828 "qapplied":
2828 "qapplied":
2829 (applied,
2829 (applied,
2830 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
2830 [('1', 'last', None, _('show only the last patch'))] + seriesopts,
2831 _('hg qapplied [-1] [-s] [PATCH]')),
2831 _('hg qapplied [-1] [-s] [PATCH]')),
2832 "qclone":
2832 "qclone":
2833 (clone,
2833 (clone,
2834 [('', 'pull', None, _('use pull protocol to copy metadata')),
2834 [('', 'pull', None, _('use pull protocol to copy metadata')),
2835 ('U', 'noupdate', None, _('do not update the new working directories')),
2835 ('U', 'noupdate', None, _('do not update the new working directories')),
2836 ('', 'uncompressed', None,
2836 ('', 'uncompressed', None,
2837 _('use uncompressed transfer (fast over LAN)')),
2837 _('use uncompressed transfer (fast over LAN)')),
2838 ('p', 'patches', '', _('location of source patch repository')),
2838 ('p', 'patches', '',
2839 _('location of source patch repository'), _('REPO')),
2839 ] + commands.remoteopts,
2840 ] + commands.remoteopts,
2840 _('hg qclone [OPTION]... SOURCE [DEST]')),
2841 _('hg qclone [OPTION]... SOURCE [DEST]')),
2841 "qcommit|qci":
2842 "qcommit|qci":
2842 (commit,
2843 (commit,
2843 commands.table["^commit|ci"][1],
2844 commands.table["^commit|ci"][1],
2844 _('hg qcommit [OPTION]... [FILE]...')),
2845 _('hg qcommit [OPTION]... [FILE]...')),
2845 "^qdiff":
2846 "^qdiff":
2846 (diff,
2847 (diff,
2847 commands.diffopts + commands.diffopts2 + commands.walkopts,
2848 commands.diffopts + commands.diffopts2 + commands.walkopts,
2848 _('hg qdiff [OPTION]... [FILE]...')),
2849 _('hg qdiff [OPTION]... [FILE]...')),
2849 "qdelete|qremove|qrm":
2850 "qdelete|qremove|qrm":
2850 (delete,
2851 (delete,
2851 [('k', 'keep', None, _('keep patch file')),
2852 [('k', 'keep', None, _('keep patch file')),
2852 ('r', 'rev', [], _('stop managing a revision (DEPRECATED)'))],
2853 ('r', 'rev', [],
2854 _('stop managing a revision (DEPRECATED)'), _('REV'))],
2853 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2855 _('hg qdelete [-k] [-r REV]... [PATCH]...')),
2854 'qfold':
2856 'qfold':
2855 (fold,
2857 (fold,
2856 [('e', 'edit', None, _('edit patch header')),
2858 [('e', 'edit', None, _('edit patch header')),
2857 ('k', 'keep', None, _('keep folded patch files')),
2859 ('k', 'keep', None, _('keep folded patch files')),
2858 ] + commands.commitopts,
2860 ] + commands.commitopts,
2859 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2861 _('hg qfold [-e] [-k] [-m TEXT] [-l FILE] PATCH...')),
2860 'qgoto':
2862 'qgoto':
2861 (goto,
2863 (goto,
2862 [('f', 'force', None, _('overwrite any local changes'))],
2864 [('f', 'force', None, _('overwrite any local changes'))],
2863 _('hg qgoto [OPTION]... PATCH')),
2865 _('hg qgoto [OPTION]... PATCH')),
2864 'qguard':
2866 'qguard':
2865 (guard,
2867 (guard,
2866 [('l', 'list', None, _('list all patches and guards')),
2868 [('l', 'list', None, _('list all patches and guards')),
2867 ('n', 'none', None, _('drop all guards'))],
2869 ('n', 'none', None, _('drop all guards'))],
2868 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
2870 _('hg qguard [-l] [-n] [PATCH] [-- [+GUARD]... [-GUARD]...]')),
2869 'qheader': (header, [], _('hg qheader [PATCH]')),
2871 'qheader': (header, [], _('hg qheader [PATCH]')),
2870 "qimport":
2872 "qimport":
2871 (qimport,
2873 (qimport,
2872 [('e', 'existing', None, _('import file in patch directory')),
2874 [('e', 'existing', None, _('import file in patch directory')),
2873 ('n', 'name', '', _('name of patch file')),
2875 ('n', 'name', '',
2876 _('name of patch file'), _('NAME')),
2874 ('f', 'force', None, _('overwrite existing files')),
2877 ('f', 'force', None, _('overwrite existing files')),
2875 ('r', 'rev', [], _('place existing revisions under mq control')),
2878 ('r', 'rev', [],
2879 _('place existing revisions under mq control'), _('REV')),
2876 ('g', 'git', None, _('use git extended diff format')),
2880 ('g', 'git', None, _('use git extended diff format')),
2877 ('P', 'push', None, _('qpush after importing'))],
2881 ('P', 'push', None, _('qpush after importing'))],
2878 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2882 _('hg qimport [-e] [-n NAME] [-f] [-g] [-P] [-r REV]... FILE...')),
2879 "^qinit":
2883 "^qinit":
2880 (init,
2884 (init,
2881 [('c', 'create-repo', None, _('create queue repository'))],
2885 [('c', 'create-repo', None, _('create queue repository'))],
2882 _('hg qinit [-c]')),
2886 _('hg qinit [-c]')),
2883 "^qnew":
2887 "^qnew":
2884 (new,
2888 (new,
2885 [('e', 'edit', None, _('edit commit message')),
2889 [('e', 'edit', None, _('edit commit message')),
2886 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2890 ('f', 'force', None, _('import uncommitted changes (DEPRECATED)')),
2887 ('g', 'git', None, _('use git extended diff format')),
2891 ('g', 'git', None, _('use git extended diff format')),
2888 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2892 ('U', 'currentuser', None, _('add "From: <current user>" to patch')),
2889 ('u', 'user', '', _('add "From: <given user>" to patch')),
2893 ('u', 'user', '',
2894 _('add "From: <USER>" to patch'), _('USER')),
2890 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2895 ('D', 'currentdate', None, _('add "Date: <current date>" to patch')),
2891 ('d', 'date', '', _('add "Date: <given date>" to patch'))
2896 ('d', 'date', '',
2897 _('add "Date: <DATE>" to patch'), _('DATE'))
2892 ] + commands.walkopts + commands.commitopts,
2898 ] + commands.walkopts + commands.commitopts,
2893 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...')),
2899 _('hg qnew [-e] [-m TEXT] [-l FILE] PATCH [FILE]...')),
2894 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2900 "qnext": (next, [] + seriesopts, _('hg qnext [-s]')),
2895 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2901 "qprev": (prev, [] + seriesopts, _('hg qprev [-s]')),
2896 "^qpop":
2902 "^qpop":
2897 (pop,
2903 (pop,
2898 [('a', 'all', None, _('pop all patches')),
2904 [('a', 'all', None, _('pop all patches')),
2899 ('n', 'name', '', _('queue name to pop (DEPRECATED)')),
2905 ('n', 'name', '',
2906 _('queue name to pop (DEPRECATED)'), _('NAME')),
2900 ('f', 'force', None, _('forget any local changes to patched files'))],
2907 ('f', 'force', None, _('forget any local changes to patched files'))],
2901 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2908 _('hg qpop [-a] [-n NAME] [-f] [PATCH | INDEX]')),
2902 "^qpush":
2909 "^qpush":
2903 (push,
2910 (push,
2904 [('f', 'force', None, _('apply if the patch has rejects')),
2911 [('f', 'force', None, _('apply if the patch has rejects')),
2905 ('l', 'list', None, _('list patch name in commit text')),
2912 ('l', 'list', None, _('list patch name in commit text')),
2906 ('a', 'all', None, _('apply all patches')),
2913 ('a', 'all', None, _('apply all patches')),
2907 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2914 ('m', 'merge', None, _('merge from another queue (DEPRECATED)')),
2908 ('n', 'name', '', _('merge queue name (DEPRECATED)')),
2915 ('n', 'name', '',
2916 _('merge queue name (DEPRECATED)'), _('NAME')),
2909 ('', 'move', None, _('reorder patch series and apply only the patch'))],
2917 ('', 'move', None, _('reorder patch series and apply only the patch'))],
2910 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [--move] [PATCH | INDEX]')),
2918 _('hg qpush [-f] [-l] [-a] [-m] [-n NAME] [--move] [PATCH | INDEX]')),
2911 "^qrefresh":
2919 "^qrefresh":
2912 (refresh,
2920 (refresh,
2913 [('e', 'edit', None, _('edit commit message')),
2921 [('e', 'edit', None, _('edit commit message')),
2914 ('g', 'git', None, _('use git extended diff format')),
2922 ('g', 'git', None, _('use git extended diff format')),
2915 ('s', 'short', None,
2923 ('s', 'short', None,
2916 _('refresh only files already in the patch and specified files')),
2924 _('refresh only files already in the patch and specified files')),
2917 ('U', 'currentuser', None,
2925 ('U', 'currentuser', None,
2918 _('add/update author field in patch with current user')),
2926 _('add/update author field in patch with current user')),
2919 ('u', 'user', '',
2927 ('u', 'user', '',
2920 _('add/update author field in patch with given user')),
2928 _('add/update author field in patch with given user'), _('USER')),
2921 ('D', 'currentdate', None,
2929 ('D', 'currentdate', None,
2922 _('add/update date field in patch with current date')),
2930 _('add/update date field in patch with current date')),
2923 ('d', 'date', '',
2931 ('d', 'date', '',
2924 _('add/update date field in patch with given date'))
2932 _('add/update date field in patch with given date'), _('DATE'))
2925 ] + commands.walkopts + commands.commitopts,
2933 ] + commands.walkopts + commands.commitopts,
2926 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2934 _('hg qrefresh [-I] [-X] [-e] [-m TEXT] [-l FILE] [-s] [FILE]...')),
2927 'qrename|qmv':
2935 'qrename|qmv':
2928 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2936 (rename, [], _('hg qrename PATCH1 [PATCH2]')),
2929 "qrestore":
2937 "qrestore":
2930 (restore,
2938 (restore,
2931 [('d', 'delete', None, _('delete save entry')),
2939 [('d', 'delete', None, _('delete save entry')),
2932 ('u', 'update', None, _('update queue working directory'))],
2940 ('u', 'update', None, _('update queue working directory'))],
2933 _('hg qrestore [-d] [-u] REV')),
2941 _('hg qrestore [-d] [-u] REV')),
2934 "qsave":
2942 "qsave":
2935 (save,
2943 (save,
2936 [('c', 'copy', None, _('copy patch directory')),
2944 [('c', 'copy', None, _('copy patch directory')),
2937 ('n', 'name', '', _('copy directory name')),
2945 ('n', 'name', '',
2946 _('copy directory name'), _('NAME')),
2938 ('e', 'empty', None, _('clear queue status file')),
2947 ('e', 'empty', None, _('clear queue status file')),
2939 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2948 ('f', 'force', None, _('force copy'))] + commands.commitopts,
2940 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2949 _('hg qsave [-m TEXT] [-l FILE] [-c] [-n NAME] [-e] [-f]')),
2941 "qselect":
2950 "qselect":
2942 (select,
2951 (select,
2943 [('n', 'none', None, _('disable all guards')),
2952 [('n', 'none', None, _('disable all guards')),
2944 ('s', 'series', None, _('list all guards in series file')),
2953 ('s', 'series', None, _('list all guards in series file')),
2945 ('', 'pop', None, _('pop to before first guarded applied patch')),
2954 ('', 'pop', None, _('pop to before first guarded applied patch')),
2946 ('', 'reapply', None, _('pop, then reapply patches'))],
2955 ('', 'reapply', None, _('pop, then reapply patches'))],
2947 _('hg qselect [OPTION]... [GUARD]...')),
2956 _('hg qselect [OPTION]... [GUARD]...')),
2948 "qseries":
2957 "qseries":
2949 (series,
2958 (series,
2950 [('m', 'missing', None, _('print patches not in series')),
2959 [('m', 'missing', None, _('print patches not in series')),
2951 ] + seriesopts,
2960 ] + seriesopts,
2952 _('hg qseries [-ms]')),
2961 _('hg qseries [-ms]')),
2953 "strip":
2962 "strip":
2954 (strip,
2963 (strip,
2955 [('f', 'force', None, _('force removal of changesets even if the '
2964 [('f', 'force', None, _('force removal of changesets even if the '
2956 'working directory has uncommitted changes')),
2965 'working directory has uncommitted changes')),
2957 ('b', 'backup', None, _('bundle only changesets with local revision'
2966 ('b', 'backup', None, _('bundle only changesets with local revision'
2958 ' number greater than REV which are not'
2967 ' number greater than REV which are not'
2959 ' descendants of REV (DEPRECATED)')),
2968 ' descendants of REV (DEPRECATED)')),
2960 ('n', 'nobackup', None, _('no backups'))],
2969 ('n', 'nobackup', None, _('no backups'))],
2961 _('hg strip [-f] [-n] REV')),
2970 _('hg strip [-f] [-n] REV')),
2962 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2971 "qtop": (top, [] + seriesopts, _('hg qtop [-s]')),
2963 "qunapplied":
2972 "qunapplied":
2964 (unapplied,
2973 (unapplied,
2965 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2974 [('1', 'first', None, _('show only the first patch'))] + seriesopts,
2966 _('hg qunapplied [-1] [-s] [PATCH]')),
2975 _('hg qunapplied [-1] [-s] [PATCH]')),
2967 "qfinish":
2976 "qfinish":
2968 (finish,
2977 (finish,
2969 [('a', 'applied', None, _('finish all applied changesets'))],
2978 [('a', 'applied', None, _('finish all applied changesets'))],
2970 _('hg qfinish [-a] [REV]...')),
2979 _('hg qfinish [-a] [REV]...')),
2971 'qqueue':
2980 'qqueue':
2972 (qqueue,
2981 (qqueue,
2973 [
2982 [
2974 ('l', 'list', False, _('list all available queues')),
2983 ('l', 'list', False, _('list all available queues')),
2975 ('c', 'create', False, _('create new queue')),
2984 ('c', 'create', False, _('create new queue')),
2976 ('', 'delete', False, _('delete reference to queue')),
2985 ('', 'delete', False, _('delete reference to queue')),
2977 ],
2986 ],
2978 _('[OPTION] [QUEUE]')),
2987 _('[OPTION] [QUEUE]')),
2979 }
2988 }
2980
2989
2981 colortable = {'qguard.negative': 'red',
2990 colortable = {'qguard.negative': 'red',
2982 'qguard.positive': 'yellow',
2991 'qguard.positive': 'yellow',
2983 'qguard.unguarded': 'green',
2992 'qguard.unguarded': 'green',
2984 'qseries.applied': 'blue bold underline',
2993 'qseries.applied': 'blue bold underline',
2985 'qseries.guarded': 'black bold',
2994 'qseries.guarded': 'black bold',
2986 'qseries.missing': 'red bold',
2995 'qseries.missing': 'red bold',
2987 'qseries.unapplied': 'black bold'}
2996 'qseries.unapplied': 'black bold'}
@@ -1,533 +1,535 b''
1 # patchbomb.py - sending Mercurial changesets as patch emails
1 # patchbomb.py - sending Mercurial changesets as patch emails
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to send changesets as (a series of) patch emails
8 '''command to send changesets as (a series of) patch emails
9
9
10 The series is started off with a "[PATCH 0 of N]" introduction, which
10 The series is started off with a "[PATCH 0 of N]" introduction, which
11 describes the series as a whole.
11 describes the series as a whole.
12
12
13 Each patch email has a Subject line of "[PATCH M of N] ...", using the
13 Each patch email has a Subject line of "[PATCH M of N] ...", using the
14 first line of the changeset description as the subject text. The
14 first line of the changeset description as the subject text. The
15 message contains two or three body parts:
15 message contains two or three body parts:
16
16
17 - The changeset description.
17 - The changeset description.
18 - [Optional] The result of running diffstat on the patch.
18 - [Optional] The result of running diffstat on the patch.
19 - The patch itself, as generated by :hg:`export`.
19 - The patch itself, as generated by :hg:`export`.
20
20
21 Each message refers to the first in the series using the In-Reply-To
21 Each message refers to the first in the series using the In-Reply-To
22 and References headers, so they will show up as a sequence in threaded
22 and References headers, so they will show up as a sequence in threaded
23 mail and news readers, and in mail archives.
23 mail and news readers, and in mail archives.
24
24
25 With the -d/--diffstat option, you will be prompted for each changeset
25 With the -d/--diffstat option, you will be prompted for each changeset
26 with a diffstat summary and the changeset summary, so you can be sure
26 with a diffstat summary and the changeset summary, so you can be sure
27 you are sending the right changes.
27 you are sending the right changes.
28
28
29 To configure other defaults, add a section like this to your hgrc
29 To configure other defaults, add a section like this to your hgrc
30 file::
30 file::
31
31
32 [email]
32 [email]
33 from = My Name <my@email>
33 from = My Name <my@email>
34 to = recipient1, recipient2, ...
34 to = recipient1, recipient2, ...
35 cc = cc1, cc2, ...
35 cc = cc1, cc2, ...
36 bcc = bcc1, bcc2, ...
36 bcc = bcc1, bcc2, ...
37 reply-to = address1, address2, ...
37 reply-to = address1, address2, ...
38
38
39 Use ``[patchbomb]`` as configuration section name if you need to
39 Use ``[patchbomb]`` as configuration section name if you need to
40 override global ``[email]`` address settings.
40 override global ``[email]`` address settings.
41
41
42 Then you can use the :hg:`email` command to mail a series of
42 Then you can use the :hg:`email` command to mail a series of
43 changesets as a patchbomb.
43 changesets as a patchbomb.
44
44
45 To avoid sending patches prematurely, it is a good idea to first run
45 To avoid sending patches prematurely, it is a good idea to first run
46 the :hg:`email` command with the "-n" option (test only). You will be
46 the :hg:`email` command with the "-n" option (test only). You will be
47 prompted for an email recipient address, a subject and an introductory
47 prompted for an email recipient address, a subject and an introductory
48 message describing the patches of your patchbomb. Then when all is
48 message describing the patches of your patchbomb. Then when all is
49 done, patchbomb messages are displayed. If the PAGER environment
49 done, patchbomb messages are displayed. If the PAGER environment
50 variable is set, your pager will be fired up once for each patchbomb
50 variable is set, your pager will be fired up once for each patchbomb
51 message, so you can verify everything is alright.
51 message, so you can verify everything is alright.
52
52
53 The -m/--mbox option is also very useful. Instead of previewing each
53 The -m/--mbox option is also very useful. Instead of previewing each
54 patchbomb message in a pager or sending the messages directly, it will
54 patchbomb message in a pager or sending the messages directly, it will
55 create a UNIX mailbox file with the patch emails. This mailbox file
55 create a UNIX mailbox file with the patch emails. This mailbox file
56 can be previewed with any mail user agent which supports UNIX mbox
56 can be previewed with any mail user agent which supports UNIX mbox
57 files, e.g. with mutt::
57 files, e.g. with mutt::
58
58
59 % mutt -R -f mbox
59 % mutt -R -f mbox
60
60
61 When you are previewing the patchbomb messages, you can use ``formail``
61 When you are previewing the patchbomb messages, you can use ``formail``
62 (a utility that is commonly installed as part of the procmail
62 (a utility that is commonly installed as part of the procmail
63 package), to send each message out::
63 package), to send each message out::
64
64
65 % formail -s sendmail -bm -t < mbox
65 % formail -s sendmail -bm -t < mbox
66
66
67 That should be all. Now your patchbomb is on its way out.
67 That should be all. Now your patchbomb is on its way out.
68
68
69 You can also either configure the method option in the email section
69 You can also either configure the method option in the email section
70 to be a sendmail compatible mailer or fill out the [smtp] section so
70 to be a sendmail compatible mailer or fill out the [smtp] section so
71 that the patchbomb extension can automatically send patchbombs
71 that the patchbomb extension can automatically send patchbombs
72 directly from the commandline. See the [email] and [smtp] sections in
72 directly from the commandline. See the [email] and [smtp] sections in
73 hgrc(5) for details.
73 hgrc(5) for details.
74 '''
74 '''
75
75
76 import os, errno, socket, tempfile, cStringIO, time
76 import os, errno, socket, tempfile, cStringIO, time
77 import email.MIMEMultipart, email.MIMEBase
77 import email.MIMEMultipart, email.MIMEBase
78 import email.Utils, email.Encoders, email.Generator
78 import email.Utils, email.Encoders, email.Generator
79 from mercurial import cmdutil, commands, hg, mail, patch, util, discovery
79 from mercurial import cmdutil, commands, hg, mail, patch, util, discovery
80 from mercurial.i18n import _
80 from mercurial.i18n import _
81 from mercurial.node import bin
81 from mercurial.node import bin
82
82
83 def prompt(ui, prompt, default=None, rest=':'):
83 def prompt(ui, prompt, default=None, rest=':'):
84 if not ui.interactive():
84 if not ui.interactive():
85 if default is not None:
85 if default is not None:
86 return default
86 return default
87 raise util.Abort(_("%s Please enter a valid value" % (prompt + rest)))
87 raise util.Abort(_("%s Please enter a valid value" % (prompt + rest)))
88 if default:
88 if default:
89 prompt += ' [%s]' % default
89 prompt += ' [%s]' % default
90 prompt += rest
90 prompt += rest
91 while True:
91 while True:
92 r = ui.prompt(prompt, default=default)
92 r = ui.prompt(prompt, default=default)
93 if r:
93 if r:
94 return r
94 return r
95 if default is not None:
95 if default is not None:
96 return default
96 return default
97 ui.warn(_('Please enter a valid value.\n'))
97 ui.warn(_('Please enter a valid value.\n'))
98
98
99 def cdiffstat(ui, summary, patchlines):
99 def cdiffstat(ui, summary, patchlines):
100 s = patch.diffstat(patchlines)
100 s = patch.diffstat(patchlines)
101 if summary:
101 if summary:
102 ui.write(summary, '\n')
102 ui.write(summary, '\n')
103 ui.write(s, '\n')
103 ui.write(s, '\n')
104 ans = prompt(ui, _('does the diffstat above look okay?'), 'y')
104 ans = prompt(ui, _('does the diffstat above look okay?'), 'y')
105 if not ans.lower().startswith('y'):
105 if not ans.lower().startswith('y'):
106 raise util.Abort(_('diffstat rejected'))
106 raise util.Abort(_('diffstat rejected'))
107 return s
107 return s
108
108
109 def introneeded(opts, number):
109 def introneeded(opts, number):
110 '''is an introductory message required?'''
110 '''is an introductory message required?'''
111 return number > 1 or opts.get('intro') or opts.get('desc')
111 return number > 1 or opts.get('intro') or opts.get('desc')
112
112
113 def makepatch(ui, repo, patch, opts, _charsets, idx, total, patchname=None):
113 def makepatch(ui, repo, patch, opts, _charsets, idx, total, patchname=None):
114
114
115 desc = []
115 desc = []
116 node = None
116 node = None
117 body = ''
117 body = ''
118
118
119 for line in patch:
119 for line in patch:
120 if line.startswith('#'):
120 if line.startswith('#'):
121 if line.startswith('# Node ID'):
121 if line.startswith('# Node ID'):
122 node = line.split()[-1]
122 node = line.split()[-1]
123 continue
123 continue
124 if line.startswith('diff -r') or line.startswith('diff --git'):
124 if line.startswith('diff -r') or line.startswith('diff --git'):
125 break
125 break
126 desc.append(line)
126 desc.append(line)
127
127
128 if not patchname and not node:
128 if not patchname and not node:
129 raise ValueError
129 raise ValueError
130
130
131 if opts.get('attach'):
131 if opts.get('attach'):
132 body = ('\n'.join(desc[1:]).strip() or
132 body = ('\n'.join(desc[1:]).strip() or
133 'Patch subject is complete summary.')
133 'Patch subject is complete summary.')
134 body += '\n\n\n'
134 body += '\n\n\n'
135
135
136 if opts.get('plain'):
136 if opts.get('plain'):
137 while patch and patch[0].startswith('# '):
137 while patch and patch[0].startswith('# '):
138 patch.pop(0)
138 patch.pop(0)
139 if patch:
139 if patch:
140 patch.pop(0)
140 patch.pop(0)
141 while patch and not patch[0].strip():
141 while patch and not patch[0].strip():
142 patch.pop(0)
142 patch.pop(0)
143
143
144 if opts.get('diffstat'):
144 if opts.get('diffstat'):
145 body += cdiffstat(ui, '\n'.join(desc), patch) + '\n\n'
145 body += cdiffstat(ui, '\n'.join(desc), patch) + '\n\n'
146
146
147 if opts.get('attach') or opts.get('inline'):
147 if opts.get('attach') or opts.get('inline'):
148 msg = email.MIMEMultipart.MIMEMultipart()
148 msg = email.MIMEMultipart.MIMEMultipart()
149 if body:
149 if body:
150 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
150 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
151 p = mail.mimetextpatch('\n'.join(patch), 'x-patch', opts.get('test'))
151 p = mail.mimetextpatch('\n'.join(patch), 'x-patch', opts.get('test'))
152 binnode = bin(node)
152 binnode = bin(node)
153 # if node is mq patch, it will have the patch file's name as a tag
153 # if node is mq patch, it will have the patch file's name as a tag
154 if not patchname:
154 if not patchname:
155 patchtags = [t for t in repo.nodetags(binnode)
155 patchtags = [t for t in repo.nodetags(binnode)
156 if t.endswith('.patch') or t.endswith('.diff')]
156 if t.endswith('.patch') or t.endswith('.diff')]
157 if patchtags:
157 if patchtags:
158 patchname = patchtags[0]
158 patchname = patchtags[0]
159 elif total > 1:
159 elif total > 1:
160 patchname = cmdutil.make_filename(repo, '%b-%n.patch',
160 patchname = cmdutil.make_filename(repo, '%b-%n.patch',
161 binnode, seqno=idx, total=total)
161 binnode, seqno=idx, total=total)
162 else:
162 else:
163 patchname = cmdutil.make_filename(repo, '%b.patch', binnode)
163 patchname = cmdutil.make_filename(repo, '%b.patch', binnode)
164 disposition = 'inline'
164 disposition = 'inline'
165 if opts.get('attach'):
165 if opts.get('attach'):
166 disposition = 'attachment'
166 disposition = 'attachment'
167 p['Content-Disposition'] = disposition + '; filename=' + patchname
167 p['Content-Disposition'] = disposition + '; filename=' + patchname
168 msg.attach(p)
168 msg.attach(p)
169 else:
169 else:
170 body += '\n'.join(patch)
170 body += '\n'.join(patch)
171 msg = mail.mimetextpatch(body, display=opts.get('test'))
171 msg = mail.mimetextpatch(body, display=opts.get('test'))
172
172
173 flag = ' '.join(opts.get('flag'))
173 flag = ' '.join(opts.get('flag'))
174 if flag:
174 if flag:
175 flag = ' ' + flag
175 flag = ' ' + flag
176
176
177 subj = desc[0].strip().rstrip('. ')
177 subj = desc[0].strip().rstrip('. ')
178 if not introneeded(opts, total):
178 if not introneeded(opts, total):
179 subj = '[PATCH%s] %s' % (flag, opts.get('subject') or subj)
179 subj = '[PATCH%s] %s' % (flag, opts.get('subject') or subj)
180 else:
180 else:
181 tlen = len(str(total))
181 tlen = len(str(total))
182 subj = '[PATCH %0*d of %d%s] %s' % (tlen, idx, total, flag, subj)
182 subj = '[PATCH %0*d of %d%s] %s' % (tlen, idx, total, flag, subj)
183 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
183 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
184 msg['X-Mercurial-Node'] = node
184 msg['X-Mercurial-Node'] = node
185 return msg, subj
185 return msg, subj
186
186
187 def patchbomb(ui, repo, *revs, **opts):
187 def patchbomb(ui, repo, *revs, **opts):
188 '''send changesets by email
188 '''send changesets by email
189
189
190 By default, diffs are sent in the format generated by
190 By default, diffs are sent in the format generated by
191 :hg:`export`, one per message. The series starts with a "[PATCH 0
191 :hg:`export`, one per message. The series starts with a "[PATCH 0
192 of N]" introduction, which describes the series as a whole.
192 of N]" introduction, which describes the series as a whole.
193
193
194 Each patch email has a Subject line of "[PATCH M of N] ...", using
194 Each patch email has a Subject line of "[PATCH M of N] ...", using
195 the first line of the changeset description as the subject text.
195 the first line of the changeset description as the subject text.
196 The message contains two or three parts. First, the changeset
196 The message contains two or three parts. First, the changeset
197 description. Next, (optionally) if the diffstat program is
197 description. Next, (optionally) if the diffstat program is
198 installed and -d/--diffstat is used, the result of running
198 installed and -d/--diffstat is used, the result of running
199 diffstat on the patch. Finally, the patch itself, as generated by
199 diffstat on the patch. Finally, the patch itself, as generated by
200 :hg:`export`.
200 :hg:`export`.
201
201
202 By default the patch is included as text in the email body for
202 By default the patch is included as text in the email body for
203 easy reviewing. Using the -a/--attach option will instead create
203 easy reviewing. Using the -a/--attach option will instead create
204 an attachment for the patch. With -i/--inline an inline attachment
204 an attachment for the patch. With -i/--inline an inline attachment
205 will be created.
205 will be created.
206
206
207 With -o/--outgoing, emails will be generated for patches not found
207 With -o/--outgoing, emails will be generated for patches not found
208 in the destination repository (or only those which are ancestors
208 in the destination repository (or only those which are ancestors
209 of the specified revisions if any are provided)
209 of the specified revisions if any are provided)
210
210
211 With -b/--bundle, changesets are selected as for --outgoing, but a
211 With -b/--bundle, changesets are selected as for --outgoing, but a
212 single email containing a binary Mercurial bundle as an attachment
212 single email containing a binary Mercurial bundle as an attachment
213 will be sent.
213 will be sent.
214
214
215 Examples::
215 Examples::
216
216
217 hg email -r 3000 # send patch 3000 only
217 hg email -r 3000 # send patch 3000 only
218 hg email -r 3000 -r 3001 # send patches 3000 and 3001
218 hg email -r 3000 -r 3001 # send patches 3000 and 3001
219 hg email -r 3000:3005 # send patches 3000 through 3005
219 hg email -r 3000:3005 # send patches 3000 through 3005
220 hg email 3000 # send patch 3000 (deprecated)
220 hg email 3000 # send patch 3000 (deprecated)
221
221
222 hg email -o # send all patches not in default
222 hg email -o # send all patches not in default
223 hg email -o DEST # send all patches not in DEST
223 hg email -o DEST # send all patches not in DEST
224 hg email -o -r 3000 # send all ancestors of 3000 not in default
224 hg email -o -r 3000 # send all ancestors of 3000 not in default
225 hg email -o -r 3000 DEST # send all ancestors of 3000 not in DEST
225 hg email -o -r 3000 DEST # send all ancestors of 3000 not in DEST
226
226
227 hg email -b # send bundle of all patches not in default
227 hg email -b # send bundle of all patches not in default
228 hg email -b DEST # send bundle of all patches not in DEST
228 hg email -b DEST # send bundle of all patches not in DEST
229 hg email -b -r 3000 # bundle of all ancestors of 3000 not in default
229 hg email -b -r 3000 # bundle of all ancestors of 3000 not in default
230 hg email -b -r 3000 DEST # bundle of all ancestors of 3000 not in DEST
230 hg email -b -r 3000 DEST # bundle of all ancestors of 3000 not in DEST
231
231
232 Before using this command, you will need to enable email in your
232 Before using this command, you will need to enable email in your
233 hgrc. See the [email] section in hgrc(5) for details.
233 hgrc. See the [email] section in hgrc(5) for details.
234 '''
234 '''
235
235
236 _charsets = mail._charsets(ui)
236 _charsets = mail._charsets(ui)
237
237
238 def outgoing(dest, revs):
238 def outgoing(dest, revs):
239 '''Return the revisions present locally but not in dest'''
239 '''Return the revisions present locally but not in dest'''
240 dest = ui.expandpath(dest or 'default-push', dest or 'default')
240 dest = ui.expandpath(dest or 'default-push', dest or 'default')
241 dest, branches = hg.parseurl(dest)
241 dest, branches = hg.parseurl(dest)
242 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
242 revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
243 if revs:
243 if revs:
244 revs = [repo.lookup(rev) for rev in revs]
244 revs = [repo.lookup(rev) for rev in revs]
245 other = hg.repository(hg.remoteui(repo, opts), dest)
245 other = hg.repository(hg.remoteui(repo, opts), dest)
246 ui.status(_('comparing with %s\n') % dest)
246 ui.status(_('comparing with %s\n') % dest)
247 o = discovery.findoutgoing(repo, other)
247 o = discovery.findoutgoing(repo, other)
248 if not o:
248 if not o:
249 ui.status(_("no changes found\n"))
249 ui.status(_("no changes found\n"))
250 return []
250 return []
251 o = repo.changelog.nodesbetween(o, revs)[0]
251 o = repo.changelog.nodesbetween(o, revs)[0]
252 return [str(repo.changelog.rev(r)) for r in o]
252 return [str(repo.changelog.rev(r)) for r in o]
253
253
254 def getpatches(revs):
254 def getpatches(revs):
255 for r in cmdutil.revrange(repo, revs):
255 for r in cmdutil.revrange(repo, revs):
256 output = cStringIO.StringIO()
256 output = cStringIO.StringIO()
257 cmdutil.export(repo, [r], fp=output,
257 cmdutil.export(repo, [r], fp=output,
258 opts=patch.diffopts(ui, opts))
258 opts=patch.diffopts(ui, opts))
259 yield output.getvalue().split('\n')
259 yield output.getvalue().split('\n')
260
260
261 def getbundle(dest):
261 def getbundle(dest):
262 tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-')
262 tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-')
263 tmpfn = os.path.join(tmpdir, 'bundle')
263 tmpfn = os.path.join(tmpdir, 'bundle')
264 try:
264 try:
265 commands.bundle(ui, repo, tmpfn, dest, **opts)
265 commands.bundle(ui, repo, tmpfn, dest, **opts)
266 return open(tmpfn, 'rb').read()
266 return open(tmpfn, 'rb').read()
267 finally:
267 finally:
268 try:
268 try:
269 os.unlink(tmpfn)
269 os.unlink(tmpfn)
270 except:
270 except:
271 pass
271 pass
272 os.rmdir(tmpdir)
272 os.rmdir(tmpdir)
273
273
274 if not (opts.get('test') or opts.get('mbox')):
274 if not (opts.get('test') or opts.get('mbox')):
275 # really sending
275 # really sending
276 mail.validateconfig(ui)
276 mail.validateconfig(ui)
277
277
278 if not (revs or opts.get('rev')
278 if not (revs or opts.get('rev')
279 or opts.get('outgoing') or opts.get('bundle')
279 or opts.get('outgoing') or opts.get('bundle')
280 or opts.get('patches')):
280 or opts.get('patches')):
281 raise util.Abort(_('specify at least one changeset with -r or -o'))
281 raise util.Abort(_('specify at least one changeset with -r or -o'))
282
282
283 if opts.get('outgoing') and opts.get('bundle'):
283 if opts.get('outgoing') and opts.get('bundle'):
284 raise util.Abort(_("--outgoing mode always on with --bundle;"
284 raise util.Abort(_("--outgoing mode always on with --bundle;"
285 " do not re-specify --outgoing"))
285 " do not re-specify --outgoing"))
286
286
287 if opts.get('outgoing') or opts.get('bundle'):
287 if opts.get('outgoing') or opts.get('bundle'):
288 if len(revs) > 1:
288 if len(revs) > 1:
289 raise util.Abort(_("too many destinations"))
289 raise util.Abort(_("too many destinations"))
290 dest = revs and revs[0] or None
290 dest = revs and revs[0] or None
291 revs = []
291 revs = []
292
292
293 if opts.get('rev'):
293 if opts.get('rev'):
294 if revs:
294 if revs:
295 raise util.Abort(_('use only one form to specify the revision'))
295 raise util.Abort(_('use only one form to specify the revision'))
296 revs = opts.get('rev')
296 revs = opts.get('rev')
297
297
298 if opts.get('outgoing'):
298 if opts.get('outgoing'):
299 revs = outgoing(dest, opts.get('rev'))
299 revs = outgoing(dest, opts.get('rev'))
300 if opts.get('bundle'):
300 if opts.get('bundle'):
301 opts['revs'] = revs
301 opts['revs'] = revs
302
302
303 # start
303 # start
304 if opts.get('date'):
304 if opts.get('date'):
305 start_time = util.parsedate(opts.get('date'))
305 start_time = util.parsedate(opts.get('date'))
306 else:
306 else:
307 start_time = util.makedate()
307 start_time = util.makedate()
308
308
309 def genmsgid(id):
309 def genmsgid(id):
310 return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())
310 return '<%s.%s@%s>' % (id[:20], int(start_time[0]), socket.getfqdn())
311
311
312 def getdescription(body, sender):
312 def getdescription(body, sender):
313 if opts.get('desc'):
313 if opts.get('desc'):
314 body = open(opts.get('desc')).read()
314 body = open(opts.get('desc')).read()
315 else:
315 else:
316 ui.write(_('\nWrite the introductory message for the '
316 ui.write(_('\nWrite the introductory message for the '
317 'patch series.\n\n'))
317 'patch series.\n\n'))
318 body = ui.edit(body, sender)
318 body = ui.edit(body, sender)
319 return body
319 return body
320
320
321 def getpatchmsgs(patches, patchnames=None):
321 def getpatchmsgs(patches, patchnames=None):
322 jumbo = []
322 jumbo = []
323 msgs = []
323 msgs = []
324
324
325 ui.write(_('This patch series consists of %d patches.\n\n')
325 ui.write(_('This patch series consists of %d patches.\n\n')
326 % len(patches))
326 % len(patches))
327
327
328 name = None
328 name = None
329 for i, p in enumerate(patches):
329 for i, p in enumerate(patches):
330 jumbo.extend(p)
330 jumbo.extend(p)
331 if patchnames:
331 if patchnames:
332 name = patchnames[i]
332 name = patchnames[i]
333 msg = makepatch(ui, repo, p, opts, _charsets, i + 1,
333 msg = makepatch(ui, repo, p, opts, _charsets, i + 1,
334 len(patches), name)
334 len(patches), name)
335 msgs.append(msg)
335 msgs.append(msg)
336
336
337 if introneeded(opts, len(patches)):
337 if introneeded(opts, len(patches)):
338 tlen = len(str(len(patches)))
338 tlen = len(str(len(patches)))
339
339
340 flag = ' '.join(opts.get('flag'))
340 flag = ' '.join(opts.get('flag'))
341 if flag:
341 if flag:
342 subj = '[PATCH %0*d of %d %s]' % (tlen, 0, len(patches), flag)
342 subj = '[PATCH %0*d of %d %s]' % (tlen, 0, len(patches), flag)
343 else:
343 else:
344 subj = '[PATCH %0*d of %d]' % (tlen, 0, len(patches))
344 subj = '[PATCH %0*d of %d]' % (tlen, 0, len(patches))
345 subj += ' ' + (opts.get('subject') or
345 subj += ' ' + (opts.get('subject') or
346 prompt(ui, 'Subject: ', rest=subj))
346 prompt(ui, 'Subject: ', rest=subj))
347
347
348 body = ''
348 body = ''
349 if opts.get('diffstat'):
349 if opts.get('diffstat'):
350 d = cdiffstat(ui, _('Final summary:\n'), jumbo)
350 d = cdiffstat(ui, _('Final summary:\n'), jumbo)
351 if d:
351 if d:
352 body = '\n' + d
352 body = '\n' + d
353
353
354 body = getdescription(body, sender)
354 body = getdescription(body, sender)
355 msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
355 msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
356 msg['Subject'] = mail.headencode(ui, subj, _charsets,
356 msg['Subject'] = mail.headencode(ui, subj, _charsets,
357 opts.get('test'))
357 opts.get('test'))
358
358
359 msgs.insert(0, (msg, subj))
359 msgs.insert(0, (msg, subj))
360 return msgs
360 return msgs
361
361
362 def getbundlemsgs(bundle):
362 def getbundlemsgs(bundle):
363 subj = (opts.get('subject')
363 subj = (opts.get('subject')
364 or prompt(ui, 'Subject:', 'A bundle for your repository'))
364 or prompt(ui, 'Subject:', 'A bundle for your repository'))
365
365
366 body = getdescription('', sender)
366 body = getdescription('', sender)
367 msg = email.MIMEMultipart.MIMEMultipart()
367 msg = email.MIMEMultipart.MIMEMultipart()
368 if body:
368 if body:
369 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
369 msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
370 datapart = email.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
370 datapart = email.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
371 datapart.set_payload(bundle)
371 datapart.set_payload(bundle)
372 bundlename = '%s.hg' % opts.get('bundlename', 'bundle')
372 bundlename = '%s.hg' % opts.get('bundlename', 'bundle')
373 datapart.add_header('Content-Disposition', 'attachment',
373 datapart.add_header('Content-Disposition', 'attachment',
374 filename=bundlename)
374 filename=bundlename)
375 email.Encoders.encode_base64(datapart)
375 email.Encoders.encode_base64(datapart)
376 msg.attach(datapart)
376 msg.attach(datapart)
377 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
377 msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
378 return [(msg, subj)]
378 return [(msg, subj)]
379
379
380 sender = (opts.get('from') or ui.config('email', 'from') or
380 sender = (opts.get('from') or ui.config('email', 'from') or
381 ui.config('patchbomb', 'from') or
381 ui.config('patchbomb', 'from') or
382 prompt(ui, 'From', ui.username()))
382 prompt(ui, 'From', ui.username()))
383
383
384 # internal option used by pbranches
384 # internal option used by pbranches
385 patches = opts.get('patches')
385 patches = opts.get('patches')
386 if patches:
386 if patches:
387 msgs = getpatchmsgs(patches, opts.get('patchnames'))
387 msgs = getpatchmsgs(patches, opts.get('patchnames'))
388 elif opts.get('bundle'):
388 elif opts.get('bundle'):
389 msgs = getbundlemsgs(getbundle(dest))
389 msgs = getbundlemsgs(getbundle(dest))
390 else:
390 else:
391 msgs = getpatchmsgs(list(getpatches(revs)))
391 msgs = getpatchmsgs(list(getpatches(revs)))
392
392
393 def getaddrs(opt, prpt=None, default=None):
393 def getaddrs(opt, prpt=None, default=None):
394 addrs = opts.get(opt.replace('-', '_'))
394 addrs = opts.get(opt.replace('-', '_'))
395 if addrs:
395 if addrs:
396 return mail.addrlistencode(ui, addrs, _charsets,
396 return mail.addrlistencode(ui, addrs, _charsets,
397 opts.get('test'))
397 opts.get('test'))
398
398
399 addrs = (ui.config('email', opt) or
399 addrs = (ui.config('email', opt) or
400 ui.config('patchbomb', opt) or '')
400 ui.config('patchbomb', opt) or '')
401 if not addrs and prpt:
401 if not addrs and prpt:
402 addrs = prompt(ui, prpt, default)
402 addrs = prompt(ui, prpt, default)
403
403
404 return mail.addrlistencode(ui, [addrs], _charsets, opts.get('test'))
404 return mail.addrlistencode(ui, [addrs], _charsets, opts.get('test'))
405
405
406 to = getaddrs('to', 'To')
406 to = getaddrs('to', 'To')
407 cc = getaddrs('cc', 'Cc', '')
407 cc = getaddrs('cc', 'Cc', '')
408 bcc = getaddrs('bcc')
408 bcc = getaddrs('bcc')
409 replyto = getaddrs('reply-to')
409 replyto = getaddrs('reply-to')
410
410
411 ui.write('\n')
411 ui.write('\n')
412
412
413 parent = opts.get('in_reply_to') or None
413 parent = opts.get('in_reply_to') or None
414 # angle brackets may be omitted, they're not semantically part of the msg-id
414 # angle brackets may be omitted, they're not semantically part of the msg-id
415 if parent is not None:
415 if parent is not None:
416 if not parent.startswith('<'):
416 if not parent.startswith('<'):
417 parent = '<' + parent
417 parent = '<' + parent
418 if not parent.endswith('>'):
418 if not parent.endswith('>'):
419 parent += '>'
419 parent += '>'
420
420
421 first = True
421 first = True
422
422
423 sender_addr = email.Utils.parseaddr(sender)[1]
423 sender_addr = email.Utils.parseaddr(sender)[1]
424 sender = mail.addressencode(ui, sender, _charsets, opts.get('test'))
424 sender = mail.addressencode(ui, sender, _charsets, opts.get('test'))
425 sendmail = None
425 sendmail = None
426 for m, subj in msgs:
426 for m, subj in msgs:
427 try:
427 try:
428 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
428 m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
429 except TypeError:
429 except TypeError:
430 m['Message-Id'] = genmsgid('patchbomb')
430 m['Message-Id'] = genmsgid('patchbomb')
431 if parent:
431 if parent:
432 m['In-Reply-To'] = parent
432 m['In-Reply-To'] = parent
433 m['References'] = parent
433 m['References'] = parent
434 if first:
434 if first:
435 parent = m['Message-Id']
435 parent = m['Message-Id']
436 first = False
436 first = False
437
437
438 m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version()
438 m['User-Agent'] = 'Mercurial-patchbomb/%s' % util.version()
439 m['Date'] = email.Utils.formatdate(start_time[0], localtime=True)
439 m['Date'] = email.Utils.formatdate(start_time[0], localtime=True)
440
440
441 start_time = (start_time[0] + 1, start_time[1])
441 start_time = (start_time[0] + 1, start_time[1])
442 m['From'] = sender
442 m['From'] = sender
443 m['To'] = ', '.join(to)
443 m['To'] = ', '.join(to)
444 if cc:
444 if cc:
445 m['Cc'] = ', '.join(cc)
445 m['Cc'] = ', '.join(cc)
446 if bcc:
446 if bcc:
447 m['Bcc'] = ', '.join(bcc)
447 m['Bcc'] = ', '.join(bcc)
448 if replyto:
448 if replyto:
449 m['Reply-To'] = ', '.join(replyto)
449 m['Reply-To'] = ', '.join(replyto)
450 if opts.get('test'):
450 if opts.get('test'):
451 ui.status(_('Displaying '), subj, ' ...\n')
451 ui.status(_('Displaying '), subj, ' ...\n')
452 ui.flush()
452 ui.flush()
453 if 'PAGER' in os.environ and not ui.plain():
453 if 'PAGER' in os.environ and not ui.plain():
454 fp = util.popen(os.environ['PAGER'], 'w')
454 fp = util.popen(os.environ['PAGER'], 'w')
455 else:
455 else:
456 fp = ui
456 fp = ui
457 generator = email.Generator.Generator(fp, mangle_from_=False)
457 generator = email.Generator.Generator(fp, mangle_from_=False)
458 try:
458 try:
459 generator.flatten(m, 0)
459 generator.flatten(m, 0)
460 fp.write('\n')
460 fp.write('\n')
461 except IOError, inst:
461 except IOError, inst:
462 if inst.errno != errno.EPIPE:
462 if inst.errno != errno.EPIPE:
463 raise
463 raise
464 if fp is not ui:
464 if fp is not ui:
465 fp.close()
465 fp.close()
466 elif opts.get('mbox'):
466 elif opts.get('mbox'):
467 ui.status(_('Writing '), subj, ' ...\n')
467 ui.status(_('Writing '), subj, ' ...\n')
468 fp = open(opts.get('mbox'), 'In-Reply-To' in m and 'ab+' or 'wb+')
468 fp = open(opts.get('mbox'), 'In-Reply-To' in m and 'ab+' or 'wb+')
469 generator = email.Generator.Generator(fp, mangle_from_=True)
469 generator = email.Generator.Generator(fp, mangle_from_=True)
470 # Should be time.asctime(), but Windows prints 2-characters day
470 # Should be time.asctime(), but Windows prints 2-characters day
471 # of month instead of one. Make them print the same thing.
471 # of month instead of one. Make them print the same thing.
472 date = time.strftime('%a %b %d %H:%M:%S %Y',
472 date = time.strftime('%a %b %d %H:%M:%S %Y',
473 time.localtime(start_time[0]))
473 time.localtime(start_time[0]))
474 fp.write('From %s %s\n' % (sender_addr, date))
474 fp.write('From %s %s\n' % (sender_addr, date))
475 generator.flatten(m, 0)
475 generator.flatten(m, 0)
476 fp.write('\n\n')
476 fp.write('\n\n')
477 fp.close()
477 fp.close()
478 else:
478 else:
479 if not sendmail:
479 if not sendmail:
480 sendmail = mail.connect(ui)
480 sendmail = mail.connect(ui)
481 ui.status(_('Sending '), subj, ' ...\n')
481 ui.status(_('Sending '), subj, ' ...\n')
482 # Exim does not remove the Bcc field
482 # Exim does not remove the Bcc field
483 del m['Bcc']
483 del m['Bcc']
484 fp = cStringIO.StringIO()
484 fp = cStringIO.StringIO()
485 generator = email.Generator.Generator(fp, mangle_from_=False)
485 generator = email.Generator.Generator(fp, mangle_from_=False)
486 generator.flatten(m, 0)
486 generator.flatten(m, 0)
487 sendmail(sender, to + bcc + cc, fp.getvalue())
487 sendmail(sender, to + bcc + cc, fp.getvalue())
488
488
489 emailopts = [
489 emailopts = [
490 ('a', 'attach', None, _('send patches as attachments')),
490 ('a', 'attach', None, _('send patches as attachments')),
491 ('i', 'inline', None, _('send patches as inline attachments')),
491 ('i', 'inline', None, _('send patches as inline attachments')),
492 ('', 'bcc', [], _('email addresses of blind carbon copy recipients')),
492 ('', 'bcc', [], _('email addresses of blind carbon copy recipients')),
493 ('c', 'cc', [], _('email addresses of copy recipients')),
493 ('c', 'cc', [], _('email addresses of copy recipients')),
494 ('d', 'diffstat', None, _('add diffstat output to messages')),
494 ('d', 'diffstat', None, _('add diffstat output to messages')),
495 ('', 'date', '', _('use the given date as the sending date')),
495 ('', 'date', '', _('use the given date as the sending date')),
496 ('', 'desc', '', _('use the given file as the series description')),
496 ('', 'desc', '', _('use the given file as the series description')),
497 ('f', 'from', '', _('email address of sender')),
497 ('f', 'from', '', _('email address of sender')),
498 ('n', 'test', None, _('print messages that would be sent')),
498 ('n', 'test', None, _('print messages that would be sent')),
499 ('m', 'mbox', '',
499 ('m', 'mbox', '',
500 _('write messages to mbox file instead of sending them')),
500 _('write messages to mbox file instead of sending them')),
501 ('', 'reply-to', [], _('email addresses replies should be sent to')),
501 ('', 'reply-to', [], _('email addresses replies should be sent to')),
502 ('s', 'subject', '',
502 ('s', 'subject', '',
503 _('subject of first message (intro or single patch)')),
503 _('subject of first message (intro or single patch)')),
504 ('', 'in-reply-to', '',
504 ('', 'in-reply-to', '',
505 _('message identifier to reply to')),
505 _('message identifier to reply to')),
506 ('', 'flag', [], _('flags to add in subject prefixes')),
506 ('', 'flag', [], _('flags to add in subject prefixes')),
507 ('t', 'to', [], _('email addresses of recipients')),
507 ('t', 'to', [], _('email addresses of recipients')),
508 ]
508 ]
509
509
510
510
511 cmdtable = {
511 cmdtable = {
512 "email":
512 "email":
513 (patchbomb,
513 (patchbomb,
514 [('g', 'git', None, _('use git extended diff format')),
514 [('g', 'git', None, _('use git extended diff format')),
515 ('', 'plain', None, _('omit hg patch header')),
515 ('', 'plain', None, _('omit hg patch header')),
516 ('o', 'outgoing', None,
516 ('o', 'outgoing', None,
517 _('send changes not found in the target repository')),
517 _('send changes not found in the target repository')),
518 ('b', 'bundle', None,
518 ('b', 'bundle', None,
519 _('send changes not in target as a binary bundle')),
519 _('send changes not in target as a binary bundle')),
520 ('', 'bundlename', 'bundle',
520 ('', 'bundlename', 'bundle',
521 _('name of the bundle attachment file')),
521 _('name of the bundle attachment file'), _('NAME')),
522 ('r', 'rev', [], _('a revision to send')),
522 ('r', 'rev', [],
523 _('a revision to send'), _('REV')),
523 ('', 'force', None,
524 ('', 'force', None,
524 _('run even when remote repository is unrelated '
525 _('run even when remote repository is unrelated '
525 '(with -b/--bundle)')),
526 '(with -b/--bundle)')),
526 ('', 'base', [],
527 ('', 'base', [],
527 _('a base changeset to specify instead of a destination '
528 _('a base changeset to specify instead of a destination '
528 '(with -b/--bundle)')),
529 '(with -b/--bundle)'),
530 _('REV')),
529 ('', 'intro', None,
531 ('', 'intro', None,
530 _('send an introduction email for a single patch')),
532 _('send an introduction email for a single patch')),
531 ] + emailopts + commands.remoteopts,
533 ] + emailopts + commands.remoteopts,
532 _('hg email [OPTION]... [DEST]...'))
534 _('hg email [OPTION]... [DEST]...'))
533 }
535 }
@@ -1,555 +1,559 b''
1 # rebase.py - rebasing feature for mercurial
1 # rebase.py - rebasing feature for mercurial
2 #
2 #
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to move sets of revisions to a different ancestor
8 '''command to move sets of revisions to a different ancestor
9
9
10 This extension lets you rebase changesets in an existing Mercurial
10 This extension lets you rebase changesets in an existing Mercurial
11 repository.
11 repository.
12
12
13 For more information:
13 For more information:
14 http://mercurial.selenic.com/wiki/RebaseExtension
14 http://mercurial.selenic.com/wiki/RebaseExtension
15 '''
15 '''
16
16
17 from mercurial import hg, util, repair, merge, cmdutil, commands, error
17 from mercurial import hg, util, repair, merge, cmdutil, commands, error
18 from mercurial import extensions, ancestor, copies, patch
18 from mercurial import extensions, ancestor, copies, patch
19 from mercurial.commands import templateopts
19 from mercurial.commands import templateopts
20 from mercurial.node import nullrev
20 from mercurial.node import nullrev
21 from mercurial.lock import release
21 from mercurial.lock import release
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23 import os, errno
23 import os, errno
24
24
25 nullmerge = -2
25 nullmerge = -2
26
26
27 def rebase(ui, repo, **opts):
27 def rebase(ui, repo, **opts):
28 """move changeset (and descendants) to a different branch
28 """move changeset (and descendants) to a different branch
29
29
30 Rebase uses repeated merging to graft changesets from one part of
30 Rebase uses repeated merging to graft changesets from one part of
31 history (the source) onto another (the destination). This can be
31 history (the source) onto another (the destination). This can be
32 useful for linearizing *local* changes relative to a master
32 useful for linearizing *local* changes relative to a master
33 development tree.
33 development tree.
34
34
35 You should not rebase changesets that have already been shared
35 You should not rebase changesets that have already been shared
36 with others. Doing so will force everybody else to perform the
36 with others. Doing so will force everybody else to perform the
37 same rebase or they will end up with duplicated changesets after
37 same rebase or they will end up with duplicated changesets after
38 pulling in your rebased changesets.
38 pulling in your rebased changesets.
39
39
40 If you don't specify a destination changeset (``-d/--dest``),
40 If you don't specify a destination changeset (``-d/--dest``),
41 rebase uses the tipmost head of the current named branch as the
41 rebase uses the tipmost head of the current named branch as the
42 destination. (The destination changeset is not modified by
42 destination. (The destination changeset is not modified by
43 rebasing, but new changesets are added as its descendants.)
43 rebasing, but new changesets are added as its descendants.)
44
44
45 You can specify which changesets to rebase in two ways: as a
45 You can specify which changesets to rebase in two ways: as a
46 "source" changeset or as a "base" changeset. Both are shorthand
46 "source" changeset or as a "base" changeset. Both are shorthand
47 for a topologically related set of changesets (the "source
47 for a topologically related set of changesets (the "source
48 branch"). If you specify source (``-s/--source``), rebase will
48 branch"). If you specify source (``-s/--source``), rebase will
49 rebase that changeset and all of its descendants onto dest. If you
49 rebase that changeset and all of its descendants onto dest. If you
50 specify base (``-b/--base``), rebase will select ancestors of base
50 specify base (``-b/--base``), rebase will select ancestors of base
51 back to but not including the common ancestor with dest. Thus,
51 back to but not including the common ancestor with dest. Thus,
52 ``-b`` is less precise but more convenient than ``-s``: you can
52 ``-b`` is less precise but more convenient than ``-s``: you can
53 specify any changeset in the source branch, and rebase will select
53 specify any changeset in the source branch, and rebase will select
54 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
54 the whole branch. If you specify neither ``-s`` nor ``-b``, rebase
55 uses the parent of the working directory as the base.
55 uses the parent of the working directory as the base.
56
56
57 By default, rebase recreates the changesets in the source branch
57 By default, rebase recreates the changesets in the source branch
58 as descendants of dest and then destroys the originals. Use
58 as descendants of dest and then destroys the originals. Use
59 ``--keep`` to preserve the original source changesets. Some
59 ``--keep`` to preserve the original source changesets. Some
60 changesets in the source branch (e.g. merges from the destination
60 changesets in the source branch (e.g. merges from the destination
61 branch) may be dropped if they no longer contribute any change.
61 branch) may be dropped if they no longer contribute any change.
62
62
63 One result of the rules for selecting the destination changeset
63 One result of the rules for selecting the destination changeset
64 and source branch is that, unlike ``merge``, rebase will do
64 and source branch is that, unlike ``merge``, rebase will do
65 nothing if you are at the latest (tipmost) head of a named branch
65 nothing if you are at the latest (tipmost) head of a named branch
66 with two heads. You need to explicitly specify source and/or
66 with two heads. You need to explicitly specify source and/or
67 destination (or ``update`` to the other head, if it's the head of
67 destination (or ``update`` to the other head, if it's the head of
68 the intended source branch).
68 the intended source branch).
69
69
70 If a rebase is interrupted to manually resolve a merge, it can be
70 If a rebase is interrupted to manually resolve a merge, it can be
71 continued with --continue/-c or aborted with --abort/-a.
71 continued with --continue/-c or aborted with --abort/-a.
72
72
73 Returns 0 on success, 1 if nothing to rebase.
73 Returns 0 on success, 1 if nothing to rebase.
74 """
74 """
75 originalwd = target = None
75 originalwd = target = None
76 external = nullrev
76 external = nullrev
77 state = {}
77 state = {}
78 skipped = set()
78 skipped = set()
79 targetancestors = set()
79 targetancestors = set()
80
80
81 lock = wlock = None
81 lock = wlock = None
82 try:
82 try:
83 lock = repo.lock()
83 lock = repo.lock()
84 wlock = repo.wlock()
84 wlock = repo.wlock()
85
85
86 # Validate input and define rebasing points
86 # Validate input and define rebasing points
87 destf = opts.get('dest', None)
87 destf = opts.get('dest', None)
88 srcf = opts.get('source', None)
88 srcf = opts.get('source', None)
89 basef = opts.get('base', None)
89 basef = opts.get('base', None)
90 contf = opts.get('continue')
90 contf = opts.get('continue')
91 abortf = opts.get('abort')
91 abortf = opts.get('abort')
92 collapsef = opts.get('collapse', False)
92 collapsef = opts.get('collapse', False)
93 extrafn = opts.get('extrafn')
93 extrafn = opts.get('extrafn')
94 keepf = opts.get('keep', False)
94 keepf = opts.get('keep', False)
95 keepbranchesf = opts.get('keepbranches', False)
95 keepbranchesf = opts.get('keepbranches', False)
96 detachf = opts.get('detach', False)
96 detachf = opts.get('detach', False)
97 # keepopen is not meant for use on the command line, but by
97 # keepopen is not meant for use on the command line, but by
98 # other extensions
98 # other extensions
99 keepopen = opts.get('keepopen', False)
99 keepopen = opts.get('keepopen', False)
100
100
101 if contf or abortf:
101 if contf or abortf:
102 if contf and abortf:
102 if contf and abortf:
103 raise util.Abort(_('cannot use both abort and continue'))
103 raise util.Abort(_('cannot use both abort and continue'))
104 if collapsef:
104 if collapsef:
105 raise util.Abort(
105 raise util.Abort(
106 _('cannot use collapse with continue or abort'))
106 _('cannot use collapse with continue or abort'))
107 if detachf:
107 if detachf:
108 raise util.Abort(_('cannot use detach with continue or abort'))
108 raise util.Abort(_('cannot use detach with continue or abort'))
109 if srcf or basef or destf:
109 if srcf or basef or destf:
110 raise util.Abort(
110 raise util.Abort(
111 _('abort and continue do not allow specifying revisions'))
111 _('abort and continue do not allow specifying revisions'))
112
112
113 (originalwd, target, state, collapsef, keepf,
113 (originalwd, target, state, collapsef, keepf,
114 keepbranchesf, external) = restorestatus(repo)
114 keepbranchesf, external) = restorestatus(repo)
115 if abortf:
115 if abortf:
116 return abort(repo, originalwd, target, state)
116 return abort(repo, originalwd, target, state)
117 else:
117 else:
118 if srcf and basef:
118 if srcf and basef:
119 raise util.Abort(_('cannot specify both a '
119 raise util.Abort(_('cannot specify both a '
120 'revision and a base'))
120 'revision and a base'))
121 if detachf:
121 if detachf:
122 if not srcf:
122 if not srcf:
123 raise util.Abort(
123 raise util.Abort(
124 _('detach requires a revision to be specified'))
124 _('detach requires a revision to be specified'))
125 if basef:
125 if basef:
126 raise util.Abort(_('cannot specify a base with detach'))
126 raise util.Abort(_('cannot specify a base with detach'))
127
127
128 cmdutil.bail_if_changed(repo)
128 cmdutil.bail_if_changed(repo)
129 result = buildstate(repo, destf, srcf, basef, detachf)
129 result = buildstate(repo, destf, srcf, basef, detachf)
130 if not result:
130 if not result:
131 # Empty state built, nothing to rebase
131 # Empty state built, nothing to rebase
132 ui.status(_('nothing to rebase\n'))
132 ui.status(_('nothing to rebase\n'))
133 return 1
133 return 1
134 else:
134 else:
135 originalwd, target, state = result
135 originalwd, target, state = result
136 if collapsef:
136 if collapsef:
137 targetancestors = set(repo.changelog.ancestors(target))
137 targetancestors = set(repo.changelog.ancestors(target))
138 external = checkexternal(repo, state, targetancestors)
138 external = checkexternal(repo, state, targetancestors)
139
139
140 if keepbranchesf:
140 if keepbranchesf:
141 if extrafn:
141 if extrafn:
142 raise util.Abort(_('cannot use both keepbranches and extrafn'))
142 raise util.Abort(_('cannot use both keepbranches and extrafn'))
143 def extrafn(ctx, extra):
143 def extrafn(ctx, extra):
144 extra['branch'] = ctx.branch()
144 extra['branch'] = ctx.branch()
145
145
146 # Rebase
146 # Rebase
147 if not targetancestors:
147 if not targetancestors:
148 targetancestors = set(repo.changelog.ancestors(target))
148 targetancestors = set(repo.changelog.ancestors(target))
149 targetancestors.add(target)
149 targetancestors.add(target)
150
150
151 for rev in sorted(state):
151 for rev in sorted(state):
152 if state[rev] == -1:
152 if state[rev] == -1:
153 ui.debug("rebasing %d:%s\n" % (rev, repo[rev]))
153 ui.debug("rebasing %d:%s\n" % (rev, repo[rev]))
154 storestatus(repo, originalwd, target, state, collapsef, keepf,
154 storestatus(repo, originalwd, target, state, collapsef, keepf,
155 keepbranchesf, external)
155 keepbranchesf, external)
156 p1, p2 = defineparents(repo, rev, target, state,
156 p1, p2 = defineparents(repo, rev, target, state,
157 targetancestors)
157 targetancestors)
158 if len(repo.parents()) == 2:
158 if len(repo.parents()) == 2:
159 repo.ui.debug('resuming interrupted rebase\n')
159 repo.ui.debug('resuming interrupted rebase\n')
160 else:
160 else:
161 stats = rebasenode(repo, rev, p1, p2, state)
161 stats = rebasenode(repo, rev, p1, p2, state)
162 if stats and stats[3] > 0:
162 if stats and stats[3] > 0:
163 raise util.Abort(_('fix unresolved conflicts with hg '
163 raise util.Abort(_('fix unresolved conflicts with hg '
164 'resolve then run hg rebase --continue'))
164 'resolve then run hg rebase --continue'))
165 updatedirstate(repo, rev, target, p2)
165 updatedirstate(repo, rev, target, p2)
166 if not collapsef:
166 if not collapsef:
167 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn)
167 newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn)
168 else:
168 else:
169 # Skip commit if we are collapsing
169 # Skip commit if we are collapsing
170 repo.dirstate.setparents(repo[p1].node())
170 repo.dirstate.setparents(repo[p1].node())
171 newrev = None
171 newrev = None
172 # Update the state
172 # Update the state
173 if newrev is not None:
173 if newrev is not None:
174 state[rev] = repo[newrev].rev()
174 state[rev] = repo[newrev].rev()
175 else:
175 else:
176 if not collapsef:
176 if not collapsef:
177 ui.note(_('no changes, revision %d skipped\n') % rev)
177 ui.note(_('no changes, revision %d skipped\n') % rev)
178 ui.debug('next revision set to %s\n' % p1)
178 ui.debug('next revision set to %s\n' % p1)
179 skipped.add(rev)
179 skipped.add(rev)
180 state[rev] = p1
180 state[rev] = p1
181
181
182 ui.note(_('rebase merging completed\n'))
182 ui.note(_('rebase merging completed\n'))
183
183
184 if collapsef and not keepopen:
184 if collapsef and not keepopen:
185 p1, p2 = defineparents(repo, min(state), target,
185 p1, p2 = defineparents(repo, min(state), target,
186 state, targetancestors)
186 state, targetancestors)
187 commitmsg = 'Collapsed revision'
187 commitmsg = 'Collapsed revision'
188 for rebased in state:
188 for rebased in state:
189 if rebased not in skipped and state[rebased] != nullmerge:
189 if rebased not in skipped and state[rebased] != nullmerge:
190 commitmsg += '\n* %s' % repo[rebased].description()
190 commitmsg += '\n* %s' % repo[rebased].description()
191 commitmsg = ui.edit(commitmsg, repo.ui.username())
191 commitmsg = ui.edit(commitmsg, repo.ui.username())
192 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
192 newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
193 extrafn=extrafn)
193 extrafn=extrafn)
194
194
195 if 'qtip' in repo.tags():
195 if 'qtip' in repo.tags():
196 updatemq(repo, state, skipped, **opts)
196 updatemq(repo, state, skipped, **opts)
197
197
198 if not keepf:
198 if not keepf:
199 # Remove no more useful revisions
199 # Remove no more useful revisions
200 rebased = [rev for rev in state if state[rev] != nullmerge]
200 rebased = [rev for rev in state if state[rev] != nullmerge]
201 if rebased:
201 if rebased:
202 if set(repo.changelog.descendants(min(rebased))) - set(state):
202 if set(repo.changelog.descendants(min(rebased))) - set(state):
203 ui.warn(_("warning: new changesets detected "
203 ui.warn(_("warning: new changesets detected "
204 "on source branch, not stripping\n"))
204 "on source branch, not stripping\n"))
205 else:
205 else:
206 # backup the old csets by default
206 # backup the old csets by default
207 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
207 repair.strip(ui, repo, repo[min(rebased)].node(), "all")
208
208
209 clearstatus(repo)
209 clearstatus(repo)
210 ui.note(_("rebase completed\n"))
210 ui.note(_("rebase completed\n"))
211 if os.path.exists(repo.sjoin('undo')):
211 if os.path.exists(repo.sjoin('undo')):
212 util.unlink(repo.sjoin('undo'))
212 util.unlink(repo.sjoin('undo'))
213 if skipped:
213 if skipped:
214 ui.note(_("%d revisions have been skipped\n") % len(skipped))
214 ui.note(_("%d revisions have been skipped\n") % len(skipped))
215 finally:
215 finally:
216 release(lock, wlock)
216 release(lock, wlock)
217
217
218 def rebasemerge(repo, rev, first=False):
218 def rebasemerge(repo, rev, first=False):
219 'return the correct ancestor'
219 'return the correct ancestor'
220 oldancestor = ancestor.ancestor
220 oldancestor = ancestor.ancestor
221
221
222 def newancestor(a, b, pfunc):
222 def newancestor(a, b, pfunc):
223 if b == rev:
223 if b == rev:
224 return repo[rev].parents()[0].rev()
224 return repo[rev].parents()[0].rev()
225 return oldancestor(a, b, pfunc)
225 return oldancestor(a, b, pfunc)
226
226
227 if not first:
227 if not first:
228 ancestor.ancestor = newancestor
228 ancestor.ancestor = newancestor
229 else:
229 else:
230 repo.ui.debug("first revision, do not change ancestor\n")
230 repo.ui.debug("first revision, do not change ancestor\n")
231 try:
231 try:
232 stats = merge.update(repo, rev, True, True, False)
232 stats = merge.update(repo, rev, True, True, False)
233 return stats
233 return stats
234 finally:
234 finally:
235 ancestor.ancestor = oldancestor
235 ancestor.ancestor = oldancestor
236
236
237 def checkexternal(repo, state, targetancestors):
237 def checkexternal(repo, state, targetancestors):
238 """Check whether one or more external revisions need to be taken in
238 """Check whether one or more external revisions need to be taken in
239 consideration. In the latter case, abort.
239 consideration. In the latter case, abort.
240 """
240 """
241 external = nullrev
241 external = nullrev
242 source = min(state)
242 source = min(state)
243 for rev in state:
243 for rev in state:
244 if rev == source:
244 if rev == source:
245 continue
245 continue
246 # Check externals and fail if there are more than one
246 # Check externals and fail if there are more than one
247 for p in repo[rev].parents():
247 for p in repo[rev].parents():
248 if (p.rev() not in state
248 if (p.rev() not in state
249 and p.rev() not in targetancestors):
249 and p.rev() not in targetancestors):
250 if external != nullrev:
250 if external != nullrev:
251 raise util.Abort(_('unable to collapse, there is more '
251 raise util.Abort(_('unable to collapse, there is more '
252 'than one external parent'))
252 'than one external parent'))
253 external = p.rev()
253 external = p.rev()
254 return external
254 return external
255
255
256 def updatedirstate(repo, rev, p1, p2):
256 def updatedirstate(repo, rev, p1, p2):
257 """Keep track of renamed files in the revision that is going to be rebased
257 """Keep track of renamed files in the revision that is going to be rebased
258 """
258 """
259 # Here we simulate the copies and renames in the source changeset
259 # Here we simulate the copies and renames in the source changeset
260 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
260 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
261 m1 = repo[rev].manifest()
261 m1 = repo[rev].manifest()
262 m2 = repo[p1].manifest()
262 m2 = repo[p1].manifest()
263 for k, v in cop.iteritems():
263 for k, v in cop.iteritems():
264 if k in m1:
264 if k in m1:
265 if v in m1 or v in m2:
265 if v in m1 or v in m2:
266 repo.dirstate.copy(v, k)
266 repo.dirstate.copy(v, k)
267 if v in m2 and v not in m1:
267 if v in m2 and v not in m1:
268 repo.dirstate.remove(v)
268 repo.dirstate.remove(v)
269
269
270 def concludenode(repo, rev, p1, p2, commitmsg=None, extrafn=None):
270 def concludenode(repo, rev, p1, p2, commitmsg=None, extrafn=None):
271 'Commit the changes and store useful information in extra'
271 'Commit the changes and store useful information in extra'
272 try:
272 try:
273 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
273 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
274 if commitmsg is None:
274 if commitmsg is None:
275 commitmsg = repo[rev].description()
275 commitmsg = repo[rev].description()
276 ctx = repo[rev]
276 ctx = repo[rev]
277 extra = {'rebase_source': ctx.hex()}
277 extra = {'rebase_source': ctx.hex()}
278 if extrafn:
278 if extrafn:
279 extrafn(ctx, extra)
279 extrafn(ctx, extra)
280 # Commit might fail if unresolved files exist
280 # Commit might fail if unresolved files exist
281 newrev = repo.commit(text=commitmsg, user=ctx.user(),
281 newrev = repo.commit(text=commitmsg, user=ctx.user(),
282 date=ctx.date(), extra=extra)
282 date=ctx.date(), extra=extra)
283 repo.dirstate.setbranch(repo[newrev].branch())
283 repo.dirstate.setbranch(repo[newrev].branch())
284 return newrev
284 return newrev
285 except util.Abort:
285 except util.Abort:
286 # Invalidate the previous setparents
286 # Invalidate the previous setparents
287 repo.dirstate.invalidate()
287 repo.dirstate.invalidate()
288 raise
288 raise
289
289
290 def rebasenode(repo, rev, p1, p2, state):
290 def rebasenode(repo, rev, p1, p2, state):
291 'Rebase a single revision'
291 'Rebase a single revision'
292 # Merge phase
292 # Merge phase
293 # Update to target and merge it with local
293 # Update to target and merge it with local
294 if repo['.'].rev() != repo[p1].rev():
294 if repo['.'].rev() != repo[p1].rev():
295 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
295 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
296 merge.update(repo, p1, False, True, False)
296 merge.update(repo, p1, False, True, False)
297 else:
297 else:
298 repo.ui.debug(" already in target\n")
298 repo.ui.debug(" already in target\n")
299 repo.dirstate.write()
299 repo.dirstate.write()
300 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
300 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
301 first = repo[rev].rev() == repo[min(state)].rev()
301 first = repo[rev].rev() == repo[min(state)].rev()
302 stats = rebasemerge(repo, rev, first)
302 stats = rebasemerge(repo, rev, first)
303 return stats
303 return stats
304
304
305 def defineparents(repo, rev, target, state, targetancestors):
305 def defineparents(repo, rev, target, state, targetancestors):
306 'Return the new parent relationship of the revision that will be rebased'
306 'Return the new parent relationship of the revision that will be rebased'
307 parents = repo[rev].parents()
307 parents = repo[rev].parents()
308 p1 = p2 = nullrev
308 p1 = p2 = nullrev
309
309
310 P1n = parents[0].rev()
310 P1n = parents[0].rev()
311 if P1n in targetancestors:
311 if P1n in targetancestors:
312 p1 = target
312 p1 = target
313 elif P1n in state:
313 elif P1n in state:
314 if state[P1n] == nullmerge:
314 if state[P1n] == nullmerge:
315 p1 = target
315 p1 = target
316 else:
316 else:
317 p1 = state[P1n]
317 p1 = state[P1n]
318 else: # P1n external
318 else: # P1n external
319 p1 = target
319 p1 = target
320 p2 = P1n
320 p2 = P1n
321
321
322 if len(parents) == 2 and parents[1].rev() not in targetancestors:
322 if len(parents) == 2 and parents[1].rev() not in targetancestors:
323 P2n = parents[1].rev()
323 P2n = parents[1].rev()
324 # interesting second parent
324 # interesting second parent
325 if P2n in state:
325 if P2n in state:
326 if p1 == target: # P1n in targetancestors or external
326 if p1 == target: # P1n in targetancestors or external
327 p1 = state[P2n]
327 p1 = state[P2n]
328 else:
328 else:
329 p2 = state[P2n]
329 p2 = state[P2n]
330 else: # P2n external
330 else: # P2n external
331 if p2 != nullrev: # P1n external too => rev is a merged revision
331 if p2 != nullrev: # P1n external too => rev is a merged revision
332 raise util.Abort(_('cannot use revision %d as base, result '
332 raise util.Abort(_('cannot use revision %d as base, result '
333 'would have 3 parents') % rev)
333 'would have 3 parents') % rev)
334 p2 = P2n
334 p2 = P2n
335 repo.ui.debug(" future parents are %d and %d\n" %
335 repo.ui.debug(" future parents are %d and %d\n" %
336 (repo[p1].rev(), repo[p2].rev()))
336 (repo[p1].rev(), repo[p2].rev()))
337 return p1, p2
337 return p1, p2
338
338
339 def isagitpatch(repo, patchname):
339 def isagitpatch(repo, patchname):
340 'Return true if the given patch is in git format'
340 'Return true if the given patch is in git format'
341 mqpatch = os.path.join(repo.mq.path, patchname)
341 mqpatch = os.path.join(repo.mq.path, patchname)
342 for line in patch.linereader(file(mqpatch, 'rb')):
342 for line in patch.linereader(file(mqpatch, 'rb')):
343 if line.startswith('diff --git'):
343 if line.startswith('diff --git'):
344 return True
344 return True
345 return False
345 return False
346
346
347 def updatemq(repo, state, skipped, **opts):
347 def updatemq(repo, state, skipped, **opts):
348 'Update rebased mq patches - finalize and then import them'
348 'Update rebased mq patches - finalize and then import them'
349 mqrebase = {}
349 mqrebase = {}
350 for p in repo.mq.applied:
350 for p in repo.mq.applied:
351 if repo[p.node].rev() in state:
351 if repo[p.node].rev() in state:
352 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
352 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
353 (repo[p.node].rev(), p.name))
353 (repo[p.node].rev(), p.name))
354 mqrebase[repo[p.node].rev()] = (p.name, isagitpatch(repo, p.name))
354 mqrebase[repo[p.node].rev()] = (p.name, isagitpatch(repo, p.name))
355
355
356 if mqrebase:
356 if mqrebase:
357 repo.mq.finish(repo, mqrebase.keys())
357 repo.mq.finish(repo, mqrebase.keys())
358
358
359 # We must start import from the newest revision
359 # We must start import from the newest revision
360 for rev in sorted(mqrebase, reverse=True):
360 for rev in sorted(mqrebase, reverse=True):
361 if rev not in skipped:
361 if rev not in skipped:
362 repo.ui.debug('import mq patch %d (%s)\n'
362 repo.ui.debug('import mq patch %d (%s)\n'
363 % (state[rev], mqrebase[rev][0]))
363 % (state[rev], mqrebase[rev][0]))
364 repo.mq.qimport(repo, (), patchname=mqrebase[rev][0],
364 repo.mq.qimport(repo, (), patchname=mqrebase[rev][0],
365 git=mqrebase[rev][1],rev=[str(state[rev])])
365 git=mqrebase[rev][1],rev=[str(state[rev])])
366 repo.mq.save_dirty()
366 repo.mq.save_dirty()
367
367
368 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
368 def storestatus(repo, originalwd, target, state, collapse, keep, keepbranches,
369 external):
369 external):
370 'Store the current status to allow recovery'
370 'Store the current status to allow recovery'
371 f = repo.opener("rebasestate", "w")
371 f = repo.opener("rebasestate", "w")
372 f.write(repo[originalwd].hex() + '\n')
372 f.write(repo[originalwd].hex() + '\n')
373 f.write(repo[target].hex() + '\n')
373 f.write(repo[target].hex() + '\n')
374 f.write(repo[external].hex() + '\n')
374 f.write(repo[external].hex() + '\n')
375 f.write('%d\n' % int(collapse))
375 f.write('%d\n' % int(collapse))
376 f.write('%d\n' % int(keep))
376 f.write('%d\n' % int(keep))
377 f.write('%d\n' % int(keepbranches))
377 f.write('%d\n' % int(keepbranches))
378 for d, v in state.iteritems():
378 for d, v in state.iteritems():
379 oldrev = repo[d].hex()
379 oldrev = repo[d].hex()
380 newrev = repo[v].hex()
380 newrev = repo[v].hex()
381 f.write("%s:%s\n" % (oldrev, newrev))
381 f.write("%s:%s\n" % (oldrev, newrev))
382 f.close()
382 f.close()
383 repo.ui.debug('rebase status stored\n')
383 repo.ui.debug('rebase status stored\n')
384
384
385 def clearstatus(repo):
385 def clearstatus(repo):
386 'Remove the status files'
386 'Remove the status files'
387 if os.path.exists(repo.join("rebasestate")):
387 if os.path.exists(repo.join("rebasestate")):
388 util.unlink(repo.join("rebasestate"))
388 util.unlink(repo.join("rebasestate"))
389
389
390 def restorestatus(repo):
390 def restorestatus(repo):
391 'Restore a previously stored status'
391 'Restore a previously stored status'
392 try:
392 try:
393 target = None
393 target = None
394 collapse = False
394 collapse = False
395 external = nullrev
395 external = nullrev
396 state = {}
396 state = {}
397 f = repo.opener("rebasestate")
397 f = repo.opener("rebasestate")
398 for i, l in enumerate(f.read().splitlines()):
398 for i, l in enumerate(f.read().splitlines()):
399 if i == 0:
399 if i == 0:
400 originalwd = repo[l].rev()
400 originalwd = repo[l].rev()
401 elif i == 1:
401 elif i == 1:
402 target = repo[l].rev()
402 target = repo[l].rev()
403 elif i == 2:
403 elif i == 2:
404 external = repo[l].rev()
404 external = repo[l].rev()
405 elif i == 3:
405 elif i == 3:
406 collapse = bool(int(l))
406 collapse = bool(int(l))
407 elif i == 4:
407 elif i == 4:
408 keep = bool(int(l))
408 keep = bool(int(l))
409 elif i == 5:
409 elif i == 5:
410 keepbranches = bool(int(l))
410 keepbranches = bool(int(l))
411 else:
411 else:
412 oldrev, newrev = l.split(':')
412 oldrev, newrev = l.split(':')
413 state[repo[oldrev].rev()] = repo[newrev].rev()
413 state[repo[oldrev].rev()] = repo[newrev].rev()
414 repo.ui.debug('rebase status resumed\n')
414 repo.ui.debug('rebase status resumed\n')
415 return originalwd, target, state, collapse, keep, keepbranches, external
415 return originalwd, target, state, collapse, keep, keepbranches, external
416 except IOError, err:
416 except IOError, err:
417 if err.errno != errno.ENOENT:
417 if err.errno != errno.ENOENT:
418 raise
418 raise
419 raise util.Abort(_('no rebase in progress'))
419 raise util.Abort(_('no rebase in progress'))
420
420
421 def abort(repo, originalwd, target, state):
421 def abort(repo, originalwd, target, state):
422 'Restore the repository to its original state'
422 'Restore the repository to its original state'
423 if set(repo.changelog.descendants(target)) - set(state.values()):
423 if set(repo.changelog.descendants(target)) - set(state.values()):
424 repo.ui.warn(_("warning: new changesets detected on target branch, "
424 repo.ui.warn(_("warning: new changesets detected on target branch, "
425 "can't abort\n"))
425 "can't abort\n"))
426 return -1
426 return -1
427 else:
427 else:
428 # Strip from the first rebased revision
428 # Strip from the first rebased revision
429 merge.update(repo, repo[originalwd].rev(), False, True, False)
429 merge.update(repo, repo[originalwd].rev(), False, True, False)
430 rebased = filter(lambda x: x > -1 and x != target, state.values())
430 rebased = filter(lambda x: x > -1 and x != target, state.values())
431 if rebased:
431 if rebased:
432 strippoint = min(rebased)
432 strippoint = min(rebased)
433 # no backup of rebased cset versions needed
433 # no backup of rebased cset versions needed
434 repair.strip(repo.ui, repo, repo[strippoint].node())
434 repair.strip(repo.ui, repo, repo[strippoint].node())
435 clearstatus(repo)
435 clearstatus(repo)
436 repo.ui.status(_('rebase aborted\n'))
436 repo.ui.status(_('rebase aborted\n'))
437 return 0
437 return 0
438
438
439 def buildstate(repo, dest, src, base, detach):
439 def buildstate(repo, dest, src, base, detach):
440 'Define which revisions are going to be rebased and where'
440 'Define which revisions are going to be rebased and where'
441 targetancestors = set()
441 targetancestors = set()
442 detachset = set()
442 detachset = set()
443
443
444 if not dest:
444 if not dest:
445 # Destination defaults to the latest revision in the current branch
445 # Destination defaults to the latest revision in the current branch
446 branch = repo[None].branch()
446 branch = repo[None].branch()
447 dest = repo[branch].rev()
447 dest = repo[branch].rev()
448 else:
448 else:
449 dest = repo[dest].rev()
449 dest = repo[dest].rev()
450
450
451 # This check isn't strictly necessary, since mq detects commits over an
451 # This check isn't strictly necessary, since mq detects commits over an
452 # applied patch. But it prevents messing up the working directory when
452 # applied patch. But it prevents messing up the working directory when
453 # a partially completed rebase is blocked by mq.
453 # a partially completed rebase is blocked by mq.
454 if 'qtip' in repo.tags() and (repo[dest].node() in
454 if 'qtip' in repo.tags() and (repo[dest].node() in
455 [s.node for s in repo.mq.applied]):
455 [s.node for s in repo.mq.applied]):
456 raise util.Abort(_('cannot rebase onto an applied mq patch'))
456 raise util.Abort(_('cannot rebase onto an applied mq patch'))
457
457
458 if src:
458 if src:
459 commonbase = repo[src].ancestor(repo[dest])
459 commonbase = repo[src].ancestor(repo[dest])
460 if commonbase == repo[src]:
460 if commonbase == repo[src]:
461 raise util.Abort(_('source is ancestor of destination'))
461 raise util.Abort(_('source is ancestor of destination'))
462 if commonbase == repo[dest]:
462 if commonbase == repo[dest]:
463 raise util.Abort(_('source is descendant of destination'))
463 raise util.Abort(_('source is descendant of destination'))
464 source = repo[src].rev()
464 source = repo[src].rev()
465 if detach:
465 if detach:
466 # We need to keep track of source's ancestors up to the common base
466 # We need to keep track of source's ancestors up to the common base
467 srcancestors = set(repo.changelog.ancestors(source))
467 srcancestors = set(repo.changelog.ancestors(source))
468 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
468 baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
469 detachset = srcancestors - baseancestors
469 detachset = srcancestors - baseancestors
470 detachset.remove(commonbase.rev())
470 detachset.remove(commonbase.rev())
471 else:
471 else:
472 if base:
472 if base:
473 cwd = repo[base].rev()
473 cwd = repo[base].rev()
474 else:
474 else:
475 cwd = repo['.'].rev()
475 cwd = repo['.'].rev()
476
476
477 if cwd == dest:
477 if cwd == dest:
478 repo.ui.debug('source and destination are the same\n')
478 repo.ui.debug('source and destination are the same\n')
479 return None
479 return None
480
480
481 targetancestors = set(repo.changelog.ancestors(dest))
481 targetancestors = set(repo.changelog.ancestors(dest))
482 if cwd in targetancestors:
482 if cwd in targetancestors:
483 repo.ui.debug('source is ancestor of destination\n')
483 repo.ui.debug('source is ancestor of destination\n')
484 return None
484 return None
485
485
486 cwdancestors = set(repo.changelog.ancestors(cwd))
486 cwdancestors = set(repo.changelog.ancestors(cwd))
487 if dest in cwdancestors:
487 if dest in cwdancestors:
488 repo.ui.debug('source is descendant of destination\n')
488 repo.ui.debug('source is descendant of destination\n')
489 return None
489 return None
490
490
491 cwdancestors.add(cwd)
491 cwdancestors.add(cwd)
492 rebasingbranch = cwdancestors - targetancestors
492 rebasingbranch = cwdancestors - targetancestors
493 source = min(rebasingbranch)
493 source = min(rebasingbranch)
494
494
495 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
495 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
496 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
496 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
497 state.update(dict.fromkeys(detachset, nullmerge))
497 state.update(dict.fromkeys(detachset, nullmerge))
498 state[source] = nullrev
498 state[source] = nullrev
499 return repo['.'].rev(), repo[dest].rev(), state
499 return repo['.'].rev(), repo[dest].rev(), state
500
500
501 def pullrebase(orig, ui, repo, *args, **opts):
501 def pullrebase(orig, ui, repo, *args, **opts):
502 'Call rebase after pull if the latter has been invoked with --rebase'
502 'Call rebase after pull if the latter has been invoked with --rebase'
503 if opts.get('rebase'):
503 if opts.get('rebase'):
504 if opts.get('update'):
504 if opts.get('update'):
505 del opts['update']
505 del opts['update']
506 ui.debug('--update and --rebase are not compatible, ignoring '
506 ui.debug('--update and --rebase are not compatible, ignoring '
507 'the update flag\n')
507 'the update flag\n')
508
508
509 cmdutil.bail_if_changed(repo)
509 cmdutil.bail_if_changed(repo)
510 revsprepull = len(repo)
510 revsprepull = len(repo)
511 origpostincoming = commands.postincoming
511 origpostincoming = commands.postincoming
512 def _dummy(*args, **kwargs):
512 def _dummy(*args, **kwargs):
513 pass
513 pass
514 commands.postincoming = _dummy
514 commands.postincoming = _dummy
515 try:
515 try:
516 orig(ui, repo, *args, **opts)
516 orig(ui, repo, *args, **opts)
517 finally:
517 finally:
518 commands.postincoming = origpostincoming
518 commands.postincoming = origpostincoming
519 revspostpull = len(repo)
519 revspostpull = len(repo)
520 if revspostpull > revsprepull:
520 if revspostpull > revsprepull:
521 rebase(ui, repo, **opts)
521 rebase(ui, repo, **opts)
522 branch = repo[None].branch()
522 branch = repo[None].branch()
523 dest = repo[branch].rev()
523 dest = repo[branch].rev()
524 if dest != repo['.'].rev():
524 if dest != repo['.'].rev():
525 # there was nothing to rebase we force an update
525 # there was nothing to rebase we force an update
526 hg.update(repo, dest)
526 hg.update(repo, dest)
527 else:
527 else:
528 orig(ui, repo, *args, **opts)
528 orig(ui, repo, *args, **opts)
529
529
530 def uisetup(ui):
530 def uisetup(ui):
531 'Replace pull with a decorator to provide --rebase option'
531 'Replace pull with a decorator to provide --rebase option'
532 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
532 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
533 entry[1].append(('', 'rebase', None,
533 entry[1].append(('', 'rebase', None,
534 _("rebase working directory to branch head"))
534 _("rebase working directory to branch head"))
535 )
535 )
536
536
537 cmdtable = {
537 cmdtable = {
538 "rebase":
538 "rebase":
539 (rebase,
539 (rebase,
540 [
540 [
541 ('s', 'source', '', _('rebase from the specified changeset')),
541 ('s', 'source', '',
542 ('b', 'base', '', _('rebase from the base of the specified changeset '
542 _('rebase from the specified changeset'), _('REV')),
543 '(up to greatest common ancestor of base and dest)')),
543 ('b', 'base', '',
544 ('d', 'dest', '', _('rebase onto the specified changeset')),
544 _('rebase from the base of the specified changeset '
545 '(up to greatest common ancestor of base and dest)'),
546 _('REV')),
547 ('d', 'dest', '',
548 _('rebase onto the specified changeset'), _('REV')),
545 ('', 'collapse', False, _('collapse the rebased changesets')),
549 ('', 'collapse', False, _('collapse the rebased changesets')),
546 ('', 'keep', False, _('keep original changesets')),
550 ('', 'keep', False, _('keep original changesets')),
547 ('', 'keepbranches', False, _('keep original branch names')),
551 ('', 'keepbranches', False, _('keep original branch names')),
548 ('', 'detach', False, _('force detaching of source from its original '
552 ('', 'detach', False, _('force detaching of source from its original '
549 'branch')),
553 'branch')),
550 ('c', 'continue', False, _('continue an interrupted rebase')),
554 ('c', 'continue', False, _('continue an interrupted rebase')),
551 ('a', 'abort', False, _('abort an interrupted rebase'))] +
555 ('a', 'abort', False, _('abort an interrupted rebase'))] +
552 templateopts,
556 templateopts,
553 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
557 _('hg rebase [-s REV | -b REV] [-d REV] [options]\n'
554 'hg rebase {-a|-c}'))
558 'hg rebase {-a|-c}'))
555 }
559 }
@@ -1,606 +1,611 b''
1 # Patch transplanting extension for Mercurial
1 # Patch transplanting extension for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''command to transplant changesets from another branch
8 '''command to transplant changesets from another branch
9
9
10 This extension allows you to transplant patches from another branch.
10 This extension allows you to transplant patches from another branch.
11
11
12 Transplanted patches are recorded in .hg/transplant/transplants, as a
12 Transplanted patches are recorded in .hg/transplant/transplants, as a
13 map from a changeset hash to its hash in the source repository.
13 map from a changeset hash to its hash in the source repository.
14 '''
14 '''
15
15
16 from mercurial.i18n import _
16 from mercurial.i18n import _
17 import os, tempfile
17 import os, tempfile
18 from mercurial import bundlerepo, changegroup, cmdutil, hg, merge, match
18 from mercurial import bundlerepo, changegroup, cmdutil, hg, merge, match
19 from mercurial import patch, revlog, util, error, discovery
19 from mercurial import patch, revlog, util, error, discovery
20
20
21 class transplantentry(object):
21 class transplantentry(object):
22 def __init__(self, lnode, rnode):
22 def __init__(self, lnode, rnode):
23 self.lnode = lnode
23 self.lnode = lnode
24 self.rnode = rnode
24 self.rnode = rnode
25
25
26 class transplants(object):
26 class transplants(object):
27 def __init__(self, path=None, transplantfile=None, opener=None):
27 def __init__(self, path=None, transplantfile=None, opener=None):
28 self.path = path
28 self.path = path
29 self.transplantfile = transplantfile
29 self.transplantfile = transplantfile
30 self.opener = opener
30 self.opener = opener
31
31
32 if not opener:
32 if not opener:
33 self.opener = util.opener(self.path)
33 self.opener = util.opener(self.path)
34 self.transplants = []
34 self.transplants = []
35 self.dirty = False
35 self.dirty = False
36 self.read()
36 self.read()
37
37
38 def read(self):
38 def read(self):
39 abspath = os.path.join(self.path, self.transplantfile)
39 abspath = os.path.join(self.path, self.transplantfile)
40 if self.transplantfile and os.path.exists(abspath):
40 if self.transplantfile and os.path.exists(abspath):
41 for line in self.opener(self.transplantfile).read().splitlines():
41 for line in self.opener(self.transplantfile).read().splitlines():
42 lnode, rnode = map(revlog.bin, line.split(':'))
42 lnode, rnode = map(revlog.bin, line.split(':'))
43 self.transplants.append(transplantentry(lnode, rnode))
43 self.transplants.append(transplantentry(lnode, rnode))
44
44
45 def write(self):
45 def write(self):
46 if self.dirty and self.transplantfile:
46 if self.dirty and self.transplantfile:
47 if not os.path.isdir(self.path):
47 if not os.path.isdir(self.path):
48 os.mkdir(self.path)
48 os.mkdir(self.path)
49 fp = self.opener(self.transplantfile, 'w')
49 fp = self.opener(self.transplantfile, 'w')
50 for c in self.transplants:
50 for c in self.transplants:
51 l, r = map(revlog.hex, (c.lnode, c.rnode))
51 l, r = map(revlog.hex, (c.lnode, c.rnode))
52 fp.write(l + ':' + r + '\n')
52 fp.write(l + ':' + r + '\n')
53 fp.close()
53 fp.close()
54 self.dirty = False
54 self.dirty = False
55
55
56 def get(self, rnode):
56 def get(self, rnode):
57 return [t for t in self.transplants if t.rnode == rnode]
57 return [t for t in self.transplants if t.rnode == rnode]
58
58
59 def set(self, lnode, rnode):
59 def set(self, lnode, rnode):
60 self.transplants.append(transplantentry(lnode, rnode))
60 self.transplants.append(transplantentry(lnode, rnode))
61 self.dirty = True
61 self.dirty = True
62
62
63 def remove(self, transplant):
63 def remove(self, transplant):
64 del self.transplants[self.transplants.index(transplant)]
64 del self.transplants[self.transplants.index(transplant)]
65 self.dirty = True
65 self.dirty = True
66
66
67 class transplanter(object):
67 class transplanter(object):
68 def __init__(self, ui, repo):
68 def __init__(self, ui, repo):
69 self.ui = ui
69 self.ui = ui
70 self.path = repo.join('transplant')
70 self.path = repo.join('transplant')
71 self.opener = util.opener(self.path)
71 self.opener = util.opener(self.path)
72 self.transplants = transplants(self.path, 'transplants',
72 self.transplants = transplants(self.path, 'transplants',
73 opener=self.opener)
73 opener=self.opener)
74
74
75 def applied(self, repo, node, parent):
75 def applied(self, repo, node, parent):
76 '''returns True if a node is already an ancestor of parent
76 '''returns True if a node is already an ancestor of parent
77 or has already been transplanted'''
77 or has already been transplanted'''
78 if hasnode(repo, node):
78 if hasnode(repo, node):
79 if node in repo.changelog.reachable(parent, stop=node):
79 if node in repo.changelog.reachable(parent, stop=node):
80 return True
80 return True
81 for t in self.transplants.get(node):
81 for t in self.transplants.get(node):
82 # it might have been stripped
82 # it might have been stripped
83 if not hasnode(repo, t.lnode):
83 if not hasnode(repo, t.lnode):
84 self.transplants.remove(t)
84 self.transplants.remove(t)
85 return False
85 return False
86 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
86 if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
87 return True
87 return True
88 return False
88 return False
89
89
90 def apply(self, repo, source, revmap, merges, opts={}):
90 def apply(self, repo, source, revmap, merges, opts={}):
91 '''apply the revisions in revmap one by one in revision order'''
91 '''apply the revisions in revmap one by one in revision order'''
92 revs = sorted(revmap)
92 revs = sorted(revmap)
93 p1, p2 = repo.dirstate.parents()
93 p1, p2 = repo.dirstate.parents()
94 pulls = []
94 pulls = []
95 diffopts = patch.diffopts(self.ui, opts)
95 diffopts = patch.diffopts(self.ui, opts)
96 diffopts.git = True
96 diffopts.git = True
97
97
98 lock = wlock = None
98 lock = wlock = None
99 try:
99 try:
100 wlock = repo.wlock()
100 wlock = repo.wlock()
101 lock = repo.lock()
101 lock = repo.lock()
102 for rev in revs:
102 for rev in revs:
103 node = revmap[rev]
103 node = revmap[rev]
104 revstr = '%s:%s' % (rev, revlog.short(node))
104 revstr = '%s:%s' % (rev, revlog.short(node))
105
105
106 if self.applied(repo, node, p1):
106 if self.applied(repo, node, p1):
107 self.ui.warn(_('skipping already applied revision %s\n') %
107 self.ui.warn(_('skipping already applied revision %s\n') %
108 revstr)
108 revstr)
109 continue
109 continue
110
110
111 parents = source.changelog.parents(node)
111 parents = source.changelog.parents(node)
112 if not opts.get('filter'):
112 if not opts.get('filter'):
113 # If the changeset parent is the same as the
113 # If the changeset parent is the same as the
114 # wdir's parent, just pull it.
114 # wdir's parent, just pull it.
115 if parents[0] == p1:
115 if parents[0] == p1:
116 pulls.append(node)
116 pulls.append(node)
117 p1 = node
117 p1 = node
118 continue
118 continue
119 if pulls:
119 if pulls:
120 if source != repo:
120 if source != repo:
121 repo.pull(source, heads=pulls)
121 repo.pull(source, heads=pulls)
122 merge.update(repo, pulls[-1], False, False, None)
122 merge.update(repo, pulls[-1], False, False, None)
123 p1, p2 = repo.dirstate.parents()
123 p1, p2 = repo.dirstate.parents()
124 pulls = []
124 pulls = []
125
125
126 domerge = False
126 domerge = False
127 if node in merges:
127 if node in merges:
128 # pulling all the merge revs at once would mean we
128 # pulling all the merge revs at once would mean we
129 # couldn't transplant after the latest even if
129 # couldn't transplant after the latest even if
130 # transplants before them fail.
130 # transplants before them fail.
131 domerge = True
131 domerge = True
132 if not hasnode(repo, node):
132 if not hasnode(repo, node):
133 repo.pull(source, heads=[node])
133 repo.pull(source, heads=[node])
134
134
135 if parents[1] != revlog.nullid:
135 if parents[1] != revlog.nullid:
136 self.ui.note(_('skipping merge changeset %s:%s\n')
136 self.ui.note(_('skipping merge changeset %s:%s\n')
137 % (rev, revlog.short(node)))
137 % (rev, revlog.short(node)))
138 patchfile = None
138 patchfile = None
139 else:
139 else:
140 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
140 fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
141 fp = os.fdopen(fd, 'w')
141 fp = os.fdopen(fd, 'w')
142 gen = patch.diff(source, parents[0], node, opts=diffopts)
142 gen = patch.diff(source, parents[0], node, opts=diffopts)
143 for chunk in gen:
143 for chunk in gen:
144 fp.write(chunk)
144 fp.write(chunk)
145 fp.close()
145 fp.close()
146
146
147 del revmap[rev]
147 del revmap[rev]
148 if patchfile or domerge:
148 if patchfile or domerge:
149 try:
149 try:
150 n = self.applyone(repo, node,
150 n = self.applyone(repo, node,
151 source.changelog.read(node),
151 source.changelog.read(node),
152 patchfile, merge=domerge,
152 patchfile, merge=domerge,
153 log=opts.get('log'),
153 log=opts.get('log'),
154 filter=opts.get('filter'))
154 filter=opts.get('filter'))
155 if n and domerge:
155 if n and domerge:
156 self.ui.status(_('%s merged at %s\n') % (revstr,
156 self.ui.status(_('%s merged at %s\n') % (revstr,
157 revlog.short(n)))
157 revlog.short(n)))
158 elif n:
158 elif n:
159 self.ui.status(_('%s transplanted to %s\n')
159 self.ui.status(_('%s transplanted to %s\n')
160 % (revlog.short(node),
160 % (revlog.short(node),
161 revlog.short(n)))
161 revlog.short(n)))
162 finally:
162 finally:
163 if patchfile:
163 if patchfile:
164 os.unlink(patchfile)
164 os.unlink(patchfile)
165 if pulls:
165 if pulls:
166 repo.pull(source, heads=pulls)
166 repo.pull(source, heads=pulls)
167 merge.update(repo, pulls[-1], False, False, None)
167 merge.update(repo, pulls[-1], False, False, None)
168 finally:
168 finally:
169 self.saveseries(revmap, merges)
169 self.saveseries(revmap, merges)
170 self.transplants.write()
170 self.transplants.write()
171 lock.release()
171 lock.release()
172 wlock.release()
172 wlock.release()
173
173
174 def filter(self, filter, changelog, patchfile):
174 def filter(self, filter, changelog, patchfile):
175 '''arbitrarily rewrite changeset before applying it'''
175 '''arbitrarily rewrite changeset before applying it'''
176
176
177 self.ui.status(_('filtering %s\n') % patchfile)
177 self.ui.status(_('filtering %s\n') % patchfile)
178 user, date, msg = (changelog[1], changelog[2], changelog[4])
178 user, date, msg = (changelog[1], changelog[2], changelog[4])
179
179
180 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
180 fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
181 fp = os.fdopen(fd, 'w')
181 fp = os.fdopen(fd, 'w')
182 fp.write("# HG changeset patch\n")
182 fp.write("# HG changeset patch\n")
183 fp.write("# User %s\n" % user)
183 fp.write("# User %s\n" % user)
184 fp.write("# Date %d %d\n" % date)
184 fp.write("# Date %d %d\n" % date)
185 fp.write(msg + '\n')
185 fp.write(msg + '\n')
186 fp.close()
186 fp.close()
187
187
188 try:
188 try:
189 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
189 util.system('%s %s %s' % (filter, util.shellquote(headerfile),
190 util.shellquote(patchfile)),
190 util.shellquote(patchfile)),
191 environ={'HGUSER': changelog[1]},
191 environ={'HGUSER': changelog[1]},
192 onerr=util.Abort, errprefix=_('filter failed'))
192 onerr=util.Abort, errprefix=_('filter failed'))
193 user, date, msg = self.parselog(file(headerfile))[1:4]
193 user, date, msg = self.parselog(file(headerfile))[1:4]
194 finally:
194 finally:
195 os.unlink(headerfile)
195 os.unlink(headerfile)
196
196
197 return (user, date, msg)
197 return (user, date, msg)
198
198
199 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
199 def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
200 filter=None):
200 filter=None):
201 '''apply the patch in patchfile to the repository as a transplant'''
201 '''apply the patch in patchfile to the repository as a transplant'''
202 (manifest, user, (time, timezone), files, message) = cl[:5]
202 (manifest, user, (time, timezone), files, message) = cl[:5]
203 date = "%d %d" % (time, timezone)
203 date = "%d %d" % (time, timezone)
204 extra = {'transplant_source': node}
204 extra = {'transplant_source': node}
205 if filter:
205 if filter:
206 (user, date, message) = self.filter(filter, cl, patchfile)
206 (user, date, message) = self.filter(filter, cl, patchfile)
207
207
208 if log:
208 if log:
209 # we don't translate messages inserted into commits
209 # we don't translate messages inserted into commits
210 message += '\n(transplanted from %s)' % revlog.hex(node)
210 message += '\n(transplanted from %s)' % revlog.hex(node)
211
211
212 self.ui.status(_('applying %s\n') % revlog.short(node))
212 self.ui.status(_('applying %s\n') % revlog.short(node))
213 self.ui.note('%s %s\n%s\n' % (user, date, message))
213 self.ui.note('%s %s\n%s\n' % (user, date, message))
214
214
215 if not patchfile and not merge:
215 if not patchfile and not merge:
216 raise util.Abort(_('can only omit patchfile if merging'))
216 raise util.Abort(_('can only omit patchfile if merging'))
217 if patchfile:
217 if patchfile:
218 try:
218 try:
219 files = {}
219 files = {}
220 try:
220 try:
221 patch.patch(patchfile, self.ui, cwd=repo.root,
221 patch.patch(patchfile, self.ui, cwd=repo.root,
222 files=files, eolmode=None)
222 files=files, eolmode=None)
223 if not files:
223 if not files:
224 self.ui.warn(_('%s: empty changeset')
224 self.ui.warn(_('%s: empty changeset')
225 % revlog.hex(node))
225 % revlog.hex(node))
226 return None
226 return None
227 finally:
227 finally:
228 files = patch.updatedir(self.ui, repo, files)
228 files = patch.updatedir(self.ui, repo, files)
229 except Exception, inst:
229 except Exception, inst:
230 seriespath = os.path.join(self.path, 'series')
230 seriespath = os.path.join(self.path, 'series')
231 if os.path.exists(seriespath):
231 if os.path.exists(seriespath):
232 os.unlink(seriespath)
232 os.unlink(seriespath)
233 p1 = repo.dirstate.parents()[0]
233 p1 = repo.dirstate.parents()[0]
234 p2 = node
234 p2 = node
235 self.log(user, date, message, p1, p2, merge=merge)
235 self.log(user, date, message, p1, p2, merge=merge)
236 self.ui.write(str(inst) + '\n')
236 self.ui.write(str(inst) + '\n')
237 raise util.Abort(_('Fix up the merge and run '
237 raise util.Abort(_('Fix up the merge and run '
238 'hg transplant --continue'))
238 'hg transplant --continue'))
239 else:
239 else:
240 files = None
240 files = None
241 if merge:
241 if merge:
242 p1, p2 = repo.dirstate.parents()
242 p1, p2 = repo.dirstate.parents()
243 repo.dirstate.setparents(p1, node)
243 repo.dirstate.setparents(p1, node)
244 m = match.always(repo.root, '')
244 m = match.always(repo.root, '')
245 else:
245 else:
246 m = match.exact(repo.root, '', files)
246 m = match.exact(repo.root, '', files)
247
247
248 n = repo.commit(message, user, date, extra=extra, match=m)
248 n = repo.commit(message, user, date, extra=extra, match=m)
249 if not merge:
249 if not merge:
250 self.transplants.set(n, node)
250 self.transplants.set(n, node)
251
251
252 return n
252 return n
253
253
254 def resume(self, repo, source, opts=None):
254 def resume(self, repo, source, opts=None):
255 '''recover last transaction and apply remaining changesets'''
255 '''recover last transaction and apply remaining changesets'''
256 if os.path.exists(os.path.join(self.path, 'journal')):
256 if os.path.exists(os.path.join(self.path, 'journal')):
257 n, node = self.recover(repo)
257 n, node = self.recover(repo)
258 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
258 self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
259 revlog.short(n)))
259 revlog.short(n)))
260 seriespath = os.path.join(self.path, 'series')
260 seriespath = os.path.join(self.path, 'series')
261 if not os.path.exists(seriespath):
261 if not os.path.exists(seriespath):
262 self.transplants.write()
262 self.transplants.write()
263 return
263 return
264 nodes, merges = self.readseries()
264 nodes, merges = self.readseries()
265 revmap = {}
265 revmap = {}
266 for n in nodes:
266 for n in nodes:
267 revmap[source.changelog.rev(n)] = n
267 revmap[source.changelog.rev(n)] = n
268 os.unlink(seriespath)
268 os.unlink(seriespath)
269
269
270 self.apply(repo, source, revmap, merges, opts)
270 self.apply(repo, source, revmap, merges, opts)
271
271
272 def recover(self, repo):
272 def recover(self, repo):
273 '''commit working directory using journal metadata'''
273 '''commit working directory using journal metadata'''
274 node, user, date, message, parents = self.readlog()
274 node, user, date, message, parents = self.readlog()
275 merge = len(parents) == 2
275 merge = len(parents) == 2
276
276
277 if not user or not date or not message or not parents[0]:
277 if not user or not date or not message or not parents[0]:
278 raise util.Abort(_('transplant log file is corrupt'))
278 raise util.Abort(_('transplant log file is corrupt'))
279
279
280 extra = {'transplant_source': node}
280 extra = {'transplant_source': node}
281 wlock = repo.wlock()
281 wlock = repo.wlock()
282 try:
282 try:
283 p1, p2 = repo.dirstate.parents()
283 p1, p2 = repo.dirstate.parents()
284 if p1 != parents[0]:
284 if p1 != parents[0]:
285 raise util.Abort(
285 raise util.Abort(
286 _('working dir not at transplant parent %s') %
286 _('working dir not at transplant parent %s') %
287 revlog.hex(parents[0]))
287 revlog.hex(parents[0]))
288 if merge:
288 if merge:
289 repo.dirstate.setparents(p1, parents[1])
289 repo.dirstate.setparents(p1, parents[1])
290 n = repo.commit(message, user, date, extra=extra)
290 n = repo.commit(message, user, date, extra=extra)
291 if not n:
291 if not n:
292 raise util.Abort(_('commit failed'))
292 raise util.Abort(_('commit failed'))
293 if not merge:
293 if not merge:
294 self.transplants.set(n, node)
294 self.transplants.set(n, node)
295 self.unlog()
295 self.unlog()
296
296
297 return n, node
297 return n, node
298 finally:
298 finally:
299 wlock.release()
299 wlock.release()
300
300
301 def readseries(self):
301 def readseries(self):
302 nodes = []
302 nodes = []
303 merges = []
303 merges = []
304 cur = nodes
304 cur = nodes
305 for line in self.opener('series').read().splitlines():
305 for line in self.opener('series').read().splitlines():
306 if line.startswith('# Merges'):
306 if line.startswith('# Merges'):
307 cur = merges
307 cur = merges
308 continue
308 continue
309 cur.append(revlog.bin(line))
309 cur.append(revlog.bin(line))
310
310
311 return (nodes, merges)
311 return (nodes, merges)
312
312
313 def saveseries(self, revmap, merges):
313 def saveseries(self, revmap, merges):
314 if not revmap:
314 if not revmap:
315 return
315 return
316
316
317 if not os.path.isdir(self.path):
317 if not os.path.isdir(self.path):
318 os.mkdir(self.path)
318 os.mkdir(self.path)
319 series = self.opener('series', 'w')
319 series = self.opener('series', 'w')
320 for rev in sorted(revmap):
320 for rev in sorted(revmap):
321 series.write(revlog.hex(revmap[rev]) + '\n')
321 series.write(revlog.hex(revmap[rev]) + '\n')
322 if merges:
322 if merges:
323 series.write('# Merges\n')
323 series.write('# Merges\n')
324 for m in merges:
324 for m in merges:
325 series.write(revlog.hex(m) + '\n')
325 series.write(revlog.hex(m) + '\n')
326 series.close()
326 series.close()
327
327
328 def parselog(self, fp):
328 def parselog(self, fp):
329 parents = []
329 parents = []
330 message = []
330 message = []
331 node = revlog.nullid
331 node = revlog.nullid
332 inmsg = False
332 inmsg = False
333 for line in fp.read().splitlines():
333 for line in fp.read().splitlines():
334 if inmsg:
334 if inmsg:
335 message.append(line)
335 message.append(line)
336 elif line.startswith('# User '):
336 elif line.startswith('# User '):
337 user = line[7:]
337 user = line[7:]
338 elif line.startswith('# Date '):
338 elif line.startswith('# Date '):
339 date = line[7:]
339 date = line[7:]
340 elif line.startswith('# Node ID '):
340 elif line.startswith('# Node ID '):
341 node = revlog.bin(line[10:])
341 node = revlog.bin(line[10:])
342 elif line.startswith('# Parent '):
342 elif line.startswith('# Parent '):
343 parents.append(revlog.bin(line[9:]))
343 parents.append(revlog.bin(line[9:]))
344 elif not line.startswith('#'):
344 elif not line.startswith('#'):
345 inmsg = True
345 inmsg = True
346 message.append(line)
346 message.append(line)
347 return (node, user, date, '\n'.join(message), parents)
347 return (node, user, date, '\n'.join(message), parents)
348
348
349 def log(self, user, date, message, p1, p2, merge=False):
349 def log(self, user, date, message, p1, p2, merge=False):
350 '''journal changelog metadata for later recover'''
350 '''journal changelog metadata for later recover'''
351
351
352 if not os.path.isdir(self.path):
352 if not os.path.isdir(self.path):
353 os.mkdir(self.path)
353 os.mkdir(self.path)
354 fp = self.opener('journal', 'w')
354 fp = self.opener('journal', 'w')
355 fp.write('# User %s\n' % user)
355 fp.write('# User %s\n' % user)
356 fp.write('# Date %s\n' % date)
356 fp.write('# Date %s\n' % date)
357 fp.write('# Node ID %s\n' % revlog.hex(p2))
357 fp.write('# Node ID %s\n' % revlog.hex(p2))
358 fp.write('# Parent ' + revlog.hex(p1) + '\n')
358 fp.write('# Parent ' + revlog.hex(p1) + '\n')
359 if merge:
359 if merge:
360 fp.write('# Parent ' + revlog.hex(p2) + '\n')
360 fp.write('# Parent ' + revlog.hex(p2) + '\n')
361 fp.write(message.rstrip() + '\n')
361 fp.write(message.rstrip() + '\n')
362 fp.close()
362 fp.close()
363
363
364 def readlog(self):
364 def readlog(self):
365 return self.parselog(self.opener('journal'))
365 return self.parselog(self.opener('journal'))
366
366
367 def unlog(self):
367 def unlog(self):
368 '''remove changelog journal'''
368 '''remove changelog journal'''
369 absdst = os.path.join(self.path, 'journal')
369 absdst = os.path.join(self.path, 'journal')
370 if os.path.exists(absdst):
370 if os.path.exists(absdst):
371 os.unlink(absdst)
371 os.unlink(absdst)
372
372
373 def transplantfilter(self, repo, source, root):
373 def transplantfilter(self, repo, source, root):
374 def matchfn(node):
374 def matchfn(node):
375 if self.applied(repo, node, root):
375 if self.applied(repo, node, root):
376 return False
376 return False
377 if source.changelog.parents(node)[1] != revlog.nullid:
377 if source.changelog.parents(node)[1] != revlog.nullid:
378 return False
378 return False
379 extra = source.changelog.read(node)[5]
379 extra = source.changelog.read(node)[5]
380 cnode = extra.get('transplant_source')
380 cnode = extra.get('transplant_source')
381 if cnode and self.applied(repo, cnode, root):
381 if cnode and self.applied(repo, cnode, root):
382 return False
382 return False
383 return True
383 return True
384
384
385 return matchfn
385 return matchfn
386
386
387 def hasnode(repo, node):
387 def hasnode(repo, node):
388 try:
388 try:
389 return repo.changelog.rev(node) != None
389 return repo.changelog.rev(node) != None
390 except error.RevlogError:
390 except error.RevlogError:
391 return False
391 return False
392
392
393 def browserevs(ui, repo, nodes, opts):
393 def browserevs(ui, repo, nodes, opts):
394 '''interactively transplant changesets'''
394 '''interactively transplant changesets'''
395 def browsehelp(ui):
395 def browsehelp(ui):
396 ui.write(_('y: transplant this changeset\n'
396 ui.write(_('y: transplant this changeset\n'
397 'n: skip this changeset\n'
397 'n: skip this changeset\n'
398 'm: merge at this changeset\n'
398 'm: merge at this changeset\n'
399 'p: show patch\n'
399 'p: show patch\n'
400 'c: commit selected changesets\n'
400 'c: commit selected changesets\n'
401 'q: cancel transplant\n'
401 'q: cancel transplant\n'
402 '?: show this help\n'))
402 '?: show this help\n'))
403
403
404 displayer = cmdutil.show_changeset(ui, repo, opts)
404 displayer = cmdutil.show_changeset(ui, repo, opts)
405 transplants = []
405 transplants = []
406 merges = []
406 merges = []
407 for node in nodes:
407 for node in nodes:
408 displayer.show(repo[node])
408 displayer.show(repo[node])
409 action = None
409 action = None
410 while not action:
410 while not action:
411 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
411 action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
412 if action == '?':
412 if action == '?':
413 browsehelp(ui)
413 browsehelp(ui)
414 action = None
414 action = None
415 elif action == 'p':
415 elif action == 'p':
416 parent = repo.changelog.parents(node)[0]
416 parent = repo.changelog.parents(node)[0]
417 for chunk in patch.diff(repo, parent, node):
417 for chunk in patch.diff(repo, parent, node):
418 ui.write(chunk)
418 ui.write(chunk)
419 action = None
419 action = None
420 elif action not in ('y', 'n', 'm', 'c', 'q'):
420 elif action not in ('y', 'n', 'm', 'c', 'q'):
421 ui.write(_('no such option\n'))
421 ui.write(_('no such option\n'))
422 action = None
422 action = None
423 if action == 'y':
423 if action == 'y':
424 transplants.append(node)
424 transplants.append(node)
425 elif action == 'm':
425 elif action == 'm':
426 merges.append(node)
426 merges.append(node)
427 elif action == 'c':
427 elif action == 'c':
428 break
428 break
429 elif action == 'q':
429 elif action == 'q':
430 transplants = ()
430 transplants = ()
431 merges = ()
431 merges = ()
432 break
432 break
433 displayer.close()
433 displayer.close()
434 return (transplants, merges)
434 return (transplants, merges)
435
435
436 def transplant(ui, repo, *revs, **opts):
436 def transplant(ui, repo, *revs, **opts):
437 '''transplant changesets from another branch
437 '''transplant changesets from another branch
438
438
439 Selected changesets will be applied on top of the current working
439 Selected changesets will be applied on top of the current working
440 directory with the log of the original changeset. If --log is
440 directory with the log of the original changeset. If --log is
441 specified, log messages will have a comment appended of the form::
441 specified, log messages will have a comment appended of the form::
442
442
443 (transplanted from CHANGESETHASH)
443 (transplanted from CHANGESETHASH)
444
444
445 You can rewrite the changelog message with the --filter option.
445 You can rewrite the changelog message with the --filter option.
446 Its argument will be invoked with the current changelog message as
446 Its argument will be invoked with the current changelog message as
447 $1 and the patch as $2.
447 $1 and the patch as $2.
448
448
449 If --source/-s is specified, selects changesets from the named
449 If --source/-s is specified, selects changesets from the named
450 repository. If --branch/-b is specified, selects changesets from
450 repository. If --branch/-b is specified, selects changesets from
451 the branch holding the named revision, up to that revision. If
451 the branch holding the named revision, up to that revision. If
452 --all/-a is specified, all changesets on the branch will be
452 --all/-a is specified, all changesets on the branch will be
453 transplanted, otherwise you will be prompted to select the
453 transplanted, otherwise you will be prompted to select the
454 changesets you want.
454 changesets you want.
455
455
456 :hg:`transplant --branch REVISION --all` will rebase the selected
456 :hg:`transplant --branch REVISION --all` will rebase the selected
457 branch (up to the named revision) onto your current working
457 branch (up to the named revision) onto your current working
458 directory.
458 directory.
459
459
460 You can optionally mark selected transplanted changesets as merge
460 You can optionally mark selected transplanted changesets as merge
461 changesets. You will not be prompted to transplant any ancestors
461 changesets. You will not be prompted to transplant any ancestors
462 of a merged transplant, and you can merge descendants of them
462 of a merged transplant, and you can merge descendants of them
463 normally instead of transplanting them.
463 normally instead of transplanting them.
464
464
465 If no merges or revisions are provided, :hg:`transplant` will
465 If no merges or revisions are provided, :hg:`transplant` will
466 start an interactive changeset browser.
466 start an interactive changeset browser.
467
467
468 If a changeset application fails, you can fix the merge by hand
468 If a changeset application fails, you can fix the merge by hand
469 and then resume where you left off by calling :hg:`transplant
469 and then resume where you left off by calling :hg:`transplant
470 --continue/-c`.
470 --continue/-c`.
471 '''
471 '''
472 def getremotechanges(repo, url):
472 def getremotechanges(repo, url):
473 sourcerepo = ui.expandpath(url)
473 sourcerepo = ui.expandpath(url)
474 source = hg.repository(ui, sourcerepo)
474 source = hg.repository(ui, sourcerepo)
475 tmp = discovery.findcommonincoming(repo, source, force=True)
475 tmp = discovery.findcommonincoming(repo, source, force=True)
476 common, incoming, rheads = tmp
476 common, incoming, rheads = tmp
477 if not incoming:
477 if not incoming:
478 return (source, None, None)
478 return (source, None, None)
479
479
480 bundle = None
480 bundle = None
481 if not source.local():
481 if not source.local():
482 if source.capable('changegroupsubset'):
482 if source.capable('changegroupsubset'):
483 cg = source.changegroupsubset(incoming, rheads, 'incoming')
483 cg = source.changegroupsubset(incoming, rheads, 'incoming')
484 else:
484 else:
485 cg = source.changegroup(incoming, 'incoming')
485 cg = source.changegroup(incoming, 'incoming')
486 bundle = changegroup.writebundle(cg, None, 'HG10UN')
486 bundle = changegroup.writebundle(cg, None, 'HG10UN')
487 source = bundlerepo.bundlerepository(ui, repo.root, bundle)
487 source = bundlerepo.bundlerepository(ui, repo.root, bundle)
488
488
489 return (source, incoming, bundle)
489 return (source, incoming, bundle)
490
490
491 def incwalk(repo, incoming, branches, match=util.always):
491 def incwalk(repo, incoming, branches, match=util.always):
492 if not branches:
492 if not branches:
493 branches = None
493 branches = None
494 for node in repo.changelog.nodesbetween(incoming, branches)[0]:
494 for node in repo.changelog.nodesbetween(incoming, branches)[0]:
495 if match(node):
495 if match(node):
496 yield node
496 yield node
497
497
498 def transplantwalk(repo, root, branches, match=util.always):
498 def transplantwalk(repo, root, branches, match=util.always):
499 if not branches:
499 if not branches:
500 branches = repo.heads()
500 branches = repo.heads()
501 ancestors = []
501 ancestors = []
502 for branch in branches:
502 for branch in branches:
503 ancestors.append(repo.changelog.ancestor(root, branch))
503 ancestors.append(repo.changelog.ancestor(root, branch))
504 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
504 for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
505 if match(node):
505 if match(node):
506 yield node
506 yield node
507
507
508 def checkopts(opts, revs):
508 def checkopts(opts, revs):
509 if opts.get('continue'):
509 if opts.get('continue'):
510 if opts.get('branch') or opts.get('all') or opts.get('merge'):
510 if opts.get('branch') or opts.get('all') or opts.get('merge'):
511 raise util.Abort(_('--continue is incompatible with '
511 raise util.Abort(_('--continue is incompatible with '
512 'branch, all or merge'))
512 'branch, all or merge'))
513 return
513 return
514 if not (opts.get('source') or revs or
514 if not (opts.get('source') or revs or
515 opts.get('merge') or opts.get('branch')):
515 opts.get('merge') or opts.get('branch')):
516 raise util.Abort(_('no source URL, branch tag or revision '
516 raise util.Abort(_('no source URL, branch tag or revision '
517 'list provided'))
517 'list provided'))
518 if opts.get('all'):
518 if opts.get('all'):
519 if not opts.get('branch'):
519 if not opts.get('branch'):
520 raise util.Abort(_('--all requires a branch revision'))
520 raise util.Abort(_('--all requires a branch revision'))
521 if revs:
521 if revs:
522 raise util.Abort(_('--all is incompatible with a '
522 raise util.Abort(_('--all is incompatible with a '
523 'revision list'))
523 'revision list'))
524
524
525 checkopts(opts, revs)
525 checkopts(opts, revs)
526
526
527 if not opts.get('log'):
527 if not opts.get('log'):
528 opts['log'] = ui.config('transplant', 'log')
528 opts['log'] = ui.config('transplant', 'log')
529 if not opts.get('filter'):
529 if not opts.get('filter'):
530 opts['filter'] = ui.config('transplant', 'filter')
530 opts['filter'] = ui.config('transplant', 'filter')
531
531
532 tp = transplanter(ui, repo)
532 tp = transplanter(ui, repo)
533
533
534 p1, p2 = repo.dirstate.parents()
534 p1, p2 = repo.dirstate.parents()
535 if len(repo) > 0 and p1 == revlog.nullid:
535 if len(repo) > 0 and p1 == revlog.nullid:
536 raise util.Abort(_('no revision checked out'))
536 raise util.Abort(_('no revision checked out'))
537 if not opts.get('continue'):
537 if not opts.get('continue'):
538 if p2 != revlog.nullid:
538 if p2 != revlog.nullid:
539 raise util.Abort(_('outstanding uncommitted merges'))
539 raise util.Abort(_('outstanding uncommitted merges'))
540 m, a, r, d = repo.status()[:4]
540 m, a, r, d = repo.status()[:4]
541 if m or a or r or d:
541 if m or a or r or d:
542 raise util.Abort(_('outstanding local changes'))
542 raise util.Abort(_('outstanding local changes'))
543
543
544 bundle = None
544 bundle = None
545 source = opts.get('source')
545 source = opts.get('source')
546 if source:
546 if source:
547 (source, incoming, bundle) = getremotechanges(repo, source)
547 (source, incoming, bundle) = getremotechanges(repo, source)
548 else:
548 else:
549 source = repo
549 source = repo
550
550
551 try:
551 try:
552 if opts.get('continue'):
552 if opts.get('continue'):
553 tp.resume(repo, source, opts)
553 tp.resume(repo, source, opts)
554 return
554 return
555
555
556 tf = tp.transplantfilter(repo, source, p1)
556 tf = tp.transplantfilter(repo, source, p1)
557 if opts.get('prune'):
557 if opts.get('prune'):
558 prune = [source.lookup(r)
558 prune = [source.lookup(r)
559 for r in cmdutil.revrange(source, opts.get('prune'))]
559 for r in cmdutil.revrange(source, opts.get('prune'))]
560 matchfn = lambda x: tf(x) and x not in prune
560 matchfn = lambda x: tf(x) and x not in prune
561 else:
561 else:
562 matchfn = tf
562 matchfn = tf
563 branches = map(source.lookup, opts.get('branch', ()))
563 branches = map(source.lookup, opts.get('branch', ()))
564 merges = map(source.lookup, opts.get('merge', ()))
564 merges = map(source.lookup, opts.get('merge', ()))
565 revmap = {}
565 revmap = {}
566 if revs:
566 if revs:
567 for r in cmdutil.revrange(source, revs):
567 for r in cmdutil.revrange(source, revs):
568 revmap[int(r)] = source.lookup(r)
568 revmap[int(r)] = source.lookup(r)
569 elif opts.get('all') or not merges:
569 elif opts.get('all') or not merges:
570 if source != repo:
570 if source != repo:
571 alltransplants = incwalk(source, incoming, branches,
571 alltransplants = incwalk(source, incoming, branches,
572 match=matchfn)
572 match=matchfn)
573 else:
573 else:
574 alltransplants = transplantwalk(source, p1, branches,
574 alltransplants = transplantwalk(source, p1, branches,
575 match=matchfn)
575 match=matchfn)
576 if opts.get('all'):
576 if opts.get('all'):
577 revs = alltransplants
577 revs = alltransplants
578 else:
578 else:
579 revs, newmerges = browserevs(ui, source, alltransplants, opts)
579 revs, newmerges = browserevs(ui, source, alltransplants, opts)
580 merges.extend(newmerges)
580 merges.extend(newmerges)
581 for r in revs:
581 for r in revs:
582 revmap[source.changelog.rev(r)] = r
582 revmap[source.changelog.rev(r)] = r
583 for r in merges:
583 for r in merges:
584 revmap[source.changelog.rev(r)] = r
584 revmap[source.changelog.rev(r)] = r
585
585
586 tp.apply(repo, source, revmap, merges, opts)
586 tp.apply(repo, source, revmap, merges, opts)
587 finally:
587 finally:
588 if bundle:
588 if bundle:
589 source.close()
589 source.close()
590 os.unlink(bundle)
590 os.unlink(bundle)
591
591
592 cmdtable = {
592 cmdtable = {
593 "transplant":
593 "transplant":
594 (transplant,
594 (transplant,
595 [('s', 'source', '', _('pull patches from REPOSITORY')),
595 [('s', 'source', '',
596 ('b', 'branch', [], _('pull patches from branch BRANCH')),
596 _('pull patches from REPO'), _('REPO')),
597 ('b', 'branch', [],
598 _('pull patches from branch BRANCH'), _('BRANCH')),
597 ('a', 'all', None, _('pull all changesets up to BRANCH')),
599 ('a', 'all', None, _('pull all changesets up to BRANCH')),
598 ('p', 'prune', [], _('skip over REV')),
600 ('p', 'prune', [],
599 ('m', 'merge', [], _('merge at REV')),
601 _('skip over REV'), _('REV')),
602 ('m', 'merge', [],
603 _('merge at REV'), _('REV')),
600 ('', 'log', None, _('append transplant info to log message')),
604 ('', 'log', None, _('append transplant info to log message')),
601 ('c', 'continue', None, _('continue last transplant session '
605 ('c', 'continue', None, _('continue last transplant session '
602 'after repair')),
606 'after repair')),
603 ('', 'filter', '', _('filter changesets through FILTER'))],
607 ('', 'filter', '',
604 _('hg transplant [-s REPOSITORY] [-b BRANCH [-a]] [-p REV] '
608 _('filter changesets through command'), _('CMD'))],
609 _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
605 '[-m REV] [REV]...'))
610 '[-m REV] [REV]...'))
606 }
611 }
@@ -1,4128 +1,4221 b''
1 # commands.py - command processing for mercurial
1 # commands.py - command processing for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from lock import release
9 from lock import release
10 from i18n import _, gettext
10 from i18n import _, gettext
11 import os, re, sys, difflib, time, tempfile
11 import os, re, sys, difflib, time, tempfile
12 import hg, util, revlog, bundlerepo, extensions, copies, error
12 import hg, util, revlog, bundlerepo, extensions, copies, error
13 import patch, help, mdiff, url, encoding, templatekw, discovery
13 import patch, help, mdiff, url, encoding, templatekw, discovery
14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
14 import archival, changegroup, cmdutil, sshserver, hbisect, hgweb, hgweb.server
15 import merge as mergemod
15 import merge as mergemod
16 import minirst, revset
16 import minirst, revset
17
17
18 # Commands start here, listed alphabetically
18 # Commands start here, listed alphabetically
19
19
20 def add(ui, repo, *pats, **opts):
20 def add(ui, repo, *pats, **opts):
21 """add the specified files on the next commit
21 """add the specified files on the next commit
22
22
23 Schedule files to be version controlled and added to the
23 Schedule files to be version controlled and added to the
24 repository.
24 repository.
25
25
26 The files will be added to the repository at the next commit. To
26 The files will be added to the repository at the next commit. To
27 undo an add before that, see :hg:`forget`.
27 undo an add before that, see :hg:`forget`.
28
28
29 If no names are given, add all files to the repository.
29 If no names are given, add all files to the repository.
30
30
31 .. container:: verbose
31 .. container:: verbose
32
32
33 An example showing how new (unknown) files are added
33 An example showing how new (unknown) files are added
34 automatically by :hg:`add`::
34 automatically by :hg:`add`::
35
35
36 $ ls
36 $ ls
37 foo.c
37 foo.c
38 $ hg status
38 $ hg status
39 ? foo.c
39 ? foo.c
40 $ hg add
40 $ hg add
41 adding foo.c
41 adding foo.c
42 $ hg status
42 $ hg status
43 A foo.c
43 A foo.c
44 """
44 """
45
45
46 bad = []
46 bad = []
47 names = []
47 names = []
48 m = cmdutil.match(repo, pats, opts)
48 m = cmdutil.match(repo, pats, opts)
49 oldbad = m.bad
49 oldbad = m.bad
50 m.bad = lambda x, y: bad.append(x) or oldbad(x, y)
50 m.bad = lambda x, y: bad.append(x) or oldbad(x, y)
51
51
52 for f in repo.walk(m):
52 for f in repo.walk(m):
53 exact = m.exact(f)
53 exact = m.exact(f)
54 if exact or f not in repo.dirstate:
54 if exact or f not in repo.dirstate:
55 names.append(f)
55 names.append(f)
56 if ui.verbose or not exact:
56 if ui.verbose or not exact:
57 ui.status(_('adding %s\n') % m.rel(f))
57 ui.status(_('adding %s\n') % m.rel(f))
58 if not opts.get('dry_run'):
58 if not opts.get('dry_run'):
59 bad += [f for f in repo[None].add(names) if f in m.files()]
59 bad += [f for f in repo[None].add(names) if f in m.files()]
60 return bad and 1 or 0
60 return bad and 1 or 0
61
61
62 def addremove(ui, repo, *pats, **opts):
62 def addremove(ui, repo, *pats, **opts):
63 """add all new files, delete all missing files
63 """add all new files, delete all missing files
64
64
65 Add all new files and remove all missing files from the
65 Add all new files and remove all missing files from the
66 repository.
66 repository.
67
67
68 New files are ignored if they match any of the patterns in
68 New files are ignored if they match any of the patterns in
69 .hgignore. As with add, these changes take effect at the next
69 .hgignore. As with add, these changes take effect at the next
70 commit.
70 commit.
71
71
72 Use the -s/--similarity option to detect renamed files. With a
72 Use the -s/--similarity option to detect renamed files. With a
73 parameter greater than 0, this compares every removed file with
73 parameter greater than 0, this compares every removed file with
74 every added file and records those similar enough as renames. This
74 every added file and records those similar enough as renames. This
75 option takes a percentage between 0 (disabled) and 100 (files must
75 option takes a percentage between 0 (disabled) and 100 (files must
76 be identical) as its parameter. Detecting renamed files this way
76 be identical) as its parameter. Detecting renamed files this way
77 can be expensive.
77 can be expensive.
78
78
79 Returns 0 if all files are successfully added.
79 Returns 0 if all files are successfully added.
80 """
80 """
81 try:
81 try:
82 sim = float(opts.get('similarity') or 0)
82 sim = float(opts.get('similarity') or 0)
83 except ValueError:
83 except ValueError:
84 raise util.Abort(_('similarity must be a number'))
84 raise util.Abort(_('similarity must be a number'))
85 if sim < 0 or sim > 100:
85 if sim < 0 or sim > 100:
86 raise util.Abort(_('similarity must be between 0 and 100'))
86 raise util.Abort(_('similarity must be between 0 and 100'))
87 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
87 return cmdutil.addremove(repo, pats, opts, similarity=sim / 100.0)
88
88
89 def annotate(ui, repo, *pats, **opts):
89 def annotate(ui, repo, *pats, **opts):
90 """show changeset information by line for each file
90 """show changeset information by line for each file
91
91
92 List changes in files, showing the revision id responsible for
92 List changes in files, showing the revision id responsible for
93 each line
93 each line
94
94
95 This command is useful for discovering when a change was made and
95 This command is useful for discovering when a change was made and
96 by whom.
96 by whom.
97
97
98 Without the -a/--text option, annotate will avoid processing files
98 Without the -a/--text option, annotate will avoid processing files
99 it detects as binary. With -a, annotate will annotate the file
99 it detects as binary. With -a, annotate will annotate the file
100 anyway, although the results will probably be neither useful
100 anyway, although the results will probably be neither useful
101 nor desirable.
101 nor desirable.
102
102
103 Returns 0 on success.
103 Returns 0 on success.
104 """
104 """
105 if opts.get('follow'):
105 if opts.get('follow'):
106 # --follow is deprecated and now just an alias for -f/--file
106 # --follow is deprecated and now just an alias for -f/--file
107 # to mimic the behavior of Mercurial before version 1.5
107 # to mimic the behavior of Mercurial before version 1.5
108 opts['file'] = 1
108 opts['file'] = 1
109
109
110 datefunc = ui.quiet and util.shortdate or util.datestr
110 datefunc = ui.quiet and util.shortdate or util.datestr
111 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
111 getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
112
112
113 if not pats:
113 if not pats:
114 raise util.Abort(_('at least one filename or pattern is required'))
114 raise util.Abort(_('at least one filename or pattern is required'))
115
115
116 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
116 opmap = [('user', lambda x: ui.shortuser(x[0].user())),
117 ('number', lambda x: str(x[0].rev())),
117 ('number', lambda x: str(x[0].rev())),
118 ('changeset', lambda x: short(x[0].node())),
118 ('changeset', lambda x: short(x[0].node())),
119 ('date', getdate),
119 ('date', getdate),
120 ('file', lambda x: x[0].path()),
120 ('file', lambda x: x[0].path()),
121 ]
121 ]
122
122
123 if (not opts.get('user') and not opts.get('changeset')
123 if (not opts.get('user') and not opts.get('changeset')
124 and not opts.get('date') and not opts.get('file')):
124 and not opts.get('date') and not opts.get('file')):
125 opts['number'] = 1
125 opts['number'] = 1
126
126
127 linenumber = opts.get('line_number') is not None
127 linenumber = opts.get('line_number') is not None
128 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
128 if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
129 raise util.Abort(_('at least one of -n/-c is required for -l'))
129 raise util.Abort(_('at least one of -n/-c is required for -l'))
130
130
131 funcmap = [func for op, func in opmap if opts.get(op)]
131 funcmap = [func for op, func in opmap if opts.get(op)]
132 if linenumber:
132 if linenumber:
133 lastfunc = funcmap[-1]
133 lastfunc = funcmap[-1]
134 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
134 funcmap[-1] = lambda x: "%s:%s" % (lastfunc(x), x[1])
135
135
136 ctx = repo[opts.get('rev')]
136 ctx = repo[opts.get('rev')]
137 m = cmdutil.match(repo, pats, opts)
137 m = cmdutil.match(repo, pats, opts)
138 follow = not opts.get('no_follow')
138 follow = not opts.get('no_follow')
139 for abs in ctx.walk(m):
139 for abs in ctx.walk(m):
140 fctx = ctx[abs]
140 fctx = ctx[abs]
141 if not opts.get('text') and util.binary(fctx.data()):
141 if not opts.get('text') and util.binary(fctx.data()):
142 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
142 ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
143 continue
143 continue
144
144
145 lines = fctx.annotate(follow=follow, linenumber=linenumber)
145 lines = fctx.annotate(follow=follow, linenumber=linenumber)
146 pieces = []
146 pieces = []
147
147
148 for f in funcmap:
148 for f in funcmap:
149 l = [f(n) for n, dummy in lines]
149 l = [f(n) for n, dummy in lines]
150 if l:
150 if l:
151 ml = max(map(len, l))
151 ml = max(map(len, l))
152 pieces.append(["%*s" % (ml, x) for x in l])
152 pieces.append(["%*s" % (ml, x) for x in l])
153
153
154 if pieces:
154 if pieces:
155 for p, l in zip(zip(*pieces), lines):
155 for p, l in zip(zip(*pieces), lines):
156 ui.write("%s: %s" % (" ".join(p), l[1]))
156 ui.write("%s: %s" % (" ".join(p), l[1]))
157
157
158 def archive(ui, repo, dest, **opts):
158 def archive(ui, repo, dest, **opts):
159 '''create an unversioned archive of a repository revision
159 '''create an unversioned archive of a repository revision
160
160
161 By default, the revision used is the parent of the working
161 By default, the revision used is the parent of the working
162 directory; use -r/--rev to specify a different revision.
162 directory; use -r/--rev to specify a different revision.
163
163
164 The archive type is automatically detected based on file
164 The archive type is automatically detected based on file
165 extension (or override using -t/--type).
165 extension (or override using -t/--type).
166
166
167 Valid types are:
167 Valid types are:
168
168
169 :``files``: a directory full of files (default)
169 :``files``: a directory full of files (default)
170 :``tar``: tar archive, uncompressed
170 :``tar``: tar archive, uncompressed
171 :``tbz2``: tar archive, compressed using bzip2
171 :``tbz2``: tar archive, compressed using bzip2
172 :``tgz``: tar archive, compressed using gzip
172 :``tgz``: tar archive, compressed using gzip
173 :``uzip``: zip archive, uncompressed
173 :``uzip``: zip archive, uncompressed
174 :``zip``: zip archive, compressed using deflate
174 :``zip``: zip archive, compressed using deflate
175
175
176 The exact name of the destination archive or directory is given
176 The exact name of the destination archive or directory is given
177 using a format string; see :hg:`help export` for details.
177 using a format string; see :hg:`help export` for details.
178
178
179 Each member added to an archive file has a directory prefix
179 Each member added to an archive file has a directory prefix
180 prepended. Use -p/--prefix to specify a format string for the
180 prepended. Use -p/--prefix to specify a format string for the
181 prefix. The default is the basename of the archive, with suffixes
181 prefix. The default is the basename of the archive, with suffixes
182 removed.
182 removed.
183
183
184 Returns 0 on success.
184 Returns 0 on success.
185 '''
185 '''
186
186
187 ctx = repo[opts.get('rev')]
187 ctx = repo[opts.get('rev')]
188 if not ctx:
188 if not ctx:
189 raise util.Abort(_('no working directory: please specify a revision'))
189 raise util.Abort(_('no working directory: please specify a revision'))
190 node = ctx.node()
190 node = ctx.node()
191 dest = cmdutil.make_filename(repo, dest, node)
191 dest = cmdutil.make_filename(repo, dest, node)
192 if os.path.realpath(dest) == repo.root:
192 if os.path.realpath(dest) == repo.root:
193 raise util.Abort(_('repository root cannot be destination'))
193 raise util.Abort(_('repository root cannot be destination'))
194
194
195 def guess_type():
195 def guess_type():
196 exttypes = {
196 exttypes = {
197 'tar': ['.tar'],
197 'tar': ['.tar'],
198 'tbz2': ['.tbz2', '.tar.bz2'],
198 'tbz2': ['.tbz2', '.tar.bz2'],
199 'tgz': ['.tgz', '.tar.gz'],
199 'tgz': ['.tgz', '.tar.gz'],
200 'zip': ['.zip'],
200 'zip': ['.zip'],
201 }
201 }
202
202
203 for type, extensions in exttypes.items():
203 for type, extensions in exttypes.items():
204 if util.any(dest.endswith(ext) for ext in extensions):
204 if util.any(dest.endswith(ext) for ext in extensions):
205 return type
205 return type
206 return None
206 return None
207
207
208 kind = opts.get('type') or guess_type() or 'files'
208 kind = opts.get('type') or guess_type() or 'files'
209 prefix = opts.get('prefix')
209 prefix = opts.get('prefix')
210
210
211 if dest == '-':
211 if dest == '-':
212 if kind == 'files':
212 if kind == 'files':
213 raise util.Abort(_('cannot archive plain files to stdout'))
213 raise util.Abort(_('cannot archive plain files to stdout'))
214 dest = sys.stdout
214 dest = sys.stdout
215 if not prefix:
215 if not prefix:
216 prefix = os.path.basename(repo.root) + '-%h'
216 prefix = os.path.basename(repo.root) + '-%h'
217
217
218 prefix = cmdutil.make_filename(repo, prefix, node)
218 prefix = cmdutil.make_filename(repo, prefix, node)
219 matchfn = cmdutil.match(repo, [], opts)
219 matchfn = cmdutil.match(repo, [], opts)
220 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
220 archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
221 matchfn, prefix)
221 matchfn, prefix)
222
222
223 def backout(ui, repo, node=None, rev=None, **opts):
223 def backout(ui, repo, node=None, rev=None, **opts):
224 '''reverse effect of earlier changeset
224 '''reverse effect of earlier changeset
225
225
226 Commit the backed out changes as a new changeset. The new
226 Commit the backed out changes as a new changeset. The new
227 changeset is a child of the backed out changeset.
227 changeset is a child of the backed out changeset.
228
228
229 If you backout a changeset other than the tip, a new head is
229 If you backout a changeset other than the tip, a new head is
230 created. This head will be the new tip and you should merge this
230 created. This head will be the new tip and you should merge this
231 backout changeset with another head.
231 backout changeset with another head.
232
232
233 The --merge option remembers the parent of the working directory
233 The --merge option remembers the parent of the working directory
234 before starting the backout, then merges the new head with that
234 before starting the backout, then merges the new head with that
235 changeset afterwards. This saves you from doing the merge by hand.
235 changeset afterwards. This saves you from doing the merge by hand.
236 The result of this merge is not committed, as with a normal merge.
236 The result of this merge is not committed, as with a normal merge.
237
237
238 See :hg:`help dates` for a list of formats valid for -d/--date.
238 See :hg:`help dates` for a list of formats valid for -d/--date.
239
239
240 Returns 0 on success.
240 Returns 0 on success.
241 '''
241 '''
242 if rev and node:
242 if rev and node:
243 raise util.Abort(_("please specify just one revision"))
243 raise util.Abort(_("please specify just one revision"))
244
244
245 if not rev:
245 if not rev:
246 rev = node
246 rev = node
247
247
248 if not rev:
248 if not rev:
249 raise util.Abort(_("please specify a revision to backout"))
249 raise util.Abort(_("please specify a revision to backout"))
250
250
251 date = opts.get('date')
251 date = opts.get('date')
252 if date:
252 if date:
253 opts['date'] = util.parsedate(date)
253 opts['date'] = util.parsedate(date)
254
254
255 cmdutil.bail_if_changed(repo)
255 cmdutil.bail_if_changed(repo)
256 node = repo.lookup(rev)
256 node = repo.lookup(rev)
257
257
258 op1, op2 = repo.dirstate.parents()
258 op1, op2 = repo.dirstate.parents()
259 a = repo.changelog.ancestor(op1, node)
259 a = repo.changelog.ancestor(op1, node)
260 if a != node:
260 if a != node:
261 raise util.Abort(_('cannot backout change on a different branch'))
261 raise util.Abort(_('cannot backout change on a different branch'))
262
262
263 p1, p2 = repo.changelog.parents(node)
263 p1, p2 = repo.changelog.parents(node)
264 if p1 == nullid:
264 if p1 == nullid:
265 raise util.Abort(_('cannot backout a change with no parents'))
265 raise util.Abort(_('cannot backout a change with no parents'))
266 if p2 != nullid:
266 if p2 != nullid:
267 if not opts.get('parent'):
267 if not opts.get('parent'):
268 raise util.Abort(_('cannot backout a merge changeset without '
268 raise util.Abort(_('cannot backout a merge changeset without '
269 '--parent'))
269 '--parent'))
270 p = repo.lookup(opts['parent'])
270 p = repo.lookup(opts['parent'])
271 if p not in (p1, p2):
271 if p not in (p1, p2):
272 raise util.Abort(_('%s is not a parent of %s') %
272 raise util.Abort(_('%s is not a parent of %s') %
273 (short(p), short(node)))
273 (short(p), short(node)))
274 parent = p
274 parent = p
275 else:
275 else:
276 if opts.get('parent'):
276 if opts.get('parent'):
277 raise util.Abort(_('cannot use --parent on non-merge changeset'))
277 raise util.Abort(_('cannot use --parent on non-merge changeset'))
278 parent = p1
278 parent = p1
279
279
280 # the backout should appear on the same branch
280 # the backout should appear on the same branch
281 branch = repo.dirstate.branch()
281 branch = repo.dirstate.branch()
282 hg.clean(repo, node, show_stats=False)
282 hg.clean(repo, node, show_stats=False)
283 repo.dirstate.setbranch(branch)
283 repo.dirstate.setbranch(branch)
284 revert_opts = opts.copy()
284 revert_opts = opts.copy()
285 revert_opts['date'] = None
285 revert_opts['date'] = None
286 revert_opts['all'] = True
286 revert_opts['all'] = True
287 revert_opts['rev'] = hex(parent)
287 revert_opts['rev'] = hex(parent)
288 revert_opts['no_backup'] = None
288 revert_opts['no_backup'] = None
289 revert(ui, repo, **revert_opts)
289 revert(ui, repo, **revert_opts)
290 commit_opts = opts.copy()
290 commit_opts = opts.copy()
291 commit_opts['addremove'] = False
291 commit_opts['addremove'] = False
292 if not commit_opts['message'] and not commit_opts['logfile']:
292 if not commit_opts['message'] and not commit_opts['logfile']:
293 # we don't translate commit messages
293 # we don't translate commit messages
294 commit_opts['message'] = "Backed out changeset %s" % short(node)
294 commit_opts['message'] = "Backed out changeset %s" % short(node)
295 commit_opts['force_editor'] = True
295 commit_opts['force_editor'] = True
296 commit(ui, repo, **commit_opts)
296 commit(ui, repo, **commit_opts)
297 def nice(node):
297 def nice(node):
298 return '%d:%s' % (repo.changelog.rev(node), short(node))
298 return '%d:%s' % (repo.changelog.rev(node), short(node))
299 ui.status(_('changeset %s backs out changeset %s\n') %
299 ui.status(_('changeset %s backs out changeset %s\n') %
300 (nice(repo.changelog.tip()), nice(node)))
300 (nice(repo.changelog.tip()), nice(node)))
301 if op1 != node:
301 if op1 != node:
302 hg.clean(repo, op1, show_stats=False)
302 hg.clean(repo, op1, show_stats=False)
303 if opts.get('merge'):
303 if opts.get('merge'):
304 ui.status(_('merging with changeset %s\n')
304 ui.status(_('merging with changeset %s\n')
305 % nice(repo.changelog.tip()))
305 % nice(repo.changelog.tip()))
306 hg.merge(repo, hex(repo.changelog.tip()))
306 hg.merge(repo, hex(repo.changelog.tip()))
307 else:
307 else:
308 ui.status(_('the backout changeset is a new head - '
308 ui.status(_('the backout changeset is a new head - '
309 'do not forget to merge\n'))
309 'do not forget to merge\n'))
310 ui.status(_('(use "backout --merge" '
310 ui.status(_('(use "backout --merge" '
311 'if you want to auto-merge)\n'))
311 'if you want to auto-merge)\n'))
312
312
313 def bisect(ui, repo, rev=None, extra=None, command=None,
313 def bisect(ui, repo, rev=None, extra=None, command=None,
314 reset=None, good=None, bad=None, skip=None, noupdate=None):
314 reset=None, good=None, bad=None, skip=None, noupdate=None):
315 """subdivision search of changesets
315 """subdivision search of changesets
316
316
317 This command helps to find changesets which introduce problems. To
317 This command helps to find changesets which introduce problems. To
318 use, mark the earliest changeset you know exhibits the problem as
318 use, mark the earliest changeset you know exhibits the problem as
319 bad, then mark the latest changeset which is free from the problem
319 bad, then mark the latest changeset which is free from the problem
320 as good. Bisect will update your working directory to a revision
320 as good. Bisect will update your working directory to a revision
321 for testing (unless the -U/--noupdate option is specified). Once
321 for testing (unless the -U/--noupdate option is specified). Once
322 you have performed tests, mark the working directory as good or
322 you have performed tests, mark the working directory as good or
323 bad, and bisect will either update to another candidate changeset
323 bad, and bisect will either update to another candidate changeset
324 or announce that it has found the bad revision.
324 or announce that it has found the bad revision.
325
325
326 As a shortcut, you can also use the revision argument to mark a
326 As a shortcut, you can also use the revision argument to mark a
327 revision as good or bad without checking it out first.
327 revision as good or bad without checking it out first.
328
328
329 If you supply a command, it will be used for automatic bisection.
329 If you supply a command, it will be used for automatic bisection.
330 Its exit status will be used to mark revisions as good or bad:
330 Its exit status will be used to mark revisions as good or bad:
331 status 0 means good, 125 means to skip the revision, 127
331 status 0 means good, 125 means to skip the revision, 127
332 (command not found) will abort the bisection, and any other
332 (command not found) will abort the bisection, and any other
333 non-zero exit status means the revision is bad.
333 non-zero exit status means the revision is bad.
334
334
335 Returns 0 on success.
335 Returns 0 on success.
336 """
336 """
337 def print_result(nodes, good):
337 def print_result(nodes, good):
338 displayer = cmdutil.show_changeset(ui, repo, {})
338 displayer = cmdutil.show_changeset(ui, repo, {})
339 if len(nodes) == 1:
339 if len(nodes) == 1:
340 # narrowed it down to a single revision
340 # narrowed it down to a single revision
341 if good:
341 if good:
342 ui.write(_("The first good revision is:\n"))
342 ui.write(_("The first good revision is:\n"))
343 else:
343 else:
344 ui.write(_("The first bad revision is:\n"))
344 ui.write(_("The first bad revision is:\n"))
345 displayer.show(repo[nodes[0]])
345 displayer.show(repo[nodes[0]])
346 else:
346 else:
347 # multiple possible revisions
347 # multiple possible revisions
348 if good:
348 if good:
349 ui.write(_("Due to skipped revisions, the first "
349 ui.write(_("Due to skipped revisions, the first "
350 "good revision could be any of:\n"))
350 "good revision could be any of:\n"))
351 else:
351 else:
352 ui.write(_("Due to skipped revisions, the first "
352 ui.write(_("Due to skipped revisions, the first "
353 "bad revision could be any of:\n"))
353 "bad revision could be any of:\n"))
354 for n in nodes:
354 for n in nodes:
355 displayer.show(repo[n])
355 displayer.show(repo[n])
356 displayer.close()
356 displayer.close()
357
357
358 def check_state(state, interactive=True):
358 def check_state(state, interactive=True):
359 if not state['good'] or not state['bad']:
359 if not state['good'] or not state['bad']:
360 if (good or bad or skip or reset) and interactive:
360 if (good or bad or skip or reset) and interactive:
361 return
361 return
362 if not state['good']:
362 if not state['good']:
363 raise util.Abort(_('cannot bisect (no known good revisions)'))
363 raise util.Abort(_('cannot bisect (no known good revisions)'))
364 else:
364 else:
365 raise util.Abort(_('cannot bisect (no known bad revisions)'))
365 raise util.Abort(_('cannot bisect (no known bad revisions)'))
366 return True
366 return True
367
367
368 # backward compatibility
368 # backward compatibility
369 if rev in "good bad reset init".split():
369 if rev in "good bad reset init".split():
370 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
370 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
371 cmd, rev, extra = rev, extra, None
371 cmd, rev, extra = rev, extra, None
372 if cmd == "good":
372 if cmd == "good":
373 good = True
373 good = True
374 elif cmd == "bad":
374 elif cmd == "bad":
375 bad = True
375 bad = True
376 else:
376 else:
377 reset = True
377 reset = True
378 elif extra or good + bad + skip + reset + bool(command) > 1:
378 elif extra or good + bad + skip + reset + bool(command) > 1:
379 raise util.Abort(_('incompatible arguments'))
379 raise util.Abort(_('incompatible arguments'))
380
380
381 if reset:
381 if reset:
382 p = repo.join("bisect.state")
382 p = repo.join("bisect.state")
383 if os.path.exists(p):
383 if os.path.exists(p):
384 os.unlink(p)
384 os.unlink(p)
385 return
385 return
386
386
387 state = hbisect.load_state(repo)
387 state = hbisect.load_state(repo)
388
388
389 if command:
389 if command:
390 changesets = 1
390 changesets = 1
391 try:
391 try:
392 while changesets:
392 while changesets:
393 # update state
393 # update state
394 status = util.system(command)
394 status = util.system(command)
395 if status == 125:
395 if status == 125:
396 transition = "skip"
396 transition = "skip"
397 elif status == 0:
397 elif status == 0:
398 transition = "good"
398 transition = "good"
399 # status < 0 means process was killed
399 # status < 0 means process was killed
400 elif status == 127:
400 elif status == 127:
401 raise util.Abort(_("failed to execute %s") % command)
401 raise util.Abort(_("failed to execute %s") % command)
402 elif status < 0:
402 elif status < 0:
403 raise util.Abort(_("%s killed") % command)
403 raise util.Abort(_("%s killed") % command)
404 else:
404 else:
405 transition = "bad"
405 transition = "bad"
406 ctx = repo[rev or '.']
406 ctx = repo[rev or '.']
407 state[transition].append(ctx.node())
407 state[transition].append(ctx.node())
408 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
408 ui.status(_('Changeset %d:%s: %s\n') % (ctx, ctx, transition))
409 check_state(state, interactive=False)
409 check_state(state, interactive=False)
410 # bisect
410 # bisect
411 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
411 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
412 # update to next check
412 # update to next check
413 cmdutil.bail_if_changed(repo)
413 cmdutil.bail_if_changed(repo)
414 hg.clean(repo, nodes[0], show_stats=False)
414 hg.clean(repo, nodes[0], show_stats=False)
415 finally:
415 finally:
416 hbisect.save_state(repo, state)
416 hbisect.save_state(repo, state)
417 print_result(nodes, good)
417 print_result(nodes, good)
418 return
418 return
419
419
420 # update state
420 # update state
421 node = repo.lookup(rev or '.')
421 node = repo.lookup(rev or '.')
422 if good or bad or skip:
422 if good or bad or skip:
423 if good:
423 if good:
424 state['good'].append(node)
424 state['good'].append(node)
425 elif bad:
425 elif bad:
426 state['bad'].append(node)
426 state['bad'].append(node)
427 elif skip:
427 elif skip:
428 state['skip'].append(node)
428 state['skip'].append(node)
429 hbisect.save_state(repo, state)
429 hbisect.save_state(repo, state)
430
430
431 if not check_state(state):
431 if not check_state(state):
432 return
432 return
433
433
434 # actually bisect
434 # actually bisect
435 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
435 nodes, changesets, good = hbisect.bisect(repo.changelog, state)
436 if changesets == 0:
436 if changesets == 0:
437 print_result(nodes, good)
437 print_result(nodes, good)
438 else:
438 else:
439 assert len(nodes) == 1 # only a single node can be tested next
439 assert len(nodes) == 1 # only a single node can be tested next
440 node = nodes[0]
440 node = nodes[0]
441 # compute the approximate number of remaining tests
441 # compute the approximate number of remaining tests
442 tests, size = 0, 2
442 tests, size = 0, 2
443 while size <= changesets:
443 while size <= changesets:
444 tests, size = tests + 1, size * 2
444 tests, size = tests + 1, size * 2
445 rev = repo.changelog.rev(node)
445 rev = repo.changelog.rev(node)
446 ui.write(_("Testing changeset %d:%s "
446 ui.write(_("Testing changeset %d:%s "
447 "(%d changesets remaining, ~%d tests)\n")
447 "(%d changesets remaining, ~%d tests)\n")
448 % (rev, short(node), changesets, tests))
448 % (rev, short(node), changesets, tests))
449 if not noupdate:
449 if not noupdate:
450 cmdutil.bail_if_changed(repo)
450 cmdutil.bail_if_changed(repo)
451 return hg.clean(repo, node)
451 return hg.clean(repo, node)
452
452
453 def branch(ui, repo, label=None, **opts):
453 def branch(ui, repo, label=None, **opts):
454 """set or show the current branch name
454 """set or show the current branch name
455
455
456 With no argument, show the current branch name. With one argument,
456 With no argument, show the current branch name. With one argument,
457 set the working directory branch name (the branch will not exist
457 set the working directory branch name (the branch will not exist
458 in the repository until the next commit). Standard practice
458 in the repository until the next commit). Standard practice
459 recommends that primary development take place on the 'default'
459 recommends that primary development take place on the 'default'
460 branch.
460 branch.
461
461
462 Unless -f/--force is specified, branch will not let you set a
462 Unless -f/--force is specified, branch will not let you set a
463 branch name that already exists, even if it's inactive.
463 branch name that already exists, even if it's inactive.
464
464
465 Use -C/--clean to reset the working directory branch to that of
465 Use -C/--clean to reset the working directory branch to that of
466 the parent of the working directory, negating a previous branch
466 the parent of the working directory, negating a previous branch
467 change.
467 change.
468
468
469 Use the command :hg:`update` to switch to an existing branch. Use
469 Use the command :hg:`update` to switch to an existing branch. Use
470 :hg:`commit --close-branch` to mark this branch as closed.
470 :hg:`commit --close-branch` to mark this branch as closed.
471
471
472 Returns 0 on success.
472 Returns 0 on success.
473 """
473 """
474
474
475 if opts.get('clean'):
475 if opts.get('clean'):
476 label = repo[None].parents()[0].branch()
476 label = repo[None].parents()[0].branch()
477 repo.dirstate.setbranch(label)
477 repo.dirstate.setbranch(label)
478 ui.status(_('reset working directory to branch %s\n') % label)
478 ui.status(_('reset working directory to branch %s\n') % label)
479 elif label:
479 elif label:
480 utflabel = encoding.fromlocal(label)
480 utflabel = encoding.fromlocal(label)
481 if not opts.get('force') and utflabel in repo.branchtags():
481 if not opts.get('force') and utflabel in repo.branchtags():
482 if label not in [p.branch() for p in repo.parents()]:
482 if label not in [p.branch() for p in repo.parents()]:
483 raise util.Abort(_('a branch of the same name already exists'
483 raise util.Abort(_('a branch of the same name already exists'
484 " (use 'hg update' to switch to it)"))
484 " (use 'hg update' to switch to it)"))
485 repo.dirstate.setbranch(utflabel)
485 repo.dirstate.setbranch(utflabel)
486 ui.status(_('marked working directory as branch %s\n') % label)
486 ui.status(_('marked working directory as branch %s\n') % label)
487 else:
487 else:
488 ui.write("%s\n" % encoding.tolocal(repo.dirstate.branch()))
488 ui.write("%s\n" % encoding.tolocal(repo.dirstate.branch()))
489
489
490 def branches(ui, repo, active=False, closed=False):
490 def branches(ui, repo, active=False, closed=False):
491 """list repository named branches
491 """list repository named branches
492
492
493 List the repository's named branches, indicating which ones are
493 List the repository's named branches, indicating which ones are
494 inactive. If -c/--closed is specified, also list branches which have
494 inactive. If -c/--closed is specified, also list branches which have
495 been marked closed (see :hg:`commit --close-branch`).
495 been marked closed (see :hg:`commit --close-branch`).
496
496
497 If -a/--active is specified, only show active branches. A branch
497 If -a/--active is specified, only show active branches. A branch
498 is considered active if it contains repository heads.
498 is considered active if it contains repository heads.
499
499
500 Use the command :hg:`update` to switch to an existing branch.
500 Use the command :hg:`update` to switch to an existing branch.
501
501
502 Returns 0.
502 Returns 0.
503 """
503 """
504
504
505 hexfunc = ui.debugflag and hex or short
505 hexfunc = ui.debugflag and hex or short
506 activebranches = [repo[n].branch() for n in repo.heads()]
506 activebranches = [repo[n].branch() for n in repo.heads()]
507 def testactive(tag, node):
507 def testactive(tag, node):
508 realhead = tag in activebranches
508 realhead = tag in activebranches
509 open = node in repo.branchheads(tag, closed=False)
509 open = node in repo.branchheads(tag, closed=False)
510 return realhead and open
510 return realhead and open
511 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
511 branches = sorted([(testactive(tag, node), repo.changelog.rev(node), tag)
512 for tag, node in repo.branchtags().items()],
512 for tag, node in repo.branchtags().items()],
513 reverse=True)
513 reverse=True)
514
514
515 for isactive, node, tag in branches:
515 for isactive, node, tag in branches:
516 if (not active) or isactive:
516 if (not active) or isactive:
517 encodedtag = encoding.tolocal(tag)
517 encodedtag = encoding.tolocal(tag)
518 if ui.quiet:
518 if ui.quiet:
519 ui.write("%s\n" % encodedtag)
519 ui.write("%s\n" % encodedtag)
520 else:
520 else:
521 hn = repo.lookup(node)
521 hn = repo.lookup(node)
522 if isactive:
522 if isactive:
523 notice = ''
523 notice = ''
524 elif hn not in repo.branchheads(tag, closed=False):
524 elif hn not in repo.branchheads(tag, closed=False):
525 if not closed:
525 if not closed:
526 continue
526 continue
527 notice = _(' (closed)')
527 notice = _(' (closed)')
528 else:
528 else:
529 notice = _(' (inactive)')
529 notice = _(' (inactive)')
530 rev = str(node).rjust(31 - encoding.colwidth(encodedtag))
530 rev = str(node).rjust(31 - encoding.colwidth(encodedtag))
531 data = encodedtag, rev, hexfunc(hn), notice
531 data = encodedtag, rev, hexfunc(hn), notice
532 ui.write("%s %s:%s%s\n" % data)
532 ui.write("%s %s:%s%s\n" % data)
533
533
534 def bundle(ui, repo, fname, dest=None, **opts):
534 def bundle(ui, repo, fname, dest=None, **opts):
535 """create a changegroup file
535 """create a changegroup file
536
536
537 Generate a compressed changegroup file collecting changesets not
537 Generate a compressed changegroup file collecting changesets not
538 known to be in another repository.
538 known to be in another repository.
539
539
540 If you omit the destination repository, then hg assumes the
540 If you omit the destination repository, then hg assumes the
541 destination will have all the nodes you specify with --base
541 destination will have all the nodes you specify with --base
542 parameters. To create a bundle containing all changesets, use
542 parameters. To create a bundle containing all changesets, use
543 -a/--all (or --base null).
543 -a/--all (or --base null).
544
544
545 You can change compression method with the -t/--type option.
545 You can change compression method with the -t/--type option.
546 The available compression methods are: none, bzip2, and
546 The available compression methods are: none, bzip2, and
547 gzip (by default, bundles are compressed using bzip2).
547 gzip (by default, bundles are compressed using bzip2).
548
548
549 The bundle file can then be transferred using conventional means
549 The bundle file can then be transferred using conventional means
550 and applied to another repository with the unbundle or pull
550 and applied to another repository with the unbundle or pull
551 command. This is useful when direct push and pull are not
551 command. This is useful when direct push and pull are not
552 available or when exporting an entire repository is undesirable.
552 available or when exporting an entire repository is undesirable.
553
553
554 Applying bundles preserves all changeset contents including
554 Applying bundles preserves all changeset contents including
555 permissions, copy/rename information, and revision history.
555 permissions, copy/rename information, and revision history.
556
556
557 Returns 0 on success, 1 if no changes found.
557 Returns 0 on success, 1 if no changes found.
558 """
558 """
559 revs = opts.get('rev') or None
559 revs = opts.get('rev') or None
560 if revs:
560 if revs:
561 revs = [repo.lookup(rev) for rev in revs]
561 revs = [repo.lookup(rev) for rev in revs]
562 if opts.get('all'):
562 if opts.get('all'):
563 base = ['null']
563 base = ['null']
564 else:
564 else:
565 base = opts.get('base')
565 base = opts.get('base')
566 if base:
566 if base:
567 if dest:
567 if dest:
568 raise util.Abort(_("--base is incompatible with specifying "
568 raise util.Abort(_("--base is incompatible with specifying "
569 "a destination"))
569 "a destination"))
570 base = [repo.lookup(rev) for rev in base]
570 base = [repo.lookup(rev) for rev in base]
571 # create the right base
571 # create the right base
572 # XXX: nodesbetween / changegroup* should be "fixed" instead
572 # XXX: nodesbetween / changegroup* should be "fixed" instead
573 o = []
573 o = []
574 has = set((nullid,))
574 has = set((nullid,))
575 for n in base:
575 for n in base:
576 has.update(repo.changelog.reachable(n))
576 has.update(repo.changelog.reachable(n))
577 if revs:
577 if revs:
578 visit = list(revs)
578 visit = list(revs)
579 has.difference_update(revs)
579 has.difference_update(revs)
580 else:
580 else:
581 visit = repo.changelog.heads()
581 visit = repo.changelog.heads()
582 seen = {}
582 seen = {}
583 while visit:
583 while visit:
584 n = visit.pop(0)
584 n = visit.pop(0)
585 parents = [p for p in repo.changelog.parents(n) if p not in has]
585 parents = [p for p in repo.changelog.parents(n) if p not in has]
586 if len(parents) == 0:
586 if len(parents) == 0:
587 if n not in has:
587 if n not in has:
588 o.append(n)
588 o.append(n)
589 else:
589 else:
590 for p in parents:
590 for p in parents:
591 if p not in seen:
591 if p not in seen:
592 seen[p] = 1
592 seen[p] = 1
593 visit.append(p)
593 visit.append(p)
594 else:
594 else:
595 dest = ui.expandpath(dest or 'default-push', dest or 'default')
595 dest = ui.expandpath(dest or 'default-push', dest or 'default')
596 dest, branches = hg.parseurl(dest, opts.get('branch'))
596 dest, branches = hg.parseurl(dest, opts.get('branch'))
597 other = hg.repository(hg.remoteui(repo, opts), dest)
597 other = hg.repository(hg.remoteui(repo, opts), dest)
598 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
598 revs, checkout = hg.addbranchrevs(repo, other, branches, revs)
599 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
599 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
600
600
601 if not o:
601 if not o:
602 ui.status(_("no changes found\n"))
602 ui.status(_("no changes found\n"))
603 return 1
603 return 1
604
604
605 if revs:
605 if revs:
606 cg = repo.changegroupsubset(o, revs, 'bundle')
606 cg = repo.changegroupsubset(o, revs, 'bundle')
607 else:
607 else:
608 cg = repo.changegroup(o, 'bundle')
608 cg = repo.changegroup(o, 'bundle')
609
609
610 bundletype = opts.get('type', 'bzip2').lower()
610 bundletype = opts.get('type', 'bzip2').lower()
611 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
611 btypes = {'none': 'HG10UN', 'bzip2': 'HG10BZ', 'gzip': 'HG10GZ'}
612 bundletype = btypes.get(bundletype)
612 bundletype = btypes.get(bundletype)
613 if bundletype not in changegroup.bundletypes:
613 if bundletype not in changegroup.bundletypes:
614 raise util.Abort(_('unknown bundle type specified with --type'))
614 raise util.Abort(_('unknown bundle type specified with --type'))
615
615
616 changegroup.writebundle(cg, fname, bundletype)
616 changegroup.writebundle(cg, fname, bundletype)
617
617
618 def cat(ui, repo, file1, *pats, **opts):
618 def cat(ui, repo, file1, *pats, **opts):
619 """output the current or given revision of files
619 """output the current or given revision of files
620
620
621 Print the specified files as they were at the given revision. If
621 Print the specified files as they were at the given revision. If
622 no revision is given, the parent of the working directory is used,
622 no revision is given, the parent of the working directory is used,
623 or tip if no revision is checked out.
623 or tip if no revision is checked out.
624
624
625 Output may be to a file, in which case the name of the file is
625 Output may be to a file, in which case the name of the file is
626 given using a format string. The formatting rules are the same as
626 given using a format string. The formatting rules are the same as
627 for the export command, with the following additions:
627 for the export command, with the following additions:
628
628
629 :``%s``: basename of file being printed
629 :``%s``: basename of file being printed
630 :``%d``: dirname of file being printed, or '.' if in repository root
630 :``%d``: dirname of file being printed, or '.' if in repository root
631 :``%p``: root-relative path name of file being printed
631 :``%p``: root-relative path name of file being printed
632
632
633 Returns 0 on success.
633 Returns 0 on success.
634 """
634 """
635 ctx = repo[opts.get('rev')]
635 ctx = repo[opts.get('rev')]
636 err = 1
636 err = 1
637 m = cmdutil.match(repo, (file1,) + pats, opts)
637 m = cmdutil.match(repo, (file1,) + pats, opts)
638 for abs in ctx.walk(m):
638 for abs in ctx.walk(m):
639 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
639 fp = cmdutil.make_file(repo, opts.get('output'), ctx.node(), pathname=abs)
640 data = ctx[abs].data()
640 data = ctx[abs].data()
641 if opts.get('decode'):
641 if opts.get('decode'):
642 data = repo.wwritedata(abs, data)
642 data = repo.wwritedata(abs, data)
643 fp.write(data)
643 fp.write(data)
644 err = 0
644 err = 0
645 return err
645 return err
646
646
647 def clone(ui, source, dest=None, **opts):
647 def clone(ui, source, dest=None, **opts):
648 """make a copy of an existing repository
648 """make a copy of an existing repository
649
649
650 Create a copy of an existing repository in a new directory.
650 Create a copy of an existing repository in a new directory.
651
651
652 If no destination directory name is specified, it defaults to the
652 If no destination directory name is specified, it defaults to the
653 basename of the source.
653 basename of the source.
654
654
655 The location of the source is added to the new repository's
655 The location of the source is added to the new repository's
656 .hg/hgrc file, as the default to be used for future pulls.
656 .hg/hgrc file, as the default to be used for future pulls.
657
657
658 See :hg:`help urls` for valid source format details.
658 See :hg:`help urls` for valid source format details.
659
659
660 It is possible to specify an ``ssh://`` URL as the destination, but no
660 It is possible to specify an ``ssh://`` URL as the destination, but no
661 .hg/hgrc and working directory will be created on the remote side.
661 .hg/hgrc and working directory will be created on the remote side.
662 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
662 Please see :hg:`help urls` for important details about ``ssh://`` URLs.
663
663
664 A set of changesets (tags, or branch names) to pull may be specified
664 A set of changesets (tags, or branch names) to pull may be specified
665 by listing each changeset (tag, or branch name) with -r/--rev.
665 by listing each changeset (tag, or branch name) with -r/--rev.
666 If -r/--rev is used, the cloned repository will contain only a subset
666 If -r/--rev is used, the cloned repository will contain only a subset
667 of the changesets of the source repository. Only the set of changesets
667 of the changesets of the source repository. Only the set of changesets
668 defined by all -r/--rev options (including all their ancestors)
668 defined by all -r/--rev options (including all their ancestors)
669 will be pulled into the destination repository.
669 will be pulled into the destination repository.
670 No subsequent changesets (including subsequent tags) will be present
670 No subsequent changesets (including subsequent tags) will be present
671 in the destination.
671 in the destination.
672
672
673 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
673 Using -r/--rev (or 'clone src#rev dest') implies --pull, even for
674 local source repositories.
674 local source repositories.
675
675
676 For efficiency, hardlinks are used for cloning whenever the source
676 For efficiency, hardlinks are used for cloning whenever the source
677 and destination are on the same filesystem (note this applies only
677 and destination are on the same filesystem (note this applies only
678 to the repository data, not to the working directory). Some
678 to the repository data, not to the working directory). Some
679 filesystems, such as AFS, implement hardlinking incorrectly, but
679 filesystems, such as AFS, implement hardlinking incorrectly, but
680 do not report errors. In these cases, use the --pull option to
680 do not report errors. In these cases, use the --pull option to
681 avoid hardlinking.
681 avoid hardlinking.
682
682
683 In some cases, you can clone repositories and the working directory
683 In some cases, you can clone repositories and the working directory
684 using full hardlinks with ::
684 using full hardlinks with ::
685
685
686 $ cp -al REPO REPOCLONE
686 $ cp -al REPO REPOCLONE
687
687
688 This is the fastest way to clone, but it is not always safe. The
688 This is the fastest way to clone, but it is not always safe. The
689 operation is not atomic (making sure REPO is not modified during
689 operation is not atomic (making sure REPO is not modified during
690 the operation is up to you) and you have to make sure your editor
690 the operation is up to you) and you have to make sure your editor
691 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
691 breaks hardlinks (Emacs and most Linux Kernel tools do so). Also,
692 this is not compatible with certain extensions that place their
692 this is not compatible with certain extensions that place their
693 metadata under the .hg directory, such as mq.
693 metadata under the .hg directory, such as mq.
694
694
695 Mercurial will update the working directory to the first applicable
695 Mercurial will update the working directory to the first applicable
696 revision from this list:
696 revision from this list:
697
697
698 a) null if -U or the source repository has no changesets
698 a) null if -U or the source repository has no changesets
699 b) if -u . and the source repository is local, the first parent of
699 b) if -u . and the source repository is local, the first parent of
700 the source repository's working directory
700 the source repository's working directory
701 c) the changeset specified with -u (if a branch name, this means the
701 c) the changeset specified with -u (if a branch name, this means the
702 latest head of that branch)
702 latest head of that branch)
703 d) the changeset specified with -r
703 d) the changeset specified with -r
704 e) the tipmost head specified with -b
704 e) the tipmost head specified with -b
705 f) the tipmost head specified with the url#branch source syntax
705 f) the tipmost head specified with the url#branch source syntax
706 g) the tipmost head of the default branch
706 g) the tipmost head of the default branch
707 h) tip
707 h) tip
708
708
709 Returns 0 on success.
709 Returns 0 on success.
710 """
710 """
711 if opts.get('noupdate') and opts.get('updaterev'):
711 if opts.get('noupdate') and opts.get('updaterev'):
712 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
712 raise util.Abort(_("cannot specify both --noupdate and --updaterev"))
713
713
714 r = hg.clone(hg.remoteui(ui, opts), source, dest,
714 r = hg.clone(hg.remoteui(ui, opts), source, dest,
715 pull=opts.get('pull'),
715 pull=opts.get('pull'),
716 stream=opts.get('uncompressed'),
716 stream=opts.get('uncompressed'),
717 rev=opts.get('rev'),
717 rev=opts.get('rev'),
718 update=opts.get('updaterev') or not opts.get('noupdate'),
718 update=opts.get('updaterev') or not opts.get('noupdate'),
719 branch=opts.get('branch'))
719 branch=opts.get('branch'))
720
720
721 return r is None
721 return r is None
722
722
723 def commit(ui, repo, *pats, **opts):
723 def commit(ui, repo, *pats, **opts):
724 """commit the specified files or all outstanding changes
724 """commit the specified files or all outstanding changes
725
725
726 Commit changes to the given files into the repository. Unlike a
726 Commit changes to the given files into the repository. Unlike a
727 centralized RCS, this operation is a local operation. See
727 centralized RCS, this operation is a local operation. See
728 :hg:`push` for a way to actively distribute your changes.
728 :hg:`push` for a way to actively distribute your changes.
729
729
730 If a list of files is omitted, all changes reported by :hg:`status`
730 If a list of files is omitted, all changes reported by :hg:`status`
731 will be committed.
731 will be committed.
732
732
733 If you are committing the result of a merge, do not provide any
733 If you are committing the result of a merge, do not provide any
734 filenames or -I/-X filters.
734 filenames or -I/-X filters.
735
735
736 If no commit message is specified, the configured editor is
736 If no commit message is specified, the configured editor is
737 started to prompt you for a message.
737 started to prompt you for a message.
738
738
739 See :hg:`help dates` for a list of formats valid for -d/--date.
739 See :hg:`help dates` for a list of formats valid for -d/--date.
740
740
741 Returns 0 on success, 1 if nothing changed.
741 Returns 0 on success, 1 if nothing changed.
742 """
742 """
743 extra = {}
743 extra = {}
744 if opts.get('close_branch'):
744 if opts.get('close_branch'):
745 if repo['.'].node() not in repo.branchheads():
745 if repo['.'].node() not in repo.branchheads():
746 # The topo heads set is included in the branch heads set of the
746 # The topo heads set is included in the branch heads set of the
747 # current branch, so it's sufficient to test branchheads
747 # current branch, so it's sufficient to test branchheads
748 raise util.Abort(_('can only close branch heads'))
748 raise util.Abort(_('can only close branch heads'))
749 extra['close'] = 1
749 extra['close'] = 1
750 e = cmdutil.commiteditor
750 e = cmdutil.commiteditor
751 if opts.get('force_editor'):
751 if opts.get('force_editor'):
752 e = cmdutil.commitforceeditor
752 e = cmdutil.commitforceeditor
753
753
754 def commitfunc(ui, repo, message, match, opts):
754 def commitfunc(ui, repo, message, match, opts):
755 return repo.commit(message, opts.get('user'), opts.get('date'), match,
755 return repo.commit(message, opts.get('user'), opts.get('date'), match,
756 editor=e, extra=extra)
756 editor=e, extra=extra)
757
757
758 branch = repo[None].branch()
758 branch = repo[None].branch()
759 bheads = repo.branchheads(branch)
759 bheads = repo.branchheads(branch)
760
760
761 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
761 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
762 if not node:
762 if not node:
763 ui.status(_("nothing changed\n"))
763 ui.status(_("nothing changed\n"))
764 return 1
764 return 1
765
765
766 ctx = repo[node]
766 ctx = repo[node]
767 parents = ctx.parents()
767 parents = ctx.parents()
768
768
769 if bheads and not [x for x in parents
769 if bheads and not [x for x in parents
770 if x.node() in bheads and x.branch() == branch]:
770 if x.node() in bheads and x.branch() == branch]:
771 ui.status(_('created new head\n'))
771 ui.status(_('created new head\n'))
772 # The message is not printed for initial roots. For the other
772 # The message is not printed for initial roots. For the other
773 # changesets, it is printed in the following situations:
773 # changesets, it is printed in the following situations:
774 #
774 #
775 # Par column: for the 2 parents with ...
775 # Par column: for the 2 parents with ...
776 # N: null or no parent
776 # N: null or no parent
777 # B: parent is on another named branch
777 # B: parent is on another named branch
778 # C: parent is a regular non head changeset
778 # C: parent is a regular non head changeset
779 # H: parent was a branch head of the current branch
779 # H: parent was a branch head of the current branch
780 # Msg column: whether we print "created new head" message
780 # Msg column: whether we print "created new head" message
781 # In the following, it is assumed that there already exists some
781 # In the following, it is assumed that there already exists some
782 # initial branch heads of the current branch, otherwise nothing is
782 # initial branch heads of the current branch, otherwise nothing is
783 # printed anyway.
783 # printed anyway.
784 #
784 #
785 # Par Msg Comment
785 # Par Msg Comment
786 # NN y additional topo root
786 # NN y additional topo root
787 #
787 #
788 # BN y additional branch root
788 # BN y additional branch root
789 # CN y additional topo head
789 # CN y additional topo head
790 # HN n usual case
790 # HN n usual case
791 #
791 #
792 # BB y weird additional branch root
792 # BB y weird additional branch root
793 # CB y branch merge
793 # CB y branch merge
794 # HB n merge with named branch
794 # HB n merge with named branch
795 #
795 #
796 # CC y additional head from merge
796 # CC y additional head from merge
797 # CH n merge with a head
797 # CH n merge with a head
798 #
798 #
799 # HH n head merge: head count decreases
799 # HH n head merge: head count decreases
800
800
801 if not opts.get('close_branch'):
801 if not opts.get('close_branch'):
802 for r in parents:
802 for r in parents:
803 if r.extra().get('close'):
803 if r.extra().get('close'):
804 ui.status(_('reopening closed branch head %d\n') % r)
804 ui.status(_('reopening closed branch head %d\n') % r)
805
805
806 if ui.debugflag:
806 if ui.debugflag:
807 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
807 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
808 elif ui.verbose:
808 elif ui.verbose:
809 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
809 ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
810
810
811 def copy(ui, repo, *pats, **opts):
811 def copy(ui, repo, *pats, **opts):
812 """mark files as copied for the next commit
812 """mark files as copied for the next commit
813
813
814 Mark dest as having copies of source files. If dest is a
814 Mark dest as having copies of source files. If dest is a
815 directory, copies are put in that directory. If dest is a file,
815 directory, copies are put in that directory. If dest is a file,
816 the source must be a single file.
816 the source must be a single file.
817
817
818 By default, this command copies the contents of files as they
818 By default, this command copies the contents of files as they
819 exist in the working directory. If invoked with -A/--after, the
819 exist in the working directory. If invoked with -A/--after, the
820 operation is recorded, but no copying is performed.
820 operation is recorded, but no copying is performed.
821
821
822 This command takes effect with the next commit. To undo a copy
822 This command takes effect with the next commit. To undo a copy
823 before that, see :hg:`revert`.
823 before that, see :hg:`revert`.
824
824
825 Returns 0 on success, 1 if errors are encountered.
825 Returns 0 on success, 1 if errors are encountered.
826 """
826 """
827 wlock = repo.wlock(False)
827 wlock = repo.wlock(False)
828 try:
828 try:
829 return cmdutil.copy(ui, repo, pats, opts)
829 return cmdutil.copy(ui, repo, pats, opts)
830 finally:
830 finally:
831 wlock.release()
831 wlock.release()
832
832
833 def debugancestor(ui, repo, *args):
833 def debugancestor(ui, repo, *args):
834 """find the ancestor revision of two revisions in a given index"""
834 """find the ancestor revision of two revisions in a given index"""
835 if len(args) == 3:
835 if len(args) == 3:
836 index, rev1, rev2 = args
836 index, rev1, rev2 = args
837 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
837 r = revlog.revlog(util.opener(os.getcwd(), audit=False), index)
838 lookup = r.lookup
838 lookup = r.lookup
839 elif len(args) == 2:
839 elif len(args) == 2:
840 if not repo:
840 if not repo:
841 raise util.Abort(_("There is no Mercurial repository here "
841 raise util.Abort(_("There is no Mercurial repository here "
842 "(.hg not found)"))
842 "(.hg not found)"))
843 rev1, rev2 = args
843 rev1, rev2 = args
844 r = repo.changelog
844 r = repo.changelog
845 lookup = repo.lookup
845 lookup = repo.lookup
846 else:
846 else:
847 raise util.Abort(_('either two or three arguments required'))
847 raise util.Abort(_('either two or three arguments required'))
848 a = r.ancestor(lookup(rev1), lookup(rev2))
848 a = r.ancestor(lookup(rev1), lookup(rev2))
849 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
849 ui.write("%d:%s\n" % (r.rev(a), hex(a)))
850
850
851 def debugcommands(ui, cmd='', *args):
851 def debugcommands(ui, cmd='', *args):
852 """list all available commands and options"""
852 """list all available commands and options"""
853 for cmd, vals in sorted(table.iteritems()):
853 for cmd, vals in sorted(table.iteritems()):
854 cmd = cmd.split('|')[0].strip('^')
854 cmd = cmd.split('|')[0].strip('^')
855 opts = ', '.join([i[1] for i in vals[1]])
855 opts = ', '.join([i[1] for i in vals[1]])
856 ui.write('%s: %s\n' % (cmd, opts))
856 ui.write('%s: %s\n' % (cmd, opts))
857
857
858 def debugcomplete(ui, cmd='', **opts):
858 def debugcomplete(ui, cmd='', **opts):
859 """returns the completion list associated with the given command"""
859 """returns the completion list associated with the given command"""
860
860
861 if opts.get('options'):
861 if opts.get('options'):
862 options = []
862 options = []
863 otables = [globalopts]
863 otables = [globalopts]
864 if cmd:
864 if cmd:
865 aliases, entry = cmdutil.findcmd(cmd, table, False)
865 aliases, entry = cmdutil.findcmd(cmd, table, False)
866 otables.append(entry[1])
866 otables.append(entry[1])
867 for t in otables:
867 for t in otables:
868 for o in t:
868 for o in t:
869 if "(DEPRECATED)" in o[3]:
869 if "(DEPRECATED)" in o[3]:
870 continue
870 continue
871 if o[0]:
871 if o[0]:
872 options.append('-%s' % o[0])
872 options.append('-%s' % o[0])
873 options.append('--%s' % o[1])
873 options.append('--%s' % o[1])
874 ui.write("%s\n" % "\n".join(options))
874 ui.write("%s\n" % "\n".join(options))
875 return
875 return
876
876
877 cmdlist = cmdutil.findpossible(cmd, table)
877 cmdlist = cmdutil.findpossible(cmd, table)
878 if ui.verbose:
878 if ui.verbose:
879 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
879 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
880 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
880 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
881
881
882 def debugfsinfo(ui, path = "."):
882 def debugfsinfo(ui, path = "."):
883 """show information detected about current filesystem"""
883 """show information detected about current filesystem"""
884 open('.debugfsinfo', 'w').write('')
884 open('.debugfsinfo', 'w').write('')
885 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
885 ui.write('exec: %s\n' % (util.checkexec(path) and 'yes' or 'no'))
886 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
886 ui.write('symlink: %s\n' % (util.checklink(path) and 'yes' or 'no'))
887 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
887 ui.write('case-sensitive: %s\n' % (util.checkcase('.debugfsinfo')
888 and 'yes' or 'no'))
888 and 'yes' or 'no'))
889 os.unlink('.debugfsinfo')
889 os.unlink('.debugfsinfo')
890
890
891 def debugrebuildstate(ui, repo, rev="tip"):
891 def debugrebuildstate(ui, repo, rev="tip"):
892 """rebuild the dirstate as it would look like for the given revision"""
892 """rebuild the dirstate as it would look like for the given revision"""
893 ctx = repo[rev]
893 ctx = repo[rev]
894 wlock = repo.wlock()
894 wlock = repo.wlock()
895 try:
895 try:
896 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
896 repo.dirstate.rebuild(ctx.node(), ctx.manifest())
897 finally:
897 finally:
898 wlock.release()
898 wlock.release()
899
899
900 def debugcheckstate(ui, repo):
900 def debugcheckstate(ui, repo):
901 """validate the correctness of the current dirstate"""
901 """validate the correctness of the current dirstate"""
902 parent1, parent2 = repo.dirstate.parents()
902 parent1, parent2 = repo.dirstate.parents()
903 m1 = repo[parent1].manifest()
903 m1 = repo[parent1].manifest()
904 m2 = repo[parent2].manifest()
904 m2 = repo[parent2].manifest()
905 errors = 0
905 errors = 0
906 for f in repo.dirstate:
906 for f in repo.dirstate:
907 state = repo.dirstate[f]
907 state = repo.dirstate[f]
908 if state in "nr" and f not in m1:
908 if state in "nr" and f not in m1:
909 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
909 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
910 errors += 1
910 errors += 1
911 if state in "a" and f in m1:
911 if state in "a" and f in m1:
912 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
912 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
913 errors += 1
913 errors += 1
914 if state in "m" and f not in m1 and f not in m2:
914 if state in "m" and f not in m1 and f not in m2:
915 ui.warn(_("%s in state %s, but not in either manifest\n") %
915 ui.warn(_("%s in state %s, but not in either manifest\n") %
916 (f, state))
916 (f, state))
917 errors += 1
917 errors += 1
918 for f in m1:
918 for f in m1:
919 state = repo.dirstate[f]
919 state = repo.dirstate[f]
920 if state not in "nrm":
920 if state not in "nrm":
921 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
921 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
922 errors += 1
922 errors += 1
923 if errors:
923 if errors:
924 error = _(".hg/dirstate inconsistent with current parent's manifest")
924 error = _(".hg/dirstate inconsistent with current parent's manifest")
925 raise util.Abort(error)
925 raise util.Abort(error)
926
926
927 def showconfig(ui, repo, *values, **opts):
927 def showconfig(ui, repo, *values, **opts):
928 """show combined config settings from all hgrc files
928 """show combined config settings from all hgrc files
929
929
930 With no arguments, print names and values of all config items.
930 With no arguments, print names and values of all config items.
931
931
932 With one argument of the form section.name, print just the value
932 With one argument of the form section.name, print just the value
933 of that config item.
933 of that config item.
934
934
935 With multiple arguments, print names and values of all config
935 With multiple arguments, print names and values of all config
936 items with matching section names.
936 items with matching section names.
937
937
938 With --debug, the source (filename and line number) is printed
938 With --debug, the source (filename and line number) is printed
939 for each config item.
939 for each config item.
940
940
941 Returns 0 on success.
941 Returns 0 on success.
942 """
942 """
943
943
944 for f in util.rcpath():
944 for f in util.rcpath():
945 ui.debug(_('read config from: %s\n') % f)
945 ui.debug(_('read config from: %s\n') % f)
946 untrusted = bool(opts.get('untrusted'))
946 untrusted = bool(opts.get('untrusted'))
947 if values:
947 if values:
948 if len([v for v in values if '.' in v]) > 1:
948 if len([v for v in values if '.' in v]) > 1:
949 raise util.Abort(_('only one config item permitted'))
949 raise util.Abort(_('only one config item permitted'))
950 for section, name, value in ui.walkconfig(untrusted=untrusted):
950 for section, name, value in ui.walkconfig(untrusted=untrusted):
951 sectname = section + '.' + name
951 sectname = section + '.' + name
952 if values:
952 if values:
953 for v in values:
953 for v in values:
954 if v == section:
954 if v == section:
955 ui.debug('%s: ' %
955 ui.debug('%s: ' %
956 ui.configsource(section, name, untrusted))
956 ui.configsource(section, name, untrusted))
957 ui.write('%s=%s\n' % (sectname, value))
957 ui.write('%s=%s\n' % (sectname, value))
958 elif v == sectname:
958 elif v == sectname:
959 ui.debug('%s: ' %
959 ui.debug('%s: ' %
960 ui.configsource(section, name, untrusted))
960 ui.configsource(section, name, untrusted))
961 ui.write(value, '\n')
961 ui.write(value, '\n')
962 else:
962 else:
963 ui.debug('%s: ' %
963 ui.debug('%s: ' %
964 ui.configsource(section, name, untrusted))
964 ui.configsource(section, name, untrusted))
965 ui.write('%s=%s\n' % (sectname, value))
965 ui.write('%s=%s\n' % (sectname, value))
966
966
967 def debugrevspec(ui, repo, expr):
967 def debugrevspec(ui, repo, expr):
968 '''parse and apply a revision specification'''
968 '''parse and apply a revision specification'''
969 if ui.verbose:
969 if ui.verbose:
970 tree = revset.parse(expr)
970 tree = revset.parse(expr)
971 ui.note(tree, "\n")
971 ui.note(tree, "\n")
972 func = revset.match(expr)
972 func = revset.match(expr)
973 for c in func(repo, range(len(repo))):
973 for c in func(repo, range(len(repo))):
974 ui.write("%s\n" % c)
974 ui.write("%s\n" % c)
975
975
976 def debugsetparents(ui, repo, rev1, rev2=None):
976 def debugsetparents(ui, repo, rev1, rev2=None):
977 """manually set the parents of the current working directory
977 """manually set the parents of the current working directory
978
978
979 This is useful for writing repository conversion tools, but should
979 This is useful for writing repository conversion tools, but should
980 be used with care.
980 be used with care.
981
981
982 Returns 0 on success.
982 Returns 0 on success.
983 """
983 """
984
984
985 if not rev2:
985 if not rev2:
986 rev2 = hex(nullid)
986 rev2 = hex(nullid)
987
987
988 wlock = repo.wlock()
988 wlock = repo.wlock()
989 try:
989 try:
990 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
990 repo.dirstate.setparents(repo.lookup(rev1), repo.lookup(rev2))
991 finally:
991 finally:
992 wlock.release()
992 wlock.release()
993
993
994 def debugstate(ui, repo, nodates=None):
994 def debugstate(ui, repo, nodates=None):
995 """show the contents of the current dirstate"""
995 """show the contents of the current dirstate"""
996 timestr = ""
996 timestr = ""
997 showdate = not nodates
997 showdate = not nodates
998 for file_, ent in sorted(repo.dirstate._map.iteritems()):
998 for file_, ent in sorted(repo.dirstate._map.iteritems()):
999 if showdate:
999 if showdate:
1000 if ent[3] == -1:
1000 if ent[3] == -1:
1001 # Pad or slice to locale representation
1001 # Pad or slice to locale representation
1002 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
1002 locale_len = len(time.strftime("%Y-%m-%d %H:%M:%S ",
1003 time.localtime(0)))
1003 time.localtime(0)))
1004 timestr = 'unset'
1004 timestr = 'unset'
1005 timestr = (timestr[:locale_len] +
1005 timestr = (timestr[:locale_len] +
1006 ' ' * (locale_len - len(timestr)))
1006 ' ' * (locale_len - len(timestr)))
1007 else:
1007 else:
1008 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
1008 timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
1009 time.localtime(ent[3]))
1009 time.localtime(ent[3]))
1010 if ent[1] & 020000:
1010 if ent[1] & 020000:
1011 mode = 'lnk'
1011 mode = 'lnk'
1012 else:
1012 else:
1013 mode = '%3o' % (ent[1] & 0777)
1013 mode = '%3o' % (ent[1] & 0777)
1014 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
1014 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
1015 for f in repo.dirstate.copies():
1015 for f in repo.dirstate.copies():
1016 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1016 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
1017
1017
1018 def debugsub(ui, repo, rev=None):
1018 def debugsub(ui, repo, rev=None):
1019 if rev == '':
1019 if rev == '':
1020 rev = None
1020 rev = None
1021 for k, v in sorted(repo[rev].substate.items()):
1021 for k, v in sorted(repo[rev].substate.items()):
1022 ui.write('path %s\n' % k)
1022 ui.write('path %s\n' % k)
1023 ui.write(' source %s\n' % v[0])
1023 ui.write(' source %s\n' % v[0])
1024 ui.write(' revision %s\n' % v[1])
1024 ui.write(' revision %s\n' % v[1])
1025
1025
1026 def debugdata(ui, file_, rev):
1026 def debugdata(ui, file_, rev):
1027 """dump the contents of a data file revision"""
1027 """dump the contents of a data file revision"""
1028 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
1028 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_[:-2] + ".i")
1029 try:
1029 try:
1030 ui.write(r.revision(r.lookup(rev)))
1030 ui.write(r.revision(r.lookup(rev)))
1031 except KeyError:
1031 except KeyError:
1032 raise util.Abort(_('invalid revision identifier %s') % rev)
1032 raise util.Abort(_('invalid revision identifier %s') % rev)
1033
1033
1034 def debugdate(ui, date, range=None, **opts):
1034 def debugdate(ui, date, range=None, **opts):
1035 """parse and display a date"""
1035 """parse and display a date"""
1036 if opts["extended"]:
1036 if opts["extended"]:
1037 d = util.parsedate(date, util.extendeddateformats)
1037 d = util.parsedate(date, util.extendeddateformats)
1038 else:
1038 else:
1039 d = util.parsedate(date)
1039 d = util.parsedate(date)
1040 ui.write("internal: %s %s\n" % d)
1040 ui.write("internal: %s %s\n" % d)
1041 ui.write("standard: %s\n" % util.datestr(d))
1041 ui.write("standard: %s\n" % util.datestr(d))
1042 if range:
1042 if range:
1043 m = util.matchdate(range)
1043 m = util.matchdate(range)
1044 ui.write("match: %s\n" % m(d[0]))
1044 ui.write("match: %s\n" % m(d[0]))
1045
1045
1046 def debugindex(ui, file_):
1046 def debugindex(ui, file_):
1047 """dump the contents of an index file"""
1047 """dump the contents of an index file"""
1048 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1048 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1049 ui.write(" rev offset length base linkrev"
1049 ui.write(" rev offset length base linkrev"
1050 " nodeid p1 p2\n")
1050 " nodeid p1 p2\n")
1051 for i in r:
1051 for i in r:
1052 node = r.node(i)
1052 node = r.node(i)
1053 try:
1053 try:
1054 pp = r.parents(node)
1054 pp = r.parents(node)
1055 except:
1055 except:
1056 pp = [nullid, nullid]
1056 pp = [nullid, nullid]
1057 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1057 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
1058 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
1058 i, r.start(i), r.length(i), r.base(i), r.linkrev(i),
1059 short(node), short(pp[0]), short(pp[1])))
1059 short(node), short(pp[0]), short(pp[1])))
1060
1060
1061 def debugindexdot(ui, file_):
1061 def debugindexdot(ui, file_):
1062 """dump an index DAG as a graphviz dot file"""
1062 """dump an index DAG as a graphviz dot file"""
1063 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1063 r = revlog.revlog(util.opener(os.getcwd(), audit=False), file_)
1064 ui.write("digraph G {\n")
1064 ui.write("digraph G {\n")
1065 for i in r:
1065 for i in r:
1066 node = r.node(i)
1066 node = r.node(i)
1067 pp = r.parents(node)
1067 pp = r.parents(node)
1068 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1068 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1069 if pp[1] != nullid:
1069 if pp[1] != nullid:
1070 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1070 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1071 ui.write("}\n")
1071 ui.write("}\n")
1072
1072
1073 def debuginstall(ui):
1073 def debuginstall(ui):
1074 '''test Mercurial installation
1074 '''test Mercurial installation
1075
1075
1076 Returns 0 on success.
1076 Returns 0 on success.
1077 '''
1077 '''
1078
1078
1079 def writetemp(contents):
1079 def writetemp(contents):
1080 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1080 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1081 f = os.fdopen(fd, "wb")
1081 f = os.fdopen(fd, "wb")
1082 f.write(contents)
1082 f.write(contents)
1083 f.close()
1083 f.close()
1084 return name
1084 return name
1085
1085
1086 problems = 0
1086 problems = 0
1087
1087
1088 # encoding
1088 # encoding
1089 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1089 ui.status(_("Checking encoding (%s)...\n") % encoding.encoding)
1090 try:
1090 try:
1091 encoding.fromlocal("test")
1091 encoding.fromlocal("test")
1092 except util.Abort, inst:
1092 except util.Abort, inst:
1093 ui.write(" %s\n" % inst)
1093 ui.write(" %s\n" % inst)
1094 ui.write(_(" (check that your locale is properly set)\n"))
1094 ui.write(_(" (check that your locale is properly set)\n"))
1095 problems += 1
1095 problems += 1
1096
1096
1097 # compiled modules
1097 # compiled modules
1098 ui.status(_("Checking extensions...\n"))
1098 ui.status(_("Checking extensions...\n"))
1099 try:
1099 try:
1100 import bdiff, mpatch, base85
1100 import bdiff, mpatch, base85
1101 except Exception, inst:
1101 except Exception, inst:
1102 ui.write(" %s\n" % inst)
1102 ui.write(" %s\n" % inst)
1103 ui.write(_(" One or more extensions could not be found"))
1103 ui.write(_(" One or more extensions could not be found"))
1104 ui.write(_(" (check that you compiled the extensions)\n"))
1104 ui.write(_(" (check that you compiled the extensions)\n"))
1105 problems += 1
1105 problems += 1
1106
1106
1107 # templates
1107 # templates
1108 ui.status(_("Checking templates...\n"))
1108 ui.status(_("Checking templates...\n"))
1109 try:
1109 try:
1110 import templater
1110 import templater
1111 templater.templater(templater.templatepath("map-cmdline.default"))
1111 templater.templater(templater.templatepath("map-cmdline.default"))
1112 except Exception, inst:
1112 except Exception, inst:
1113 ui.write(" %s\n" % inst)
1113 ui.write(" %s\n" % inst)
1114 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1114 ui.write(_(" (templates seem to have been installed incorrectly)\n"))
1115 problems += 1
1115 problems += 1
1116
1116
1117 # patch
1117 # patch
1118 ui.status(_("Checking patch...\n"))
1118 ui.status(_("Checking patch...\n"))
1119 patchproblems = 0
1119 patchproblems = 0
1120 a = "1\n2\n3\n4\n"
1120 a = "1\n2\n3\n4\n"
1121 b = "1\n2\n3\ninsert\n4\n"
1121 b = "1\n2\n3\ninsert\n4\n"
1122 fa = writetemp(a)
1122 fa = writetemp(a)
1123 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
1123 d = mdiff.unidiff(a, None, b, None, os.path.basename(fa),
1124 os.path.basename(fa))
1124 os.path.basename(fa))
1125 fd = writetemp(d)
1125 fd = writetemp(d)
1126
1126
1127 files = {}
1127 files = {}
1128 try:
1128 try:
1129 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
1129 patch.patch(fd, ui, cwd=os.path.dirname(fa), files=files)
1130 except util.Abort, e:
1130 except util.Abort, e:
1131 ui.write(_(" patch call failed:\n"))
1131 ui.write(_(" patch call failed:\n"))
1132 ui.write(" " + str(e) + "\n")
1132 ui.write(" " + str(e) + "\n")
1133 patchproblems += 1
1133 patchproblems += 1
1134 else:
1134 else:
1135 if list(files) != [os.path.basename(fa)]:
1135 if list(files) != [os.path.basename(fa)]:
1136 ui.write(_(" unexpected patch output!\n"))
1136 ui.write(_(" unexpected patch output!\n"))
1137 patchproblems += 1
1137 patchproblems += 1
1138 a = open(fa).read()
1138 a = open(fa).read()
1139 if a != b:
1139 if a != b:
1140 ui.write(_(" patch test failed!\n"))
1140 ui.write(_(" patch test failed!\n"))
1141 patchproblems += 1
1141 patchproblems += 1
1142
1142
1143 if patchproblems:
1143 if patchproblems:
1144 if ui.config('ui', 'patch'):
1144 if ui.config('ui', 'patch'):
1145 ui.write(_(" (Current patch tool may be incompatible with patch,"
1145 ui.write(_(" (Current patch tool may be incompatible with patch,"
1146 " or misconfigured. Please check your .hgrc file)\n"))
1146 " or misconfigured. Please check your .hgrc file)\n"))
1147 else:
1147 else:
1148 ui.write(_(" Internal patcher failure, please report this error"
1148 ui.write(_(" Internal patcher failure, please report this error"
1149 " to http://mercurial.selenic.com/bts/\n"))
1149 " to http://mercurial.selenic.com/bts/\n"))
1150 problems += patchproblems
1150 problems += patchproblems
1151
1151
1152 os.unlink(fa)
1152 os.unlink(fa)
1153 os.unlink(fd)
1153 os.unlink(fd)
1154
1154
1155 # editor
1155 # editor
1156 ui.status(_("Checking commit editor...\n"))
1156 ui.status(_("Checking commit editor...\n"))
1157 editor = ui.geteditor()
1157 editor = ui.geteditor()
1158 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1158 cmdpath = util.find_exe(editor) or util.find_exe(editor.split()[0])
1159 if not cmdpath:
1159 if not cmdpath:
1160 if editor == 'vi':
1160 if editor == 'vi':
1161 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1161 ui.write(_(" No commit editor set and can't find vi in PATH\n"))
1162 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
1162 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
1163 else:
1163 else:
1164 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1164 ui.write(_(" Can't find editor '%s' in PATH\n") % editor)
1165 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
1165 ui.write(_(" (specify a commit editor in your .hgrc file)\n"))
1166 problems += 1
1166 problems += 1
1167
1167
1168 # check username
1168 # check username
1169 ui.status(_("Checking username...\n"))
1169 ui.status(_("Checking username...\n"))
1170 try:
1170 try:
1171 user = ui.username()
1171 user = ui.username()
1172 except util.Abort, e:
1172 except util.Abort, e:
1173 ui.write(" %s\n" % e)
1173 ui.write(" %s\n" % e)
1174 ui.write(_(" (specify a username in your .hgrc file)\n"))
1174 ui.write(_(" (specify a username in your .hgrc file)\n"))
1175 problems += 1
1175 problems += 1
1176
1176
1177 if not problems:
1177 if not problems:
1178 ui.status(_("No problems detected\n"))
1178 ui.status(_("No problems detected\n"))
1179 else:
1179 else:
1180 ui.write(_("%s problems detected,"
1180 ui.write(_("%s problems detected,"
1181 " please check your install!\n") % problems)
1181 " please check your install!\n") % problems)
1182
1182
1183 return problems
1183 return problems
1184
1184
1185 def debugrename(ui, repo, file1, *pats, **opts):
1185 def debugrename(ui, repo, file1, *pats, **opts):
1186 """dump rename information"""
1186 """dump rename information"""
1187
1187
1188 ctx = repo[opts.get('rev')]
1188 ctx = repo[opts.get('rev')]
1189 m = cmdutil.match(repo, (file1,) + pats, opts)
1189 m = cmdutil.match(repo, (file1,) + pats, opts)
1190 for abs in ctx.walk(m):
1190 for abs in ctx.walk(m):
1191 fctx = ctx[abs]
1191 fctx = ctx[abs]
1192 o = fctx.filelog().renamed(fctx.filenode())
1192 o = fctx.filelog().renamed(fctx.filenode())
1193 rel = m.rel(abs)
1193 rel = m.rel(abs)
1194 if o:
1194 if o:
1195 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1195 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1196 else:
1196 else:
1197 ui.write(_("%s not renamed\n") % rel)
1197 ui.write(_("%s not renamed\n") % rel)
1198
1198
1199 def debugwalk(ui, repo, *pats, **opts):
1199 def debugwalk(ui, repo, *pats, **opts):
1200 """show how files match on given patterns"""
1200 """show how files match on given patterns"""
1201 m = cmdutil.match(repo, pats, opts)
1201 m = cmdutil.match(repo, pats, opts)
1202 items = list(repo.walk(m))
1202 items = list(repo.walk(m))
1203 if not items:
1203 if not items:
1204 return
1204 return
1205 fmt = 'f %%-%ds %%-%ds %%s' % (
1205 fmt = 'f %%-%ds %%-%ds %%s' % (
1206 max([len(abs) for abs in items]),
1206 max([len(abs) for abs in items]),
1207 max([len(m.rel(abs)) for abs in items]))
1207 max([len(m.rel(abs)) for abs in items]))
1208 for abs in items:
1208 for abs in items:
1209 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1209 line = fmt % (abs, m.rel(abs), m.exact(abs) and 'exact' or '')
1210 ui.write("%s\n" % line.rstrip())
1210 ui.write("%s\n" % line.rstrip())
1211
1211
1212 def diff(ui, repo, *pats, **opts):
1212 def diff(ui, repo, *pats, **opts):
1213 """diff repository (or selected files)
1213 """diff repository (or selected files)
1214
1214
1215 Show differences between revisions for the specified files.
1215 Show differences between revisions for the specified files.
1216
1216
1217 Differences between files are shown using the unified diff format.
1217 Differences between files are shown using the unified diff format.
1218
1218
1219 NOTE: diff may generate unexpected results for merges, as it will
1219 NOTE: diff may generate unexpected results for merges, as it will
1220 default to comparing against the working directory's first parent
1220 default to comparing against the working directory's first parent
1221 changeset if no revisions are specified.
1221 changeset if no revisions are specified.
1222
1222
1223 When two revision arguments are given, then changes are shown
1223 When two revision arguments are given, then changes are shown
1224 between those revisions. If only one revision is specified then
1224 between those revisions. If only one revision is specified then
1225 that revision is compared to the working directory, and, when no
1225 that revision is compared to the working directory, and, when no
1226 revisions are specified, the working directory files are compared
1226 revisions are specified, the working directory files are compared
1227 to its parent.
1227 to its parent.
1228
1228
1229 Alternatively you can specify -c/--change with a revision to see
1229 Alternatively you can specify -c/--change with a revision to see
1230 the changes in that changeset relative to its first parent.
1230 the changes in that changeset relative to its first parent.
1231
1231
1232 Without the -a/--text option, diff will avoid generating diffs of
1232 Without the -a/--text option, diff will avoid generating diffs of
1233 files it detects as binary. With -a, diff will generate a diff
1233 files it detects as binary. With -a, diff will generate a diff
1234 anyway, probably with undesirable results.
1234 anyway, probably with undesirable results.
1235
1235
1236 Use the -g/--git option to generate diffs in the git extended diff
1236 Use the -g/--git option to generate diffs in the git extended diff
1237 format. For more information, read :hg:`help diffs`.
1237 format. For more information, read :hg:`help diffs`.
1238
1238
1239 Returns 0 on success.
1239 Returns 0 on success.
1240 """
1240 """
1241
1241
1242 revs = opts.get('rev')
1242 revs = opts.get('rev')
1243 change = opts.get('change')
1243 change = opts.get('change')
1244 stat = opts.get('stat')
1244 stat = opts.get('stat')
1245 reverse = opts.get('reverse')
1245 reverse = opts.get('reverse')
1246
1246
1247 if revs and change:
1247 if revs and change:
1248 msg = _('cannot specify --rev and --change at the same time')
1248 msg = _('cannot specify --rev and --change at the same time')
1249 raise util.Abort(msg)
1249 raise util.Abort(msg)
1250 elif change:
1250 elif change:
1251 node2 = repo.lookup(change)
1251 node2 = repo.lookup(change)
1252 node1 = repo[node2].parents()[0].node()
1252 node1 = repo[node2].parents()[0].node()
1253 else:
1253 else:
1254 node1, node2 = cmdutil.revpair(repo, revs)
1254 node1, node2 = cmdutil.revpair(repo, revs)
1255
1255
1256 if reverse:
1256 if reverse:
1257 node1, node2 = node2, node1
1257 node1, node2 = node2, node1
1258
1258
1259 diffopts = patch.diffopts(ui, opts)
1259 diffopts = patch.diffopts(ui, opts)
1260 m = cmdutil.match(repo, pats, opts)
1260 m = cmdutil.match(repo, pats, opts)
1261 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat)
1261 cmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat)
1262
1262
1263 def export(ui, repo, *changesets, **opts):
1263 def export(ui, repo, *changesets, **opts):
1264 """dump the header and diffs for one or more changesets
1264 """dump the header and diffs for one or more changesets
1265
1265
1266 Print the changeset header and diffs for one or more revisions.
1266 Print the changeset header and diffs for one or more revisions.
1267
1267
1268 The information shown in the changeset header is: author, date,
1268 The information shown in the changeset header is: author, date,
1269 branch name (if non-default), changeset hash, parent(s) and commit
1269 branch name (if non-default), changeset hash, parent(s) and commit
1270 comment.
1270 comment.
1271
1271
1272 NOTE: export may generate unexpected diff output for merge
1272 NOTE: export may generate unexpected diff output for merge
1273 changesets, as it will compare the merge changeset against its
1273 changesets, as it will compare the merge changeset against its
1274 first parent only.
1274 first parent only.
1275
1275
1276 Output may be to a file, in which case the name of the file is
1276 Output may be to a file, in which case the name of the file is
1277 given using a format string. The formatting rules are as follows:
1277 given using a format string. The formatting rules are as follows:
1278
1278
1279 :``%%``: literal "%" character
1279 :``%%``: literal "%" character
1280 :``%H``: changeset hash (40 bytes of hexadecimal)
1280 :``%H``: changeset hash (40 bytes of hexadecimal)
1281 :``%N``: number of patches being generated
1281 :``%N``: number of patches being generated
1282 :``%R``: changeset revision number
1282 :``%R``: changeset revision number
1283 :``%b``: basename of the exporting repository
1283 :``%b``: basename of the exporting repository
1284 :``%h``: short-form changeset hash (12 bytes of hexadecimal)
1284 :``%h``: short-form changeset hash (12 bytes of hexadecimal)
1285 :``%n``: zero-padded sequence number, starting at 1
1285 :``%n``: zero-padded sequence number, starting at 1
1286 :``%r``: zero-padded changeset revision number
1286 :``%r``: zero-padded changeset revision number
1287
1287
1288 Without the -a/--text option, export will avoid generating diffs
1288 Without the -a/--text option, export will avoid generating diffs
1289 of files it detects as binary. With -a, export will generate a
1289 of files it detects as binary. With -a, export will generate a
1290 diff anyway, probably with undesirable results.
1290 diff anyway, probably with undesirable results.
1291
1291
1292 Use the -g/--git option to generate diffs in the git extended diff
1292 Use the -g/--git option to generate diffs in the git extended diff
1293 format. See :hg:`help diffs` for more information.
1293 format. See :hg:`help diffs` for more information.
1294
1294
1295 With the --switch-parent option, the diff will be against the
1295 With the --switch-parent option, the diff will be against the
1296 second parent. It can be useful to review a merge.
1296 second parent. It can be useful to review a merge.
1297
1297
1298 Returns 0 on success.
1298 Returns 0 on success.
1299 """
1299 """
1300 changesets += tuple(opts.get('rev', []))
1300 changesets += tuple(opts.get('rev', []))
1301 if not changesets:
1301 if not changesets:
1302 raise util.Abort(_("export requires at least one changeset"))
1302 raise util.Abort(_("export requires at least one changeset"))
1303 revs = cmdutil.revrange(repo, changesets)
1303 revs = cmdutil.revrange(repo, changesets)
1304 if len(revs) > 1:
1304 if len(revs) > 1:
1305 ui.note(_('exporting patches:\n'))
1305 ui.note(_('exporting patches:\n'))
1306 else:
1306 else:
1307 ui.note(_('exporting patch:\n'))
1307 ui.note(_('exporting patch:\n'))
1308 cmdutil.export(repo, revs, template=opts.get('output'),
1308 cmdutil.export(repo, revs, template=opts.get('output'),
1309 switch_parent=opts.get('switch_parent'),
1309 switch_parent=opts.get('switch_parent'),
1310 opts=patch.diffopts(ui, opts))
1310 opts=patch.diffopts(ui, opts))
1311
1311
1312 def forget(ui, repo, *pats, **opts):
1312 def forget(ui, repo, *pats, **opts):
1313 """forget the specified files on the next commit
1313 """forget the specified files on the next commit
1314
1314
1315 Mark the specified files so they will no longer be tracked
1315 Mark the specified files so they will no longer be tracked
1316 after the next commit.
1316 after the next commit.
1317
1317
1318 This only removes files from the current branch, not from the
1318 This only removes files from the current branch, not from the
1319 entire project history, and it does not delete them from the
1319 entire project history, and it does not delete them from the
1320 working directory.
1320 working directory.
1321
1321
1322 To undo a forget before the next commit, see :hg:`add`.
1322 To undo a forget before the next commit, see :hg:`add`.
1323
1323
1324 Returns 0 on success.
1324 Returns 0 on success.
1325 """
1325 """
1326
1326
1327 if not pats:
1327 if not pats:
1328 raise util.Abort(_('no files specified'))
1328 raise util.Abort(_('no files specified'))
1329
1329
1330 m = cmdutil.match(repo, pats, opts)
1330 m = cmdutil.match(repo, pats, opts)
1331 s = repo.status(match=m, clean=True)
1331 s = repo.status(match=m, clean=True)
1332 forget = sorted(s[0] + s[1] + s[3] + s[6])
1332 forget = sorted(s[0] + s[1] + s[3] + s[6])
1333 errs = 0
1333 errs = 0
1334
1334
1335 for f in m.files():
1335 for f in m.files():
1336 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1336 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
1337 ui.warn(_('not removing %s: file is already untracked\n')
1337 ui.warn(_('not removing %s: file is already untracked\n')
1338 % m.rel(f))
1338 % m.rel(f))
1339 errs = 1
1339 errs = 1
1340
1340
1341 for f in forget:
1341 for f in forget:
1342 if ui.verbose or not m.exact(f):
1342 if ui.verbose or not m.exact(f):
1343 ui.status(_('removing %s\n') % m.rel(f))
1343 ui.status(_('removing %s\n') % m.rel(f))
1344
1344
1345 repo[None].remove(forget, unlink=False)
1345 repo[None].remove(forget, unlink=False)
1346 return errs
1346 return errs
1347
1347
1348 def grep(ui, repo, pattern, *pats, **opts):
1348 def grep(ui, repo, pattern, *pats, **opts):
1349 """search for a pattern in specified files and revisions
1349 """search for a pattern in specified files and revisions
1350
1350
1351 Search revisions of files for a regular expression.
1351 Search revisions of files for a regular expression.
1352
1352
1353 This command behaves differently than Unix grep. It only accepts
1353 This command behaves differently than Unix grep. It only accepts
1354 Python/Perl regexps. It searches repository history, not the
1354 Python/Perl regexps. It searches repository history, not the
1355 working directory. It always prints the revision number in which a
1355 working directory. It always prints the revision number in which a
1356 match appears.
1356 match appears.
1357
1357
1358 By default, grep only prints output for the first revision of a
1358 By default, grep only prints output for the first revision of a
1359 file in which it finds a match. To get it to print every revision
1359 file in which it finds a match. To get it to print every revision
1360 that contains a change in match status ("-" for a match that
1360 that contains a change in match status ("-" for a match that
1361 becomes a non-match, or "+" for a non-match that becomes a match),
1361 becomes a non-match, or "+" for a non-match that becomes a match),
1362 use the --all flag.
1362 use the --all flag.
1363
1363
1364 Returns 0 if a match is found, 1 otherwise.
1364 Returns 0 if a match is found, 1 otherwise.
1365 """
1365 """
1366 reflags = 0
1366 reflags = 0
1367 if opts.get('ignore_case'):
1367 if opts.get('ignore_case'):
1368 reflags |= re.I
1368 reflags |= re.I
1369 try:
1369 try:
1370 regexp = re.compile(pattern, reflags)
1370 regexp = re.compile(pattern, reflags)
1371 except Exception, inst:
1371 except Exception, inst:
1372 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1372 ui.warn(_("grep: invalid match pattern: %s\n") % inst)
1373 return 1
1373 return 1
1374 sep, eol = ':', '\n'
1374 sep, eol = ':', '\n'
1375 if opts.get('print0'):
1375 if opts.get('print0'):
1376 sep = eol = '\0'
1376 sep = eol = '\0'
1377
1377
1378 getfile = util.lrucachefunc(repo.file)
1378 getfile = util.lrucachefunc(repo.file)
1379
1379
1380 def matchlines(body):
1380 def matchlines(body):
1381 begin = 0
1381 begin = 0
1382 linenum = 0
1382 linenum = 0
1383 while True:
1383 while True:
1384 match = regexp.search(body, begin)
1384 match = regexp.search(body, begin)
1385 if not match:
1385 if not match:
1386 break
1386 break
1387 mstart, mend = match.span()
1387 mstart, mend = match.span()
1388 linenum += body.count('\n', begin, mstart) + 1
1388 linenum += body.count('\n', begin, mstart) + 1
1389 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1389 lstart = body.rfind('\n', begin, mstart) + 1 or begin
1390 begin = body.find('\n', mend) + 1 or len(body)
1390 begin = body.find('\n', mend) + 1 or len(body)
1391 lend = begin - 1
1391 lend = begin - 1
1392 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1392 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
1393
1393
1394 class linestate(object):
1394 class linestate(object):
1395 def __init__(self, line, linenum, colstart, colend):
1395 def __init__(self, line, linenum, colstart, colend):
1396 self.line = line
1396 self.line = line
1397 self.linenum = linenum
1397 self.linenum = linenum
1398 self.colstart = colstart
1398 self.colstart = colstart
1399 self.colend = colend
1399 self.colend = colend
1400
1400
1401 def __hash__(self):
1401 def __hash__(self):
1402 return hash((self.linenum, self.line))
1402 return hash((self.linenum, self.line))
1403
1403
1404 def __eq__(self, other):
1404 def __eq__(self, other):
1405 return self.line == other.line
1405 return self.line == other.line
1406
1406
1407 matches = {}
1407 matches = {}
1408 copies = {}
1408 copies = {}
1409 def grepbody(fn, rev, body):
1409 def grepbody(fn, rev, body):
1410 matches[rev].setdefault(fn, [])
1410 matches[rev].setdefault(fn, [])
1411 m = matches[rev][fn]
1411 m = matches[rev][fn]
1412 for lnum, cstart, cend, line in matchlines(body):
1412 for lnum, cstart, cend, line in matchlines(body):
1413 s = linestate(line, lnum, cstart, cend)
1413 s = linestate(line, lnum, cstart, cend)
1414 m.append(s)
1414 m.append(s)
1415
1415
1416 def difflinestates(a, b):
1416 def difflinestates(a, b):
1417 sm = difflib.SequenceMatcher(None, a, b)
1417 sm = difflib.SequenceMatcher(None, a, b)
1418 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1418 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
1419 if tag == 'insert':
1419 if tag == 'insert':
1420 for i in xrange(blo, bhi):
1420 for i in xrange(blo, bhi):
1421 yield ('+', b[i])
1421 yield ('+', b[i])
1422 elif tag == 'delete':
1422 elif tag == 'delete':
1423 for i in xrange(alo, ahi):
1423 for i in xrange(alo, ahi):
1424 yield ('-', a[i])
1424 yield ('-', a[i])
1425 elif tag == 'replace':
1425 elif tag == 'replace':
1426 for i in xrange(alo, ahi):
1426 for i in xrange(alo, ahi):
1427 yield ('-', a[i])
1427 yield ('-', a[i])
1428 for i in xrange(blo, bhi):
1428 for i in xrange(blo, bhi):
1429 yield ('+', b[i])
1429 yield ('+', b[i])
1430
1430
1431 def display(fn, ctx, pstates, states):
1431 def display(fn, ctx, pstates, states):
1432 rev = ctx.rev()
1432 rev = ctx.rev()
1433 datefunc = ui.quiet and util.shortdate or util.datestr
1433 datefunc = ui.quiet and util.shortdate or util.datestr
1434 found = False
1434 found = False
1435 filerevmatches = {}
1435 filerevmatches = {}
1436 if opts.get('all'):
1436 if opts.get('all'):
1437 iter = difflinestates(pstates, states)
1437 iter = difflinestates(pstates, states)
1438 else:
1438 else:
1439 iter = [('', l) for l in states]
1439 iter = [('', l) for l in states]
1440 for change, l in iter:
1440 for change, l in iter:
1441 cols = [fn, str(rev)]
1441 cols = [fn, str(rev)]
1442 before, match, after = None, None, None
1442 before, match, after = None, None, None
1443 if opts.get('line_number'):
1443 if opts.get('line_number'):
1444 cols.append(str(l.linenum))
1444 cols.append(str(l.linenum))
1445 if opts.get('all'):
1445 if opts.get('all'):
1446 cols.append(change)
1446 cols.append(change)
1447 if opts.get('user'):
1447 if opts.get('user'):
1448 cols.append(ui.shortuser(ctx.user()))
1448 cols.append(ui.shortuser(ctx.user()))
1449 if opts.get('date'):
1449 if opts.get('date'):
1450 cols.append(datefunc(ctx.date()))
1450 cols.append(datefunc(ctx.date()))
1451 if opts.get('files_with_matches'):
1451 if opts.get('files_with_matches'):
1452 c = (fn, rev)
1452 c = (fn, rev)
1453 if c in filerevmatches:
1453 if c in filerevmatches:
1454 continue
1454 continue
1455 filerevmatches[c] = 1
1455 filerevmatches[c] = 1
1456 else:
1456 else:
1457 before = l.line[:l.colstart]
1457 before = l.line[:l.colstart]
1458 match = l.line[l.colstart:l.colend]
1458 match = l.line[l.colstart:l.colend]
1459 after = l.line[l.colend:]
1459 after = l.line[l.colend:]
1460 ui.write(sep.join(cols))
1460 ui.write(sep.join(cols))
1461 if before is not None:
1461 if before is not None:
1462 ui.write(sep + before)
1462 ui.write(sep + before)
1463 ui.write(match, label='grep.match')
1463 ui.write(match, label='grep.match')
1464 ui.write(after)
1464 ui.write(after)
1465 ui.write(eol)
1465 ui.write(eol)
1466 found = True
1466 found = True
1467 return found
1467 return found
1468
1468
1469 skip = {}
1469 skip = {}
1470 revfiles = {}
1470 revfiles = {}
1471 matchfn = cmdutil.match(repo, pats, opts)
1471 matchfn = cmdutil.match(repo, pats, opts)
1472 found = False
1472 found = False
1473 follow = opts.get('follow')
1473 follow = opts.get('follow')
1474
1474
1475 def prep(ctx, fns):
1475 def prep(ctx, fns):
1476 rev = ctx.rev()
1476 rev = ctx.rev()
1477 pctx = ctx.parents()[0]
1477 pctx = ctx.parents()[0]
1478 parent = pctx.rev()
1478 parent = pctx.rev()
1479 matches.setdefault(rev, {})
1479 matches.setdefault(rev, {})
1480 matches.setdefault(parent, {})
1480 matches.setdefault(parent, {})
1481 files = revfiles.setdefault(rev, [])
1481 files = revfiles.setdefault(rev, [])
1482 for fn in fns:
1482 for fn in fns:
1483 flog = getfile(fn)
1483 flog = getfile(fn)
1484 try:
1484 try:
1485 fnode = ctx.filenode(fn)
1485 fnode = ctx.filenode(fn)
1486 except error.LookupError:
1486 except error.LookupError:
1487 continue
1487 continue
1488
1488
1489 copied = flog.renamed(fnode)
1489 copied = flog.renamed(fnode)
1490 copy = follow and copied and copied[0]
1490 copy = follow and copied and copied[0]
1491 if copy:
1491 if copy:
1492 copies.setdefault(rev, {})[fn] = copy
1492 copies.setdefault(rev, {})[fn] = copy
1493 if fn in skip:
1493 if fn in skip:
1494 if copy:
1494 if copy:
1495 skip[copy] = True
1495 skip[copy] = True
1496 continue
1496 continue
1497 files.append(fn)
1497 files.append(fn)
1498
1498
1499 if fn not in matches[rev]:
1499 if fn not in matches[rev]:
1500 grepbody(fn, rev, flog.read(fnode))
1500 grepbody(fn, rev, flog.read(fnode))
1501
1501
1502 pfn = copy or fn
1502 pfn = copy or fn
1503 if pfn not in matches[parent]:
1503 if pfn not in matches[parent]:
1504 try:
1504 try:
1505 fnode = pctx.filenode(pfn)
1505 fnode = pctx.filenode(pfn)
1506 grepbody(pfn, parent, flog.read(fnode))
1506 grepbody(pfn, parent, flog.read(fnode))
1507 except error.LookupError:
1507 except error.LookupError:
1508 pass
1508 pass
1509
1509
1510 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1510 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
1511 rev = ctx.rev()
1511 rev = ctx.rev()
1512 parent = ctx.parents()[0].rev()
1512 parent = ctx.parents()[0].rev()
1513 for fn in sorted(revfiles.get(rev, [])):
1513 for fn in sorted(revfiles.get(rev, [])):
1514 states = matches[rev][fn]
1514 states = matches[rev][fn]
1515 copy = copies.get(rev, {}).get(fn)
1515 copy = copies.get(rev, {}).get(fn)
1516 if fn in skip:
1516 if fn in skip:
1517 if copy:
1517 if copy:
1518 skip[copy] = True
1518 skip[copy] = True
1519 continue
1519 continue
1520 pstates = matches.get(parent, {}).get(copy or fn, [])
1520 pstates = matches.get(parent, {}).get(copy or fn, [])
1521 if pstates or states:
1521 if pstates or states:
1522 r = display(fn, ctx, pstates, states)
1522 r = display(fn, ctx, pstates, states)
1523 found = found or r
1523 found = found or r
1524 if r and not opts.get('all'):
1524 if r and not opts.get('all'):
1525 skip[fn] = True
1525 skip[fn] = True
1526 if copy:
1526 if copy:
1527 skip[copy] = True
1527 skip[copy] = True
1528 del matches[rev]
1528 del matches[rev]
1529 del revfiles[rev]
1529 del revfiles[rev]
1530
1530
1531 return not found
1531 return not found
1532
1532
1533 def heads(ui, repo, *branchrevs, **opts):
1533 def heads(ui, repo, *branchrevs, **opts):
1534 """show current repository heads or show branch heads
1534 """show current repository heads or show branch heads
1535
1535
1536 With no arguments, show all repository branch heads.
1536 With no arguments, show all repository branch heads.
1537
1537
1538 Repository "heads" are changesets with no child changesets. They are
1538 Repository "heads" are changesets with no child changesets. They are
1539 where development generally takes place and are the usual targets
1539 where development generally takes place and are the usual targets
1540 for update and merge operations. Branch heads are changesets that have
1540 for update and merge operations. Branch heads are changesets that have
1541 no child changeset on the same branch.
1541 no child changeset on the same branch.
1542
1542
1543 If one or more REVs are given, only branch heads on the branches
1543 If one or more REVs are given, only branch heads on the branches
1544 associated with the specified changesets are shown.
1544 associated with the specified changesets are shown.
1545
1545
1546 If -c/--closed is specified, also show branch heads marked closed
1546 If -c/--closed is specified, also show branch heads marked closed
1547 (see :hg:`commit --close-branch`).
1547 (see :hg:`commit --close-branch`).
1548
1548
1549 If STARTREV is specified, only those heads that are descendants of
1549 If STARTREV is specified, only those heads that are descendants of
1550 STARTREV will be displayed.
1550 STARTREV will be displayed.
1551
1551
1552 If -t/--topo is specified, named branch mechanics will be ignored and only
1552 If -t/--topo is specified, named branch mechanics will be ignored and only
1553 changesets without children will be shown.
1553 changesets without children will be shown.
1554
1554
1555 Returns 0 if matching heads are found, 1 if not.
1555 Returns 0 if matching heads are found, 1 if not.
1556 """
1556 """
1557
1557
1558 if opts.get('rev'):
1558 if opts.get('rev'):
1559 start = repo.lookup(opts['rev'])
1559 start = repo.lookup(opts['rev'])
1560 else:
1560 else:
1561 start = None
1561 start = None
1562
1562
1563 if opts.get('topo'):
1563 if opts.get('topo'):
1564 heads = [repo[h] for h in repo.heads(start)]
1564 heads = [repo[h] for h in repo.heads(start)]
1565 else:
1565 else:
1566 heads = []
1566 heads = []
1567 for b, ls in repo.branchmap().iteritems():
1567 for b, ls in repo.branchmap().iteritems():
1568 if start is None:
1568 if start is None:
1569 heads += [repo[h] for h in ls]
1569 heads += [repo[h] for h in ls]
1570 continue
1570 continue
1571 startrev = repo.changelog.rev(start)
1571 startrev = repo.changelog.rev(start)
1572 descendants = set(repo.changelog.descendants(startrev))
1572 descendants = set(repo.changelog.descendants(startrev))
1573 descendants.add(startrev)
1573 descendants.add(startrev)
1574 rev = repo.changelog.rev
1574 rev = repo.changelog.rev
1575 heads += [repo[h] for h in ls if rev(h) in descendants]
1575 heads += [repo[h] for h in ls if rev(h) in descendants]
1576
1576
1577 if branchrevs:
1577 if branchrevs:
1578 decode, encode = encoding.fromlocal, encoding.tolocal
1578 decode, encode = encoding.fromlocal, encoding.tolocal
1579 branches = set(repo[decode(br)].branch() for br in branchrevs)
1579 branches = set(repo[decode(br)].branch() for br in branchrevs)
1580 heads = [h for h in heads if h.branch() in branches]
1580 heads = [h for h in heads if h.branch() in branches]
1581
1581
1582 if not opts.get('closed'):
1582 if not opts.get('closed'):
1583 heads = [h for h in heads if not h.extra().get('close')]
1583 heads = [h for h in heads if not h.extra().get('close')]
1584
1584
1585 if opts.get('active') and branchrevs:
1585 if opts.get('active') and branchrevs:
1586 dagheads = repo.heads(start)
1586 dagheads = repo.heads(start)
1587 heads = [h for h in heads if h.node() in dagheads]
1587 heads = [h for h in heads if h.node() in dagheads]
1588
1588
1589 if branchrevs:
1589 if branchrevs:
1590 haveheads = set(h.branch() for h in heads)
1590 haveheads = set(h.branch() for h in heads)
1591 if branches - haveheads:
1591 if branches - haveheads:
1592 headless = ', '.join(encode(b) for b in branches - haveheads)
1592 headless = ', '.join(encode(b) for b in branches - haveheads)
1593 msg = _('no open branch heads found on branches %s')
1593 msg = _('no open branch heads found on branches %s')
1594 if opts.get('rev'):
1594 if opts.get('rev'):
1595 msg += _(' (started at %s)' % opts['rev'])
1595 msg += _(' (started at %s)' % opts['rev'])
1596 ui.warn((msg + '\n') % headless)
1596 ui.warn((msg + '\n') % headless)
1597
1597
1598 if not heads:
1598 if not heads:
1599 return 1
1599 return 1
1600
1600
1601 heads = sorted(heads, key=lambda x: -x.rev())
1601 heads = sorted(heads, key=lambda x: -x.rev())
1602 displayer = cmdutil.show_changeset(ui, repo, opts)
1602 displayer = cmdutil.show_changeset(ui, repo, opts)
1603 for ctx in heads:
1603 for ctx in heads:
1604 displayer.show(ctx)
1604 displayer.show(ctx)
1605 displayer.close()
1605 displayer.close()
1606
1606
1607 def help_(ui, name=None, with_version=False, unknowncmd=False):
1607 def help_(ui, name=None, with_version=False, unknowncmd=False):
1608 """show help for a given topic or a help overview
1608 """show help for a given topic or a help overview
1609
1609
1610 With no arguments, print a list of commands with short help messages.
1610 With no arguments, print a list of commands with short help messages.
1611
1611
1612 Given a topic, extension, or command name, print help for that
1612 Given a topic, extension, or command name, print help for that
1613 topic.
1613 topic.
1614
1614
1615 Returns 0 if successful.
1615 Returns 0 if successful.
1616 """
1616 """
1617 option_lists = []
1617 option_lists = []
1618 textwidth = util.termwidth() - 2
1618 textwidth = util.termwidth() - 2
1619
1619
1620 def addglobalopts(aliases):
1620 def addglobalopts(aliases):
1621 if ui.verbose:
1621 if ui.verbose:
1622 option_lists.append((_("global options:"), globalopts))
1622 option_lists.append((_("global options:"), globalopts))
1623 if name == 'shortlist':
1623 if name == 'shortlist':
1624 option_lists.append((_('use "hg help" for the full list '
1624 option_lists.append((_('use "hg help" for the full list '
1625 'of commands'), ()))
1625 'of commands'), ()))
1626 else:
1626 else:
1627 if name == 'shortlist':
1627 if name == 'shortlist':
1628 msg = _('use "hg help" for the full list of commands '
1628 msg = _('use "hg help" for the full list of commands '
1629 'or "hg -v" for details')
1629 'or "hg -v" for details')
1630 elif aliases:
1630 elif aliases:
1631 msg = _('use "hg -v help%s" to show aliases and '
1631 msg = _('use "hg -v help%s" to show aliases and '
1632 'global options') % (name and " " + name or "")
1632 'global options') % (name and " " + name or "")
1633 else:
1633 else:
1634 msg = _('use "hg -v help %s" to show global options') % name
1634 msg = _('use "hg -v help %s" to show global options') % name
1635 option_lists.append((msg, ()))
1635 option_lists.append((msg, ()))
1636
1636
1637 def helpcmd(name):
1637 def helpcmd(name):
1638 if with_version:
1638 if with_version:
1639 version_(ui)
1639 version_(ui)
1640 ui.write('\n')
1640 ui.write('\n')
1641
1641
1642 try:
1642 try:
1643 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
1643 aliases, entry = cmdutil.findcmd(name, table, strict=unknowncmd)
1644 except error.AmbiguousCommand, inst:
1644 except error.AmbiguousCommand, inst:
1645 # py3k fix: except vars can't be used outside the scope of the
1645 # py3k fix: except vars can't be used outside the scope of the
1646 # except block, nor can be used inside a lambda. python issue4617
1646 # except block, nor can be used inside a lambda. python issue4617
1647 prefix = inst.args[0]
1647 prefix = inst.args[0]
1648 select = lambda c: c.lstrip('^').startswith(prefix)
1648 select = lambda c: c.lstrip('^').startswith(prefix)
1649 helplist(_('list of commands:\n\n'), select)
1649 helplist(_('list of commands:\n\n'), select)
1650 return
1650 return
1651
1651
1652 # check if it's an invalid alias and display its error if it is
1652 # check if it's an invalid alias and display its error if it is
1653 if getattr(entry[0], 'badalias', False):
1653 if getattr(entry[0], 'badalias', False):
1654 if not unknowncmd:
1654 if not unknowncmd:
1655 entry[0](ui)
1655 entry[0](ui)
1656 return
1656 return
1657
1657
1658 # synopsis
1658 # synopsis
1659 if len(entry) > 2:
1659 if len(entry) > 2:
1660 if entry[2].startswith('hg'):
1660 if entry[2].startswith('hg'):
1661 ui.write("%s\n" % entry[2])
1661 ui.write("%s\n" % entry[2])
1662 else:
1662 else:
1663 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
1663 ui.write('hg %s %s\n' % (aliases[0], entry[2]))
1664 else:
1664 else:
1665 ui.write('hg %s\n' % aliases[0])
1665 ui.write('hg %s\n' % aliases[0])
1666
1666
1667 # aliases
1667 # aliases
1668 if not ui.quiet and len(aliases) > 1:
1668 if not ui.quiet and len(aliases) > 1:
1669 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1669 ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:]))
1670
1670
1671 # description
1671 # description
1672 doc = gettext(entry[0].__doc__)
1672 doc = gettext(entry[0].__doc__)
1673 if not doc:
1673 if not doc:
1674 doc = _("(no help text available)")
1674 doc = _("(no help text available)")
1675 if hasattr(entry[0], 'definition'): # aliased command
1675 if hasattr(entry[0], 'definition'): # aliased command
1676 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
1676 doc = _('alias for: hg %s\n\n%s') % (entry[0].definition, doc)
1677 if ui.quiet:
1677 if ui.quiet:
1678 doc = doc.splitlines()[0]
1678 doc = doc.splitlines()[0]
1679 keep = ui.verbose and ['verbose'] or []
1679 keep = ui.verbose and ['verbose'] or []
1680 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
1680 formatted, pruned = minirst.format(doc, textwidth, keep=keep)
1681 ui.write("\n%s\n" % formatted)
1681 ui.write("\n%s\n" % formatted)
1682 if pruned:
1682 if pruned:
1683 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
1683 ui.write(_('\nuse "hg -v help %s" to show verbose help\n') % name)
1684
1684
1685 if not ui.quiet:
1685 if not ui.quiet:
1686 # options
1686 # options
1687 if entry[1]:
1687 if entry[1]:
1688 option_lists.append((_("options:\n"), entry[1]))
1688 option_lists.append((_("options:\n"), entry[1]))
1689
1689
1690 addglobalopts(False)
1690 addglobalopts(False)
1691
1691
1692 def helplist(header, select=None):
1692 def helplist(header, select=None):
1693 h = {}
1693 h = {}
1694 cmds = {}
1694 cmds = {}
1695 for c, e in table.iteritems():
1695 for c, e in table.iteritems():
1696 f = c.split("|", 1)[0]
1696 f = c.split("|", 1)[0]
1697 if select and not select(f):
1697 if select and not select(f):
1698 continue
1698 continue
1699 if (not select and name != 'shortlist' and
1699 if (not select and name != 'shortlist' and
1700 e[0].__module__ != __name__):
1700 e[0].__module__ != __name__):
1701 continue
1701 continue
1702 if name == "shortlist" and not f.startswith("^"):
1702 if name == "shortlist" and not f.startswith("^"):
1703 continue
1703 continue
1704 f = f.lstrip("^")
1704 f = f.lstrip("^")
1705 if not ui.debugflag and f.startswith("debug"):
1705 if not ui.debugflag and f.startswith("debug"):
1706 continue
1706 continue
1707 doc = e[0].__doc__
1707 doc = e[0].__doc__
1708 if doc and 'DEPRECATED' in doc and not ui.verbose:
1708 if doc and 'DEPRECATED' in doc and not ui.verbose:
1709 continue
1709 continue
1710 doc = gettext(doc)
1710 doc = gettext(doc)
1711 if not doc:
1711 if not doc:
1712 doc = _("(no help text available)")
1712 doc = _("(no help text available)")
1713 h[f] = doc.splitlines()[0].rstrip()
1713 h[f] = doc.splitlines()[0].rstrip()
1714 cmds[f] = c.lstrip("^")
1714 cmds[f] = c.lstrip("^")
1715
1715
1716 if not h:
1716 if not h:
1717 ui.status(_('no commands defined\n'))
1717 ui.status(_('no commands defined\n'))
1718 return
1718 return
1719
1719
1720 ui.status(header)
1720 ui.status(header)
1721 fns = sorted(h)
1721 fns = sorted(h)
1722 m = max(map(len, fns))
1722 m = max(map(len, fns))
1723 for f in fns:
1723 for f in fns:
1724 if ui.verbose:
1724 if ui.verbose:
1725 commands = cmds[f].replace("|",", ")
1725 commands = cmds[f].replace("|",", ")
1726 ui.write(" %s:\n %s\n"%(commands, h[f]))
1726 ui.write(" %s:\n %s\n"%(commands, h[f]))
1727 else:
1727 else:
1728 ui.write('%s\n' % (util.wrap(h[f],
1728 ui.write('%s\n' % (util.wrap(h[f],
1729 initindent=' %-*s ' % (m, f),
1729 initindent=' %-*s ' % (m, f),
1730 hangindent=' ' * (m + 4))))
1730 hangindent=' ' * (m + 4))))
1731
1731
1732 if not ui.quiet:
1732 if not ui.quiet:
1733 addglobalopts(True)
1733 addglobalopts(True)
1734
1734
1735 def helptopic(name):
1735 def helptopic(name):
1736 for names, header, doc in help.helptable:
1736 for names, header, doc in help.helptable:
1737 if name in names:
1737 if name in names:
1738 break
1738 break
1739 else:
1739 else:
1740 raise error.UnknownCommand(name)
1740 raise error.UnknownCommand(name)
1741
1741
1742 # description
1742 # description
1743 if not doc:
1743 if not doc:
1744 doc = _("(no help text available)")
1744 doc = _("(no help text available)")
1745 if hasattr(doc, '__call__'):
1745 if hasattr(doc, '__call__'):
1746 doc = doc()
1746 doc = doc()
1747
1747
1748 ui.write("%s\n\n" % header)
1748 ui.write("%s\n\n" % header)
1749 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
1749 ui.write("%s\n" % minirst.format(doc, textwidth, indent=4))
1750
1750
1751 def helpext(name):
1751 def helpext(name):
1752 try:
1752 try:
1753 mod = extensions.find(name)
1753 mod = extensions.find(name)
1754 doc = gettext(mod.__doc__) or _('no help text available')
1754 doc = gettext(mod.__doc__) or _('no help text available')
1755 except KeyError:
1755 except KeyError:
1756 mod = None
1756 mod = None
1757 doc = extensions.disabledext(name)
1757 doc = extensions.disabledext(name)
1758 if not doc:
1758 if not doc:
1759 raise error.UnknownCommand(name)
1759 raise error.UnknownCommand(name)
1760
1760
1761 if '\n' not in doc:
1761 if '\n' not in doc:
1762 head, tail = doc, ""
1762 head, tail = doc, ""
1763 else:
1763 else:
1764 head, tail = doc.split('\n', 1)
1764 head, tail = doc.split('\n', 1)
1765 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
1765 ui.write(_('%s extension - %s\n\n') % (name.split('.')[-1], head))
1766 if tail:
1766 if tail:
1767 ui.write(minirst.format(tail, textwidth))
1767 ui.write(minirst.format(tail, textwidth))
1768 ui.status('\n\n')
1768 ui.status('\n\n')
1769
1769
1770 if mod:
1770 if mod:
1771 try:
1771 try:
1772 ct = mod.cmdtable
1772 ct = mod.cmdtable
1773 except AttributeError:
1773 except AttributeError:
1774 ct = {}
1774 ct = {}
1775 modcmds = set([c.split('|', 1)[0] for c in ct])
1775 modcmds = set([c.split('|', 1)[0] for c in ct])
1776 helplist(_('list of commands:\n\n'), modcmds.__contains__)
1776 helplist(_('list of commands:\n\n'), modcmds.__contains__)
1777 else:
1777 else:
1778 ui.write(_('use "hg help extensions" for information on enabling '
1778 ui.write(_('use "hg help extensions" for information on enabling '
1779 'extensions\n'))
1779 'extensions\n'))
1780
1780
1781 def helpextcmd(name):
1781 def helpextcmd(name):
1782 cmd, ext, mod = extensions.disabledcmd(name, ui.config('ui', 'strict'))
1782 cmd, ext, mod = extensions.disabledcmd(name, ui.config('ui', 'strict'))
1783 doc = gettext(mod.__doc__).splitlines()[0]
1783 doc = gettext(mod.__doc__).splitlines()[0]
1784
1784
1785 msg = help.listexts(_("'%s' is provided by the following "
1785 msg = help.listexts(_("'%s' is provided by the following "
1786 "extension:") % cmd, {ext: doc}, len(ext),
1786 "extension:") % cmd, {ext: doc}, len(ext),
1787 indent=4)
1787 indent=4)
1788 ui.write(minirst.format(msg, textwidth))
1788 ui.write(minirst.format(msg, textwidth))
1789 ui.write('\n\n')
1789 ui.write('\n\n')
1790 ui.write(_('use "hg help extensions" for information on enabling '
1790 ui.write(_('use "hg help extensions" for information on enabling '
1791 'extensions\n'))
1791 'extensions\n'))
1792
1792
1793 if name and name != 'shortlist':
1793 if name and name != 'shortlist':
1794 i = None
1794 i = None
1795 if unknowncmd:
1795 if unknowncmd:
1796 queries = (helpextcmd,)
1796 queries = (helpextcmd,)
1797 else:
1797 else:
1798 queries = (helptopic, helpcmd, helpext, helpextcmd)
1798 queries = (helptopic, helpcmd, helpext, helpextcmd)
1799 for f in queries:
1799 for f in queries:
1800 try:
1800 try:
1801 f(name)
1801 f(name)
1802 i = None
1802 i = None
1803 break
1803 break
1804 except error.UnknownCommand, inst:
1804 except error.UnknownCommand, inst:
1805 i = inst
1805 i = inst
1806 if i:
1806 if i:
1807 raise i
1807 raise i
1808
1808
1809 else:
1809 else:
1810 # program name
1810 # program name
1811 if ui.verbose or with_version:
1811 if ui.verbose or with_version:
1812 version_(ui)
1812 version_(ui)
1813 else:
1813 else:
1814 ui.status(_("Mercurial Distributed SCM\n"))
1814 ui.status(_("Mercurial Distributed SCM\n"))
1815 ui.status('\n')
1815 ui.status('\n')
1816
1816
1817 # list of commands
1817 # list of commands
1818 if name == "shortlist":
1818 if name == "shortlist":
1819 header = _('basic commands:\n\n')
1819 header = _('basic commands:\n\n')
1820 else:
1820 else:
1821 header = _('list of commands:\n\n')
1821 header = _('list of commands:\n\n')
1822
1822
1823 helplist(header)
1823 helplist(header)
1824 if name != 'shortlist':
1824 if name != 'shortlist':
1825 exts, maxlength = extensions.enabled()
1825 exts, maxlength = extensions.enabled()
1826 text = help.listexts(_('enabled extensions:'), exts, maxlength)
1826 text = help.listexts(_('enabled extensions:'), exts, maxlength)
1827 if text:
1827 if text:
1828 ui.write("\n%s\n" % minirst.format(text, textwidth))
1828 ui.write("\n%s\n" % minirst.format(text, textwidth))
1829
1829
1830 # list all option lists
1830 # list all option lists
1831 opt_output = []
1831 opt_output = []
1832 multioccur = False
1832 for title, options in option_lists:
1833 for title, options in option_lists:
1833 opt_output.append(("\n%s" % title, None))
1834 opt_output.append(("\n%s" % title, None))
1834 for shortopt, longopt, default, desc in options:
1835 for option in options:
1836 if len(option) == 5:
1837 shortopt, longopt, default, desc, optlabel = option
1838 else:
1839 shortopt, longopt, default, desc = option
1840 optlabel = _("VALUE") # default label
1841
1835 if _("DEPRECATED") in desc and not ui.verbose:
1842 if _("DEPRECATED") in desc and not ui.verbose:
1836 continue
1843 continue
1837 opt_output.append(("%2s%s" % (shortopt and "-%s" % shortopt,
1844 if isinstance(default, list):
1838 longopt and " --%s" % longopt),
1845 numqualifier = " %s [+]" % optlabel
1846 multioccur = True
1847 elif (default is not None) and not isinstance(default, bool):
1848 numqualifier = " %s" % optlabel
1849 else:
1850 numqualifier = ""
1851 opt_output.append(("%2s%s" %
1852 (shortopt and "-%s" % shortopt,
1853 longopt and " --%s%s" %
1854 (longopt, numqualifier)),
1839 "%s%s" % (desc,
1855 "%s%s" % (desc,
1840 default
1856 default
1841 and _(" (default: %s)") % default
1857 and _(" (default: %s)") % default
1842 or "")))
1858 or "")))
1859 if multioccur:
1860 msg = _("\n[+] marked option can be specified multiple times")
1861 if ui.verbose and name != 'shortlist':
1862 opt_output.append((msg, ()))
1863 else:
1864 opt_output.insert(-1, (msg, ()))
1843
1865
1844 if not name:
1866 if not name:
1845 ui.write(_("\nadditional help topics:\n\n"))
1867 ui.write(_("\nadditional help topics:\n\n"))
1846 topics = []
1868 topics = []
1847 for names, header, doc in help.helptable:
1869 for names, header, doc in help.helptable:
1848 topics.append((sorted(names, key=len, reverse=True)[0], header))
1870 topics.append((sorted(names, key=len, reverse=True)[0], header))
1849 topics_len = max([len(s[0]) for s in topics])
1871 topics_len = max([len(s[0]) for s in topics])
1850 for t, desc in topics:
1872 for t, desc in topics:
1851 ui.write(" %-*s %s\n" % (topics_len, t, desc))
1873 ui.write(" %-*s %s\n" % (topics_len, t, desc))
1852
1874
1853 if opt_output:
1875 if opt_output:
1854 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1876 opts_len = max([len(line[0]) for line in opt_output if line[1]] or [0])
1855 for first, second in opt_output:
1877 for first, second in opt_output:
1856 if second:
1878 if second:
1857 initindent = ' %-*s ' % (opts_len, first)
1879 initindent = ' %-*s ' % (opts_len, first)
1858 hangindent = ' ' * (opts_len + 3)
1880 hangindent = ' ' * (opts_len + 3)
1859 ui.write('%s\n' % (util.wrap(second,
1881 ui.write('%s\n' % (util.wrap(second,
1860 initindent=initindent,
1882 initindent=initindent,
1861 hangindent=hangindent)))
1883 hangindent=hangindent)))
1862 else:
1884 else:
1863 ui.write("%s\n" % first)
1885 ui.write("%s\n" % first)
1864
1886
1865 def identify(ui, repo, source=None,
1887 def identify(ui, repo, source=None,
1866 rev=None, num=None, id=None, branch=None, tags=None):
1888 rev=None, num=None, id=None, branch=None, tags=None):
1867 """identify the working copy or specified revision
1889 """identify the working copy or specified revision
1868
1890
1869 With no revision, print a summary of the current state of the
1891 With no revision, print a summary of the current state of the
1870 repository.
1892 repository.
1871
1893
1872 Specifying a path to a repository root or Mercurial bundle will
1894 Specifying a path to a repository root or Mercurial bundle will
1873 cause lookup to operate on that repository/bundle.
1895 cause lookup to operate on that repository/bundle.
1874
1896
1875 This summary identifies the repository state using one or two
1897 This summary identifies the repository state using one or two
1876 parent hash identifiers, followed by a "+" if there are
1898 parent hash identifiers, followed by a "+" if there are
1877 uncommitted changes in the working directory, a list of tags for
1899 uncommitted changes in the working directory, a list of tags for
1878 this revision and a branch name for non-default branches.
1900 this revision and a branch name for non-default branches.
1879
1901
1880 Returns 0 if successful.
1902 Returns 0 if successful.
1881 """
1903 """
1882
1904
1883 if not repo and not source:
1905 if not repo and not source:
1884 raise util.Abort(_("There is no Mercurial repository here "
1906 raise util.Abort(_("There is no Mercurial repository here "
1885 "(.hg not found)"))
1907 "(.hg not found)"))
1886
1908
1887 hexfunc = ui.debugflag and hex or short
1909 hexfunc = ui.debugflag and hex or short
1888 default = not (num or id or branch or tags)
1910 default = not (num or id or branch or tags)
1889 output = []
1911 output = []
1890
1912
1891 revs = []
1913 revs = []
1892 if source:
1914 if source:
1893 source, branches = hg.parseurl(ui.expandpath(source))
1915 source, branches = hg.parseurl(ui.expandpath(source))
1894 repo = hg.repository(ui, source)
1916 repo = hg.repository(ui, source)
1895 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
1917 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
1896
1918
1897 if not repo.local():
1919 if not repo.local():
1898 if not rev and revs:
1920 if not rev and revs:
1899 rev = revs[0]
1921 rev = revs[0]
1900 if not rev:
1922 if not rev:
1901 rev = "tip"
1923 rev = "tip"
1902 if num or branch or tags:
1924 if num or branch or tags:
1903 raise util.Abort(
1925 raise util.Abort(
1904 "can't query remote revision number, branch, or tags")
1926 "can't query remote revision number, branch, or tags")
1905 output = [hexfunc(repo.lookup(rev))]
1927 output = [hexfunc(repo.lookup(rev))]
1906 elif not rev:
1928 elif not rev:
1907 ctx = repo[None]
1929 ctx = repo[None]
1908 parents = ctx.parents()
1930 parents = ctx.parents()
1909 changed = False
1931 changed = False
1910 if default or id or num:
1932 if default or id or num:
1911 changed = util.any(repo.status())
1933 changed = util.any(repo.status())
1912 if default or id:
1934 if default or id:
1913 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1935 output = ["%s%s" % ('+'.join([hexfunc(p.node()) for p in parents]),
1914 (changed) and "+" or "")]
1936 (changed) and "+" or "")]
1915 if num:
1937 if num:
1916 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1938 output.append("%s%s" % ('+'.join([str(p.rev()) for p in parents]),
1917 (changed) and "+" or ""))
1939 (changed) and "+" or ""))
1918 else:
1940 else:
1919 ctx = repo[rev]
1941 ctx = repo[rev]
1920 if default or id:
1942 if default or id:
1921 output = [hexfunc(ctx.node())]
1943 output = [hexfunc(ctx.node())]
1922 if num:
1944 if num:
1923 output.append(str(ctx.rev()))
1945 output.append(str(ctx.rev()))
1924
1946
1925 if repo.local() and default and not ui.quiet:
1947 if repo.local() and default and not ui.quiet:
1926 b = encoding.tolocal(ctx.branch())
1948 b = encoding.tolocal(ctx.branch())
1927 if b != 'default':
1949 if b != 'default':
1928 output.append("(%s)" % b)
1950 output.append("(%s)" % b)
1929
1951
1930 # multiple tags for a single parent separated by '/'
1952 # multiple tags for a single parent separated by '/'
1931 t = "/".join(ctx.tags())
1953 t = "/".join(ctx.tags())
1932 if t:
1954 if t:
1933 output.append(t)
1955 output.append(t)
1934
1956
1935 if branch:
1957 if branch:
1936 output.append(encoding.tolocal(ctx.branch()))
1958 output.append(encoding.tolocal(ctx.branch()))
1937
1959
1938 if tags:
1960 if tags:
1939 output.extend(ctx.tags())
1961 output.extend(ctx.tags())
1940
1962
1941 ui.write("%s\n" % ' '.join(output))
1963 ui.write("%s\n" % ' '.join(output))
1942
1964
1943 def import_(ui, repo, patch1, *patches, **opts):
1965 def import_(ui, repo, patch1, *patches, **opts):
1944 """import an ordered set of patches
1966 """import an ordered set of patches
1945
1967
1946 Import a list of patches and commit them individually (unless
1968 Import a list of patches and commit them individually (unless
1947 --no-commit is specified).
1969 --no-commit is specified).
1948
1970
1949 If there are outstanding changes in the working directory, import
1971 If there are outstanding changes in the working directory, import
1950 will abort unless given the -f/--force flag.
1972 will abort unless given the -f/--force flag.
1951
1973
1952 You can import a patch straight from a mail message. Even patches
1974 You can import a patch straight from a mail message. Even patches
1953 as attachments work (to use the body part, it must have type
1975 as attachments work (to use the body part, it must have type
1954 text/plain or text/x-patch). From and Subject headers of email
1976 text/plain or text/x-patch). From and Subject headers of email
1955 message are used as default committer and commit message. All
1977 message are used as default committer and commit message. All
1956 text/plain body parts before first diff are added to commit
1978 text/plain body parts before first diff are added to commit
1957 message.
1979 message.
1958
1980
1959 If the imported patch was generated by :hg:`export`, user and
1981 If the imported patch was generated by :hg:`export`, user and
1960 description from patch override values from message headers and
1982 description from patch override values from message headers and
1961 body. Values given on command line with -m/--message and -u/--user
1983 body. Values given on command line with -m/--message and -u/--user
1962 override these.
1984 override these.
1963
1985
1964 If --exact is specified, import will set the working directory to
1986 If --exact is specified, import will set the working directory to
1965 the parent of each patch before applying it, and will abort if the
1987 the parent of each patch before applying it, and will abort if the
1966 resulting changeset has a different ID than the one recorded in
1988 resulting changeset has a different ID than the one recorded in
1967 the patch. This may happen due to character set problems or other
1989 the patch. This may happen due to character set problems or other
1968 deficiencies in the text patch format.
1990 deficiencies in the text patch format.
1969
1991
1970 With -s/--similarity, hg will attempt to discover renames and
1992 With -s/--similarity, hg will attempt to discover renames and
1971 copies in the patch in the same way as 'addremove'.
1993 copies in the patch in the same way as 'addremove'.
1972
1994
1973 To read a patch from standard input, use "-" as the patch name. If
1995 To read a patch from standard input, use "-" as the patch name. If
1974 a URL is specified, the patch will be downloaded from it.
1996 a URL is specified, the patch will be downloaded from it.
1975 See :hg:`help dates` for a list of formats valid for -d/--date.
1997 See :hg:`help dates` for a list of formats valid for -d/--date.
1976
1998
1977 Returns 0 on success.
1999 Returns 0 on success.
1978 """
2000 """
1979 patches = (patch1,) + patches
2001 patches = (patch1,) + patches
1980
2002
1981 date = opts.get('date')
2003 date = opts.get('date')
1982 if date:
2004 if date:
1983 opts['date'] = util.parsedate(date)
2005 opts['date'] = util.parsedate(date)
1984
2006
1985 try:
2007 try:
1986 sim = float(opts.get('similarity') or 0)
2008 sim = float(opts.get('similarity') or 0)
1987 except ValueError:
2009 except ValueError:
1988 raise util.Abort(_('similarity must be a number'))
2010 raise util.Abort(_('similarity must be a number'))
1989 if sim < 0 or sim > 100:
2011 if sim < 0 or sim > 100:
1990 raise util.Abort(_('similarity must be between 0 and 100'))
2012 raise util.Abort(_('similarity must be between 0 and 100'))
1991
2013
1992 if opts.get('exact') or not opts.get('force'):
2014 if opts.get('exact') or not opts.get('force'):
1993 cmdutil.bail_if_changed(repo)
2015 cmdutil.bail_if_changed(repo)
1994
2016
1995 d = opts["base"]
2017 d = opts["base"]
1996 strip = opts["strip"]
2018 strip = opts["strip"]
1997 wlock = lock = None
2019 wlock = lock = None
1998
2020
1999 def tryone(ui, hunk):
2021 def tryone(ui, hunk):
2000 tmpname, message, user, date, branch, nodeid, p1, p2 = \
2022 tmpname, message, user, date, branch, nodeid, p1, p2 = \
2001 patch.extract(ui, hunk)
2023 patch.extract(ui, hunk)
2002
2024
2003 if not tmpname:
2025 if not tmpname:
2004 return None
2026 return None
2005 commitid = _('to working directory')
2027 commitid = _('to working directory')
2006
2028
2007 try:
2029 try:
2008 cmdline_message = cmdutil.logmessage(opts)
2030 cmdline_message = cmdutil.logmessage(opts)
2009 if cmdline_message:
2031 if cmdline_message:
2010 # pickup the cmdline msg
2032 # pickup the cmdline msg
2011 message = cmdline_message
2033 message = cmdline_message
2012 elif message:
2034 elif message:
2013 # pickup the patch msg
2035 # pickup the patch msg
2014 message = message.strip()
2036 message = message.strip()
2015 else:
2037 else:
2016 # launch the editor
2038 # launch the editor
2017 message = None
2039 message = None
2018 ui.debug('message:\n%s\n' % message)
2040 ui.debug('message:\n%s\n' % message)
2019
2041
2020 wp = repo.parents()
2042 wp = repo.parents()
2021 if opts.get('exact'):
2043 if opts.get('exact'):
2022 if not nodeid or not p1:
2044 if not nodeid or not p1:
2023 raise util.Abort(_('not a Mercurial patch'))
2045 raise util.Abort(_('not a Mercurial patch'))
2024 p1 = repo.lookup(p1)
2046 p1 = repo.lookup(p1)
2025 p2 = repo.lookup(p2 or hex(nullid))
2047 p2 = repo.lookup(p2 or hex(nullid))
2026
2048
2027 if p1 != wp[0].node():
2049 if p1 != wp[0].node():
2028 hg.clean(repo, p1)
2050 hg.clean(repo, p1)
2029 repo.dirstate.setparents(p1, p2)
2051 repo.dirstate.setparents(p1, p2)
2030 elif p2:
2052 elif p2:
2031 try:
2053 try:
2032 p1 = repo.lookup(p1)
2054 p1 = repo.lookup(p1)
2033 p2 = repo.lookup(p2)
2055 p2 = repo.lookup(p2)
2034 if p1 == wp[0].node():
2056 if p1 == wp[0].node():
2035 repo.dirstate.setparents(p1, p2)
2057 repo.dirstate.setparents(p1, p2)
2036 except error.RepoError:
2058 except error.RepoError:
2037 pass
2059 pass
2038 if opts.get('exact') or opts.get('import_branch'):
2060 if opts.get('exact') or opts.get('import_branch'):
2039 repo.dirstate.setbranch(branch or 'default')
2061 repo.dirstate.setbranch(branch or 'default')
2040
2062
2041 files = {}
2063 files = {}
2042 try:
2064 try:
2043 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
2065 patch.patch(tmpname, ui, strip=strip, cwd=repo.root,
2044 files=files, eolmode=None)
2066 files=files, eolmode=None)
2045 finally:
2067 finally:
2046 files = patch.updatedir(ui, repo, files,
2068 files = patch.updatedir(ui, repo, files,
2047 similarity=sim / 100.0)
2069 similarity=sim / 100.0)
2048 if not opts.get('no_commit'):
2070 if not opts.get('no_commit'):
2049 if opts.get('exact'):
2071 if opts.get('exact'):
2050 m = None
2072 m = None
2051 else:
2073 else:
2052 m = cmdutil.matchfiles(repo, files or [])
2074 m = cmdutil.matchfiles(repo, files or [])
2053 n = repo.commit(message, opts.get('user') or user,
2075 n = repo.commit(message, opts.get('user') or user,
2054 opts.get('date') or date, match=m,
2076 opts.get('date') or date, match=m,
2055 editor=cmdutil.commiteditor)
2077 editor=cmdutil.commiteditor)
2056 if opts.get('exact'):
2078 if opts.get('exact'):
2057 if hex(n) != nodeid:
2079 if hex(n) != nodeid:
2058 repo.rollback()
2080 repo.rollback()
2059 raise util.Abort(_('patch is damaged'
2081 raise util.Abort(_('patch is damaged'
2060 ' or loses information'))
2082 ' or loses information'))
2061 # Force a dirstate write so that the next transaction
2083 # Force a dirstate write so that the next transaction
2062 # backups an up-do-date file.
2084 # backups an up-do-date file.
2063 repo.dirstate.write()
2085 repo.dirstate.write()
2064 if n:
2086 if n:
2065 commitid = short(n)
2087 commitid = short(n)
2066
2088
2067 return commitid
2089 return commitid
2068 finally:
2090 finally:
2069 os.unlink(tmpname)
2091 os.unlink(tmpname)
2070
2092
2071 try:
2093 try:
2072 wlock = repo.wlock()
2094 wlock = repo.wlock()
2073 lock = repo.lock()
2095 lock = repo.lock()
2074 lastcommit = None
2096 lastcommit = None
2075 for p in patches:
2097 for p in patches:
2076 pf = os.path.join(d, p)
2098 pf = os.path.join(d, p)
2077
2099
2078 if pf == '-':
2100 if pf == '-':
2079 ui.status(_("applying patch from stdin\n"))
2101 ui.status(_("applying patch from stdin\n"))
2080 pf = sys.stdin
2102 pf = sys.stdin
2081 else:
2103 else:
2082 ui.status(_("applying %s\n") % p)
2104 ui.status(_("applying %s\n") % p)
2083 pf = url.open(ui, pf)
2105 pf = url.open(ui, pf)
2084
2106
2085 haspatch = False
2107 haspatch = False
2086 for hunk in patch.split(pf):
2108 for hunk in patch.split(pf):
2087 commitid = tryone(ui, hunk)
2109 commitid = tryone(ui, hunk)
2088 if commitid:
2110 if commitid:
2089 haspatch = True
2111 haspatch = True
2090 if lastcommit:
2112 if lastcommit:
2091 ui.status(_('applied %s\n') % lastcommit)
2113 ui.status(_('applied %s\n') % lastcommit)
2092 lastcommit = commitid
2114 lastcommit = commitid
2093
2115
2094 if not haspatch:
2116 if not haspatch:
2095 raise util.Abort(_('no diffs found'))
2117 raise util.Abort(_('no diffs found'))
2096
2118
2097 finally:
2119 finally:
2098 release(lock, wlock)
2120 release(lock, wlock)
2099
2121
2100 def incoming(ui, repo, source="default", **opts):
2122 def incoming(ui, repo, source="default", **opts):
2101 """show new changesets found in source
2123 """show new changesets found in source
2102
2124
2103 Show new changesets found in the specified path/URL or the default
2125 Show new changesets found in the specified path/URL or the default
2104 pull location. These are the changesets that would have been pulled
2126 pull location. These are the changesets that would have been pulled
2105 if a pull at the time you issued this command.
2127 if a pull at the time you issued this command.
2106
2128
2107 For remote repository, using --bundle avoids downloading the
2129 For remote repository, using --bundle avoids downloading the
2108 changesets twice if the incoming is followed by a pull.
2130 changesets twice if the incoming is followed by a pull.
2109
2131
2110 See pull for valid source format details.
2132 See pull for valid source format details.
2111
2133
2112 Returns 0 if there are incoming changes, 1 otherwise.
2134 Returns 0 if there are incoming changes, 1 otherwise.
2113 """
2135 """
2114 limit = cmdutil.loglimit(opts)
2136 limit = cmdutil.loglimit(opts)
2115 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2137 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2116 other = hg.repository(hg.remoteui(repo, opts), source)
2138 other = hg.repository(hg.remoteui(repo, opts), source)
2117 ui.status(_('comparing with %s\n') % url.hidepassword(source))
2139 ui.status(_('comparing with %s\n') % url.hidepassword(source))
2118 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2140 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2119 if revs:
2141 if revs:
2120 revs = [other.lookup(rev) for rev in revs]
2142 revs = [other.lookup(rev) for rev in revs]
2121
2143
2122 tmp = discovery.findcommonincoming(repo, other, heads=revs,
2144 tmp = discovery.findcommonincoming(repo, other, heads=revs,
2123 force=opts.get('force'))
2145 force=opts.get('force'))
2124 common, incoming, rheads = tmp
2146 common, incoming, rheads = tmp
2125 if not incoming:
2147 if not incoming:
2126 try:
2148 try:
2127 os.unlink(opts["bundle"])
2149 os.unlink(opts["bundle"])
2128 except:
2150 except:
2129 pass
2151 pass
2130 ui.status(_("no changes found\n"))
2152 ui.status(_("no changes found\n"))
2131 return 1
2153 return 1
2132
2154
2133 cleanup = None
2155 cleanup = None
2134 try:
2156 try:
2135 fname = opts["bundle"]
2157 fname = opts["bundle"]
2136 if fname or not other.local():
2158 if fname or not other.local():
2137 # create a bundle (uncompressed if other repo is not local)
2159 # create a bundle (uncompressed if other repo is not local)
2138
2160
2139 if revs is None and other.capable('changegroupsubset'):
2161 if revs is None and other.capable('changegroupsubset'):
2140 revs = rheads
2162 revs = rheads
2141
2163
2142 if revs is None:
2164 if revs is None:
2143 cg = other.changegroup(incoming, "incoming")
2165 cg = other.changegroup(incoming, "incoming")
2144 else:
2166 else:
2145 cg = other.changegroupsubset(incoming, revs, 'incoming')
2167 cg = other.changegroupsubset(incoming, revs, 'incoming')
2146 bundletype = other.local() and "HG10BZ" or "HG10UN"
2168 bundletype = other.local() and "HG10BZ" or "HG10UN"
2147 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
2169 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
2148 # keep written bundle?
2170 # keep written bundle?
2149 if opts["bundle"]:
2171 if opts["bundle"]:
2150 cleanup = None
2172 cleanup = None
2151 if not other.local():
2173 if not other.local():
2152 # use the created uncompressed bundlerepo
2174 # use the created uncompressed bundlerepo
2153 other = bundlerepo.bundlerepository(ui, repo.root, fname)
2175 other = bundlerepo.bundlerepository(ui, repo.root, fname)
2154
2176
2155 o = other.changelog.nodesbetween(incoming, revs)[0]
2177 o = other.changelog.nodesbetween(incoming, revs)[0]
2156 if opts.get('newest_first'):
2178 if opts.get('newest_first'):
2157 o.reverse()
2179 o.reverse()
2158 displayer = cmdutil.show_changeset(ui, other, opts)
2180 displayer = cmdutil.show_changeset(ui, other, opts)
2159 count = 0
2181 count = 0
2160 for n in o:
2182 for n in o:
2161 if limit is not None and count >= limit:
2183 if limit is not None and count >= limit:
2162 break
2184 break
2163 parents = [p for p in other.changelog.parents(n) if p != nullid]
2185 parents = [p for p in other.changelog.parents(n) if p != nullid]
2164 if opts.get('no_merges') and len(parents) == 2:
2186 if opts.get('no_merges') and len(parents) == 2:
2165 continue
2187 continue
2166 count += 1
2188 count += 1
2167 displayer.show(other[n])
2189 displayer.show(other[n])
2168 displayer.close()
2190 displayer.close()
2169 finally:
2191 finally:
2170 if hasattr(other, 'close'):
2192 if hasattr(other, 'close'):
2171 other.close()
2193 other.close()
2172 if cleanup:
2194 if cleanup:
2173 os.unlink(cleanup)
2195 os.unlink(cleanup)
2174
2196
2175 def init(ui, dest=".", **opts):
2197 def init(ui, dest=".", **opts):
2176 """create a new repository in the given directory
2198 """create a new repository in the given directory
2177
2199
2178 Initialize a new repository in the given directory. If the given
2200 Initialize a new repository in the given directory. If the given
2179 directory does not exist, it will be created.
2201 directory does not exist, it will be created.
2180
2202
2181 If no directory is given, the current directory is used.
2203 If no directory is given, the current directory is used.
2182
2204
2183 It is possible to specify an ``ssh://`` URL as the destination.
2205 It is possible to specify an ``ssh://`` URL as the destination.
2184 See :hg:`help urls` for more information.
2206 See :hg:`help urls` for more information.
2185
2207
2186 Returns 0 on success.
2208 Returns 0 on success.
2187 """
2209 """
2188 hg.repository(hg.remoteui(ui, opts), dest, create=1)
2210 hg.repository(hg.remoteui(ui, opts), dest, create=1)
2189
2211
2190 def locate(ui, repo, *pats, **opts):
2212 def locate(ui, repo, *pats, **opts):
2191 """locate files matching specific patterns
2213 """locate files matching specific patterns
2192
2214
2193 Print files under Mercurial control in the working directory whose
2215 Print files under Mercurial control in the working directory whose
2194 names match the given patterns.
2216 names match the given patterns.
2195
2217
2196 By default, this command searches all directories in the working
2218 By default, this command searches all directories in the working
2197 directory. To search just the current directory and its
2219 directory. To search just the current directory and its
2198 subdirectories, use "--include .".
2220 subdirectories, use "--include .".
2199
2221
2200 If no patterns are given to match, this command prints the names
2222 If no patterns are given to match, this command prints the names
2201 of all files under Mercurial control in the working directory.
2223 of all files under Mercurial control in the working directory.
2202
2224
2203 If you want to feed the output of this command into the "xargs"
2225 If you want to feed the output of this command into the "xargs"
2204 command, use the -0 option to both this command and "xargs". This
2226 command, use the -0 option to both this command and "xargs". This
2205 will avoid the problem of "xargs" treating single filenames that
2227 will avoid the problem of "xargs" treating single filenames that
2206 contain whitespace as multiple filenames.
2228 contain whitespace as multiple filenames.
2207
2229
2208 Returns 0 if a match is found, 1 otherwise.
2230 Returns 0 if a match is found, 1 otherwise.
2209 """
2231 """
2210 end = opts.get('print0') and '\0' or '\n'
2232 end = opts.get('print0') and '\0' or '\n'
2211 rev = opts.get('rev') or None
2233 rev = opts.get('rev') or None
2212
2234
2213 ret = 1
2235 ret = 1
2214 m = cmdutil.match(repo, pats, opts, default='relglob')
2236 m = cmdutil.match(repo, pats, opts, default='relglob')
2215 m.bad = lambda x, y: False
2237 m.bad = lambda x, y: False
2216 for abs in repo[rev].walk(m):
2238 for abs in repo[rev].walk(m):
2217 if not rev and abs not in repo.dirstate:
2239 if not rev and abs not in repo.dirstate:
2218 continue
2240 continue
2219 if opts.get('fullpath'):
2241 if opts.get('fullpath'):
2220 ui.write(repo.wjoin(abs), end)
2242 ui.write(repo.wjoin(abs), end)
2221 else:
2243 else:
2222 ui.write(((pats and m.rel(abs)) or abs), end)
2244 ui.write(((pats and m.rel(abs)) or abs), end)
2223 ret = 0
2245 ret = 0
2224
2246
2225 return ret
2247 return ret
2226
2248
2227 def log(ui, repo, *pats, **opts):
2249 def log(ui, repo, *pats, **opts):
2228 """show revision history of entire repository or files
2250 """show revision history of entire repository or files
2229
2251
2230 Print the revision history of the specified files or the entire
2252 Print the revision history of the specified files or the entire
2231 project.
2253 project.
2232
2254
2233 File history is shown without following rename or copy history of
2255 File history is shown without following rename or copy history of
2234 files. Use -f/--follow with a filename to follow history across
2256 files. Use -f/--follow with a filename to follow history across
2235 renames and copies. --follow without a filename will only show
2257 renames and copies. --follow without a filename will only show
2236 ancestors or descendants of the starting revision. --follow-first
2258 ancestors or descendants of the starting revision. --follow-first
2237 only follows the first parent of merge revisions.
2259 only follows the first parent of merge revisions.
2238
2260
2239 If no revision range is specified, the default is tip:0 unless
2261 If no revision range is specified, the default is tip:0 unless
2240 --follow is set, in which case the working directory parent is
2262 --follow is set, in which case the working directory parent is
2241 used as the starting revision.
2263 used as the starting revision.
2242
2264
2243 See :hg:`help dates` for a list of formats valid for -d/--date.
2265 See :hg:`help dates` for a list of formats valid for -d/--date.
2244
2266
2245 By default this command prints revision number and changeset id,
2267 By default this command prints revision number and changeset id,
2246 tags, non-trivial parents, user, date and time, and a summary for
2268 tags, non-trivial parents, user, date and time, and a summary for
2247 each commit. When the -v/--verbose switch is used, the list of
2269 each commit. When the -v/--verbose switch is used, the list of
2248 changed files and full commit message are shown.
2270 changed files and full commit message are shown.
2249
2271
2250 NOTE: log -p/--patch may generate unexpected diff output for merge
2272 NOTE: log -p/--patch may generate unexpected diff output for merge
2251 changesets, as it will only compare the merge changeset against
2273 changesets, as it will only compare the merge changeset against
2252 its first parent. Also, only files different from BOTH parents
2274 its first parent. Also, only files different from BOTH parents
2253 will appear in files:.
2275 will appear in files:.
2254
2276
2255 Returns 0 on success.
2277 Returns 0 on success.
2256 """
2278 """
2257
2279
2258 matchfn = cmdutil.match(repo, pats, opts)
2280 matchfn = cmdutil.match(repo, pats, opts)
2259 limit = cmdutil.loglimit(opts)
2281 limit = cmdutil.loglimit(opts)
2260 count = 0
2282 count = 0
2261
2283
2262 endrev = None
2284 endrev = None
2263 if opts.get('copies') and opts.get('rev'):
2285 if opts.get('copies') and opts.get('rev'):
2264 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2286 endrev = max(cmdutil.revrange(repo, opts.get('rev'))) + 1
2265
2287
2266 df = False
2288 df = False
2267 if opts["date"]:
2289 if opts["date"]:
2268 df = util.matchdate(opts["date"])
2290 df = util.matchdate(opts["date"])
2269
2291
2270 branches = opts.get('branch', []) + opts.get('only_branch', [])
2292 branches = opts.get('branch', []) + opts.get('only_branch', [])
2271 opts['branch'] = [repo.lookupbranch(b) for b in branches]
2293 opts['branch'] = [repo.lookupbranch(b) for b in branches]
2272
2294
2273 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
2295 displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn)
2274 def prep(ctx, fns):
2296 def prep(ctx, fns):
2275 rev = ctx.rev()
2297 rev = ctx.rev()
2276 parents = [p for p in repo.changelog.parentrevs(rev)
2298 parents = [p for p in repo.changelog.parentrevs(rev)
2277 if p != nullrev]
2299 if p != nullrev]
2278 if opts.get('no_merges') and len(parents) == 2:
2300 if opts.get('no_merges') and len(parents) == 2:
2279 return
2301 return
2280 if opts.get('only_merges') and len(parents) != 2:
2302 if opts.get('only_merges') and len(parents) != 2:
2281 return
2303 return
2282 if opts.get('branch') and ctx.branch() not in opts['branch']:
2304 if opts.get('branch') and ctx.branch() not in opts['branch']:
2283 return
2305 return
2284 if df and not df(ctx.date()[0]):
2306 if df and not df(ctx.date()[0]):
2285 return
2307 return
2286 if opts['user'] and not [k for k in opts['user'] if k in ctx.user()]:
2308 if opts['user'] and not [k for k in opts['user'] if k in ctx.user()]:
2287 return
2309 return
2288 if opts.get('keyword'):
2310 if opts.get('keyword'):
2289 for k in [kw.lower() for kw in opts['keyword']]:
2311 for k in [kw.lower() for kw in opts['keyword']]:
2290 if (k in ctx.user().lower() or
2312 if (k in ctx.user().lower() or
2291 k in ctx.description().lower() or
2313 k in ctx.description().lower() or
2292 k in " ".join(ctx.files()).lower()):
2314 k in " ".join(ctx.files()).lower()):
2293 break
2315 break
2294 else:
2316 else:
2295 return
2317 return
2296
2318
2297 copies = None
2319 copies = None
2298 if opts.get('copies') and rev:
2320 if opts.get('copies') and rev:
2299 copies = []
2321 copies = []
2300 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2322 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2301 for fn in ctx.files():
2323 for fn in ctx.files():
2302 rename = getrenamed(fn, rev)
2324 rename = getrenamed(fn, rev)
2303 if rename:
2325 if rename:
2304 copies.append((fn, rename[0]))
2326 copies.append((fn, rename[0]))
2305
2327
2306 displayer.show(ctx, copies=copies)
2328 displayer.show(ctx, copies=copies)
2307
2329
2308 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2330 for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
2309 if count == limit:
2331 if count == limit:
2310 break
2332 break
2311 if displayer.flush(ctx.rev()):
2333 if displayer.flush(ctx.rev()):
2312 count += 1
2334 count += 1
2313 displayer.close()
2335 displayer.close()
2314
2336
2315 def manifest(ui, repo, node=None, rev=None):
2337 def manifest(ui, repo, node=None, rev=None):
2316 """output the current or given revision of the project manifest
2338 """output the current or given revision of the project manifest
2317
2339
2318 Print a list of version controlled files for the given revision.
2340 Print a list of version controlled files for the given revision.
2319 If no revision is given, the first parent of the working directory
2341 If no revision is given, the first parent of the working directory
2320 is used, or the null revision if no revision is checked out.
2342 is used, or the null revision if no revision is checked out.
2321
2343
2322 With -v, print file permissions, symlink and executable bits.
2344 With -v, print file permissions, symlink and executable bits.
2323 With --debug, print file revision hashes.
2345 With --debug, print file revision hashes.
2324
2346
2325 Returns 0 on success.
2347 Returns 0 on success.
2326 """
2348 """
2327
2349
2328 if rev and node:
2350 if rev and node:
2329 raise util.Abort(_("please specify just one revision"))
2351 raise util.Abort(_("please specify just one revision"))
2330
2352
2331 if not node:
2353 if not node:
2332 node = rev
2354 node = rev
2333
2355
2334 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2356 decor = {'l':'644 @ ', 'x':'755 * ', '':'644 '}
2335 ctx = repo[node]
2357 ctx = repo[node]
2336 for f in ctx:
2358 for f in ctx:
2337 if ui.debugflag:
2359 if ui.debugflag:
2338 ui.write("%40s " % hex(ctx.manifest()[f]))
2360 ui.write("%40s " % hex(ctx.manifest()[f]))
2339 if ui.verbose:
2361 if ui.verbose:
2340 ui.write(decor[ctx.flags(f)])
2362 ui.write(decor[ctx.flags(f)])
2341 ui.write("%s\n" % f)
2363 ui.write("%s\n" % f)
2342
2364
2343 def merge(ui, repo, node=None, **opts):
2365 def merge(ui, repo, node=None, **opts):
2344 """merge working directory with another revision
2366 """merge working directory with another revision
2345
2367
2346 The current working directory is updated with all changes made in
2368 The current working directory is updated with all changes made in
2347 the requested revision since the last common predecessor revision.
2369 the requested revision since the last common predecessor revision.
2348
2370
2349 Files that changed between either parent are marked as changed for
2371 Files that changed between either parent are marked as changed for
2350 the next commit and a commit must be performed before any further
2372 the next commit and a commit must be performed before any further
2351 updates to the repository are allowed. The next commit will have
2373 updates to the repository are allowed. The next commit will have
2352 two parents.
2374 two parents.
2353
2375
2354 If no revision is specified, the working directory's parent is a
2376 If no revision is specified, the working directory's parent is a
2355 head revision, and the current branch contains exactly one other
2377 head revision, and the current branch contains exactly one other
2356 head, the other head is merged with by default. Otherwise, an
2378 head, the other head is merged with by default. Otherwise, an
2357 explicit revision with which to merge with must be provided.
2379 explicit revision with which to merge with must be provided.
2358
2380
2359 Returns 0 on success, 1 if there are unresolved files.
2381 Returns 0 on success, 1 if there are unresolved files.
2360 """
2382 """
2361
2383
2362 if opts.get('rev') and node:
2384 if opts.get('rev') and node:
2363 raise util.Abort(_("please specify just one revision"))
2385 raise util.Abort(_("please specify just one revision"))
2364 if not node:
2386 if not node:
2365 node = opts.get('rev')
2387 node = opts.get('rev')
2366
2388
2367 if not node:
2389 if not node:
2368 branch = repo.changectx(None).branch()
2390 branch = repo.changectx(None).branch()
2369 bheads = repo.branchheads(branch)
2391 bheads = repo.branchheads(branch)
2370 if len(bheads) > 2:
2392 if len(bheads) > 2:
2371 ui.warn(_("abort: branch '%s' has %d heads - "
2393 ui.warn(_("abort: branch '%s' has %d heads - "
2372 "please merge with an explicit rev\n")
2394 "please merge with an explicit rev\n")
2373 % (branch, len(bheads)))
2395 % (branch, len(bheads)))
2374 ui.status(_("(run 'hg heads .' to see heads)\n"))
2396 ui.status(_("(run 'hg heads .' to see heads)\n"))
2375 return False
2397 return False
2376
2398
2377 parent = repo.dirstate.parents()[0]
2399 parent = repo.dirstate.parents()[0]
2378 if len(bheads) == 1:
2400 if len(bheads) == 1:
2379 if len(repo.heads()) > 1:
2401 if len(repo.heads()) > 1:
2380 ui.warn(_("abort: branch '%s' has one head - "
2402 ui.warn(_("abort: branch '%s' has one head - "
2381 "please merge with an explicit rev\n" % branch))
2403 "please merge with an explicit rev\n" % branch))
2382 ui.status(_("(run 'hg heads' to see all heads)\n"))
2404 ui.status(_("(run 'hg heads' to see all heads)\n"))
2383 return False
2405 return False
2384 msg = _('there is nothing to merge')
2406 msg = _('there is nothing to merge')
2385 if parent != repo.lookup(repo[None].branch()):
2407 if parent != repo.lookup(repo[None].branch()):
2386 msg = _('%s - use "hg update" instead') % msg
2408 msg = _('%s - use "hg update" instead') % msg
2387 raise util.Abort(msg)
2409 raise util.Abort(msg)
2388
2410
2389 if parent not in bheads:
2411 if parent not in bheads:
2390 raise util.Abort(_('working dir not at a head rev - '
2412 raise util.Abort(_('working dir not at a head rev - '
2391 'use "hg update" or merge with an explicit rev'))
2413 'use "hg update" or merge with an explicit rev'))
2392 node = parent == bheads[0] and bheads[-1] or bheads[0]
2414 node = parent == bheads[0] and bheads[-1] or bheads[0]
2393
2415
2394 if opts.get('preview'):
2416 if opts.get('preview'):
2395 # find nodes that are ancestors of p2 but not of p1
2417 # find nodes that are ancestors of p2 but not of p1
2396 p1 = repo.lookup('.')
2418 p1 = repo.lookup('.')
2397 p2 = repo.lookup(node)
2419 p2 = repo.lookup(node)
2398 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2420 nodes = repo.changelog.findmissing(common=[p1], heads=[p2])
2399
2421
2400 displayer = cmdutil.show_changeset(ui, repo, opts)
2422 displayer = cmdutil.show_changeset(ui, repo, opts)
2401 for node in nodes:
2423 for node in nodes:
2402 displayer.show(repo[node])
2424 displayer.show(repo[node])
2403 displayer.close()
2425 displayer.close()
2404 return 0
2426 return 0
2405
2427
2406 return hg.merge(repo, node, force=opts.get('force'))
2428 return hg.merge(repo, node, force=opts.get('force'))
2407
2429
2408 def outgoing(ui, repo, dest=None, **opts):
2430 def outgoing(ui, repo, dest=None, **opts):
2409 """show changesets not found in the destination
2431 """show changesets not found in the destination
2410
2432
2411 Show changesets not found in the specified destination repository
2433 Show changesets not found in the specified destination repository
2412 or the default push location. These are the changesets that would
2434 or the default push location. These are the changesets that would
2413 be pushed if a push was requested.
2435 be pushed if a push was requested.
2414
2436
2415 See pull for details of valid destination formats.
2437 See pull for details of valid destination formats.
2416
2438
2417 Returns 0 if there are outgoing changes, 1 otherwise.
2439 Returns 0 if there are outgoing changes, 1 otherwise.
2418 """
2440 """
2419 limit = cmdutil.loglimit(opts)
2441 limit = cmdutil.loglimit(opts)
2420 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2442 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2421 dest, branches = hg.parseurl(dest, opts.get('branch'))
2443 dest, branches = hg.parseurl(dest, opts.get('branch'))
2422 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2444 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2423 if revs:
2445 if revs:
2424 revs = [repo.lookup(rev) for rev in revs]
2446 revs = [repo.lookup(rev) for rev in revs]
2425
2447
2426 other = hg.repository(hg.remoteui(repo, opts), dest)
2448 other = hg.repository(hg.remoteui(repo, opts), dest)
2427 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2449 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
2428 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
2450 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
2429 if not o:
2451 if not o:
2430 ui.status(_("no changes found\n"))
2452 ui.status(_("no changes found\n"))
2431 return 1
2453 return 1
2432 o = repo.changelog.nodesbetween(o, revs)[0]
2454 o = repo.changelog.nodesbetween(o, revs)[0]
2433 if opts.get('newest_first'):
2455 if opts.get('newest_first'):
2434 o.reverse()
2456 o.reverse()
2435 displayer = cmdutil.show_changeset(ui, repo, opts)
2457 displayer = cmdutil.show_changeset(ui, repo, opts)
2436 count = 0
2458 count = 0
2437 for n in o:
2459 for n in o:
2438 if limit is not None and count >= limit:
2460 if limit is not None and count >= limit:
2439 break
2461 break
2440 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2462 parents = [p for p in repo.changelog.parents(n) if p != nullid]
2441 if opts.get('no_merges') and len(parents) == 2:
2463 if opts.get('no_merges') and len(parents) == 2:
2442 continue
2464 continue
2443 count += 1
2465 count += 1
2444 displayer.show(repo[n])
2466 displayer.show(repo[n])
2445 displayer.close()
2467 displayer.close()
2446
2468
2447 def parents(ui, repo, file_=None, **opts):
2469 def parents(ui, repo, file_=None, **opts):
2448 """show the parents of the working directory or revision
2470 """show the parents of the working directory or revision
2449
2471
2450 Print the working directory's parent revisions. If a revision is
2472 Print the working directory's parent revisions. If a revision is
2451 given via -r/--rev, the parent of that revision will be printed.
2473 given via -r/--rev, the parent of that revision will be printed.
2452 If a file argument is given, the revision in which the file was
2474 If a file argument is given, the revision in which the file was
2453 last changed (before the working directory revision or the
2475 last changed (before the working directory revision or the
2454 argument to --rev if given) is printed.
2476 argument to --rev if given) is printed.
2455
2477
2456 Returns 0 on success.
2478 Returns 0 on success.
2457 """
2479 """
2458 rev = opts.get('rev')
2480 rev = opts.get('rev')
2459 if rev:
2481 if rev:
2460 ctx = repo[rev]
2482 ctx = repo[rev]
2461 else:
2483 else:
2462 ctx = repo[None]
2484 ctx = repo[None]
2463
2485
2464 if file_:
2486 if file_:
2465 m = cmdutil.match(repo, (file_,), opts)
2487 m = cmdutil.match(repo, (file_,), opts)
2466 if m.anypats() or len(m.files()) != 1:
2488 if m.anypats() or len(m.files()) != 1:
2467 raise util.Abort(_('can only specify an explicit filename'))
2489 raise util.Abort(_('can only specify an explicit filename'))
2468 file_ = m.files()[0]
2490 file_ = m.files()[0]
2469 filenodes = []
2491 filenodes = []
2470 for cp in ctx.parents():
2492 for cp in ctx.parents():
2471 if not cp:
2493 if not cp:
2472 continue
2494 continue
2473 try:
2495 try:
2474 filenodes.append(cp.filenode(file_))
2496 filenodes.append(cp.filenode(file_))
2475 except error.LookupError:
2497 except error.LookupError:
2476 pass
2498 pass
2477 if not filenodes:
2499 if not filenodes:
2478 raise util.Abort(_("'%s' not found in manifest!") % file_)
2500 raise util.Abort(_("'%s' not found in manifest!") % file_)
2479 fl = repo.file(file_)
2501 fl = repo.file(file_)
2480 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2502 p = [repo.lookup(fl.linkrev(fl.rev(fn))) for fn in filenodes]
2481 else:
2503 else:
2482 p = [cp.node() for cp in ctx.parents()]
2504 p = [cp.node() for cp in ctx.parents()]
2483
2505
2484 displayer = cmdutil.show_changeset(ui, repo, opts)
2506 displayer = cmdutil.show_changeset(ui, repo, opts)
2485 for n in p:
2507 for n in p:
2486 if n != nullid:
2508 if n != nullid:
2487 displayer.show(repo[n])
2509 displayer.show(repo[n])
2488 displayer.close()
2510 displayer.close()
2489
2511
2490 def paths(ui, repo, search=None):
2512 def paths(ui, repo, search=None):
2491 """show aliases for remote repositories
2513 """show aliases for remote repositories
2492
2514
2493 Show definition of symbolic path name NAME. If no name is given,
2515 Show definition of symbolic path name NAME. If no name is given,
2494 show definition of all available names.
2516 show definition of all available names.
2495
2517
2496 Path names are defined in the [paths] section of
2518 Path names are defined in the [paths] section of
2497 ``/etc/mercurial/hgrc`` and ``$HOME/.hgrc``. If run inside a
2519 ``/etc/mercurial/hgrc`` and ``$HOME/.hgrc``. If run inside a
2498 repository, ``.hg/hgrc`` is used, too.
2520 repository, ``.hg/hgrc`` is used, too.
2499
2521
2500 The path names ``default`` and ``default-push`` have a special
2522 The path names ``default`` and ``default-push`` have a special
2501 meaning. When performing a push or pull operation, they are used
2523 meaning. When performing a push or pull operation, they are used
2502 as fallbacks if no location is specified on the command-line.
2524 as fallbacks if no location is specified on the command-line.
2503 When ``default-push`` is set, it will be used for push and
2525 When ``default-push`` is set, it will be used for push and
2504 ``default`` will be used for pull; otherwise ``default`` is used
2526 ``default`` will be used for pull; otherwise ``default`` is used
2505 as the fallback for both. When cloning a repository, the clone
2527 as the fallback for both. When cloning a repository, the clone
2506 source is written as ``default`` in ``.hg/hgrc``. Note that
2528 source is written as ``default`` in ``.hg/hgrc``. Note that
2507 ``default`` and ``default-push`` apply to all inbound (e.g.
2529 ``default`` and ``default-push`` apply to all inbound (e.g.
2508 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
2530 :hg:`incoming`) and outbound (e.g. :hg:`outgoing`, :hg:`email` and
2509 :hg:`bundle`) operations.
2531 :hg:`bundle`) operations.
2510
2532
2511 See :hg:`help urls` for more information.
2533 See :hg:`help urls` for more information.
2512 """
2534 """
2513 if search:
2535 if search:
2514 for name, path in ui.configitems("paths"):
2536 for name, path in ui.configitems("paths"):
2515 if name == search:
2537 if name == search:
2516 ui.write("%s\n" % url.hidepassword(path))
2538 ui.write("%s\n" % url.hidepassword(path))
2517 return
2539 return
2518 ui.warn(_("not found!\n"))
2540 ui.warn(_("not found!\n"))
2519 return 1
2541 return 1
2520 else:
2542 else:
2521 for name, path in ui.configitems("paths"):
2543 for name, path in ui.configitems("paths"):
2522 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2544 ui.write("%s = %s\n" % (name, url.hidepassword(path)))
2523
2545
2524 def postincoming(ui, repo, modheads, optupdate, checkout):
2546 def postincoming(ui, repo, modheads, optupdate, checkout):
2525 if modheads == 0:
2547 if modheads == 0:
2526 return
2548 return
2527 if optupdate:
2549 if optupdate:
2528 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2550 if (modheads <= 1 or len(repo.branchheads()) == 1) or checkout:
2529 return hg.update(repo, checkout)
2551 return hg.update(repo, checkout)
2530 else:
2552 else:
2531 ui.status(_("not updating, since new heads added\n"))
2553 ui.status(_("not updating, since new heads added\n"))
2532 if modheads > 1:
2554 if modheads > 1:
2533 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2555 ui.status(_("(run 'hg heads' to see heads, 'hg merge' to merge)\n"))
2534 else:
2556 else:
2535 ui.status(_("(run 'hg update' to get a working copy)\n"))
2557 ui.status(_("(run 'hg update' to get a working copy)\n"))
2536
2558
2537 def pull(ui, repo, source="default", **opts):
2559 def pull(ui, repo, source="default", **opts):
2538 """pull changes from the specified source
2560 """pull changes from the specified source
2539
2561
2540 Pull changes from a remote repository to a local one.
2562 Pull changes from a remote repository to a local one.
2541
2563
2542 This finds all changes from the repository at the specified path
2564 This finds all changes from the repository at the specified path
2543 or URL and adds them to a local repository (the current one unless
2565 or URL and adds them to a local repository (the current one unless
2544 -R is specified). By default, this does not update the copy of the
2566 -R is specified). By default, this does not update the copy of the
2545 project in the working directory.
2567 project in the working directory.
2546
2568
2547 Use :hg:`incoming` if you want to see what would have been added
2569 Use :hg:`incoming` if you want to see what would have been added
2548 by a pull at the time you issued this command. If you then decide
2570 by a pull at the time you issued this command. If you then decide
2549 to add those changes to the repository, you should use :hg:`pull
2571 to add those changes to the repository, you should use :hg:`pull
2550 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
2572 -r X` where ``X`` is the last changeset listed by :hg:`incoming`.
2551
2573
2552 If SOURCE is omitted, the 'default' path will be used.
2574 If SOURCE is omitted, the 'default' path will be used.
2553 See :hg:`help urls` for more information.
2575 See :hg:`help urls` for more information.
2554
2576
2555 Returns 0 on success, 1 if an update had unresolved files.
2577 Returns 0 on success, 1 if an update had unresolved files.
2556 """
2578 """
2557 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2579 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
2558 other = hg.repository(hg.remoteui(repo, opts), source)
2580 other = hg.repository(hg.remoteui(repo, opts), source)
2559 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2581 ui.status(_('pulling from %s\n') % url.hidepassword(source))
2560 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2582 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
2561 if revs:
2583 if revs:
2562 try:
2584 try:
2563 revs = [other.lookup(rev) for rev in revs]
2585 revs = [other.lookup(rev) for rev in revs]
2564 except error.CapabilityError:
2586 except error.CapabilityError:
2565 err = _("Other repository doesn't support revision lookup, "
2587 err = _("Other repository doesn't support revision lookup, "
2566 "so a rev cannot be specified.")
2588 "so a rev cannot be specified.")
2567 raise util.Abort(err)
2589 raise util.Abort(err)
2568
2590
2569 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2591 modheads = repo.pull(other, heads=revs, force=opts.get('force'))
2570 if checkout:
2592 if checkout:
2571 checkout = str(repo.changelog.rev(other.lookup(checkout)))
2593 checkout = str(repo.changelog.rev(other.lookup(checkout)))
2572 return postincoming(ui, repo, modheads, opts.get('update'), checkout)
2594 return postincoming(ui, repo, modheads, opts.get('update'), checkout)
2573
2595
2574 def push(ui, repo, dest=None, **opts):
2596 def push(ui, repo, dest=None, **opts):
2575 """push changes to the specified destination
2597 """push changes to the specified destination
2576
2598
2577 Push changesets from the local repository to the specified
2599 Push changesets from the local repository to the specified
2578 destination.
2600 destination.
2579
2601
2580 This operation is symmetrical to pull: it is identical to a pull
2602 This operation is symmetrical to pull: it is identical to a pull
2581 in the destination repository from the current one.
2603 in the destination repository from the current one.
2582
2604
2583 By default, push will not allow creation of new heads at the
2605 By default, push will not allow creation of new heads at the
2584 destination, since multiple heads would make it unclear which head
2606 destination, since multiple heads would make it unclear which head
2585 to use. In this situation, it is recommended to pull and merge
2607 to use. In this situation, it is recommended to pull and merge
2586 before pushing.
2608 before pushing.
2587
2609
2588 Use --new-branch if you want to allow push to create a new named
2610 Use --new-branch if you want to allow push to create a new named
2589 branch that is not present at the destination. This allows you to
2611 branch that is not present at the destination. This allows you to
2590 only create a new branch without forcing other changes.
2612 only create a new branch without forcing other changes.
2591
2613
2592 Use -f/--force to override the default behavior and push all
2614 Use -f/--force to override the default behavior and push all
2593 changesets on all branches.
2615 changesets on all branches.
2594
2616
2595 If -r/--rev is used, the specified revision and all its ancestors
2617 If -r/--rev is used, the specified revision and all its ancestors
2596 will be pushed to the remote repository.
2618 will be pushed to the remote repository.
2597
2619
2598 Please see :hg:`help urls` for important details about ``ssh://``
2620 Please see :hg:`help urls` for important details about ``ssh://``
2599 URLs. If DESTINATION is omitted, a default path will be used.
2621 URLs. If DESTINATION is omitted, a default path will be used.
2600
2622
2601 Returns 0 if push was successful, 1 if nothing to push.
2623 Returns 0 if push was successful, 1 if nothing to push.
2602 """
2624 """
2603 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2625 dest = ui.expandpath(dest or 'default-push', dest or 'default')
2604 dest, branches = hg.parseurl(dest, opts.get('branch'))
2626 dest, branches = hg.parseurl(dest, opts.get('branch'))
2605 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2627 revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
2606 other = hg.repository(hg.remoteui(repo, opts), dest)
2628 other = hg.repository(hg.remoteui(repo, opts), dest)
2607 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2629 ui.status(_('pushing to %s\n') % url.hidepassword(dest))
2608 if revs:
2630 if revs:
2609 revs = [repo.lookup(rev) for rev in revs]
2631 revs = [repo.lookup(rev) for rev in revs]
2610
2632
2611 # push subrepos depth-first for coherent ordering
2633 # push subrepos depth-first for coherent ordering
2612 c = repo['']
2634 c = repo['']
2613 subs = c.substate # only repos that are committed
2635 subs = c.substate # only repos that are committed
2614 for s in sorted(subs):
2636 for s in sorted(subs):
2615 if not c.sub(s).push(opts.get('force')):
2637 if not c.sub(s).push(opts.get('force')):
2616 return False
2638 return False
2617
2639
2618 r = repo.push(other, opts.get('force'), revs=revs,
2640 r = repo.push(other, opts.get('force'), revs=revs,
2619 newbranch=opts.get('new_branch'))
2641 newbranch=opts.get('new_branch'))
2620 return r == 0
2642 return r == 0
2621
2643
2622 def recover(ui, repo):
2644 def recover(ui, repo):
2623 """roll back an interrupted transaction
2645 """roll back an interrupted transaction
2624
2646
2625 Recover from an interrupted commit or pull.
2647 Recover from an interrupted commit or pull.
2626
2648
2627 This command tries to fix the repository status after an
2649 This command tries to fix the repository status after an
2628 interrupted operation. It should only be necessary when Mercurial
2650 interrupted operation. It should only be necessary when Mercurial
2629 suggests it.
2651 suggests it.
2630
2652
2631 Returns 0 if successful, 1 if nothing to recover or verify fails.
2653 Returns 0 if successful, 1 if nothing to recover or verify fails.
2632 """
2654 """
2633 if repo.recover():
2655 if repo.recover():
2634 return hg.verify(repo)
2656 return hg.verify(repo)
2635 return 1
2657 return 1
2636
2658
2637 def remove(ui, repo, *pats, **opts):
2659 def remove(ui, repo, *pats, **opts):
2638 """remove the specified files on the next commit
2660 """remove the specified files on the next commit
2639
2661
2640 Schedule the indicated files for removal from the repository.
2662 Schedule the indicated files for removal from the repository.
2641
2663
2642 This only removes files from the current branch, not from the
2664 This only removes files from the current branch, not from the
2643 entire project history. -A/--after can be used to remove only
2665 entire project history. -A/--after can be used to remove only
2644 files that have already been deleted, -f/--force can be used to
2666 files that have already been deleted, -f/--force can be used to
2645 force deletion, and -Af can be used to remove files from the next
2667 force deletion, and -Af can be used to remove files from the next
2646 revision without deleting them from the working directory.
2668 revision without deleting them from the working directory.
2647
2669
2648 The following table details the behavior of remove for different
2670 The following table details the behavior of remove for different
2649 file states (columns) and option combinations (rows). The file
2671 file states (columns) and option combinations (rows). The file
2650 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
2672 states are Added [A], Clean [C], Modified [M] and Missing [!] (as
2651 reported by :hg:`status`). The actions are Warn, Remove (from
2673 reported by :hg:`status`). The actions are Warn, Remove (from
2652 branch) and Delete (from disk)::
2674 branch) and Delete (from disk)::
2653
2675
2654 A C M !
2676 A C M !
2655 none W RD W R
2677 none W RD W R
2656 -f R RD RD R
2678 -f R RD RD R
2657 -A W W W R
2679 -A W W W R
2658 -Af R R R R
2680 -Af R R R R
2659
2681
2660 This command schedules the files to be removed at the next commit.
2682 This command schedules the files to be removed at the next commit.
2661 To undo a remove before that, see :hg:`revert`.
2683 To undo a remove before that, see :hg:`revert`.
2662
2684
2663 Returns 0 on success, 1 if any warnings encountered.
2685 Returns 0 on success, 1 if any warnings encountered.
2664 """
2686 """
2665
2687
2666 ret = 0
2688 ret = 0
2667 after, force = opts.get('after'), opts.get('force')
2689 after, force = opts.get('after'), opts.get('force')
2668 if not pats and not after:
2690 if not pats and not after:
2669 raise util.Abort(_('no files specified'))
2691 raise util.Abort(_('no files specified'))
2670
2692
2671 m = cmdutil.match(repo, pats, opts)
2693 m = cmdutil.match(repo, pats, opts)
2672 s = repo.status(match=m, clean=True)
2694 s = repo.status(match=m, clean=True)
2673 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2695 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2674
2696
2675 for f in m.files():
2697 for f in m.files():
2676 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2698 if f not in repo.dirstate and not os.path.isdir(m.rel(f)):
2677 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
2699 ui.warn(_('not removing %s: file is untracked\n') % m.rel(f))
2678 ret = 1
2700 ret = 1
2679
2701
2680 def warn(files, reason):
2702 def warn(files, reason):
2681 for f in files:
2703 for f in files:
2682 ui.warn(_('not removing %s: file %s (use -f to force removal)\n')
2704 ui.warn(_('not removing %s: file %s (use -f to force removal)\n')
2683 % (m.rel(f), reason))
2705 % (m.rel(f), reason))
2684 ret = 1
2706 ret = 1
2685
2707
2686 if force:
2708 if force:
2687 remove, forget = modified + deleted + clean, added
2709 remove, forget = modified + deleted + clean, added
2688 elif after:
2710 elif after:
2689 remove, forget = deleted, []
2711 remove, forget = deleted, []
2690 warn(modified + added + clean, _('still exists'))
2712 warn(modified + added + clean, _('still exists'))
2691 else:
2713 else:
2692 remove, forget = deleted + clean, []
2714 remove, forget = deleted + clean, []
2693 warn(modified, _('is modified'))
2715 warn(modified, _('is modified'))
2694 warn(added, _('has been marked for add'))
2716 warn(added, _('has been marked for add'))
2695
2717
2696 for f in sorted(remove + forget):
2718 for f in sorted(remove + forget):
2697 if ui.verbose or not m.exact(f):
2719 if ui.verbose or not m.exact(f):
2698 ui.status(_('removing %s\n') % m.rel(f))
2720 ui.status(_('removing %s\n') % m.rel(f))
2699
2721
2700 repo[None].forget(forget)
2722 repo[None].forget(forget)
2701 repo[None].remove(remove, unlink=not after)
2723 repo[None].remove(remove, unlink=not after)
2702 return ret
2724 return ret
2703
2725
2704 def rename(ui, repo, *pats, **opts):
2726 def rename(ui, repo, *pats, **opts):
2705 """rename files; equivalent of copy + remove
2727 """rename files; equivalent of copy + remove
2706
2728
2707 Mark dest as copies of sources; mark sources for deletion. If dest
2729 Mark dest as copies of sources; mark sources for deletion. If dest
2708 is a directory, copies are put in that directory. If dest is a
2730 is a directory, copies are put in that directory. If dest is a
2709 file, there can only be one source.
2731 file, there can only be one source.
2710
2732
2711 By default, this command copies the contents of files as they
2733 By default, this command copies the contents of files as they
2712 exist in the working directory. If invoked with -A/--after, the
2734 exist in the working directory. If invoked with -A/--after, the
2713 operation is recorded, but no copying is performed.
2735 operation is recorded, but no copying is performed.
2714
2736
2715 This command takes effect at the next commit. To undo a rename
2737 This command takes effect at the next commit. To undo a rename
2716 before that, see :hg:`revert`.
2738 before that, see :hg:`revert`.
2717
2739
2718 Returns 0 on success, 1 if errors are encountered.
2740 Returns 0 on success, 1 if errors are encountered.
2719 """
2741 """
2720 wlock = repo.wlock(False)
2742 wlock = repo.wlock(False)
2721 try:
2743 try:
2722 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2744 return cmdutil.copy(ui, repo, pats, opts, rename=True)
2723 finally:
2745 finally:
2724 wlock.release()
2746 wlock.release()
2725
2747
2726 def resolve(ui, repo, *pats, **opts):
2748 def resolve(ui, repo, *pats, **opts):
2727 """various operations to help finish a merge
2749 """various operations to help finish a merge
2728
2750
2729 This command includes several actions that are often useful while
2751 This command includes several actions that are often useful while
2730 performing a merge, after running ``merge`` but before running
2752 performing a merge, after running ``merge`` but before running
2731 ``commit``. (It is only meaningful if your working directory has
2753 ``commit``. (It is only meaningful if your working directory has
2732 two parents.) It is most relevant for merges with unresolved
2754 two parents.) It is most relevant for merges with unresolved
2733 conflicts, which are typically a result of non-interactive merging with
2755 conflicts, which are typically a result of non-interactive merging with
2734 ``internal:merge`` or a command-line merge tool like ``diff3``.
2756 ``internal:merge`` or a command-line merge tool like ``diff3``.
2735
2757
2736 The available actions are:
2758 The available actions are:
2737
2759
2738 1) list files that were merged with conflicts (U, for unresolved)
2760 1) list files that were merged with conflicts (U, for unresolved)
2739 and without conflicts (R, for resolved): ``hg resolve -l``
2761 and without conflicts (R, for resolved): ``hg resolve -l``
2740 (this is like ``status`` for merges)
2762 (this is like ``status`` for merges)
2741 2) record that you have resolved conflicts in certain files:
2763 2) record that you have resolved conflicts in certain files:
2742 ``hg resolve -m [file ...]`` (default: mark all unresolved files)
2764 ``hg resolve -m [file ...]`` (default: mark all unresolved files)
2743 3) forget that you have resolved conflicts in certain files:
2765 3) forget that you have resolved conflicts in certain files:
2744 ``hg resolve -u [file ...]`` (default: unmark all resolved files)
2766 ``hg resolve -u [file ...]`` (default: unmark all resolved files)
2745 4) discard your current attempt(s) at resolving conflicts and
2767 4) discard your current attempt(s) at resolving conflicts and
2746 restart the merge from scratch: ``hg resolve file...``
2768 restart the merge from scratch: ``hg resolve file...``
2747 (or ``-a`` for all unresolved files)
2769 (or ``-a`` for all unresolved files)
2748
2770
2749 Note that Mercurial will not let you commit files with unresolved merge
2771 Note that Mercurial will not let you commit files with unresolved merge
2750 conflicts. You must use ``hg resolve -m ...`` before you can commit
2772 conflicts. You must use ``hg resolve -m ...`` before you can commit
2751 after a conflicting merge.
2773 after a conflicting merge.
2752
2774
2753 Returns 0 on success, 1 if any files fail a resolve attempt.
2775 Returns 0 on success, 1 if any files fail a resolve attempt.
2754 """
2776 """
2755
2777
2756 all, mark, unmark, show, nostatus = \
2778 all, mark, unmark, show, nostatus = \
2757 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
2779 [opts.get(o) for o in 'all mark unmark list no_status'.split()]
2758
2780
2759 if (show and (mark or unmark)) or (mark and unmark):
2781 if (show and (mark or unmark)) or (mark and unmark):
2760 raise util.Abort(_("too many options specified"))
2782 raise util.Abort(_("too many options specified"))
2761 if pats and all:
2783 if pats and all:
2762 raise util.Abort(_("can't specify --all and patterns"))
2784 raise util.Abort(_("can't specify --all and patterns"))
2763 if not (all or pats or show or mark or unmark):
2785 if not (all or pats or show or mark or unmark):
2764 raise util.Abort(_('no files or directories specified; '
2786 raise util.Abort(_('no files or directories specified; '
2765 'use --all to remerge all files'))
2787 'use --all to remerge all files'))
2766
2788
2767 ms = mergemod.mergestate(repo)
2789 ms = mergemod.mergestate(repo)
2768 m = cmdutil.match(repo, pats, opts)
2790 m = cmdutil.match(repo, pats, opts)
2769 ret = 0
2791 ret = 0
2770
2792
2771 for f in ms:
2793 for f in ms:
2772 if m(f):
2794 if m(f):
2773 if show:
2795 if show:
2774 if nostatus:
2796 if nostatus:
2775 ui.write("%s\n" % f)
2797 ui.write("%s\n" % f)
2776 else:
2798 else:
2777 ui.write("%s %s\n" % (ms[f].upper(), f),
2799 ui.write("%s %s\n" % (ms[f].upper(), f),
2778 label='resolve.' +
2800 label='resolve.' +
2779 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
2801 {'u': 'unresolved', 'r': 'resolved'}[ms[f]])
2780 elif mark:
2802 elif mark:
2781 ms.mark(f, "r")
2803 ms.mark(f, "r")
2782 elif unmark:
2804 elif unmark:
2783 ms.mark(f, "u")
2805 ms.mark(f, "u")
2784 else:
2806 else:
2785 wctx = repo[None]
2807 wctx = repo[None]
2786 mctx = wctx.parents()[-1]
2808 mctx = wctx.parents()[-1]
2787
2809
2788 # backup pre-resolve (merge uses .orig for its own purposes)
2810 # backup pre-resolve (merge uses .orig for its own purposes)
2789 a = repo.wjoin(f)
2811 a = repo.wjoin(f)
2790 util.copyfile(a, a + ".resolve")
2812 util.copyfile(a, a + ".resolve")
2791
2813
2792 # resolve file
2814 # resolve file
2793 if ms.resolve(f, wctx, mctx):
2815 if ms.resolve(f, wctx, mctx):
2794 ret = 1
2816 ret = 1
2795
2817
2796 # replace filemerge's .orig file with our resolve file
2818 # replace filemerge's .orig file with our resolve file
2797 util.rename(a + ".resolve", a + ".orig")
2819 util.rename(a + ".resolve", a + ".orig")
2798 return ret
2820 return ret
2799
2821
2800 def revert(ui, repo, *pats, **opts):
2822 def revert(ui, repo, *pats, **opts):
2801 """restore individual files or directories to an earlier state
2823 """restore individual files or directories to an earlier state
2802
2824
2803 (Use update -r to check out earlier revisions, revert does not
2825 (Use update -r to check out earlier revisions, revert does not
2804 change the working directory parents.)
2826 change the working directory parents.)
2805
2827
2806 With no revision specified, revert the named files or directories
2828 With no revision specified, revert the named files or directories
2807 to the contents they had in the parent of the working directory.
2829 to the contents they had in the parent of the working directory.
2808 This restores the contents of the affected files to an unmodified
2830 This restores the contents of the affected files to an unmodified
2809 state and unschedules adds, removes, copies, and renames. If the
2831 state and unschedules adds, removes, copies, and renames. If the
2810 working directory has two parents, you must explicitly specify a
2832 working directory has two parents, you must explicitly specify a
2811 revision.
2833 revision.
2812
2834
2813 Using the -r/--rev option, revert the given files or directories
2835 Using the -r/--rev option, revert the given files or directories
2814 to their contents as of a specific revision. This can be helpful
2836 to their contents as of a specific revision. This can be helpful
2815 to "roll back" some or all of an earlier change. See :hg:`help
2837 to "roll back" some or all of an earlier change. See :hg:`help
2816 dates` for a list of formats valid for -d/--date.
2838 dates` for a list of formats valid for -d/--date.
2817
2839
2818 Revert modifies the working directory. It does not commit any
2840 Revert modifies the working directory. It does not commit any
2819 changes, or change the parent of the working directory. If you
2841 changes, or change the parent of the working directory. If you
2820 revert to a revision other than the parent of the working
2842 revert to a revision other than the parent of the working
2821 directory, the reverted files will thus appear modified
2843 directory, the reverted files will thus appear modified
2822 afterwards.
2844 afterwards.
2823
2845
2824 If a file has been deleted, it is restored. If the executable mode
2846 If a file has been deleted, it is restored. If the executable mode
2825 of a file was changed, it is reset.
2847 of a file was changed, it is reset.
2826
2848
2827 If names are given, all files matching the names are reverted.
2849 If names are given, all files matching the names are reverted.
2828 If no arguments are given, no files are reverted.
2850 If no arguments are given, no files are reverted.
2829
2851
2830 Modified files are saved with a .orig suffix before reverting.
2852 Modified files are saved with a .orig suffix before reverting.
2831 To disable these backups, use --no-backup.
2853 To disable these backups, use --no-backup.
2832
2854
2833 Returns 0 on success.
2855 Returns 0 on success.
2834 """
2856 """
2835
2857
2836 if opts["date"]:
2858 if opts["date"]:
2837 if opts["rev"]:
2859 if opts["rev"]:
2838 raise util.Abort(_("you can't specify a revision and a date"))
2860 raise util.Abort(_("you can't specify a revision and a date"))
2839 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2861 opts["rev"] = cmdutil.finddate(ui, repo, opts["date"])
2840
2862
2841 if not pats and not opts.get('all'):
2863 if not pats and not opts.get('all'):
2842 raise util.Abort(_('no files or directories specified; '
2864 raise util.Abort(_('no files or directories specified; '
2843 'use --all to revert the whole repo'))
2865 'use --all to revert the whole repo'))
2844
2866
2845 parent, p2 = repo.dirstate.parents()
2867 parent, p2 = repo.dirstate.parents()
2846 if not opts.get('rev') and p2 != nullid:
2868 if not opts.get('rev') and p2 != nullid:
2847 raise util.Abort(_('uncommitted merge - please provide a '
2869 raise util.Abort(_('uncommitted merge - please provide a '
2848 'specific revision'))
2870 'specific revision'))
2849 ctx = repo[opts.get('rev')]
2871 ctx = repo[opts.get('rev')]
2850 node = ctx.node()
2872 node = ctx.node()
2851 mf = ctx.manifest()
2873 mf = ctx.manifest()
2852 if node == parent:
2874 if node == parent:
2853 pmf = mf
2875 pmf = mf
2854 else:
2876 else:
2855 pmf = None
2877 pmf = None
2856
2878
2857 # need all matching names in dirstate and manifest of target rev,
2879 # need all matching names in dirstate and manifest of target rev,
2858 # so have to walk both. do not print errors if files exist in one
2880 # so have to walk both. do not print errors if files exist in one
2859 # but not other.
2881 # but not other.
2860
2882
2861 names = {}
2883 names = {}
2862
2884
2863 wlock = repo.wlock()
2885 wlock = repo.wlock()
2864 try:
2886 try:
2865 # walk dirstate.
2887 # walk dirstate.
2866
2888
2867 m = cmdutil.match(repo, pats, opts)
2889 m = cmdutil.match(repo, pats, opts)
2868 m.bad = lambda x, y: False
2890 m.bad = lambda x, y: False
2869 for abs in repo.walk(m):
2891 for abs in repo.walk(m):
2870 names[abs] = m.rel(abs), m.exact(abs)
2892 names[abs] = m.rel(abs), m.exact(abs)
2871
2893
2872 # walk target manifest.
2894 # walk target manifest.
2873
2895
2874 def badfn(path, msg):
2896 def badfn(path, msg):
2875 if path in names:
2897 if path in names:
2876 return
2898 return
2877 path_ = path + '/'
2899 path_ = path + '/'
2878 for f in names:
2900 for f in names:
2879 if f.startswith(path_):
2901 if f.startswith(path_):
2880 return
2902 return
2881 ui.warn("%s: %s\n" % (m.rel(path), msg))
2903 ui.warn("%s: %s\n" % (m.rel(path), msg))
2882
2904
2883 m = cmdutil.match(repo, pats, opts)
2905 m = cmdutil.match(repo, pats, opts)
2884 m.bad = badfn
2906 m.bad = badfn
2885 for abs in repo[node].walk(m):
2907 for abs in repo[node].walk(m):
2886 if abs not in names:
2908 if abs not in names:
2887 names[abs] = m.rel(abs), m.exact(abs)
2909 names[abs] = m.rel(abs), m.exact(abs)
2888
2910
2889 m = cmdutil.matchfiles(repo, names)
2911 m = cmdutil.matchfiles(repo, names)
2890 changes = repo.status(match=m)[:4]
2912 changes = repo.status(match=m)[:4]
2891 modified, added, removed, deleted = map(set, changes)
2913 modified, added, removed, deleted = map(set, changes)
2892
2914
2893 # if f is a rename, also revert the source
2915 # if f is a rename, also revert the source
2894 cwd = repo.getcwd()
2916 cwd = repo.getcwd()
2895 for f in added:
2917 for f in added:
2896 src = repo.dirstate.copied(f)
2918 src = repo.dirstate.copied(f)
2897 if src and src not in names and repo.dirstate[src] == 'r':
2919 if src and src not in names and repo.dirstate[src] == 'r':
2898 removed.add(src)
2920 removed.add(src)
2899 names[src] = (repo.pathto(src, cwd), True)
2921 names[src] = (repo.pathto(src, cwd), True)
2900
2922
2901 def removeforget(abs):
2923 def removeforget(abs):
2902 if repo.dirstate[abs] == 'a':
2924 if repo.dirstate[abs] == 'a':
2903 return _('forgetting %s\n')
2925 return _('forgetting %s\n')
2904 return _('removing %s\n')
2926 return _('removing %s\n')
2905
2927
2906 revert = ([], _('reverting %s\n'))
2928 revert = ([], _('reverting %s\n'))
2907 add = ([], _('adding %s\n'))
2929 add = ([], _('adding %s\n'))
2908 remove = ([], removeforget)
2930 remove = ([], removeforget)
2909 undelete = ([], _('undeleting %s\n'))
2931 undelete = ([], _('undeleting %s\n'))
2910
2932
2911 disptable = (
2933 disptable = (
2912 # dispatch table:
2934 # dispatch table:
2913 # file state
2935 # file state
2914 # action if in target manifest
2936 # action if in target manifest
2915 # action if not in target manifest
2937 # action if not in target manifest
2916 # make backup if in target manifest
2938 # make backup if in target manifest
2917 # make backup if not in target manifest
2939 # make backup if not in target manifest
2918 (modified, revert, remove, True, True),
2940 (modified, revert, remove, True, True),
2919 (added, revert, remove, True, False),
2941 (added, revert, remove, True, False),
2920 (removed, undelete, None, False, False),
2942 (removed, undelete, None, False, False),
2921 (deleted, revert, remove, False, False),
2943 (deleted, revert, remove, False, False),
2922 )
2944 )
2923
2945
2924 for abs, (rel, exact) in sorted(names.items()):
2946 for abs, (rel, exact) in sorted(names.items()):
2925 mfentry = mf.get(abs)
2947 mfentry = mf.get(abs)
2926 target = repo.wjoin(abs)
2948 target = repo.wjoin(abs)
2927 def handle(xlist, dobackup):
2949 def handle(xlist, dobackup):
2928 xlist[0].append(abs)
2950 xlist[0].append(abs)
2929 if dobackup and not opts.get('no_backup') and util.lexists(target):
2951 if dobackup and not opts.get('no_backup') and util.lexists(target):
2930 bakname = "%s.orig" % rel
2952 bakname = "%s.orig" % rel
2931 ui.note(_('saving current version of %s as %s\n') %
2953 ui.note(_('saving current version of %s as %s\n') %
2932 (rel, bakname))
2954 (rel, bakname))
2933 if not opts.get('dry_run'):
2955 if not opts.get('dry_run'):
2934 util.copyfile(target, bakname)
2956 util.copyfile(target, bakname)
2935 if ui.verbose or not exact:
2957 if ui.verbose or not exact:
2936 msg = xlist[1]
2958 msg = xlist[1]
2937 if not isinstance(msg, basestring):
2959 if not isinstance(msg, basestring):
2938 msg = msg(abs)
2960 msg = msg(abs)
2939 ui.status(msg % rel)
2961 ui.status(msg % rel)
2940 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2962 for table, hitlist, misslist, backuphit, backupmiss in disptable:
2941 if abs not in table:
2963 if abs not in table:
2942 continue
2964 continue
2943 # file has changed in dirstate
2965 # file has changed in dirstate
2944 if mfentry:
2966 if mfentry:
2945 handle(hitlist, backuphit)
2967 handle(hitlist, backuphit)
2946 elif misslist is not None:
2968 elif misslist is not None:
2947 handle(misslist, backupmiss)
2969 handle(misslist, backupmiss)
2948 break
2970 break
2949 else:
2971 else:
2950 if abs not in repo.dirstate:
2972 if abs not in repo.dirstate:
2951 if mfentry:
2973 if mfentry:
2952 handle(add, True)
2974 handle(add, True)
2953 elif exact:
2975 elif exact:
2954 ui.warn(_('file not managed: %s\n') % rel)
2976 ui.warn(_('file not managed: %s\n') % rel)
2955 continue
2977 continue
2956 # file has not changed in dirstate
2978 # file has not changed in dirstate
2957 if node == parent:
2979 if node == parent:
2958 if exact:
2980 if exact:
2959 ui.warn(_('no changes needed to %s\n') % rel)
2981 ui.warn(_('no changes needed to %s\n') % rel)
2960 continue
2982 continue
2961 if pmf is None:
2983 if pmf is None:
2962 # only need parent manifest in this unlikely case,
2984 # only need parent manifest in this unlikely case,
2963 # so do not read by default
2985 # so do not read by default
2964 pmf = repo[parent].manifest()
2986 pmf = repo[parent].manifest()
2965 if abs in pmf:
2987 if abs in pmf:
2966 if mfentry:
2988 if mfentry:
2967 # if version of file is same in parent and target
2989 # if version of file is same in parent and target
2968 # manifests, do nothing
2990 # manifests, do nothing
2969 if (pmf[abs] != mfentry or
2991 if (pmf[abs] != mfentry or
2970 pmf.flags(abs) != mf.flags(abs)):
2992 pmf.flags(abs) != mf.flags(abs)):
2971 handle(revert, False)
2993 handle(revert, False)
2972 else:
2994 else:
2973 handle(remove, False)
2995 handle(remove, False)
2974
2996
2975 if not opts.get('dry_run'):
2997 if not opts.get('dry_run'):
2976 def checkout(f):
2998 def checkout(f):
2977 fc = ctx[f]
2999 fc = ctx[f]
2978 repo.wwrite(f, fc.data(), fc.flags())
3000 repo.wwrite(f, fc.data(), fc.flags())
2979
3001
2980 audit_path = util.path_auditor(repo.root)
3002 audit_path = util.path_auditor(repo.root)
2981 for f in remove[0]:
3003 for f in remove[0]:
2982 if repo.dirstate[f] == 'a':
3004 if repo.dirstate[f] == 'a':
2983 repo.dirstate.forget(f)
3005 repo.dirstate.forget(f)
2984 continue
3006 continue
2985 audit_path(f)
3007 audit_path(f)
2986 try:
3008 try:
2987 util.unlink(repo.wjoin(f))
3009 util.unlink(repo.wjoin(f))
2988 except OSError:
3010 except OSError:
2989 pass
3011 pass
2990 repo.dirstate.remove(f)
3012 repo.dirstate.remove(f)
2991
3013
2992 normal = None
3014 normal = None
2993 if node == parent:
3015 if node == parent:
2994 # We're reverting to our parent. If possible, we'd like status
3016 # We're reverting to our parent. If possible, we'd like status
2995 # to report the file as clean. We have to use normallookup for
3017 # to report the file as clean. We have to use normallookup for
2996 # merges to avoid losing information about merged/dirty files.
3018 # merges to avoid losing information about merged/dirty files.
2997 if p2 != nullid:
3019 if p2 != nullid:
2998 normal = repo.dirstate.normallookup
3020 normal = repo.dirstate.normallookup
2999 else:
3021 else:
3000 normal = repo.dirstate.normal
3022 normal = repo.dirstate.normal
3001 for f in revert[0]:
3023 for f in revert[0]:
3002 checkout(f)
3024 checkout(f)
3003 if normal:
3025 if normal:
3004 normal(f)
3026 normal(f)
3005
3027
3006 for f in add[0]:
3028 for f in add[0]:
3007 checkout(f)
3029 checkout(f)
3008 repo.dirstate.add(f)
3030 repo.dirstate.add(f)
3009
3031
3010 normal = repo.dirstate.normallookup
3032 normal = repo.dirstate.normallookup
3011 if node == parent and p2 == nullid:
3033 if node == parent and p2 == nullid:
3012 normal = repo.dirstate.normal
3034 normal = repo.dirstate.normal
3013 for f in undelete[0]:
3035 for f in undelete[0]:
3014 checkout(f)
3036 checkout(f)
3015 normal(f)
3037 normal(f)
3016
3038
3017 finally:
3039 finally:
3018 wlock.release()
3040 wlock.release()
3019
3041
3020 def rollback(ui, repo, **opts):
3042 def rollback(ui, repo, **opts):
3021 """roll back the last transaction (dangerous)
3043 """roll back the last transaction (dangerous)
3022
3044
3023 This command should be used with care. There is only one level of
3045 This command should be used with care. There is only one level of
3024 rollback, and there is no way to undo a rollback. It will also
3046 rollback, and there is no way to undo a rollback. It will also
3025 restore the dirstate at the time of the last transaction, losing
3047 restore the dirstate at the time of the last transaction, losing
3026 any dirstate changes since that time. This command does not alter
3048 any dirstate changes since that time. This command does not alter
3027 the working directory.
3049 the working directory.
3028
3050
3029 Transactions are used to encapsulate the effects of all commands
3051 Transactions are used to encapsulate the effects of all commands
3030 that create new changesets or propagate existing changesets into a
3052 that create new changesets or propagate existing changesets into a
3031 repository. For example, the following commands are transactional,
3053 repository. For example, the following commands are transactional,
3032 and their effects can be rolled back:
3054 and their effects can be rolled back:
3033
3055
3034 - commit
3056 - commit
3035 - import
3057 - import
3036 - pull
3058 - pull
3037 - push (with this repository as the destination)
3059 - push (with this repository as the destination)
3038 - unbundle
3060 - unbundle
3039
3061
3040 This command is not intended for use on public repositories. Once
3062 This command is not intended for use on public repositories. Once
3041 changes are visible for pull by other users, rolling a transaction
3063 changes are visible for pull by other users, rolling a transaction
3042 back locally is ineffective (someone else may already have pulled
3064 back locally is ineffective (someone else may already have pulled
3043 the changes). Furthermore, a race is possible with readers of the
3065 the changes). Furthermore, a race is possible with readers of the
3044 repository; for example an in-progress pull from the repository
3066 repository; for example an in-progress pull from the repository
3045 may fail if a rollback is performed.
3067 may fail if a rollback is performed.
3046
3068
3047 Returns 0 on success, 1 if no rollback data is available.
3069 Returns 0 on success, 1 if no rollback data is available.
3048 """
3070 """
3049 return repo.rollback(opts.get('dry_run'))
3071 return repo.rollback(opts.get('dry_run'))
3050
3072
3051 def root(ui, repo):
3073 def root(ui, repo):
3052 """print the root (top) of the current working directory
3074 """print the root (top) of the current working directory
3053
3075
3054 Print the root directory of the current repository.
3076 Print the root directory of the current repository.
3055
3077
3056 Returns 0 on success.
3078 Returns 0 on success.
3057 """
3079 """
3058 ui.write(repo.root + "\n")
3080 ui.write(repo.root + "\n")
3059
3081
3060 def serve(ui, repo, **opts):
3082 def serve(ui, repo, **opts):
3061 """start stand-alone webserver
3083 """start stand-alone webserver
3062
3084
3063 Start a local HTTP repository browser and pull server. You can use
3085 Start a local HTTP repository browser and pull server. You can use
3064 this for ad-hoc sharing and browing of repositories. It is
3086 this for ad-hoc sharing and browing of repositories. It is
3065 recommended to use a real web server to serve a repository for
3087 recommended to use a real web server to serve a repository for
3066 longer periods of time.
3088 longer periods of time.
3067
3089
3068 Please note that the server does not implement access control.
3090 Please note that the server does not implement access control.
3069 This means that, by default, anybody can read from the server and
3091 This means that, by default, anybody can read from the server and
3070 nobody can write to it by default. Set the ``web.allow_push``
3092 nobody can write to it by default. Set the ``web.allow_push``
3071 option to ``*`` to allow everybody to push to the server. You
3093 option to ``*`` to allow everybody to push to the server. You
3072 should use a real web server if you need to authenticate users.
3094 should use a real web server if you need to authenticate users.
3073
3095
3074 By default, the server logs accesses to stdout and errors to
3096 By default, the server logs accesses to stdout and errors to
3075 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
3097 stderr. Use the -A/--accesslog and -E/--errorlog options to log to
3076 files.
3098 files.
3077
3099
3078 To have the server choose a free port number to listen on, specify
3100 To have the server choose a free port number to listen on, specify
3079 a port number of 0; in this case, the server will print the port
3101 a port number of 0; in this case, the server will print the port
3080 number it uses.
3102 number it uses.
3081
3103
3082 Returns 0 on success.
3104 Returns 0 on success.
3083 """
3105 """
3084
3106
3085 if opts["stdio"]:
3107 if opts["stdio"]:
3086 if repo is None:
3108 if repo is None:
3087 raise error.RepoError(_("There is no Mercurial repository here"
3109 raise error.RepoError(_("There is no Mercurial repository here"
3088 " (.hg not found)"))
3110 " (.hg not found)"))
3089 s = sshserver.sshserver(ui, repo)
3111 s = sshserver.sshserver(ui, repo)
3090 s.serve_forever()
3112 s.serve_forever()
3091
3113
3092 # this way we can check if something was given in the command-line
3114 # this way we can check if something was given in the command-line
3093 if opts.get('port'):
3115 if opts.get('port'):
3094 opts['port'] = int(opts.get('port'))
3116 opts['port'] = int(opts.get('port'))
3095
3117
3096 baseui = repo and repo.baseui or ui
3118 baseui = repo and repo.baseui or ui
3097 optlist = ("name templates style address port prefix ipv6"
3119 optlist = ("name templates style address port prefix ipv6"
3098 " accesslog errorlog certificate encoding")
3120 " accesslog errorlog certificate encoding")
3099 for o in optlist.split():
3121 for o in optlist.split():
3100 val = opts.get(o, '')
3122 val = opts.get(o, '')
3101 if val in (None, ''): # should check against default options instead
3123 if val in (None, ''): # should check against default options instead
3102 continue
3124 continue
3103 baseui.setconfig("web", o, val)
3125 baseui.setconfig("web", o, val)
3104 if repo and repo.ui != baseui:
3126 if repo and repo.ui != baseui:
3105 repo.ui.setconfig("web", o, val)
3127 repo.ui.setconfig("web", o, val)
3106
3128
3107 o = opts.get('web_conf') or opts.get('webdir_conf')
3129 o = opts.get('web_conf') or opts.get('webdir_conf')
3108 if not o:
3130 if not o:
3109 if not repo:
3131 if not repo:
3110 raise error.RepoError(_("There is no Mercurial repository"
3132 raise error.RepoError(_("There is no Mercurial repository"
3111 " here (.hg not found)"))
3133 " here (.hg not found)"))
3112 o = repo.root
3134 o = repo.root
3113
3135
3114 app = hgweb.hgweb(o, baseui=ui)
3136 app = hgweb.hgweb(o, baseui=ui)
3115
3137
3116 class service(object):
3138 class service(object):
3117 def init(self):
3139 def init(self):
3118 util.set_signal_handler()
3140 util.set_signal_handler()
3119 self.httpd = hgweb.server.create_server(ui, app)
3141 self.httpd = hgweb.server.create_server(ui, app)
3120
3142
3121 if opts['port'] and not ui.verbose:
3143 if opts['port'] and not ui.verbose:
3122 return
3144 return
3123
3145
3124 if self.httpd.prefix:
3146 if self.httpd.prefix:
3125 prefix = self.httpd.prefix.strip('/') + '/'
3147 prefix = self.httpd.prefix.strip('/') + '/'
3126 else:
3148 else:
3127 prefix = ''
3149 prefix = ''
3128
3150
3129 port = ':%d' % self.httpd.port
3151 port = ':%d' % self.httpd.port
3130 if port == ':80':
3152 if port == ':80':
3131 port = ''
3153 port = ''
3132
3154
3133 bindaddr = self.httpd.addr
3155 bindaddr = self.httpd.addr
3134 if bindaddr == '0.0.0.0':
3156 if bindaddr == '0.0.0.0':
3135 bindaddr = '*'
3157 bindaddr = '*'
3136 elif ':' in bindaddr: # IPv6
3158 elif ':' in bindaddr: # IPv6
3137 bindaddr = '[%s]' % bindaddr
3159 bindaddr = '[%s]' % bindaddr
3138
3160
3139 fqaddr = self.httpd.fqaddr
3161 fqaddr = self.httpd.fqaddr
3140 if ':' in fqaddr:
3162 if ':' in fqaddr:
3141 fqaddr = '[%s]' % fqaddr
3163 fqaddr = '[%s]' % fqaddr
3142 if opts['port']:
3164 if opts['port']:
3143 write = ui.status
3165 write = ui.status
3144 else:
3166 else:
3145 write = ui.write
3167 write = ui.write
3146 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
3168 write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
3147 (fqaddr, port, prefix, bindaddr, self.httpd.port))
3169 (fqaddr, port, prefix, bindaddr, self.httpd.port))
3148
3170
3149 def run(self):
3171 def run(self):
3150 self.httpd.serve_forever()
3172 self.httpd.serve_forever()
3151
3173
3152 service = service()
3174 service = service()
3153
3175
3154 cmdutil.service(opts, initfn=service.init, runfn=service.run)
3176 cmdutil.service(opts, initfn=service.init, runfn=service.run)
3155
3177
3156 def status(ui, repo, *pats, **opts):
3178 def status(ui, repo, *pats, **opts):
3157 """show changed files in the working directory
3179 """show changed files in the working directory
3158
3180
3159 Show status of files in the repository. If names are given, only
3181 Show status of files in the repository. If names are given, only
3160 files that match are shown. Files that are clean or ignored or
3182 files that match are shown. Files that are clean or ignored or
3161 the source of a copy/move operation, are not listed unless
3183 the source of a copy/move operation, are not listed unless
3162 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
3184 -c/--clean, -i/--ignored, -C/--copies or -A/--all are given.
3163 Unless options described with "show only ..." are given, the
3185 Unless options described with "show only ..." are given, the
3164 options -mardu are used.
3186 options -mardu are used.
3165
3187
3166 Option -q/--quiet hides untracked (unknown and ignored) files
3188 Option -q/--quiet hides untracked (unknown and ignored) files
3167 unless explicitly requested with -u/--unknown or -i/--ignored.
3189 unless explicitly requested with -u/--unknown or -i/--ignored.
3168
3190
3169 NOTE: status may appear to disagree with diff if permissions have
3191 NOTE: status may appear to disagree with diff if permissions have
3170 changed or a merge has occurred. The standard diff format does not
3192 changed or a merge has occurred. The standard diff format does not
3171 report permission changes and diff only reports changes relative
3193 report permission changes and diff only reports changes relative
3172 to one merge parent.
3194 to one merge parent.
3173
3195
3174 If one revision is given, it is used as the base revision.
3196 If one revision is given, it is used as the base revision.
3175 If two revisions are given, the differences between them are
3197 If two revisions are given, the differences between them are
3176 shown. The --change option can also be used as a shortcut to list
3198 shown. The --change option can also be used as a shortcut to list
3177 the changed files of a revision from its first parent.
3199 the changed files of a revision from its first parent.
3178
3200
3179 The codes used to show the status of files are::
3201 The codes used to show the status of files are::
3180
3202
3181 M = modified
3203 M = modified
3182 A = added
3204 A = added
3183 R = removed
3205 R = removed
3184 C = clean
3206 C = clean
3185 ! = missing (deleted by non-hg command, but still tracked)
3207 ! = missing (deleted by non-hg command, but still tracked)
3186 ? = not tracked
3208 ? = not tracked
3187 I = ignored
3209 I = ignored
3188 = origin of the previous file listed as A (added)
3210 = origin of the previous file listed as A (added)
3189
3211
3190 Returns 0 on success.
3212 Returns 0 on success.
3191 """
3213 """
3192
3214
3193 revs = opts.get('rev')
3215 revs = opts.get('rev')
3194 change = opts.get('change')
3216 change = opts.get('change')
3195
3217
3196 if revs and change:
3218 if revs and change:
3197 msg = _('cannot specify --rev and --change at the same time')
3219 msg = _('cannot specify --rev and --change at the same time')
3198 raise util.Abort(msg)
3220 raise util.Abort(msg)
3199 elif change:
3221 elif change:
3200 node2 = repo.lookup(change)
3222 node2 = repo.lookup(change)
3201 node1 = repo[node2].parents()[0].node()
3223 node1 = repo[node2].parents()[0].node()
3202 else:
3224 else:
3203 node1, node2 = cmdutil.revpair(repo, revs)
3225 node1, node2 = cmdutil.revpair(repo, revs)
3204
3226
3205 cwd = (pats and repo.getcwd()) or ''
3227 cwd = (pats and repo.getcwd()) or ''
3206 end = opts.get('print0') and '\0' or '\n'
3228 end = opts.get('print0') and '\0' or '\n'
3207 copy = {}
3229 copy = {}
3208 states = 'modified added removed deleted unknown ignored clean'.split()
3230 states = 'modified added removed deleted unknown ignored clean'.split()
3209 show = [k for k in states if opts.get(k)]
3231 show = [k for k in states if opts.get(k)]
3210 if opts.get('all'):
3232 if opts.get('all'):
3211 show += ui.quiet and (states[:4] + ['clean']) or states
3233 show += ui.quiet and (states[:4] + ['clean']) or states
3212 if not show:
3234 if not show:
3213 show = ui.quiet and states[:4] or states[:5]
3235 show = ui.quiet and states[:4] or states[:5]
3214
3236
3215 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
3237 stat = repo.status(node1, node2, cmdutil.match(repo, pats, opts),
3216 'ignored' in show, 'clean' in show, 'unknown' in show)
3238 'ignored' in show, 'clean' in show, 'unknown' in show)
3217 changestates = zip(states, 'MAR!?IC', stat)
3239 changestates = zip(states, 'MAR!?IC', stat)
3218
3240
3219 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
3241 if (opts.get('all') or opts.get('copies')) and not opts.get('no_status'):
3220 ctxn = repo[nullid]
3242 ctxn = repo[nullid]
3221 ctx1 = repo[node1]
3243 ctx1 = repo[node1]
3222 ctx2 = repo[node2]
3244 ctx2 = repo[node2]
3223 added = stat[1]
3245 added = stat[1]
3224 if node2 is None:
3246 if node2 is None:
3225 added = stat[0] + stat[1] # merged?
3247 added = stat[0] + stat[1] # merged?
3226
3248
3227 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
3249 for k, v in copies.copies(repo, ctx1, ctx2, ctxn)[0].iteritems():
3228 if k in added:
3250 if k in added:
3229 copy[k] = v
3251 copy[k] = v
3230 elif v in added:
3252 elif v in added:
3231 copy[v] = k
3253 copy[v] = k
3232
3254
3233 for state, char, files in changestates:
3255 for state, char, files in changestates:
3234 if state in show:
3256 if state in show:
3235 format = "%s %%s%s" % (char, end)
3257 format = "%s %%s%s" % (char, end)
3236 if opts.get('no_status'):
3258 if opts.get('no_status'):
3237 format = "%%s%s" % end
3259 format = "%%s%s" % end
3238
3260
3239 for f in files:
3261 for f in files:
3240 ui.write(format % repo.pathto(f, cwd),
3262 ui.write(format % repo.pathto(f, cwd),
3241 label='status.' + state)
3263 label='status.' + state)
3242 if f in copy:
3264 if f in copy:
3243 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
3265 ui.write(' %s%s' % (repo.pathto(copy[f], cwd), end),
3244 label='status.copied')
3266 label='status.copied')
3245
3267
3246 def summary(ui, repo, **opts):
3268 def summary(ui, repo, **opts):
3247 """summarize working directory state
3269 """summarize working directory state
3248
3270
3249 This generates a brief summary of the working directory state,
3271 This generates a brief summary of the working directory state,
3250 including parents, branch, commit status, and available updates.
3272 including parents, branch, commit status, and available updates.
3251
3273
3252 With the --remote option, this will check the default paths for
3274 With the --remote option, this will check the default paths for
3253 incoming and outgoing changes. This can be time-consuming.
3275 incoming and outgoing changes. This can be time-consuming.
3254
3276
3255 Returns 0 on success.
3277 Returns 0 on success.
3256 """
3278 """
3257
3279
3258 ctx = repo[None]
3280 ctx = repo[None]
3259 parents = ctx.parents()
3281 parents = ctx.parents()
3260 pnode = parents[0].node()
3282 pnode = parents[0].node()
3261
3283
3262 for p in parents:
3284 for p in parents:
3263 # label with log.changeset (instead of log.parent) since this
3285 # label with log.changeset (instead of log.parent) since this
3264 # shows a working directory parent *changeset*:
3286 # shows a working directory parent *changeset*:
3265 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
3287 ui.write(_('parent: %d:%s ') % (p.rev(), str(p)),
3266 label='log.changeset')
3288 label='log.changeset')
3267 ui.write(' '.join(p.tags()), label='log.tag')
3289 ui.write(' '.join(p.tags()), label='log.tag')
3268 if p.rev() == -1:
3290 if p.rev() == -1:
3269 if not len(repo):
3291 if not len(repo):
3270 ui.write(_(' (empty repository)'))
3292 ui.write(_(' (empty repository)'))
3271 else:
3293 else:
3272 ui.write(_(' (no revision checked out)'))
3294 ui.write(_(' (no revision checked out)'))
3273 ui.write('\n')
3295 ui.write('\n')
3274 if p.description():
3296 if p.description():
3275 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
3297 ui.status(' ' + p.description().splitlines()[0].strip() + '\n',
3276 label='log.summary')
3298 label='log.summary')
3277
3299
3278 branch = ctx.branch()
3300 branch = ctx.branch()
3279 bheads = repo.branchheads(branch)
3301 bheads = repo.branchheads(branch)
3280 m = _('branch: %s\n') % branch
3302 m = _('branch: %s\n') % branch
3281 if branch != 'default':
3303 if branch != 'default':
3282 ui.write(m, label='log.branch')
3304 ui.write(m, label='log.branch')
3283 else:
3305 else:
3284 ui.status(m, label='log.branch')
3306 ui.status(m, label='log.branch')
3285
3307
3286 st = list(repo.status(unknown=True))[:6]
3308 st = list(repo.status(unknown=True))[:6]
3287
3309
3288 ms = mergemod.mergestate(repo)
3310 ms = mergemod.mergestate(repo)
3289 st.append([f for f in ms if ms[f] == 'u'])
3311 st.append([f for f in ms if ms[f] == 'u'])
3290
3312
3291 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
3313 subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
3292 st.append(subs)
3314 st.append(subs)
3293
3315
3294 labels = [ui.label(_('%d modified'), 'status.modified'),
3316 labels = [ui.label(_('%d modified'), 'status.modified'),
3295 ui.label(_('%d added'), 'status.added'),
3317 ui.label(_('%d added'), 'status.added'),
3296 ui.label(_('%d removed'), 'status.removed'),
3318 ui.label(_('%d removed'), 'status.removed'),
3297 ui.label(_('%d deleted'), 'status.deleted'),
3319 ui.label(_('%d deleted'), 'status.deleted'),
3298 ui.label(_('%d unknown'), 'status.unknown'),
3320 ui.label(_('%d unknown'), 'status.unknown'),
3299 ui.label(_('%d ignored'), 'status.ignored'),
3321 ui.label(_('%d ignored'), 'status.ignored'),
3300 ui.label(_('%d unresolved'), 'resolve.unresolved'),
3322 ui.label(_('%d unresolved'), 'resolve.unresolved'),
3301 ui.label(_('%d subrepos'), 'status.modified')]
3323 ui.label(_('%d subrepos'), 'status.modified')]
3302 t = []
3324 t = []
3303 for s, l in zip(st, labels):
3325 for s, l in zip(st, labels):
3304 if s:
3326 if s:
3305 t.append(l % len(s))
3327 t.append(l % len(s))
3306
3328
3307 t = ', '.join(t)
3329 t = ', '.join(t)
3308 cleanworkdir = False
3330 cleanworkdir = False
3309
3331
3310 if len(parents) > 1:
3332 if len(parents) > 1:
3311 t += _(' (merge)')
3333 t += _(' (merge)')
3312 elif branch != parents[0].branch():
3334 elif branch != parents[0].branch():
3313 t += _(' (new branch)')
3335 t += _(' (new branch)')
3314 elif (parents[0].extra().get('close') and
3336 elif (parents[0].extra().get('close') and
3315 pnode in repo.branchheads(branch, closed=True)):
3337 pnode in repo.branchheads(branch, closed=True)):
3316 t += _(' (head closed)')
3338 t += _(' (head closed)')
3317 elif (not st[0] and not st[1] and not st[2] and not st[7]):
3339 elif (not st[0] and not st[1] and not st[2] and not st[7]):
3318 t += _(' (clean)')
3340 t += _(' (clean)')
3319 cleanworkdir = True
3341 cleanworkdir = True
3320 elif pnode not in bheads:
3342 elif pnode not in bheads:
3321 t += _(' (new branch head)')
3343 t += _(' (new branch head)')
3322
3344
3323 if cleanworkdir:
3345 if cleanworkdir:
3324 ui.status(_('commit: %s\n') % t.strip())
3346 ui.status(_('commit: %s\n') % t.strip())
3325 else:
3347 else:
3326 ui.write(_('commit: %s\n') % t.strip())
3348 ui.write(_('commit: %s\n') % t.strip())
3327
3349
3328 # all ancestors of branch heads - all ancestors of parent = new csets
3350 # all ancestors of branch heads - all ancestors of parent = new csets
3329 new = [0] * len(repo)
3351 new = [0] * len(repo)
3330 cl = repo.changelog
3352 cl = repo.changelog
3331 for a in [cl.rev(n) for n in bheads]:
3353 for a in [cl.rev(n) for n in bheads]:
3332 new[a] = 1
3354 new[a] = 1
3333 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3355 for a in cl.ancestors(*[cl.rev(n) for n in bheads]):
3334 new[a] = 1
3356 new[a] = 1
3335 for a in [p.rev() for p in parents]:
3357 for a in [p.rev() for p in parents]:
3336 if a >= 0:
3358 if a >= 0:
3337 new[a] = 0
3359 new[a] = 0
3338 for a in cl.ancestors(*[p.rev() for p in parents]):
3360 for a in cl.ancestors(*[p.rev() for p in parents]):
3339 new[a] = 0
3361 new[a] = 0
3340 new = sum(new)
3362 new = sum(new)
3341
3363
3342 if new == 0:
3364 if new == 0:
3343 ui.status(_('update: (current)\n'))
3365 ui.status(_('update: (current)\n'))
3344 elif pnode not in bheads:
3366 elif pnode not in bheads:
3345 ui.write(_('update: %d new changesets (update)\n') % new)
3367 ui.write(_('update: %d new changesets (update)\n') % new)
3346 else:
3368 else:
3347 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3369 ui.write(_('update: %d new changesets, %d branch heads (merge)\n') %
3348 (new, len(bheads)))
3370 (new, len(bheads)))
3349
3371
3350 if opts.get('remote'):
3372 if opts.get('remote'):
3351 t = []
3373 t = []
3352 source, branches = hg.parseurl(ui.expandpath('default'))
3374 source, branches = hg.parseurl(ui.expandpath('default'))
3353 other = hg.repository(hg.remoteui(repo, {}), source)
3375 other = hg.repository(hg.remoteui(repo, {}), source)
3354 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3376 revs, checkout = hg.addbranchrevs(repo, other, branches, opts.get('rev'))
3355 ui.debug('comparing with %s\n' % url.hidepassword(source))
3377 ui.debug('comparing with %s\n' % url.hidepassword(source))
3356 repo.ui.pushbuffer()
3378 repo.ui.pushbuffer()
3357 common, incoming, rheads = discovery.findcommonincoming(repo, other)
3379 common, incoming, rheads = discovery.findcommonincoming(repo, other)
3358 repo.ui.popbuffer()
3380 repo.ui.popbuffer()
3359 if incoming:
3381 if incoming:
3360 t.append(_('1 or more incoming'))
3382 t.append(_('1 or more incoming'))
3361
3383
3362 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3384 dest, branches = hg.parseurl(ui.expandpath('default-push', 'default'))
3363 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3385 revs, checkout = hg.addbranchrevs(repo, repo, branches, None)
3364 other = hg.repository(hg.remoteui(repo, {}), dest)
3386 other = hg.repository(hg.remoteui(repo, {}), dest)
3365 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3387 ui.debug('comparing with %s\n' % url.hidepassword(dest))
3366 repo.ui.pushbuffer()
3388 repo.ui.pushbuffer()
3367 o = discovery.findoutgoing(repo, other)
3389 o = discovery.findoutgoing(repo, other)
3368 repo.ui.popbuffer()
3390 repo.ui.popbuffer()
3369 o = repo.changelog.nodesbetween(o, None)[0]
3391 o = repo.changelog.nodesbetween(o, None)[0]
3370 if o:
3392 if o:
3371 t.append(_('%d outgoing') % len(o))
3393 t.append(_('%d outgoing') % len(o))
3372
3394
3373 if t:
3395 if t:
3374 ui.write(_('remote: %s\n') % (', '.join(t)))
3396 ui.write(_('remote: %s\n') % (', '.join(t)))
3375 else:
3397 else:
3376 ui.status(_('remote: (synced)\n'))
3398 ui.status(_('remote: (synced)\n'))
3377
3399
3378 def tag(ui, repo, name1, *names, **opts):
3400 def tag(ui, repo, name1, *names, **opts):
3379 """add one or more tags for the current or given revision
3401 """add one or more tags for the current or given revision
3380
3402
3381 Name a particular revision using <name>.
3403 Name a particular revision using <name>.
3382
3404
3383 Tags are used to name particular revisions of the repository and are
3405 Tags are used to name particular revisions of the repository and are
3384 very useful to compare different revisions, to go back to significant
3406 very useful to compare different revisions, to go back to significant
3385 earlier versions or to mark branch points as releases, etc.
3407 earlier versions or to mark branch points as releases, etc.
3386
3408
3387 If no revision is given, the parent of the working directory is
3409 If no revision is given, the parent of the working directory is
3388 used, or tip if no revision is checked out.
3410 used, or tip if no revision is checked out.
3389
3411
3390 To facilitate version control, distribution, and merging of tags,
3412 To facilitate version control, distribution, and merging of tags,
3391 they are stored as a file named ".hgtags" which is managed
3413 they are stored as a file named ".hgtags" which is managed
3392 similarly to other project files and can be hand-edited if
3414 similarly to other project files and can be hand-edited if
3393 necessary. The file '.hg/localtags' is used for local tags (not
3415 necessary. The file '.hg/localtags' is used for local tags (not
3394 shared among repositories).
3416 shared among repositories).
3395
3417
3396 See :hg:`help dates` for a list of formats valid for -d/--date.
3418 See :hg:`help dates` for a list of formats valid for -d/--date.
3397
3419
3398 Since tag names have priority over branch names during revision
3420 Since tag names have priority over branch names during revision
3399 lookup, using an existing branch name as a tag name is discouraged.
3421 lookup, using an existing branch name as a tag name is discouraged.
3400
3422
3401 Returns 0 on success.
3423 Returns 0 on success.
3402 """
3424 """
3403
3425
3404 rev_ = "."
3426 rev_ = "."
3405 names = [t.strip() for t in (name1,) + names]
3427 names = [t.strip() for t in (name1,) + names]
3406 if len(names) != len(set(names)):
3428 if len(names) != len(set(names)):
3407 raise util.Abort(_('tag names must be unique'))
3429 raise util.Abort(_('tag names must be unique'))
3408 for n in names:
3430 for n in names:
3409 if n in ['tip', '.', 'null']:
3431 if n in ['tip', '.', 'null']:
3410 raise util.Abort(_('the name \'%s\' is reserved') % n)
3432 raise util.Abort(_('the name \'%s\' is reserved') % n)
3411 if opts.get('rev') and opts.get('remove'):
3433 if opts.get('rev') and opts.get('remove'):
3412 raise util.Abort(_("--rev and --remove are incompatible"))
3434 raise util.Abort(_("--rev and --remove are incompatible"))
3413 if opts.get('rev'):
3435 if opts.get('rev'):
3414 rev_ = opts['rev']
3436 rev_ = opts['rev']
3415 message = opts.get('message')
3437 message = opts.get('message')
3416 if opts.get('remove'):
3438 if opts.get('remove'):
3417 expectedtype = opts.get('local') and 'local' or 'global'
3439 expectedtype = opts.get('local') and 'local' or 'global'
3418 for n in names:
3440 for n in names:
3419 if not repo.tagtype(n):
3441 if not repo.tagtype(n):
3420 raise util.Abort(_('tag \'%s\' does not exist') % n)
3442 raise util.Abort(_('tag \'%s\' does not exist') % n)
3421 if repo.tagtype(n) != expectedtype:
3443 if repo.tagtype(n) != expectedtype:
3422 if expectedtype == 'global':
3444 if expectedtype == 'global':
3423 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
3445 raise util.Abort(_('tag \'%s\' is not a global tag') % n)
3424 else:
3446 else:
3425 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
3447 raise util.Abort(_('tag \'%s\' is not a local tag') % n)
3426 rev_ = nullid
3448 rev_ = nullid
3427 if not message:
3449 if not message:
3428 # we don't translate commit messages
3450 # we don't translate commit messages
3429 message = 'Removed tag %s' % ', '.join(names)
3451 message = 'Removed tag %s' % ', '.join(names)
3430 elif not opts.get('force'):
3452 elif not opts.get('force'):
3431 for n in names:
3453 for n in names:
3432 if n in repo.tags():
3454 if n in repo.tags():
3433 raise util.Abort(_('tag \'%s\' already exists '
3455 raise util.Abort(_('tag \'%s\' already exists '
3434 '(use -f to force)') % n)
3456 '(use -f to force)') % n)
3435 if not rev_ and repo.dirstate.parents()[1] != nullid:
3457 if not rev_ and repo.dirstate.parents()[1] != nullid:
3436 raise util.Abort(_('uncommitted merge - please provide a '
3458 raise util.Abort(_('uncommitted merge - please provide a '
3437 'specific revision'))
3459 'specific revision'))
3438 r = repo[rev_].node()
3460 r = repo[rev_].node()
3439
3461
3440 if not message:
3462 if not message:
3441 # we don't translate commit messages
3463 # we don't translate commit messages
3442 message = ('Added tag %s for changeset %s' %
3464 message = ('Added tag %s for changeset %s' %
3443 (', '.join(names), short(r)))
3465 (', '.join(names), short(r)))
3444
3466
3445 date = opts.get('date')
3467 date = opts.get('date')
3446 if date:
3468 if date:
3447 date = util.parsedate(date)
3469 date = util.parsedate(date)
3448
3470
3449 if opts.get('edit'):
3471 if opts.get('edit'):
3450 message = ui.edit(message, ui.username())
3472 message = ui.edit(message, ui.username())
3451
3473
3452 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
3474 repo.tag(names, r, message, opts.get('local'), opts.get('user'), date)
3453
3475
3454 def tags(ui, repo):
3476 def tags(ui, repo):
3455 """list repository tags
3477 """list repository tags
3456
3478
3457 This lists both regular and local tags. When the -v/--verbose
3479 This lists both regular and local tags. When the -v/--verbose
3458 switch is used, a third column "local" is printed for local tags.
3480 switch is used, a third column "local" is printed for local tags.
3459
3481
3460 Returns 0 on success.
3482 Returns 0 on success.
3461 """
3483 """
3462
3484
3463 hexfunc = ui.debugflag and hex or short
3485 hexfunc = ui.debugflag and hex or short
3464 tagtype = ""
3486 tagtype = ""
3465
3487
3466 for t, n in reversed(repo.tagslist()):
3488 for t, n in reversed(repo.tagslist()):
3467 if ui.quiet:
3489 if ui.quiet:
3468 ui.write("%s\n" % t)
3490 ui.write("%s\n" % t)
3469 continue
3491 continue
3470
3492
3471 try:
3493 try:
3472 hn = hexfunc(n)
3494 hn = hexfunc(n)
3473 r = "%5d:%s" % (repo.changelog.rev(n), hn)
3495 r = "%5d:%s" % (repo.changelog.rev(n), hn)
3474 except error.LookupError:
3496 except error.LookupError:
3475 r = " ?:%s" % hn
3497 r = " ?:%s" % hn
3476 else:
3498 else:
3477 spaces = " " * (30 - encoding.colwidth(t))
3499 spaces = " " * (30 - encoding.colwidth(t))
3478 if ui.verbose:
3500 if ui.verbose:
3479 if repo.tagtype(t) == 'local':
3501 if repo.tagtype(t) == 'local':
3480 tagtype = " local"
3502 tagtype = " local"
3481 else:
3503 else:
3482 tagtype = ""
3504 tagtype = ""
3483 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
3505 ui.write("%s%s %s%s\n" % (t, spaces, r, tagtype))
3484
3506
3485 def tip(ui, repo, **opts):
3507 def tip(ui, repo, **opts):
3486 """show the tip revision
3508 """show the tip revision
3487
3509
3488 The tip revision (usually just called the tip) is the changeset
3510 The tip revision (usually just called the tip) is the changeset
3489 most recently added to the repository (and therefore the most
3511 most recently added to the repository (and therefore the most
3490 recently changed head).
3512 recently changed head).
3491
3513
3492 If you have just made a commit, that commit will be the tip. If
3514 If you have just made a commit, that commit will be the tip. If
3493 you have just pulled changes from another repository, the tip of
3515 you have just pulled changes from another repository, the tip of
3494 that repository becomes the current tip. The "tip" tag is special
3516 that repository becomes the current tip. The "tip" tag is special
3495 and cannot be renamed or assigned to a different changeset.
3517 and cannot be renamed or assigned to a different changeset.
3496
3518
3497 Returns 0 on success.
3519 Returns 0 on success.
3498 """
3520 """
3499 displayer = cmdutil.show_changeset(ui, repo, opts)
3521 displayer = cmdutil.show_changeset(ui, repo, opts)
3500 displayer.show(repo[len(repo) - 1])
3522 displayer.show(repo[len(repo) - 1])
3501 displayer.close()
3523 displayer.close()
3502
3524
3503 def unbundle(ui, repo, fname1, *fnames, **opts):
3525 def unbundle(ui, repo, fname1, *fnames, **opts):
3504 """apply one or more changegroup files
3526 """apply one or more changegroup files
3505
3527
3506 Apply one or more compressed changegroup files generated by the
3528 Apply one or more compressed changegroup files generated by the
3507 bundle command.
3529 bundle command.
3508
3530
3509 Returns 0 on success, 1 if an update has unresolved files.
3531 Returns 0 on success, 1 if an update has unresolved files.
3510 """
3532 """
3511 fnames = (fname1,) + fnames
3533 fnames = (fname1,) + fnames
3512
3534
3513 lock = repo.lock()
3535 lock = repo.lock()
3514 try:
3536 try:
3515 for fname in fnames:
3537 for fname in fnames:
3516 f = url.open(ui, fname)
3538 f = url.open(ui, fname)
3517 gen = changegroup.readbundle(f, fname)
3539 gen = changegroup.readbundle(f, fname)
3518 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
3540 modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname)
3519 finally:
3541 finally:
3520 lock.release()
3542 lock.release()
3521
3543
3522 return postincoming(ui, repo, modheads, opts.get('update'), None)
3544 return postincoming(ui, repo, modheads, opts.get('update'), None)
3523
3545
3524 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
3546 def update(ui, repo, node=None, rev=None, clean=False, date=None, check=False):
3525 """update working directory (or switch revisions)
3547 """update working directory (or switch revisions)
3526
3548
3527 Update the repository's working directory to the specified
3549 Update the repository's working directory to the specified
3528 changeset.
3550 changeset.
3529
3551
3530 If no changeset is specified, attempt to update to the head of the
3552 If no changeset is specified, attempt to update to the head of the
3531 current branch. If this head is a descendant of the working
3553 current branch. If this head is a descendant of the working
3532 directory's parent, update to it, otherwise abort.
3554 directory's parent, update to it, otherwise abort.
3533
3555
3534 The following rules apply when the working directory contains
3556 The following rules apply when the working directory contains
3535 uncommitted changes:
3557 uncommitted changes:
3536
3558
3537 1. If neither -c/--check nor -C/--clean is specified, and if
3559 1. If neither -c/--check nor -C/--clean is specified, and if
3538 the requested changeset is an ancestor or descendant of
3560 the requested changeset is an ancestor or descendant of
3539 the working directory's parent, the uncommitted changes
3561 the working directory's parent, the uncommitted changes
3540 are merged into the requested changeset and the merged
3562 are merged into the requested changeset and the merged
3541 result is left uncommitted. If the requested changeset is
3563 result is left uncommitted. If the requested changeset is
3542 not an ancestor or descendant (that is, it is on another
3564 not an ancestor or descendant (that is, it is on another
3543 branch), the update is aborted and the uncommitted changes
3565 branch), the update is aborted and the uncommitted changes
3544 are preserved.
3566 are preserved.
3545
3567
3546 2. With the -c/--check option, the update is aborted and the
3568 2. With the -c/--check option, the update is aborted and the
3547 uncommitted changes are preserved.
3569 uncommitted changes are preserved.
3548
3570
3549 3. With the -C/--clean option, uncommitted changes are discarded and
3571 3. With the -C/--clean option, uncommitted changes are discarded and
3550 the working directory is updated to the requested changeset.
3572 the working directory is updated to the requested changeset.
3551
3573
3552 Use null as the changeset to remove the working directory (like
3574 Use null as the changeset to remove the working directory (like
3553 :hg:`clone -U`).
3575 :hg:`clone -U`).
3554
3576
3555 If you want to update just one file to an older changeset, use :hg:`revert`.
3577 If you want to update just one file to an older changeset, use :hg:`revert`.
3556
3578
3557 See :hg:`help dates` for a list of formats valid for -d/--date.
3579 See :hg:`help dates` for a list of formats valid for -d/--date.
3558
3580
3559 Returns 0 on success, 1 if there are unresolved files.
3581 Returns 0 on success, 1 if there are unresolved files.
3560 """
3582 """
3561 if rev and node:
3583 if rev and node:
3562 raise util.Abort(_("please specify just one revision"))
3584 raise util.Abort(_("please specify just one revision"))
3563
3585
3564 if not rev:
3586 if not rev:
3565 rev = node
3587 rev = node
3566
3588
3567 if check and clean:
3589 if check and clean:
3568 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
3590 raise util.Abort(_("cannot specify both -c/--check and -C/--clean"))
3569
3591
3570 if check:
3592 if check:
3571 # we could use dirty() but we can ignore merge and branch trivia
3593 # we could use dirty() but we can ignore merge and branch trivia
3572 c = repo[None]
3594 c = repo[None]
3573 if c.modified() or c.added() or c.removed():
3595 if c.modified() or c.added() or c.removed():
3574 raise util.Abort(_("uncommitted local changes"))
3596 raise util.Abort(_("uncommitted local changes"))
3575
3597
3576 if date:
3598 if date:
3577 if rev:
3599 if rev:
3578 raise util.Abort(_("you can't specify a revision and a date"))
3600 raise util.Abort(_("you can't specify a revision and a date"))
3579 rev = cmdutil.finddate(ui, repo, date)
3601 rev = cmdutil.finddate(ui, repo, date)
3580
3602
3581 if clean or check:
3603 if clean or check:
3582 return hg.clean(repo, rev)
3604 return hg.clean(repo, rev)
3583 else:
3605 else:
3584 return hg.update(repo, rev)
3606 return hg.update(repo, rev)
3585
3607
3586 def verify(ui, repo):
3608 def verify(ui, repo):
3587 """verify the integrity of the repository
3609 """verify the integrity of the repository
3588
3610
3589 Verify the integrity of the current repository.
3611 Verify the integrity of the current repository.
3590
3612
3591 This will perform an extensive check of the repository's
3613 This will perform an extensive check of the repository's
3592 integrity, validating the hashes and checksums of each entry in
3614 integrity, validating the hashes and checksums of each entry in
3593 the changelog, manifest, and tracked files, as well as the
3615 the changelog, manifest, and tracked files, as well as the
3594 integrity of their crosslinks and indices.
3616 integrity of their crosslinks and indices.
3595
3617
3596 Returns 0 on success, 1 if errors are encountered.
3618 Returns 0 on success, 1 if errors are encountered.
3597 """
3619 """
3598 return hg.verify(repo)
3620 return hg.verify(repo)
3599
3621
3600 def version_(ui):
3622 def version_(ui):
3601 """output version and copyright information"""
3623 """output version and copyright information"""
3602 ui.write(_("Mercurial Distributed SCM (version %s)\n")
3624 ui.write(_("Mercurial Distributed SCM (version %s)\n")
3603 % util.version())
3625 % util.version())
3604 ui.status(_(
3626 ui.status(_(
3605 "\nCopyright (C) 2005-2010 Matt Mackall <mpm@selenic.com> and others\n"
3627 "\nCopyright (C) 2005-2010 Matt Mackall <mpm@selenic.com> and others\n"
3606 "This is free software; see the source for copying conditions. "
3628 "This is free software; see the source for copying conditions. "
3607 "There is NO\nwarranty; "
3629 "There is NO\nwarranty; "
3608 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
3630 "not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
3609 ))
3631 ))
3610
3632
3611 # Command options and aliases are listed here, alphabetically
3633 # Command options and aliases are listed here, alphabetically
3612
3634
3613 globalopts = [
3635 globalopts = [
3614 ('R', 'repository', '',
3636 ('R', 'repository', '',
3615 _('repository root directory or name of overlay bundle file')),
3637 _('repository root directory or name of overlay bundle file'),
3616 ('', 'cwd', '', _('change working directory')),
3638 _('REPO')),
3639 ('', 'cwd', '',
3640 _('change working directory'), _('DIR')),
3617 ('y', 'noninteractive', None,
3641 ('y', 'noninteractive', None,
3618 _('do not prompt, assume \'yes\' for any required answers')),
3642 _('do not prompt, assume \'yes\' for any required answers')),
3619 ('q', 'quiet', None, _('suppress output')),
3643 ('q', 'quiet', None, _('suppress output')),
3620 ('v', 'verbose', None, _('enable additional output')),
3644 ('v', 'verbose', None, _('enable additional output')),
3621 ('', 'config', [],
3645 ('', 'config', [],
3622 _('set/override config option (use \'section.name=value\')')),
3646 _('set/override config option (use \'section.name=value\')'),
3647 _('CONFIG')),
3623 ('', 'debug', None, _('enable debugging output')),
3648 ('', 'debug', None, _('enable debugging output')),
3624 ('', 'debugger', None, _('start debugger')),
3649 ('', 'debugger', None, _('start debugger')),
3625 ('', 'encoding', encoding.encoding, _('set the charset encoding')),
3650 ('', 'encoding', encoding.encoding, _('set the charset encoding'),
3651 _('ENCODE')),
3626 ('', 'encodingmode', encoding.encodingmode,
3652 ('', 'encodingmode', encoding.encodingmode,
3627 _('set the charset encoding mode')),
3653 _('set the charset encoding mode'), _('MODE')),
3628 ('', 'traceback', None, _('always print a traceback on exception')),
3654 ('', 'traceback', None, _('always print a traceback on exception')),
3629 ('', 'time', None, _('time how long the command takes')),
3655 ('', 'time', None, _('time how long the command takes')),
3630 ('', 'profile', None, _('print command execution profile')),
3656 ('', 'profile', None, _('print command execution profile')),
3631 ('', 'version', None, _('output version information and exit')),
3657 ('', 'version', None, _('output version information and exit')),
3632 ('h', 'help', None, _('display help and exit')),
3658 ('h', 'help', None, _('display help and exit')),
3633 ]
3659 ]
3634
3660
3635 dryrunopts = [('n', 'dry-run', None,
3661 dryrunopts = [('n', 'dry-run', None,
3636 _('do not perform actions, just print output'))]
3662 _('do not perform actions, just print output'))]
3637
3663
3638 remoteopts = [
3664 remoteopts = [
3639 ('e', 'ssh', '', _('specify ssh command to use')),
3665 ('e', 'ssh', '',
3640 ('', 'remotecmd', '', _('specify hg command to run on the remote side')),
3666 _('specify ssh command to use'), _('CMD')),
3667 ('', 'remotecmd', '',
3668 _('specify hg command to run on the remote side'), _('CMD')),
3641 ]
3669 ]
3642
3670
3643 walkopts = [
3671 walkopts = [
3644 ('I', 'include', [], _('include names matching the given patterns')),
3672 ('I', 'include', [],
3645 ('X', 'exclude', [], _('exclude names matching the given patterns')),
3673 _('include names matching the given patterns'), _('PATTERN')),
3674 ('X', 'exclude', [],
3675 _('exclude names matching the given patterns'), _('PATTERN')),
3646 ]
3676 ]
3647
3677
3648 commitopts = [
3678 commitopts = [
3649 ('m', 'message', '', _('use <text> as commit message')),
3679 ('m', 'message', '',
3650 ('l', 'logfile', '', _('read commit message from <file>')),
3680 _('use text as commit message'), _('TEXT')),
3681 ('l', 'logfile', '',
3682 _('read commit message from file'), _('FILE')),
3651 ]
3683 ]
3652
3684
3653 commitopts2 = [
3685 commitopts2 = [
3654 ('d', 'date', '', _('record datecode as commit date')),
3686 ('d', 'date', '',
3655 ('u', 'user', '', _('record the specified user as committer')),
3687 _('record datecode as commit date'), _('DATE')),
3688 ('u', 'user', '',
3689 _('record the specified user as committer'), _('USER')),
3656 ]
3690 ]
3657
3691
3658 templateopts = [
3692 templateopts = [
3659 ('', 'style', '', _('display using template map file')),
3693 ('', 'style', '',
3660 ('', 'template', '', _('display with template')),
3694 _('display using template map file'), _('STYLE')),
3695 ('', 'template', '',
3696 _('display with template'), _('TEMPLATE')),
3661 ]
3697 ]
3662
3698
3663 logopts = [
3699 logopts = [
3664 ('p', 'patch', None, _('show patch')),
3700 ('p', 'patch', None, _('show patch')),
3665 ('g', 'git', None, _('use git extended diff format')),
3701 ('g', 'git', None, _('use git extended diff format')),
3666 ('l', 'limit', '', _('limit number of changes displayed')),
3702 ('l', 'limit', '',
3703 _('limit number of changes displayed'), _('NUM')),
3667 ('M', 'no-merges', None, _('do not show merges')),
3704 ('M', 'no-merges', None, _('do not show merges')),
3668 ('', 'stat', None, _('output diffstat-style summary of changes')),
3705 ('', 'stat', None, _('output diffstat-style summary of changes')),
3669 ] + templateopts
3706 ] + templateopts
3670
3707
3671 diffopts = [
3708 diffopts = [
3672 ('a', 'text', None, _('treat all files as text')),
3709 ('a', 'text', None, _('treat all files as text')),
3673 ('g', 'git', None, _('use git extended diff format')),
3710 ('g', 'git', None, _('use git extended diff format')),
3674 ('', 'nodates', None, _('omit dates from diff headers'))
3711 ('', 'nodates', None, _('omit dates from diff headers'))
3675 ]
3712 ]
3676
3713
3677 diffopts2 = [
3714 diffopts2 = [
3678 ('p', 'show-function', None, _('show which function each change is in')),
3715 ('p', 'show-function', None, _('show which function each change is in')),
3679 ('', 'reverse', None, _('produce a diff that undoes the changes')),
3716 ('', 'reverse', None, _('produce a diff that undoes the changes')),
3680 ('w', 'ignore-all-space', None,
3717 ('w', 'ignore-all-space', None,
3681 _('ignore white space when comparing lines')),
3718 _('ignore white space when comparing lines')),
3682 ('b', 'ignore-space-change', None,
3719 ('b', 'ignore-space-change', None,
3683 _('ignore changes in the amount of white space')),
3720 _('ignore changes in the amount of white space')),
3684 ('B', 'ignore-blank-lines', None,
3721 ('B', 'ignore-blank-lines', None,
3685 _('ignore changes whose lines are all blank')),
3722 _('ignore changes whose lines are all blank')),
3686 ('U', 'unified', '', _('number of lines of context to show')),
3723 ('U', 'unified', '',
3724 _('number of lines of context to show'), _('NUM')),
3687 ('', 'stat', None, _('output diffstat-style summary of changes')),
3725 ('', 'stat', None, _('output diffstat-style summary of changes')),
3688 ]
3726 ]
3689
3727
3690 similarityopts = [
3728 similarityopts = [
3691 ('s', 'similarity', '',
3729 ('s', 'similarity', '',
3692 _('guess renamed files by similarity (0<=s<=100)'))
3730 _('guess renamed files by similarity (0<=s<=100)'), _('SIMILARITY'))
3693 ]
3731 ]
3694
3732
3695 table = {
3733 table = {
3696 "^add": (add, walkopts + dryrunopts, _('[OPTION]... [FILE]...')),
3734 "^add": (add, walkopts + dryrunopts, _('[OPTION]... [FILE]...')),
3697 "addremove":
3735 "addremove":
3698 (addremove, similarityopts + walkopts + dryrunopts,
3736 (addremove, similarityopts + walkopts + dryrunopts,
3699 _('[OPTION]... [FILE]...')),
3737 _('[OPTION]... [FILE]...')),
3700 "^annotate|blame":
3738 "^annotate|blame":
3701 (annotate,
3739 (annotate,
3702 [('r', 'rev', '', _('annotate the specified revision')),
3740 [('r', 'rev', '',
3741 _('annotate the specified revision'), _('REV')),
3703 ('', 'follow', None,
3742 ('', 'follow', None,
3704 _('follow copies/renames and list the filename (DEPRECATED)')),
3743 _('follow copies/renames and list the filename (DEPRECATED)')),
3705 ('', 'no-follow', None, _("don't follow copies and renames")),
3744 ('', 'no-follow', None, _("don't follow copies and renames")),
3706 ('a', 'text', None, _('treat all files as text')),
3745 ('a', 'text', None, _('treat all files as text')),
3707 ('u', 'user', None, _('list the author (long with -v)')),
3746 ('u', 'user', None, _('list the author (long with -v)')),
3708 ('f', 'file', None, _('list the filename')),
3747 ('f', 'file', None, _('list the filename')),
3709 ('d', 'date', None, _('list the date (short with -q)')),
3748 ('d', 'date', None, _('list the date (short with -q)')),
3710 ('n', 'number', None, _('list the revision number (default)')),
3749 ('n', 'number', None, _('list the revision number (default)')),
3711 ('c', 'changeset', None, _('list the changeset')),
3750 ('c', 'changeset', None, _('list the changeset')),
3712 ('l', 'line-number', None,
3751 ('l', 'line-number', None,
3713 _('show line number at the first appearance'))
3752 _('show line number at the first appearance'))
3714 ] + walkopts,
3753 ] + walkopts,
3715 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
3754 _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...')),
3716 "archive":
3755 "archive":
3717 (archive,
3756 (archive,
3718 [('', 'no-decode', None, _('do not pass files through decoders')),
3757 [('', 'no-decode', None, _('do not pass files through decoders')),
3719 ('p', 'prefix', '', _('directory prefix for files in archive')),
3758 ('p', 'prefix', '',
3720 ('r', 'rev', '', _('revision to distribute')),
3759 _('directory prefix for files in archive'), _('PREFIX')),
3721 ('t', 'type', '', _('type of distribution to create')),
3760 ('r', 'rev', '',
3761 _('revision to distribute'), _('REV')),
3762 ('t', 'type', '',
3763 _('type of distribution to create'), _('TYPE')),
3722 ] + walkopts,
3764 ] + walkopts,
3723 _('[OPTION]... DEST')),
3765 _('[OPTION]... DEST')),
3724 "backout":
3766 "backout":
3725 (backout,
3767 (backout,
3726 [('', 'merge', None,
3768 [('', 'merge', None,
3727 _('merge with old dirstate parent after backout')),
3769 _('merge with old dirstate parent after backout')),
3728 ('', 'parent', '', _('parent to choose when backing out merge')),
3770 ('', 'parent', '',
3729 ('r', 'rev', '', _('revision to backout')),
3771 _('parent to choose when backing out merge'), _('REV')),
3772 ('r', 'rev', '',
3773 _('revision to backout'), _('REV')),
3730 ] + walkopts + commitopts + commitopts2,
3774 ] + walkopts + commitopts + commitopts2,
3731 _('[OPTION]... [-r] REV')),
3775 _('[OPTION]... [-r] REV')),
3732 "bisect":
3776 "bisect":
3733 (bisect,
3777 (bisect,
3734 [('r', 'reset', False, _('reset bisect state')),
3778 [('r', 'reset', False, _('reset bisect state')),
3735 ('g', 'good', False, _('mark changeset good')),
3779 ('g', 'good', False, _('mark changeset good')),
3736 ('b', 'bad', False, _('mark changeset bad')),
3780 ('b', 'bad', False, _('mark changeset bad')),
3737 ('s', 'skip', False, _('skip testing changeset')),
3781 ('s', 'skip', False, _('skip testing changeset')),
3738 ('c', 'command', '', _('use command to check changeset state')),
3782 ('c', 'command', '',
3783 _('use command to check changeset state'), _('CMD')),
3739 ('U', 'noupdate', False, _('do not update to target'))],
3784 ('U', 'noupdate', False, _('do not update to target'))],
3740 _("[-gbsr] [-U] [-c CMD] [REV]")),
3785 _("[-gbsr] [-U] [-c CMD] [REV]")),
3741 "branch":
3786 "branch":
3742 (branch,
3787 (branch,
3743 [('f', 'force', None,
3788 [('f', 'force', None,
3744 _('set branch name even if it shadows an existing branch')),
3789 _('set branch name even if it shadows an existing branch')),
3745 ('C', 'clean', None, _('reset branch name to parent branch name'))],
3790 ('C', 'clean', None, _('reset branch name to parent branch name'))],
3746 _('[-fC] [NAME]')),
3791 _('[-fC] [NAME]')),
3747 "branches":
3792 "branches":
3748 (branches,
3793 (branches,
3749 [('a', 'active', False,
3794 [('a', 'active', False,
3750 _('show only branches that have unmerged heads')),
3795 _('show only branches that have unmerged heads')),
3751 ('c', 'closed', False,
3796 ('c', 'closed', False,
3752 _('show normal and closed branches'))],
3797 _('show normal and closed branches'))],
3753 _('[-ac]')),
3798 _('[-ac]')),
3754 "bundle":
3799 "bundle":
3755 (bundle,
3800 (bundle,
3756 [('f', 'force', None,
3801 [('f', 'force', None,
3757 _('run even when the destination is unrelated')),
3802 _('run even when the destination is unrelated')),
3758 ('r', 'rev', [],
3803 ('r', 'rev', [],
3759 _('a changeset intended to be added to the destination')),
3804 _('a changeset intended to be added to the destination'),
3805 _('REV')),
3760 ('b', 'branch', [],
3806 ('b', 'branch', [],
3761 _('a specific branch you would like to bundle')),
3807 _('a specific branch you would like to bundle'),
3808 _('BRANCH')),
3762 ('', 'base', [],
3809 ('', 'base', [],
3763 _('a base changeset assumed to be available at the destination')),
3810 _('a base changeset assumed to be available at the destination'),
3811 _('REV')),
3764 ('a', 'all', None, _('bundle all changesets in the repository')),
3812 ('a', 'all', None, _('bundle all changesets in the repository')),
3765 ('t', 'type', 'bzip2', _('bundle compression type to use')),
3813 ('t', 'type', 'bzip2',
3814 _('bundle compression type to use'), _('TYPE')),
3766 ] + remoteopts,
3815 ] + remoteopts,
3767 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
3816 _('[-f] [-t TYPE] [-a] [-r REV]... [--base REV]... FILE [DEST]')),
3768 "cat":
3817 "cat":
3769 (cat,
3818 (cat,
3770 [('o', 'output', '', _('print output to file with formatted name')),
3819 [('o', 'output', '',
3771 ('r', 'rev', '', _('print the given revision')),
3820 _('print output to file with formatted name'), _('FORMAT')),
3821 ('r', 'rev', '',
3822 _('print the given revision'), _('REV')),
3772 ('', 'decode', None, _('apply any matching decode filter')),
3823 ('', 'decode', None, _('apply any matching decode filter')),
3773 ] + walkopts,
3824 ] + walkopts,
3774 _('[OPTION]... FILE...')),
3825 _('[OPTION]... FILE...')),
3775 "^clone":
3826 "^clone":
3776 (clone,
3827 (clone,
3777 [('U', 'noupdate', None,
3828 [('U', 'noupdate', None,
3778 _('the clone will include an empty working copy (only a repository)')),
3829 _('the clone will include an empty working copy (only a repository)')),
3779 ('u', 'updaterev', '',
3830 ('u', 'updaterev', '',
3780 _('revision, tag or branch to check out')),
3831 _('revision, tag or branch to check out'), _('REV')),
3781 ('r', 'rev', [],
3832 ('r', 'rev', [],
3782 _('include the specified changeset')),
3833 _('include the specified changeset'), _('REV')),
3783 ('b', 'branch', [],
3834 ('b', 'branch', [],
3784 _('clone only the specified branch')),
3835 _('clone only the specified branch'), _('BRANCH')),
3785 ('', 'pull', None, _('use pull protocol to copy metadata')),
3836 ('', 'pull', None, _('use pull protocol to copy metadata')),
3786 ('', 'uncompressed', None,
3837 ('', 'uncompressed', None,
3787 _('use uncompressed transfer (fast over LAN)')),
3838 _('use uncompressed transfer (fast over LAN)')),
3788 ] + remoteopts,
3839 ] + remoteopts,
3789 _('[OPTION]... SOURCE [DEST]')),
3840 _('[OPTION]... SOURCE [DEST]')),
3790 "^commit|ci":
3841 "^commit|ci":
3791 (commit,
3842 (commit,
3792 [('A', 'addremove', None,
3843 [('A', 'addremove', None,
3793 _('mark new/missing files as added/removed before committing')),
3844 _('mark new/missing files as added/removed before committing')),
3794 ('', 'close-branch', None,
3845 ('', 'close-branch', None,
3795 _('mark a branch as closed, hiding it from the branch list')),
3846 _('mark a branch as closed, hiding it from the branch list')),
3796 ] + walkopts + commitopts + commitopts2,
3847 ] + walkopts + commitopts + commitopts2,
3797 _('[OPTION]... [FILE]...')),
3848 _('[OPTION]... [FILE]...')),
3798 "copy|cp":
3849 "copy|cp":
3799 (copy,
3850 (copy,
3800 [('A', 'after', None, _('record a copy that has already occurred')),
3851 [('A', 'after', None, _('record a copy that has already occurred')),
3801 ('f', 'force', None,
3852 ('f', 'force', None,
3802 _('forcibly copy over an existing managed file')),
3853 _('forcibly copy over an existing managed file')),
3803 ] + walkopts + dryrunopts,
3854 ] + walkopts + dryrunopts,
3804 _('[OPTION]... [SOURCE]... DEST')),
3855 _('[OPTION]... [SOURCE]... DEST')),
3805 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
3856 "debugancestor": (debugancestor, [], _('[INDEX] REV1 REV2')),
3806 "debugcheckstate": (debugcheckstate, [], ''),
3857 "debugcheckstate": (debugcheckstate, [], ''),
3807 "debugcommands": (debugcommands, [], _('[COMMAND]')),
3858 "debugcommands": (debugcommands, [], _('[COMMAND]')),
3808 "debugcomplete":
3859 "debugcomplete":
3809 (debugcomplete,
3860 (debugcomplete,
3810 [('o', 'options', None, _('show the command options'))],
3861 [('o', 'options', None, _('show the command options'))],
3811 _('[-o] CMD')),
3862 _('[-o] CMD')),
3812 "debugdate":
3863 "debugdate":
3813 (debugdate,
3864 (debugdate,
3814 [('e', 'extended', None, _('try extended date formats'))],
3865 [('e', 'extended', None, _('try extended date formats'))],
3815 _('[-e] DATE [RANGE]')),
3866 _('[-e] DATE [RANGE]')),
3816 "debugdata": (debugdata, [], _('FILE REV')),
3867 "debugdata": (debugdata, [], _('FILE REV')),
3817 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
3868 "debugfsinfo": (debugfsinfo, [], _('[PATH]')),
3818 "debugindex": (debugindex, [], _('FILE')),
3869 "debugindex": (debugindex, [], _('FILE')),
3819 "debugindexdot": (debugindexdot, [], _('FILE')),
3870 "debugindexdot": (debugindexdot, [], _('FILE')),
3820 "debuginstall": (debuginstall, [], ''),
3871 "debuginstall": (debuginstall, [], ''),
3821 "debugrebuildstate":
3872 "debugrebuildstate":
3822 (debugrebuildstate,
3873 (debugrebuildstate,
3823 [('r', 'rev', '', _('revision to rebuild to'))],
3874 [('r', 'rev', '',
3875 _('revision to rebuild to'), _('REV'))],
3824 _('[-r REV] [REV]')),
3876 _('[-r REV] [REV]')),
3825 "debugrename":
3877 "debugrename":
3826 (debugrename,
3878 (debugrename,
3827 [('r', 'rev', '', _('revision to debug'))],
3879 [('r', 'rev', '',
3880 _('revision to debug'), _('REV'))],
3828 _('[-r REV] FILE')),
3881 _('[-r REV] FILE')),
3829 "debugrevspec":
3882 "debugrevspec":
3830 (debugrevspec, [], ('REVSPEC')),
3883 (debugrevspec, [], ('REVSPEC')),
3831 "debugsetparents":
3884 "debugsetparents":
3832 (debugsetparents, [], _('REV1 [REV2]')),
3885 (debugsetparents, [], _('REV1 [REV2]')),
3833 "debugstate":
3886 "debugstate":
3834 (debugstate,
3887 (debugstate,
3835 [('', 'nodates', None, _('do not display the saved mtime'))],
3888 [('', 'nodates', None, _('do not display the saved mtime'))],
3836 _('[OPTION]...')),
3889 _('[OPTION]...')),
3837 "debugsub":
3890 "debugsub":
3838 (debugsub,
3891 (debugsub,
3839 [('r', 'rev', '', _('revision to check'))],
3892 [('r', 'rev', '',
3893 _('revision to check'), _('REV'))],
3840 _('[-r REV] [REV]')),
3894 _('[-r REV] [REV]')),
3841 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
3895 "debugwalk": (debugwalk, walkopts, _('[OPTION]... [FILE]...')),
3842 "^diff":
3896 "^diff":
3843 (diff,
3897 (diff,
3844 [('r', 'rev', [], _('revision')),
3898 [('r', 'rev', [],
3845 ('c', 'change', '', _('change made by revision'))
3899 _('revision'), _('REV')),
3900 ('c', 'change', '',
3901 _('change made by revision'), _('REV'))
3846 ] + diffopts + diffopts2 + walkopts,
3902 ] + diffopts + diffopts2 + walkopts,
3847 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
3903 _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...')),
3848 "^export":
3904 "^export":
3849 (export,
3905 (export,
3850 [('o', 'output', '', _('print output to file with formatted name')),
3906 [('o', 'output', '',
3907 _('print output to file with formatted name'), _('FORMAT')),
3851 ('', 'switch-parent', None, _('diff against the second parent')),
3908 ('', 'switch-parent', None, _('diff against the second parent')),
3852 ('r', 'rev', [], _('revisions to export')),
3909 ('r', 'rev', [],
3910 _('revisions to export'), _('REV')),
3853 ] + diffopts,
3911 ] + diffopts,
3854 _('[OPTION]... [-o OUTFILESPEC] REV...')),
3912 _('[OPTION]... [-o OUTFILESPEC] REV...')),
3855 "^forget":
3913 "^forget":
3856 (forget,
3914 (forget,
3857 [] + walkopts,
3915 [] + walkopts,
3858 _('[OPTION]... FILE...')),
3916 _('[OPTION]... FILE...')),
3859 "grep":
3917 "grep":
3860 (grep,
3918 (grep,
3861 [('0', 'print0', None, _('end fields with NUL')),
3919 [('0', 'print0', None, _('end fields with NUL')),
3862 ('', 'all', None, _('print all revisions that match')),
3920 ('', 'all', None, _('print all revisions that match')),
3863 ('f', 'follow', None,
3921 ('f', 'follow', None,
3864 _('follow changeset history,'
3922 _('follow changeset history,'
3865 ' or file history across copies and renames')),
3923 ' or file history across copies and renames')),
3866 ('i', 'ignore-case', None, _('ignore case when matching')),
3924 ('i', 'ignore-case', None, _('ignore case when matching')),
3867 ('l', 'files-with-matches', None,
3925 ('l', 'files-with-matches', None,
3868 _('print only filenames and revisions that match')),
3926 _('print only filenames and revisions that match')),
3869 ('n', 'line-number', None, _('print matching line numbers')),
3927 ('n', 'line-number', None, _('print matching line numbers')),
3870 ('r', 'rev', [], _('only search files changed within revision range')),
3928 ('r', 'rev', [],
3929 _('only search files changed within revision range'), _('REV')),
3871 ('u', 'user', None, _('list the author (long with -v)')),
3930 ('u', 'user', None, _('list the author (long with -v)')),
3872 ('d', 'date', None, _('list the date (short with -q)')),
3931 ('d', 'date', None, _('list the date (short with -q)')),
3873 ] + walkopts,
3932 ] + walkopts,
3874 _('[OPTION]... PATTERN [FILE]...')),
3933 _('[OPTION]... PATTERN [FILE]...')),
3875 "heads":
3934 "heads":
3876 (heads,
3935 (heads,
3877 [('r', 'rev', '', _('show only heads which are descendants of REV')),
3936 [('r', 'rev', '',
3937 _('show only heads which are descendants of REV'), _('REV')),
3878 ('t', 'topo', False, _('show topological heads only')),
3938 ('t', 'topo', False, _('show topological heads only')),
3879 ('a', 'active', False,
3939 ('a', 'active', False,
3880 _('show active branchheads only [DEPRECATED]')),
3940 _('show active branchheads only [DEPRECATED]')),
3881 ('c', 'closed', False,
3941 ('c', 'closed', False,
3882 _('show normal and closed branch heads')),
3942 _('show normal and closed branch heads')),
3883 ] + templateopts,
3943 ] + templateopts,
3884 _('[-ac] [-r REV] [REV]...')),
3944 _('[-ac] [-r REV] [REV]...')),
3885 "help": (help_, [], _('[TOPIC]')),
3945 "help": (help_, [], _('[TOPIC]')),
3886 "identify|id":
3946 "identify|id":
3887 (identify,
3947 (identify,
3888 [('r', 'rev', '', _('identify the specified revision')),
3948 [('r', 'rev', '',
3949 _('identify the specified revision'), _('REV')),
3889 ('n', 'num', None, _('show local revision number')),
3950 ('n', 'num', None, _('show local revision number')),
3890 ('i', 'id', None, _('show global revision id')),
3951 ('i', 'id', None, _('show global revision id')),
3891 ('b', 'branch', None, _('show branch')),
3952 ('b', 'branch', None, _('show branch')),
3892 ('t', 'tags', None, _('show tags'))],
3953 ('t', 'tags', None, _('show tags'))],
3893 _('[-nibt] [-r REV] [SOURCE]')),
3954 _('[-nibt] [-r REV] [SOURCE]')),
3894 "import|patch":
3955 "import|patch":
3895 (import_,
3956 (import_,
3896 [('p', 'strip', 1,
3957 [('p', 'strip', 1,
3897 _('directory strip option for patch. This has the same '
3958 _('directory strip option for patch. This has the same '
3898 'meaning as the corresponding patch option')),
3959 'meaning as the corresponding patch option'),
3899 ('b', 'base', '', _('base path')),
3960 _('NUM')),
3961 ('b', 'base', '',
3962 _('base path'), _('PATH')),
3900 ('f', 'force', None,
3963 ('f', 'force', None,
3901 _('skip check for outstanding uncommitted changes')),
3964 _('skip check for outstanding uncommitted changes')),
3902 ('', 'no-commit', None,
3965 ('', 'no-commit', None,
3903 _("don't commit, just update the working directory")),
3966 _("don't commit, just update the working directory")),
3904 ('', 'exact', None,
3967 ('', 'exact', None,
3905 _('apply patch to the nodes from which it was generated')),
3968 _('apply patch to the nodes from which it was generated')),
3906 ('', 'import-branch', None,
3969 ('', 'import-branch', None,
3907 _('use any branch information in patch (implied by --exact)'))] +
3970 _('use any branch information in patch (implied by --exact)'))] +
3908 commitopts + commitopts2 + similarityopts,
3971 commitopts + commitopts2 + similarityopts,
3909 _('[OPTION]... PATCH...')),
3972 _('[OPTION]... PATCH...')),
3910 "incoming|in":
3973 "incoming|in":
3911 (incoming,
3974 (incoming,
3912 [('f', 'force', None,
3975 [('f', 'force', None,
3913 _('run even if remote repository is unrelated')),
3976 _('run even if remote repository is unrelated')),
3914 ('n', 'newest-first', None, _('show newest record first')),
3977 ('n', 'newest-first', None, _('show newest record first')),
3915 ('', 'bundle', '', _('file to store the bundles into')),
3978 ('', 'bundle', '',
3979 _('file to store the bundles into'), _('FILE')),
3916 ('r', 'rev', [],
3980 ('r', 'rev', [],
3917 _('a remote changeset intended to be added')),
3981 _('a remote changeset intended to be added'), _('REV')),
3918 ('b', 'branch', [],
3982 ('b', 'branch', [],
3919 _('a specific branch you would like to pull')),
3983 _('a specific branch you would like to pull'), _('BRANCH')),
3920 ] + logopts + remoteopts,
3984 ] + logopts + remoteopts,
3921 _('[-p] [-n] [-M] [-f] [-r REV]...'
3985 _('[-p] [-n] [-M] [-f] [-r REV]...'
3922 ' [--bundle FILENAME] [SOURCE]')),
3986 ' [--bundle FILENAME] [SOURCE]')),
3923 "^init":
3987 "^init":
3924 (init,
3988 (init,
3925 remoteopts,
3989 remoteopts,
3926 _('[-e CMD] [--remotecmd CMD] [DEST]')),
3990 _('[-e CMD] [--remotecmd CMD] [DEST]')),
3927 "locate":
3991 "locate":
3928 (locate,
3992 (locate,
3929 [('r', 'rev', '', _('search the repository as it is in REV')),
3993 [('r', 'rev', '',
3994 _('search the repository as it is in REV'), _('REV')),
3930 ('0', 'print0', None,
3995 ('0', 'print0', None,
3931 _('end filenames with NUL, for use with xargs')),
3996 _('end filenames with NUL, for use with xargs')),
3932 ('f', 'fullpath', None,
3997 ('f', 'fullpath', None,
3933 _('print complete paths from the filesystem root')),
3998 _('print complete paths from the filesystem root')),
3934 ] + walkopts,
3999 ] + walkopts,
3935 _('[OPTION]... [PATTERN]...')),
4000 _('[OPTION]... [PATTERN]...')),
3936 "^log|history":
4001 "^log|history":
3937 (log,
4002 (log,
3938 [('f', 'follow', None,
4003 [('f', 'follow', None,
3939 _('follow changeset history,'
4004 _('follow changeset history,'
3940 ' or file history across copies and renames')),
4005 ' or file history across copies and renames')),
3941 ('', 'follow-first', None,
4006 ('', 'follow-first', None,
3942 _('only follow the first parent of merge changesets')),
4007 _('only follow the first parent of merge changesets')),
3943 ('d', 'date', '', _('show revisions matching date spec')),
4008 ('d', 'date', '',
4009 _('show revisions matching date spec'), _('DATE')),
3944 ('C', 'copies', None, _('show copied files')),
4010 ('C', 'copies', None, _('show copied files')),
3945 ('k', 'keyword', [], _('do case-insensitive search for a keyword')),
4011 ('k', 'keyword', [],
3946 ('r', 'rev', [], _('show the specified revision or range')),
4012 _('do case-insensitive search for a given text'), _('TEXT')),
4013 ('r', 'rev', [],
4014 _('show the specified revision or range'), _('REV')),
3947 ('', 'removed', None, _('include revisions where files were removed')),
4015 ('', 'removed', None, _('include revisions where files were removed')),
3948 ('m', 'only-merges', None, _('show only merges')),
4016 ('m', 'only-merges', None, _('show only merges')),
3949 ('u', 'user', [], _('revisions committed by user')),
4017 ('u', 'user', [],
4018 _('revisions committed by user'), _('USER')),
3950 ('', 'only-branch', [],
4019 ('', 'only-branch', [],
3951 _('show only changesets within the given named branch (DEPRECATED)')),
4020 _('show only changesets within the given named branch (DEPRECATED)'),
4021 _('BRANCH')),
3952 ('b', 'branch', [],
4022 ('b', 'branch', [],
3953 _('show changesets within the given named branch')),
4023 _('show changesets within the given named branch'), _('BRANCH')),
3954 ('P', 'prune', [],
4024 ('P', 'prune', [],
3955 _('do not display revision or any of its ancestors')),
4025 _('do not display revision or any of its ancestors'), _('REV')),
3956 ] + logopts + walkopts,
4026 ] + logopts + walkopts,
3957 _('[OPTION]... [FILE]')),
4027 _('[OPTION]... [FILE]')),
3958 "manifest":
4028 "manifest":
3959 (manifest,
4029 (manifest,
3960 [('r', 'rev', '', _('revision to display'))],
4030 [('r', 'rev', '',
4031 _('revision to display'), _('REV'))],
3961 _('[-r REV]')),
4032 _('[-r REV]')),
3962 "^merge":
4033 "^merge":
3963 (merge,
4034 (merge,
3964 [('f', 'force', None, _('force a merge with outstanding changes')),
4035 [('f', 'force', None, _('force a merge with outstanding changes')),
3965 ('r', 'rev', '', _('revision to merge')),
4036 ('r', 'rev', '',
4037 _('revision to merge'), _('REV')),
3966 ('P', 'preview', None,
4038 ('P', 'preview', None,
3967 _('review revisions to merge (no merge is performed)'))],
4039 _('review revisions to merge (no merge is performed)'))],
3968 _('[-P] [-f] [[-r] REV]')),
4040 _('[-P] [-f] [[-r] REV]')),
3969 "outgoing|out":
4041 "outgoing|out":
3970 (outgoing,
4042 (outgoing,
3971 [('f', 'force', None,
4043 [('f', 'force', None,
3972 _('run even when the destination is unrelated')),
4044 _('run even when the destination is unrelated')),
3973 ('r', 'rev', [],
4045 ('r', 'rev', [],
3974 _('a changeset intended to be included in the destination')),
4046 _('a changeset intended to be included in the destination'),
4047 _('REV')),
3975 ('n', 'newest-first', None, _('show newest record first')),
4048 ('n', 'newest-first', None, _('show newest record first')),
3976 ('b', 'branch', [],
4049 ('b', 'branch', [],
3977 _('a specific branch you would like to push')),
4050 _('a specific branch you would like to push'), _('BRANCH')),
3978 ] + logopts + remoteopts,
4051 ] + logopts + remoteopts,
3979 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
4052 _('[-M] [-p] [-n] [-f] [-r REV]... [DEST]')),
3980 "parents":
4053 "parents":
3981 (parents,
4054 (parents,
3982 [('r', 'rev', '', _('show parents of the specified revision')),
4055 [('r', 'rev', '',
4056 _('show parents of the specified revision'), _('REV')),
3983 ] + templateopts,
4057 ] + templateopts,
3984 _('[-r REV] [FILE]')),
4058 _('[-r REV] [FILE]')),
3985 "paths": (paths, [], _('[NAME]')),
4059 "paths": (paths, [], _('[NAME]')),
3986 "^pull":
4060 "^pull":
3987 (pull,
4061 (pull,
3988 [('u', 'update', None,
4062 [('u', 'update', None,
3989 _('update to new branch head if changesets were pulled')),
4063 _('update to new branch head if changesets were pulled')),
3990 ('f', 'force', None,
4064 ('f', 'force', None,
3991 _('run even when remote repository is unrelated')),
4065 _('run even when remote repository is unrelated')),
3992 ('r', 'rev', [],
4066 ('r', 'rev', [],
3993 _('a remote changeset intended to be added')),
4067 _('a remote changeset intended to be added'), _('REV')),
3994 ('b', 'branch', [],
4068 ('b', 'branch', [],
3995 _('a specific branch you would like to pull')),
4069 _('a specific branch you would like to pull'), _('BRANCH')),
3996 ] + remoteopts,
4070 ] + remoteopts,
3997 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
4071 _('[-u] [-f] [-r REV]... [-e CMD] [--remotecmd CMD] [SOURCE]')),
3998 "^push":
4072 "^push":
3999 (push,
4073 (push,
4000 [('f', 'force', None, _('force push')),
4074 [('f', 'force', None, _('force push')),
4001 ('r', 'rev', [],
4075 ('r', 'rev', [],
4002 _('a changeset intended to be included in the destination')),
4076 _('a changeset intended to be included in the destination'),
4077 _('REV')),
4003 ('b', 'branch', [],
4078 ('b', 'branch', [],
4004 _('a specific branch you would like to push')),
4079 _('a specific branch you would like to push'), _('BRANCH')),
4005 ('', 'new-branch', False, _('allow pushing a new branch')),
4080 ('', 'new-branch', False, _('allow pushing a new branch')),
4006 ] + remoteopts,
4081 ] + remoteopts,
4007 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
4082 _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]')),
4008 "recover": (recover, []),
4083 "recover": (recover, []),
4009 "^remove|rm":
4084 "^remove|rm":
4010 (remove,
4085 (remove,
4011 [('A', 'after', None, _('record delete for missing files')),
4086 [('A', 'after', None, _('record delete for missing files')),
4012 ('f', 'force', None,
4087 ('f', 'force', None,
4013 _('remove (and delete) file even if added or modified')),
4088 _('remove (and delete) file even if added or modified')),
4014 ] + walkopts,
4089 ] + walkopts,
4015 _('[OPTION]... FILE...')),
4090 _('[OPTION]... FILE...')),
4016 "rename|mv":
4091 "rename|mv":
4017 (rename,
4092 (rename,
4018 [('A', 'after', None, _('record a rename that has already occurred')),
4093 [('A', 'after', None, _('record a rename that has already occurred')),
4019 ('f', 'force', None,
4094 ('f', 'force', None,
4020 _('forcibly copy over an existing managed file')),
4095 _('forcibly copy over an existing managed file')),
4021 ] + walkopts + dryrunopts,
4096 ] + walkopts + dryrunopts,
4022 _('[OPTION]... SOURCE... DEST')),
4097 _('[OPTION]... SOURCE... DEST')),
4023 "resolve":
4098 "resolve":
4024 (resolve,
4099 (resolve,
4025 [('a', 'all', None, _('select all unresolved files')),
4100 [('a', 'all', None, _('select all unresolved files')),
4026 ('l', 'list', None, _('list state of files needing merge')),
4101 ('l', 'list', None, _('list state of files needing merge')),
4027 ('m', 'mark', None, _('mark files as resolved')),
4102 ('m', 'mark', None, _('mark files as resolved')),
4028 ('u', 'unmark', None, _('unmark files as resolved')),
4103 ('u', 'unmark', None, _('unmark files as resolved')),
4029 ('n', 'no-status', None, _('hide status prefix'))]
4104 ('n', 'no-status', None, _('hide status prefix'))]
4030 + walkopts,
4105 + walkopts,
4031 _('[OPTION]... [FILE]...')),
4106 _('[OPTION]... [FILE]...')),
4032 "revert":
4107 "revert":
4033 (revert,
4108 (revert,
4034 [('a', 'all', None, _('revert all changes when no arguments given')),
4109 [('a', 'all', None, _('revert all changes when no arguments given')),
4035 ('d', 'date', '', _('tipmost revision matching date')),
4110 ('d', 'date', '',
4036 ('r', 'rev', '', _('revert to the specified revision')),
4111 _('tipmost revision matching date'), _('DATE')),
4112 ('r', 'rev', '',
4113 _('revert to the specified revision'), _('REV')),
4037 ('', 'no-backup', None, _('do not save backup copies of files')),
4114 ('', 'no-backup', None, _('do not save backup copies of files')),
4038 ] + walkopts + dryrunopts,
4115 ] + walkopts + dryrunopts,
4039 _('[OPTION]... [-r REV] [NAME]...')),
4116 _('[OPTION]... [-r REV] [NAME]...')),
4040 "rollback": (rollback, dryrunopts),
4117 "rollback": (rollback, dryrunopts),
4041 "root": (root, []),
4118 "root": (root, []),
4042 "^serve":
4119 "^serve":
4043 (serve,
4120 (serve,
4044 [('A', 'accesslog', '', _('name of access log file to write to')),
4121 [('A', 'accesslog', '',
4122 _('name of access log file to write to'), _('FILE')),
4045 ('d', 'daemon', None, _('run server in background')),
4123 ('d', 'daemon', None, _('run server in background')),
4046 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
4124 ('', 'daemon-pipefds', '',
4047 ('E', 'errorlog', '', _('name of error log file to write to')),
4125 _('used internally by daemon mode'), _('NUM')),
4126 ('E', 'errorlog', '',
4127 _('name of error log file to write to'), _('FILE')),
4048 # use string type, then we can check if something was passed
4128 # use string type, then we can check if something was passed
4049 ('p', 'port', '', _('port to listen on (default: 8000)')),
4129 ('p', 'port', '',
4130 _('port to listen on (default: 8000)'), _('PORT')),
4050 ('a', 'address', '',
4131 ('a', 'address', '',
4051 _('address to listen on (default: all interfaces)')),
4132 _('address to listen on (default: all interfaces)'), _('ADDR')),
4052 ('', 'prefix', '',
4133 ('', 'prefix', '',
4053 _('prefix path to serve from (default: server root)')),
4134 _('prefix path to serve from (default: server root)'), _('PREFIX')),
4054 ('n', 'name', '',
4135 ('n', 'name', '',
4055 _('name to show in web pages (default: working directory)')),
4136 _('name to show in web pages (default: working directory)'),
4056 ('', 'web-conf', '', _('name of the hgweb config file'
4137 _('NAME')),
4057 ' (serve more than one repository)')),
4138 ('', 'web-conf', '',
4058 ('', 'webdir-conf', '', _('name of the hgweb config file'
4139 _('name of the hgweb config file (serve more than one repository)'),
4059 ' (DEPRECATED)')),
4140 _('FILE')),
4060 ('', 'pid-file', '', _('name of file to write process ID to')),
4141 ('', 'webdir-conf', '',
4142 _('name of the hgweb config file (DEPRECATED)'), _('FILE')),
4143 ('', 'pid-file', '',
4144 _('name of file to write process ID to'), _('FILE')),
4061 ('', 'stdio', None, _('for remote clients')),
4145 ('', 'stdio', None, _('for remote clients')),
4062 ('t', 'templates', '', _('web templates to use')),
4146 ('t', 'templates', '',
4063 ('', 'style', '', _('template style to use')),
4147 _('web templates to use'), _('TEMPLATE')),
4148 ('', 'style', '',
4149 _('template style to use'), _('STYLE')),
4064 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4150 ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
4065 ('', 'certificate', '', _('SSL certificate file'))],
4151 ('', 'certificate', '',
4152 _('SSL certificate file'), _('FILE'))],
4066 _('[OPTION]...')),
4153 _('[OPTION]...')),
4067 "showconfig|debugconfig":
4154 "showconfig|debugconfig":
4068 (showconfig,
4155 (showconfig,
4069 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4156 [('u', 'untrusted', None, _('show untrusted configuration options'))],
4070 _('[-u] [NAME]...')),
4157 _('[-u] [NAME]...')),
4071 "^summary|sum":
4158 "^summary|sum":
4072 (summary,
4159 (summary,
4073 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
4160 [('', 'remote', None, _('check for push and pull'))], '[--remote]'),
4074 "^status|st":
4161 "^status|st":
4075 (status,
4162 (status,
4076 [('A', 'all', None, _('show status of all files')),
4163 [('A', 'all', None, _('show status of all files')),
4077 ('m', 'modified', None, _('show only modified files')),
4164 ('m', 'modified', None, _('show only modified files')),
4078 ('a', 'added', None, _('show only added files')),
4165 ('a', 'added', None, _('show only added files')),
4079 ('r', 'removed', None, _('show only removed files')),
4166 ('r', 'removed', None, _('show only removed files')),
4080 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4167 ('d', 'deleted', None, _('show only deleted (but tracked) files')),
4081 ('c', 'clean', None, _('show only files without changes')),
4168 ('c', 'clean', None, _('show only files without changes')),
4082 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4169 ('u', 'unknown', None, _('show only unknown (not tracked) files')),
4083 ('i', 'ignored', None, _('show only ignored files')),
4170 ('i', 'ignored', None, _('show only ignored files')),
4084 ('n', 'no-status', None, _('hide status prefix')),
4171 ('n', 'no-status', None, _('hide status prefix')),
4085 ('C', 'copies', None, _('show source of copied files')),
4172 ('C', 'copies', None, _('show source of copied files')),
4086 ('0', 'print0', None,
4173 ('0', 'print0', None,
4087 _('end filenames with NUL, for use with xargs')),
4174 _('end filenames with NUL, for use with xargs')),
4088 ('', 'rev', [], _('show difference from revision')),
4175 ('', 'rev', [],
4089 ('', 'change', '', _('list the changed files of a revision')),
4176 _('show difference from revision'), _('REV')),
4177 ('', 'change', '',
4178 _('list the changed files of a revision'), _('REV')),
4090 ] + walkopts,
4179 ] + walkopts,
4091 _('[OPTION]... [FILE]...')),
4180 _('[OPTION]... [FILE]...')),
4092 "tag":
4181 "tag":
4093 (tag,
4182 (tag,
4094 [('f', 'force', None, _('replace existing tag')),
4183 [('f', 'force', None, _('replace existing tag')),
4095 ('l', 'local', None, _('make the tag local')),
4184 ('l', 'local', None, _('make the tag local')),
4096 ('r', 'rev', '', _('revision to tag')),
4185 ('r', 'rev', '',
4186 _('revision to tag'), _('REV')),
4097 ('', 'remove', None, _('remove a tag')),
4187 ('', 'remove', None, _('remove a tag')),
4098 # -l/--local is already there, commitopts cannot be used
4188 # -l/--local is already there, commitopts cannot be used
4099 ('e', 'edit', None, _('edit commit message')),
4189 ('e', 'edit', None, _('edit commit message')),
4100 ('m', 'message', '', _('use <text> as commit message')),
4190 ('m', 'message', '',
4191 _('use <text> as commit message'), _('TEXT')),
4101 ] + commitopts2,
4192 ] + commitopts2,
4102 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
4193 _('[-f] [-l] [-m TEXT] [-d DATE] [-u USER] [-r REV] NAME...')),
4103 "tags": (tags, [], ''),
4194 "tags": (tags, [], ''),
4104 "tip":
4195 "tip":
4105 (tip,
4196 (tip,
4106 [('p', 'patch', None, _('show patch')),
4197 [('p', 'patch', None, _('show patch')),
4107 ('g', 'git', None, _('use git extended diff format')),
4198 ('g', 'git', None, _('use git extended diff format')),
4108 ] + templateopts,
4199 ] + templateopts,
4109 _('[-p] [-g]')),
4200 _('[-p] [-g]')),
4110 "unbundle":
4201 "unbundle":
4111 (unbundle,
4202 (unbundle,
4112 [('u', 'update', None,
4203 [('u', 'update', None,
4113 _('update to new branch head if changesets were unbundled'))],
4204 _('update to new branch head if changesets were unbundled'))],
4114 _('[-u] FILE...')),
4205 _('[-u] FILE...')),
4115 "^update|up|checkout|co":
4206 "^update|up|checkout|co":
4116 (update,
4207 (update,
4117 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4208 [('C', 'clean', None, _('discard uncommitted changes (no backup)')),
4118 ('c', 'check', None, _('check for uncommitted changes')),
4209 ('c', 'check', None, _('check for uncommitted changes')),
4119 ('d', 'date', '', _('tipmost revision matching date')),
4210 ('d', 'date', '',
4120 ('r', 'rev', '', _('revision'))],
4211 _('tipmost revision matching date'), _('DATE')),
4212 ('r', 'rev', '',
4213 _('revision'), _('REV'))],
4121 _('[-c] [-C] [-d DATE] [[-r] REV]')),
4214 _('[-c] [-C] [-d DATE] [[-r] REV]')),
4122 "verify": (verify, []),
4215 "verify": (verify, []),
4123 "version": (version_, []),
4216 "version": (version_, []),
4124 }
4217 }
4125
4218
4126 norepo = ("clone init version help debugcommands debugcomplete debugdata"
4219 norepo = ("clone init version help debugcommands debugcomplete debugdata"
4127 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
4220 " debugindex debugindexdot debugdate debuginstall debugfsinfo")
4128 optionalrepo = ("identify paths serve showconfig debugancestor")
4221 optionalrepo = ("identify paths serve showconfig debugancestor")
@@ -1,112 +1,117 b''
1 # fancyopts.py - better command line parsing
1 # fancyopts.py - better command line parsing
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import getopt
8 import getopt
9
9
10 def gnugetopt(args, options, longoptions):
10 def gnugetopt(args, options, longoptions):
11 """Parse options mostly like getopt.gnu_getopt.
11 """Parse options mostly like getopt.gnu_getopt.
12
12
13 This is different from getopt.gnu_getopt in that an argument of - will
13 This is different from getopt.gnu_getopt in that an argument of - will
14 become an argument of - instead of vanishing completely.
14 become an argument of - instead of vanishing completely.
15 """
15 """
16 extraargs = []
16 extraargs = []
17 if '--' in args:
17 if '--' in args:
18 stopindex = args.index('--')
18 stopindex = args.index('--')
19 extraargs = args[stopindex + 1:]
19 extraargs = args[stopindex + 1:]
20 args = args[:stopindex]
20 args = args[:stopindex]
21 opts, parseargs = getopt.getopt(args, options, longoptions)
21 opts, parseargs = getopt.getopt(args, options, longoptions)
22 args = []
22 args = []
23 while parseargs:
23 while parseargs:
24 arg = parseargs.pop(0)
24 arg = parseargs.pop(0)
25 if arg and arg[0] == '-' and len(arg) > 1:
25 if arg and arg[0] == '-' and len(arg) > 1:
26 parseargs.insert(0, arg)
26 parseargs.insert(0, arg)
27 topts, newparseargs = getopt.getopt(parseargs, options, longoptions)
27 topts, newparseargs = getopt.getopt(parseargs, options, longoptions)
28 opts = opts + topts
28 opts = opts + topts
29 parseargs = newparseargs
29 parseargs = newparseargs
30 else:
30 else:
31 args.append(arg)
31 args.append(arg)
32 args.extend(extraargs)
32 args.extend(extraargs)
33 return opts, args
33 return opts, args
34
34
35
35
36 def fancyopts(args, options, state, gnu=False):
36 def fancyopts(args, options, state, gnu=False):
37 """
37 """
38 read args, parse options, and store options in state
38 read args, parse options, and store options in state
39
39
40 each option is a tuple of:
40 each option is a tuple of:
41
41
42 short option or ''
42 short option or ''
43 long option
43 long option
44 default value
44 default value
45 description
45 description
46 option value label(optional)
46
47
47 option types include:
48 option types include:
48
49
49 boolean or none - option sets variable in state to true
50 boolean or none - option sets variable in state to true
50 string - parameter string is stored in state
51 string - parameter string is stored in state
51 list - parameter string is added to a list
52 list - parameter string is added to a list
52 integer - parameter strings is stored as int
53 integer - parameter strings is stored as int
53 function - call function with parameter
54 function - call function with parameter
54
55
55 non-option args are returned
56 non-option args are returned
56 """
57 """
57 namelist = []
58 namelist = []
58 shortlist = ''
59 shortlist = ''
59 argmap = {}
60 argmap = {}
60 defmap = {}
61 defmap = {}
61
62
62 for short, name, default, comment in options:
63 for option in options:
64 if len(option) == 5:
65 short, name, default, comment, dummy = option
66 else:
67 short, name, default, comment = option
63 # convert opts to getopt format
68 # convert opts to getopt format
64 oname = name
69 oname = name
65 name = name.replace('-', '_')
70 name = name.replace('-', '_')
66
71
67 argmap['-' + short] = argmap['--' + oname] = name
72 argmap['-' + short] = argmap['--' + oname] = name
68 defmap[name] = default
73 defmap[name] = default
69
74
70 # copy defaults to state
75 # copy defaults to state
71 if isinstance(default, list):
76 if isinstance(default, list):
72 state[name] = default[:]
77 state[name] = default[:]
73 elif hasattr(default, '__call__'):
78 elif hasattr(default, '__call__'):
74 state[name] = None
79 state[name] = None
75 else:
80 else:
76 state[name] = default
81 state[name] = default
77
82
78 # does it take a parameter?
83 # does it take a parameter?
79 if not (default is None or default is True or default is False):
84 if not (default is None or default is True or default is False):
80 if short:
85 if short:
81 short += ':'
86 short += ':'
82 if oname:
87 if oname:
83 oname += '='
88 oname += '='
84 if short:
89 if short:
85 shortlist += short
90 shortlist += short
86 if name:
91 if name:
87 namelist.append(oname)
92 namelist.append(oname)
88
93
89 # parse arguments
94 # parse arguments
90 if gnu:
95 if gnu:
91 parse = gnugetopt
96 parse = gnugetopt
92 else:
97 else:
93 parse = getopt.getopt
98 parse = getopt.getopt
94 opts, args = parse(args, shortlist, namelist)
99 opts, args = parse(args, shortlist, namelist)
95
100
96 # transfer result to state
101 # transfer result to state
97 for opt, val in opts:
102 for opt, val in opts:
98 name = argmap[opt]
103 name = argmap[opt]
99 t = type(defmap[name])
104 t = type(defmap[name])
100 if t is type(fancyopts):
105 if t is type(fancyopts):
101 state[name] = defmap[name](val)
106 state[name] = defmap[name](val)
102 elif t is type(1):
107 elif t is type(1):
103 state[name] = int(val)
108 state[name] = int(val)
104 elif t is type(''):
109 elif t is type(''):
105 state[name] = val
110 state[name] = val
106 elif t is type([]):
111 elif t is type([]):
107 state[name].append(val)
112 state[name].append(val)
108 elif t is type(None) or t is type(False):
113 elif t is type(None) or t is type(False):
109 state[name] = True
114 state[name] = True
110
115
111 # return unparsed args
116 # return unparsed args
112 return args
117 return args
@@ -1,302 +1,302 b''
1 hg convert [OPTION]... SOURCE [DEST [REVMAP]]
1 hg convert [OPTION]... SOURCE [DEST [REVMAP]]
2
2
3 convert a foreign SCM repository to a Mercurial one.
3 convert a foreign SCM repository to a Mercurial one.
4
4
5 Accepted source formats [identifiers]:
5 Accepted source formats [identifiers]:
6
6
7 - Mercurial [hg]
7 - Mercurial [hg]
8 - CVS [cvs]
8 - CVS [cvs]
9 - Darcs [darcs]
9 - Darcs [darcs]
10 - git [git]
10 - git [git]
11 - Subversion [svn]
11 - Subversion [svn]
12 - Monotone [mtn]
12 - Monotone [mtn]
13 - GNU Arch [gnuarch]
13 - GNU Arch [gnuarch]
14 - Bazaar [bzr]
14 - Bazaar [bzr]
15 - Perforce [p4]
15 - Perforce [p4]
16
16
17 Accepted destination formats [identifiers]:
17 Accepted destination formats [identifiers]:
18
18
19 - Mercurial [hg]
19 - Mercurial [hg]
20 - Subversion [svn] (history on branches is not preserved)
20 - Subversion [svn] (history on branches is not preserved)
21
21
22 If no revision is given, all revisions will be converted. Otherwise,
22 If no revision is given, all revisions will be converted. Otherwise,
23 convert will only import up to the named revision (given in a format
23 convert will only import up to the named revision (given in a format
24 understood by the source).
24 understood by the source).
25
25
26 If no destination directory name is specified, it defaults to the basename
26 If no destination directory name is specified, it defaults to the basename
27 of the source with '-hg' appended. If the destination repository doesn't
27 of the source with '-hg' appended. If the destination repository doesn't
28 exist, it will be created.
28 exist, it will be created.
29
29
30 By default, all sources except Mercurial will use --branchsort. Mercurial
30 By default, all sources except Mercurial will use --branchsort. Mercurial
31 uses --sourcesort to preserve original revision numbers order. Sort modes
31 uses --sourcesort to preserve original revision numbers order. Sort modes
32 have the following effects:
32 have the following effects:
33
33
34 --branchsort convert from parent to child revision when possible, which
34 --branchsort convert from parent to child revision when possible, which
35 means branches are usually converted one after the other. It
35 means branches are usually converted one after the other. It
36 generates more compact repositories.
36 generates more compact repositories.
37 --datesort sort revisions by date. Converted repositories have good-
37 --datesort sort revisions by date. Converted repositories have good-
38 looking changelogs but are often an order of magnitude
38 looking changelogs but are often an order of magnitude
39 larger than the same ones generated by --branchsort.
39 larger than the same ones generated by --branchsort.
40 --sourcesort try to preserve source revisions order, only supported by
40 --sourcesort try to preserve source revisions order, only supported by
41 Mercurial sources.
41 Mercurial sources.
42
42
43 If <REVMAP> isn't given, it will be put in a default location
43 If <REVMAP> isn't given, it will be put in a default location
44 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file that
44 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file that
45 maps each source commit ID to the destination ID for that revision, like
45 maps each source commit ID to the destination ID for that revision, like
46 so:
46 so:
47
47
48 <source ID> <destination ID>
48 <source ID> <destination ID>
49
49
50 If the file doesn't exist, it's automatically created. It's updated on
50 If the file doesn't exist, it's automatically created. It's updated on
51 each commit copied, so convert-repo can be interrupted and can be run
51 each commit copied, so convert-repo can be interrupted and can be run
52 repeatedly to copy new commits.
52 repeatedly to copy new commits.
53
53
54 The [username mapping] file is a simple text file that maps each source
54 The [username mapping] file is a simple text file that maps each source
55 commit author to a destination commit author. It is handy for source SCMs
55 commit author to a destination commit author. It is handy for source SCMs
56 that use unix logins to identify authors (eg: CVS). One line per author
56 that use unix logins to identify authors (eg: CVS). One line per author
57 mapping and the line format is: srcauthor=whatever string you want
57 mapping and the line format is: srcauthor=whatever string you want
58
58
59 The filemap is a file that allows filtering and remapping of files and
59 The filemap is a file that allows filtering and remapping of files and
60 directories. Comment lines start with '#'. Each line can contain one of
60 directories. Comment lines start with '#'. Each line can contain one of
61 the following directives:
61 the following directives:
62
62
63 include path/to/file
63 include path/to/file
64
64
65 exclude path/to/file
65 exclude path/to/file
66
66
67 rename from/file to/file
67 rename from/file to/file
68
68
69 The 'include' directive causes a file, or all files under a directory, to
69 The 'include' directive causes a file, or all files under a directory, to
70 be included in the destination repository, and the exclusion of all other
70 be included in the destination repository, and the exclusion of all other
71 files and directories not explicitly included. The 'exclude' directive
71 files and directories not explicitly included. The 'exclude' directive
72 causes files or directories to be omitted. The 'rename' directive renames
72 causes files or directories to be omitted. The 'rename' directive renames
73 a file or directory. To rename from a subdirectory into the root of the
73 a file or directory. To rename from a subdirectory into the root of the
74 repository, use '.' as the path to rename to.
74 repository, use '.' as the path to rename to.
75
75
76 The splicemap is a file that allows insertion of synthetic history,
76 The splicemap is a file that allows insertion of synthetic history,
77 letting you specify the parents of a revision. This is useful if you want
77 letting you specify the parents of a revision. This is useful if you want
78 to e.g. give a Subversion merge two parents, or graft two disconnected
78 to e.g. give a Subversion merge two parents, or graft two disconnected
79 series of history together. Each entry contains a key, followed by a
79 series of history together. Each entry contains a key, followed by a
80 space, followed by one or two comma-separated values. The key is the
80 space, followed by one or two comma-separated values. The key is the
81 revision ID in the source revision control system whose parents should be
81 revision ID in the source revision control system whose parents should be
82 modified (same format as a key in .hg/shamap). The values are the revision
82 modified (same format as a key in .hg/shamap). The values are the revision
83 IDs (in either the source or destination revision control system) that
83 IDs (in either the source or destination revision control system) that
84 should be used as the new parents for that node. For example, if you have
84 should be used as the new parents for that node. For example, if you have
85 merged "release-1.0" into "trunk", then you should specify the revision on
85 merged "release-1.0" into "trunk", then you should specify the revision on
86 "trunk" as the first parent and the one on the "release-1.0" branch as the
86 "trunk" as the first parent and the one on the "release-1.0" branch as the
87 second.
87 second.
88
88
89 The branchmap is a file that allows you to rename a branch when it is
89 The branchmap is a file that allows you to rename a branch when it is
90 being brought in from whatever external repository. When used in
90 being brought in from whatever external repository. When used in
91 conjunction with a splicemap, it allows for a powerful combination to help
91 conjunction with a splicemap, it allows for a powerful combination to help
92 fix even the most badly mismanaged repositories and turn them into nicely
92 fix even the most badly mismanaged repositories and turn them into nicely
93 structured Mercurial repositories. The branchmap contains lines of the
93 structured Mercurial repositories. The branchmap contains lines of the
94 form "original_branch_name new_branch_name". "original_branch_name" is the
94 form "original_branch_name new_branch_name". "original_branch_name" is the
95 name of the branch in the source repository, and "new_branch_name" is the
95 name of the branch in the source repository, and "new_branch_name" is the
96 name of the branch is the destination repository. This can be used to (for
96 name of the branch is the destination repository. This can be used to (for
97 instance) move code in one repository from "default" to a named branch.
97 instance) move code in one repository from "default" to a named branch.
98
98
99 Mercurial Source
99 Mercurial Source
100 ----------------
100 ----------------
101
101
102 --config convert.hg.ignoreerrors=False (boolean)
102 --config convert.hg.ignoreerrors=False (boolean)
103 ignore integrity errors when reading. Use it to fix Mercurial
103 ignore integrity errors when reading. Use it to fix Mercurial
104 repositories with missing revlogs, by converting from and to
104 repositories with missing revlogs, by converting from and to
105 Mercurial.
105 Mercurial.
106
106
107 --config convert.hg.saverev=False (boolean)
107 --config convert.hg.saverev=False (boolean)
108 store original revision ID in changeset (forces target IDs to change)
108 store original revision ID in changeset (forces target IDs to change)
109
109
110 --config convert.hg.startrev=0 (hg revision identifier)
110 --config convert.hg.startrev=0 (hg revision identifier)
111 convert start revision and its descendants
111 convert start revision and its descendants
112
112
113 CVS Source
113 CVS Source
114 ----------
114 ----------
115
115
116 CVS source will use a sandbox (i.e. a checked-out copy) from CVS to
116 CVS source will use a sandbox (i.e. a checked-out copy) from CVS to
117 indicate the starting point of what will be converted. Direct access to
117 indicate the starting point of what will be converted. Direct access to
118 the repository files is not needed, unless of course the repository is
118 the repository files is not needed, unless of course the repository is
119 :local:. The conversion uses the top level directory in the sandbox to
119 :local:. The conversion uses the top level directory in the sandbox to
120 find the CVS repository, and then uses CVS rlog commands to find files to
120 find the CVS repository, and then uses CVS rlog commands to find files to
121 convert. This means that unless a filemap is given, all files under the
121 convert. This means that unless a filemap is given, all files under the
122 starting directory will be converted, and that any directory
122 starting directory will be converted, and that any directory
123 reorganization in the CVS sandbox is ignored.
123 reorganization in the CVS sandbox is ignored.
124
124
125 The options shown are the defaults.
125 The options shown are the defaults.
126
126
127 --config convert.cvsps.cache=True (boolean)
127 --config convert.cvsps.cache=True (boolean)
128 Set to False to disable remote log caching, for testing and debugging
128 Set to False to disable remote log caching, for testing and debugging
129 purposes.
129 purposes.
130
130
131 --config convert.cvsps.fuzz=60 (integer)
131 --config convert.cvsps.fuzz=60 (integer)
132 Specify the maximum time (in seconds) that is allowed between commits
132 Specify the maximum time (in seconds) that is allowed between commits
133 with identical user and log message in a single changeset. When very
133 with identical user and log message in a single changeset. When very
134 large files were checked in as part of a changeset then the default
134 large files were checked in as part of a changeset then the default
135 may not be long enough.
135 may not be long enough.
136
136
137 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
137 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
138 Specify a regular expression to which commit log messages are matched.
138 Specify a regular expression to which commit log messages are matched.
139 If a match occurs, then the conversion process will insert a dummy
139 If a match occurs, then the conversion process will insert a dummy
140 revision merging the branch on which this log message occurs to the
140 revision merging the branch on which this log message occurs to the
141 branch indicated in the regex.
141 branch indicated in the regex.
142
142
143 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
143 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
144 Specify a regular expression to which commit log messages are matched.
144 Specify a regular expression to which commit log messages are matched.
145 If a match occurs, then the conversion process will add the most
145 If a match occurs, then the conversion process will add the most
146 recent revision on the branch indicated in the regex as the second
146 recent revision on the branch indicated in the regex as the second
147 parent of the changeset.
147 parent of the changeset.
148
148
149 --config hook.cvslog
149 --config hook.cvslog
150 Specify a Python function to be called at the end of gathering the CVS
150 Specify a Python function to be called at the end of gathering the CVS
151 log. The function is passed a list with the log entries, and can
151 log. The function is passed a list with the log entries, and can
152 modify the entries in-place, or add or delete them.
152 modify the entries in-place, or add or delete them.
153
153
154 --config hook.cvschangesets
154 --config hook.cvschangesets
155 Specify a Python function to be called after the changesets are
155 Specify a Python function to be called after the changesets are
156 calculated from the the CVS log. The function is passed a list with
156 calculated from the the CVS log. The function is passed a list with
157 the changeset entries, and can modify the changesets in-place, or add
157 the changeset entries, and can modify the changesets in-place, or add
158 or delete them.
158 or delete them.
159
159
160 An additional "debugcvsps" Mercurial command allows the builtin changeset
160 An additional "debugcvsps" Mercurial command allows the builtin changeset
161 merging code to be run without doing a conversion. Its parameters and
161 merging code to be run without doing a conversion. Its parameters and
162 output are similar to that of cvsps 2.1. Please see the command help for
162 output are similar to that of cvsps 2.1. Please see the command help for
163 more details.
163 more details.
164
164
165 Subversion Source
165 Subversion Source
166 -----------------
166 -----------------
167
167
168 Subversion source detects classical trunk/branches/tags layouts. By
168 Subversion source detects classical trunk/branches/tags layouts. By
169 default, the supplied "svn://repo/path/" source URL is converted as a
169 default, the supplied "svn://repo/path/" source URL is converted as a
170 single branch. If "svn://repo/path/trunk" exists it replaces the default
170 single branch. If "svn://repo/path/trunk" exists it replaces the default
171 branch. If "svn://repo/path/branches" exists, its subdirectories are
171 branch. If "svn://repo/path/branches" exists, its subdirectories are
172 listed as possible branches. If "svn://repo/path/tags" exists, it is
172 listed as possible branches. If "svn://repo/path/tags" exists, it is
173 looked for tags referencing converted branches. Default "trunk",
173 looked for tags referencing converted branches. Default "trunk",
174 "branches" and "tags" values can be overridden with following options. Set
174 "branches" and "tags" values can be overridden with following options. Set
175 them to paths relative to the source URL, or leave them blank to disable
175 them to paths relative to the source URL, or leave them blank to disable
176 auto detection.
176 auto detection.
177
177
178 --config convert.svn.branches=branches (directory name)
178 --config convert.svn.branches=branches (directory name)
179 specify the directory containing branches
179 specify the directory containing branches
180
180
181 --config convert.svn.tags=tags (directory name)
181 --config convert.svn.tags=tags (directory name)
182 specify the directory containing tags
182 specify the directory containing tags
183
183
184 --config convert.svn.trunk=trunk (directory name)
184 --config convert.svn.trunk=trunk (directory name)
185 specify the name of the trunk branch
185 specify the name of the trunk branch
186
186
187 Source history can be retrieved starting at a specific revision, instead
187 Source history can be retrieved starting at a specific revision, instead
188 of being integrally converted. Only single branch conversions are
188 of being integrally converted. Only single branch conversions are
189 supported.
189 supported.
190
190
191 --config convert.svn.startrev=0 (svn revision number)
191 --config convert.svn.startrev=0 (svn revision number)
192 specify start Subversion revision.
192 specify start Subversion revision.
193
193
194 Perforce Source
194 Perforce Source
195 ---------------
195 ---------------
196
196
197 The Perforce (P4) importer can be given a p4 depot path or a client
197 The Perforce (P4) importer can be given a p4 depot path or a client
198 specification as source. It will convert all files in the source to a flat
198 specification as source. It will convert all files in the source to a flat
199 Mercurial repository, ignoring labels, branches and integrations. Note
199 Mercurial repository, ignoring labels, branches and integrations. Note
200 that when a depot path is given you then usually should specify a target
200 that when a depot path is given you then usually should specify a target
201 directory, because otherwise the target may be named ...-hg.
201 directory, because otherwise the target may be named ...-hg.
202
202
203 It is possible to limit the amount of source history to be converted by
203 It is possible to limit the amount of source history to be converted by
204 specifying an initial Perforce revision.
204 specifying an initial Perforce revision.
205
205
206 --config convert.p4.startrev=0 (perforce changelist number)
206 --config convert.p4.startrev=0 (perforce changelist number)
207 specify initial Perforce revision.
207 specify initial Perforce revision.
208
208
209 Mercurial Destination
209 Mercurial Destination
210 ---------------------
210 ---------------------
211
211
212 --config convert.hg.clonebranches=False (boolean)
212 --config convert.hg.clonebranches=False (boolean)
213 dispatch source branches in separate clones.
213 dispatch source branches in separate clones.
214
214
215 --config convert.hg.tagsbranch=default (branch name)
215 --config convert.hg.tagsbranch=default (branch name)
216 tag revisions branch name
216 tag revisions branch name
217
217
218 --config convert.hg.usebranchnames=True (boolean)
218 --config convert.hg.usebranchnames=True (boolean)
219 preserve branch names
219 preserve branch names
220
220
221 options:
221 options:
222
222
223 -A --authors username mapping filename
223 -A --authors FILE username mapping filename
224 -d --dest-type destination repository type
224 -d --dest-type TYPE destination repository type
225 --filemap remap file names using contents of file
225 --filemap FILE remap file names using contents of file
226 -r --rev import up to target revision REV
226 -r --rev REV import up to target revision REV
227 -s --source-type source repository type
227 -s --source-type TYPE source repository type
228 --splicemap splice synthesized history into place
228 --splicemap FILE splice synthesized history into place
229 --branchmap change branch names while converting
229 --branchmap FILE change branch names while converting
230 --branchsort try to sort changesets by branches
230 --branchsort try to sort changesets by branches
231 --datesort try to sort changesets by date
231 --datesort try to sort changesets by date
232 --sourcesort preserve source changesets order
232 --sourcesort preserve source changesets order
233
233
234 use "hg -v help convert" to show global options
234 use "hg -v help convert" to show global options
235 adding a
235 adding a
236 assuming destination a-hg
236 assuming destination a-hg
237 initializing destination a-hg repository
237 initializing destination a-hg repository
238 scanning source...
238 scanning source...
239 sorting...
239 sorting...
240 converting...
240 converting...
241 4 a
241 4 a
242 3 b
242 3 b
243 2 c
243 2 c
244 1 d
244 1 d
245 0 e
245 0 e
246 pulling from ../a
246 pulling from ../a
247 searching for changes
247 searching for changes
248 no changes found
248 no changes found
249 % should fail
249 % should fail
250 initializing destination bogusfile repository
250 initializing destination bogusfile repository
251 abort: cannot create new bundle repository
251 abort: cannot create new bundle repository
252 % should fail
252 % should fail
253 abort: Permission denied: bogusdir
253 abort: Permission denied: bogusdir
254 % should succeed
254 % should succeed
255 initializing destination bogusdir repository
255 initializing destination bogusdir repository
256 scanning source...
256 scanning source...
257 sorting...
257 sorting...
258 converting...
258 converting...
259 4 a
259 4 a
260 3 b
260 3 b
261 2 c
261 2 c
262 1 d
262 1 d
263 0 e
263 0 e
264 % test pre and post conversion actions
264 % test pre and post conversion actions
265 run hg source pre-conversion action
265 run hg source pre-conversion action
266 run hg sink pre-conversion action
266 run hg sink pre-conversion action
267 run hg sink post-conversion action
267 run hg sink post-conversion action
268 run hg source post-conversion action
268 run hg source post-conversion action
269 % converting empty dir should fail nicely
269 % converting empty dir should fail nicely
270 assuming destination emptydir-hg
270 assuming destination emptydir-hg
271 initializing destination emptydir-hg repository
271 initializing destination emptydir-hg repository
272 emptydir does not look like a CVS checkout
272 emptydir does not look like a CVS checkout
273 emptydir does not look like a Git repository
273 emptydir does not look like a Git repository
274 emptydir does not look like a Subversion repository
274 emptydir does not look like a Subversion repository
275 emptydir is not a local Mercurial repository
275 emptydir is not a local Mercurial repository
276 emptydir does not look like a darcs repository
276 emptydir does not look like a darcs repository
277 emptydir does not look like a monotone repository
277 emptydir does not look like a monotone repository
278 emptydir does not look like a GNU Arch repository
278 emptydir does not look like a GNU Arch repository
279 emptydir does not look like a Bazaar repository
279 emptydir does not look like a Bazaar repository
280 cannot find required "p4" tool
280 cannot find required "p4" tool
281 abort: emptydir: missing or unsupported repository
281 abort: emptydir: missing or unsupported repository
282 % convert with imaginary source type
282 % convert with imaginary source type
283 initializing destination a-foo repository
283 initializing destination a-foo repository
284 abort: foo: invalid source repository type
284 abort: foo: invalid source repository type
285 % convert with imaginary sink type
285 % convert with imaginary sink type
286 abort: foo: invalid destination repository type
286 abort: foo: invalid destination repository type
287
287
288 % testing: convert must not produce duplicate entries in fncache
288 % testing: convert must not produce duplicate entries in fncache
289 initializing destination b repository
289 initializing destination b repository
290 scanning source...
290 scanning source...
291 sorting...
291 sorting...
292 converting...
292 converting...
293 4 a
293 4 a
294 3 b
294 3 b
295 2 c
295 2 c
296 1 d
296 1 d
297 0 e
297 0 e
298 % contents of fncache file:
298 % contents of fncache file:
299 data/a.i
299 data/a.i
300 data/b.i
300 data/b.i
301 % test bogus URL
301 % test bogus URL
302 abort: bzr+ssh://foobar@selenic.com/baz: missing or unsupported repository
302 abort: bzr+ssh://foobar@selenic.com/baz: missing or unsupported repository
@@ -1,35 +1,37 b''
1 adding a
1 adding a
2 # missing arg
2 # missing arg
3 hg cat: invalid arguments
3 hg cat: invalid arguments
4 hg cat [OPTION]... FILE...
4 hg cat [OPTION]... FILE...
5
5
6 output the current or given revision of files
6 output the current or given revision of files
7
7
8 Print the specified files as they were at the given revision. If no
8 Print the specified files as they were at the given revision. If no
9 revision is given, the parent of the working directory is used, or tip if
9 revision is given, the parent of the working directory is used, or tip if
10 no revision is checked out.
10 no revision is checked out.
11
11
12 Output may be to a file, in which case the name of the file is given using
12 Output may be to a file, in which case the name of the file is given using
13 a format string. The formatting rules are the same as for the export
13 a format string. The formatting rules are the same as for the export
14 command, with the following additions:
14 command, with the following additions:
15
15
16 "%s" basename of file being printed
16 "%s" basename of file being printed
17 "%d" dirname of file being printed, or '.' if in repository root
17 "%d" dirname of file being printed, or '.' if in repository root
18 "%p" root-relative path name of file being printed
18 "%p" root-relative path name of file being printed
19
19
20 Returns 0 on success.
20 Returns 0 on success.
21
21
22 options:
22 options:
23
23
24 -o --output print output to file with formatted name
24 -o --output FORMAT print output to file with formatted name
25 -r --rev print the given revision
25 -r --rev REV print the given revision
26 --decode apply any matching decode filter
26 --decode apply any matching decode filter
27 -I --include include names matching the given patterns
27 -I --include PATTERN [+] include names matching the given patterns
28 -X --exclude exclude names matching the given patterns
28 -X --exclude PATTERN [+] exclude names matching the given patterns
29
30 [+] marked option can be specified multiple times
29
31
30 use "hg -v help cat" to show global options
32 use "hg -v help cat" to show global options
31 % [defaults]
33 % [defaults]
32 a
34 a
33 a: No such file in rev 000000000000
35 a: No such file in rev 000000000000
34 % no repo
36 % no repo
35 abort: There is no Mercurial repository here (.hg not found)!
37 abort: There is no Mercurial repository here (.hg not found)!
@@ -1,75 +1,77 b''
1 adding a
1 adding a
2 adding b
2 adding b
3 Only in a: a
3 Only in a: a
4 Only in a: b
4 Only in a: b
5 diffing a.000000000000 a
5 diffing a.000000000000 a
6 hg falabala [OPTION]... [FILE]...
6 hg falabala [OPTION]... [FILE]...
7
7
8 use 'echo' to diff repository (or selected files)
8 use 'echo' to diff repository (or selected files)
9
9
10 Show differences between revisions for the specified files, using the
10 Show differences between revisions for the specified files, using the
11 'echo' program.
11 'echo' program.
12
12
13 When two revision arguments are given, then changes are shown between
13 When two revision arguments are given, then changes are shown between
14 those revisions. If only one revision is specified then that revision is
14 those revisions. If only one revision is specified then that revision is
15 compared to the working directory, and, when no revisions are specified,
15 compared to the working directory, and, when no revisions are specified,
16 the working directory files are compared to its parent.
16 the working directory files are compared to its parent.
17
17
18 options:
18 options:
19
19
20 -o --option pass option to comparison program
20 -o --option OPT [+] pass option to comparison program
21 -r --rev revision
21 -r --rev REV [+] revision
22 -c --change change made by revision
22 -c --change REV change made by revision
23 -I --include include names matching the given patterns
23 -I --include PATTERN [+] include names matching the given patterns
24 -X --exclude exclude names matching the given patterns
24 -X --exclude PATTERN [+] exclude names matching the given patterns
25
26 [+] marked option can be specified multiple times
25
27
26 use "hg -v help falabala" to show global options
28 use "hg -v help falabala" to show global options
27 diffing a.8a5febb7f867/a a.34eed99112ab/a
29 diffing a.8a5febb7f867/a a.34eed99112ab/a
28 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
30 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 created new head
31 created new head
30 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
32 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
31 (branch merge, don't forget to commit)
33 (branch merge, don't forget to commit)
32 diffing a.2a13a4d2da36/a [tmp]/test-extdiff/a/a
34 diffing a.2a13a4d2da36/a [tmp]/test-extdiff/a/a
33 diffing a.8a5febb7f867/a a.34eed99112ab/a
35 diffing a.8a5febb7f867/a a.34eed99112ab/a
34 diffing a.2a13a4d2da36/a a.46c0e4daeb72/a
36 diffing a.2a13a4d2da36/a a.46c0e4daeb72/a
35 diff-like tools yield a non-zero exit code
37 diff-like tools yield a non-zero exit code
36
38
37 % test extdiff of multiple files in tmp dir:
39 % test extdiff of multiple files in tmp dir:
38 % diff in working directory, before
40 % diff in working directory, before
39 diff --git a/a b/a
41 diff --git a/a b/a
40 --- a/a
42 --- a/a
41 +++ b/a
43 +++ b/a
42 @@ -1,1 +1,1 @@
44 @@ -1,1 +1,1 @@
43 -a
45 -a
44 +changed
46 +changed
45 diff --git a/b b/b
47 diff --git a/b b/b
46 old mode 100644
48 old mode 100644
47 new mode 100755
49 new mode 100755
48 --- a/b
50 --- a/b
49 +++ b/b
51 +++ b/b
50 @@ -1,1 +1,1 @@
52 @@ -1,1 +1,1 @@
51 -b
53 -b
52 +changed
54 +changed
53 % edit with extdiff -p
55 % edit with extdiff -p
54 % diff in working directory, after
56 % diff in working directory, after
55 diff --git a/a b/a
57 diff --git a/a b/a
56 --- a/a
58 --- a/a
57 +++ b/a
59 +++ b/a
58 @@ -1,1 +1,2 @@
60 @@ -1,1 +1,2 @@
59 -a
61 -a
60 +changed
62 +changed
61 +edited
63 +edited
62 diff --git a/b b/b
64 diff --git a/b b/b
63 old mode 100644
65 old mode 100644
64 new mode 100755
66 new mode 100755
65 --- a/b
67 --- a/b
66 +++ b/b
68 +++ b/b
67 @@ -1,1 +1,2 @@
69 @@ -1,1 +1,2 @@
68 -b
70 -b
69 +changed
71 +changed
70 +edited
72 +edited
71
73
72 % test extdiff with --option
74 % test extdiff with --option
73 this a.8a5febb7f867/a a.34eed99112ab/a
75 this a.8a5febb7f867/a a.34eed99112ab/a
74 diffing this a.8a5febb7f867/a a.34eed99112ab/a
76 diffing this a.8a5febb7f867/a a.34eed99112ab/a
75
77
@@ -1,128 +1,134 b''
1 uisetup called
1 uisetup called
2 reposetup called for a
2 reposetup called for a
3 ui == repo.ui
3 ui == repo.ui
4 Foo
4 Foo
5 uisetup called
5 uisetup called
6 reposetup called for a
6 reposetup called for a
7 ui == repo.ui
7 ui == repo.ui
8 reposetup called for b
8 reposetup called for b
9 ui == repo.ui
9 ui == repo.ui
10 updating to branch default
10 updating to branch default
11 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
11 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
12 uisetup called
12 uisetup called
13 Bar
13 Bar
14 % module/__init__.py-style
14 % module/__init__.py-style
15 uisetup called
15 uisetup called
16 reposetup called for a
16 reposetup called for a
17 ui == repo.ui
17 ui == repo.ui
18 Foo
18 Foo
19 1) foo imported
19 1) foo imported
20 1) bar imported
20 1) bar imported
21 2) foo uisetup
21 2) foo uisetup
22 2) bar uisetup
22 2) bar uisetup
23 3) foo extsetup
23 3) foo extsetup
24 3) bar extsetup
24 3) bar extsetup
25 4) foo reposetup
25 4) foo reposetup
26 4) bar reposetup
26 4) bar reposetup
27 % hgweb.cgi
27 % hgweb.cgi
28 1) foo imported
28 1) foo imported
29 1) bar imported
29 1) bar imported
30 2) foo uisetup
30 2) foo uisetup
31 2) bar uisetup
31 2) bar uisetup
32 3) foo extsetup
32 3) foo extsetup
33 3) bar extsetup
33 3) bar extsetup
34 4) foo reposetup
34 4) foo reposetup
35 4) bar reposetup
35 4) bar reposetup
36 4) foo reposetup
36 4) foo reposetup
37 4) bar reposetup
37 4) bar reposetup
38 empty extension - empty cmdtable
38 empty extension - empty cmdtable
39
39
40 no commands defined
40 no commands defined
41 % hg help
41 % hg help
42 debugextension extension - only debugcommands
42 debugextension extension - only debugcommands
43
43
44 no commands defined
44 no commands defined
45 % hg help --verbose
45 % hg help --verbose
46 debugextension extension - only debugcommands
46 debugextension extension - only debugcommands
47
47
48 list of commands:
48 list of commands:
49
49
50 foo:
50 foo:
51 yet another foo command
51 yet another foo command
52
52
53 global options:
53 global options:
54 -R --repository repository root directory or name of overlay bundle file
54 -R --repository REPO repository root directory or name of overlay bundle
55 --cwd change working directory
55 file
56 -y --noninteractive do not prompt, assume 'yes' for any required answers
56 --cwd DIR change working directory
57 -q --quiet suppress output
57 -y --noninteractive do not prompt, assume 'yes' for any required answers
58 -v --verbose enable additional output
58 -q --quiet suppress output
59 --config set/override config option (use 'section.name=value')
59 -v --verbose enable additional output
60 --debug enable debugging output
60 --config CONFIG [+] set/override config option (use 'section.name=value')
61 --debugger start debugger
61 --debug enable debugging output
62 --encoding set the charset encoding (default: ascii)
62 --debugger start debugger
63 --encodingmode set the charset encoding mode (default: strict)
63 --encoding ENCODE set the charset encoding (default: ascii)
64 --traceback always print a traceback on exception
64 --encodingmode MODE set the charset encoding mode (default: strict)
65 --time time how long the command takes
65 --traceback always print a traceback on exception
66 --profile print command execution profile
66 --time time how long the command takes
67 --version output version information and exit
67 --profile print command execution profile
68 -h --help display help and exit
68 --version output version information and exit
69 -h --help display help and exit
70
71 [+] marked option can be specified multiple times
69 % hg help --debug
72 % hg help --debug
70 debugextension extension - only debugcommands
73 debugextension extension - only debugcommands
71
74
72 list of commands:
75 list of commands:
73
76
74 debugfoobar:
77 debugfoobar:
75 yet another debug command
78 yet another debug command
76 foo:
79 foo:
77 yet another foo command
80 yet another foo command
78
81
79 global options:
82 global options:
80 -R --repository repository root directory or name of overlay bundle file
83 -R --repository REPO repository root directory or name of overlay bundle
81 --cwd change working directory
84 file
82 -y --noninteractive do not prompt, assume 'yes' for any required answers
85 --cwd DIR change working directory
83 -q --quiet suppress output
86 -y --noninteractive do not prompt, assume 'yes' for any required answers
84 -v --verbose enable additional output
87 -q --quiet suppress output
85 --config set/override config option (use 'section.name=value')
88 -v --verbose enable additional output
86 --debug enable debugging output
89 --config CONFIG [+] set/override config option (use 'section.name=value')
87 --debugger start debugger
90 --debug enable debugging output
88 --encoding set the charset encoding (default: ascii)
91 --debugger start debugger
89 --encodingmode set the charset encoding mode (default: strict)
92 --encoding ENCODE set the charset encoding (default: ascii)
90 --traceback always print a traceback on exception
93 --encodingmode MODE set the charset encoding mode (default: strict)
91 --time time how long the command takes
94 --traceback always print a traceback on exception
92 --profile print command execution profile
95 --time time how long the command takes
93 --version output version information and exit
96 --profile print command execution profile
94 -h --help display help and exit
97 --version output version information and exit
98 -h --help display help and exit
99
100 [+] marked option can be specified multiple times
95 % issue811
101 % issue811
96 % show extensions
102 % show extensions
97 debugissue811
103 debugissue811
98 mq
104 mq
99 % disabled extension commands
105 % disabled extension commands
100 'email' is provided by the following extension:
106 'email' is provided by the following extension:
101
107
102 patchbomb command to send changesets as (a series of) patch emails
108 patchbomb command to send changesets as (a series of) patch emails
103
109
104 use "hg help extensions" for information on enabling extensions
110 use "hg help extensions" for information on enabling extensions
105 hg: unknown command 'qdel'
111 hg: unknown command 'qdel'
106 'qdelete' is provided by the following extension:
112 'qdelete' is provided by the following extension:
107
113
108 mq manage a stack of patches
114 mq manage a stack of patches
109
115
110 use "hg help extensions" for information on enabling extensions
116 use "hg help extensions" for information on enabling extensions
111 hg: unknown command 'churn'
117 hg: unknown command 'churn'
112 'churn' is provided by the following extension:
118 'churn' is provided by the following extension:
113
119
114 churn command to display statistics about repository history
120 churn command to display statistics about repository history
115
121
116 use "hg help extensions" for information on enabling extensions
122 use "hg help extensions" for information on enabling extensions
117 % disabled extensions
123 % disabled extensions
118 churn extension - command to display statistics about repository history
124 churn extension - command to display statistics about repository history
119
125
120 use "hg help extensions" for information on enabling extensions
126 use "hg help extensions" for information on enabling extensions
121 patchbomb extension - command to send changesets as (a series of) patch emails
127 patchbomb extension - command to send changesets as (a series of) patch emails
122
128
123 use "hg help extensions" for information on enabling extensions
129 use "hg help extensions" for information on enabling extensions
124 % broken disabled extension and command
130 % broken disabled extension and command
125 broken extension - (no help text available)
131 broken extension - (no help text available)
126
132
127 use "hg help extensions" for information on enabling extensions
133 use "hg help extensions" for information on enabling extensions
128 hg: unknown command 'foo'
134 hg: unknown command 'foo'
@@ -1,646 +1,664 b''
1 Mercurial Distributed SCM
1 Mercurial Distributed SCM
2
2
3 basic commands:
3 basic commands:
4
4
5 add add the specified files on the next commit
5 add add the specified files on the next commit
6 annotate show changeset information by line for each file
6 annotate show changeset information by line for each file
7 clone make a copy of an existing repository
7 clone make a copy of an existing repository
8 commit commit the specified files or all outstanding changes
8 commit commit the specified files or all outstanding changes
9 diff diff repository (or selected files)
9 diff diff repository (or selected files)
10 export dump the header and diffs for one or more changesets
10 export dump the header and diffs for one or more changesets
11 forget forget the specified files on the next commit
11 forget forget the specified files on the next commit
12 init create a new repository in the given directory
12 init create a new repository in the given directory
13 log show revision history of entire repository or files
13 log show revision history of entire repository or files
14 merge merge working directory with another revision
14 merge merge working directory with another revision
15 pull pull changes from the specified source
15 pull pull changes from the specified source
16 push push changes to the specified destination
16 push push changes to the specified destination
17 remove remove the specified files on the next commit
17 remove remove the specified files on the next commit
18 serve start stand-alone webserver
18 serve start stand-alone webserver
19 status show changed files in the working directory
19 status show changed files in the working directory
20 summary summarize working directory state
20 summary summarize working directory state
21 update update working directory (or switch revisions)
21 update update working directory (or switch revisions)
22
22
23 use "hg help" for the full list of commands or "hg -v" for details
23 use "hg help" for the full list of commands or "hg -v" for details
24 add add the specified files on the next commit
24 add add the specified files on the next commit
25 annotate show changeset information by line for each file
25 annotate show changeset information by line for each file
26 clone make a copy of an existing repository
26 clone make a copy of an existing repository
27 commit commit the specified files or all outstanding changes
27 commit commit the specified files or all outstanding changes
28 diff diff repository (or selected files)
28 diff diff repository (or selected files)
29 export dump the header and diffs for one or more changesets
29 export dump the header and diffs for one or more changesets
30 forget forget the specified files on the next commit
30 forget forget the specified files on the next commit
31 init create a new repository in the given directory
31 init create a new repository in the given directory
32 log show revision history of entire repository or files
32 log show revision history of entire repository or files
33 merge merge working directory with another revision
33 merge merge working directory with another revision
34 pull pull changes from the specified source
34 pull pull changes from the specified source
35 push push changes to the specified destination
35 push push changes to the specified destination
36 remove remove the specified files on the next commit
36 remove remove the specified files on the next commit
37 serve start stand-alone webserver
37 serve start stand-alone webserver
38 status show changed files in the working directory
38 status show changed files in the working directory
39 summary summarize working directory state
39 summary summarize working directory state
40 update update working directory (or switch revisions)
40 update update working directory (or switch revisions)
41 Mercurial Distributed SCM
41 Mercurial Distributed SCM
42
42
43 list of commands:
43 list of commands:
44
44
45 add add the specified files on the next commit
45 add add the specified files on the next commit
46 addremove add all new files, delete all missing files
46 addremove add all new files, delete all missing files
47 annotate show changeset information by line for each file
47 annotate show changeset information by line for each file
48 archive create an unversioned archive of a repository revision
48 archive create an unversioned archive of a repository revision
49 backout reverse effect of earlier changeset
49 backout reverse effect of earlier changeset
50 bisect subdivision search of changesets
50 bisect subdivision search of changesets
51 branch set or show the current branch name
51 branch set or show the current branch name
52 branches list repository named branches
52 branches list repository named branches
53 bundle create a changegroup file
53 bundle create a changegroup file
54 cat output the current or given revision of files
54 cat output the current or given revision of files
55 clone make a copy of an existing repository
55 clone make a copy of an existing repository
56 commit commit the specified files or all outstanding changes
56 commit commit the specified files or all outstanding changes
57 copy mark files as copied for the next commit
57 copy mark files as copied for the next commit
58 diff diff repository (or selected files)
58 diff diff repository (or selected files)
59 export dump the header and diffs for one or more changesets
59 export dump the header and diffs for one or more changesets
60 forget forget the specified files on the next commit
60 forget forget the specified files on the next commit
61 grep search for a pattern in specified files and revisions
61 grep search for a pattern in specified files and revisions
62 heads show current repository heads or show branch heads
62 heads show current repository heads or show branch heads
63 help show help for a given topic or a help overview
63 help show help for a given topic or a help overview
64 identify identify the working copy or specified revision
64 identify identify the working copy or specified revision
65 import import an ordered set of patches
65 import import an ordered set of patches
66 incoming show new changesets found in source
66 incoming show new changesets found in source
67 init create a new repository in the given directory
67 init create a new repository in the given directory
68 locate locate files matching specific patterns
68 locate locate files matching specific patterns
69 log show revision history of entire repository or files
69 log show revision history of entire repository or files
70 manifest output the current or given revision of the project manifest
70 manifest output the current or given revision of the project manifest
71 merge merge working directory with another revision
71 merge merge working directory with another revision
72 outgoing show changesets not found in the destination
72 outgoing show changesets not found in the destination
73 parents show the parents of the working directory or revision
73 parents show the parents of the working directory or revision
74 paths show aliases for remote repositories
74 paths show aliases for remote repositories
75 pull pull changes from the specified source
75 pull pull changes from the specified source
76 push push changes to the specified destination
76 push push changes to the specified destination
77 recover roll back an interrupted transaction
77 recover roll back an interrupted transaction
78 remove remove the specified files on the next commit
78 remove remove the specified files on the next commit
79 rename rename files; equivalent of copy + remove
79 rename rename files; equivalent of copy + remove
80 resolve various operations to help finish a merge
80 resolve various operations to help finish a merge
81 revert restore individual files or directories to an earlier state
81 revert restore individual files or directories to an earlier state
82 rollback roll back the last transaction (dangerous)
82 rollback roll back the last transaction (dangerous)
83 root print the root (top) of the current working directory
83 root print the root (top) of the current working directory
84 serve start stand-alone webserver
84 serve start stand-alone webserver
85 showconfig show combined config settings from all hgrc files
85 showconfig show combined config settings from all hgrc files
86 status show changed files in the working directory
86 status show changed files in the working directory
87 summary summarize working directory state
87 summary summarize working directory state
88 tag add one or more tags for the current or given revision
88 tag add one or more tags for the current or given revision
89 tags list repository tags
89 tags list repository tags
90 tip show the tip revision
90 tip show the tip revision
91 unbundle apply one or more changegroup files
91 unbundle apply one or more changegroup files
92 update update working directory (or switch revisions)
92 update update working directory (or switch revisions)
93 verify verify the integrity of the repository
93 verify verify the integrity of the repository
94 version output version and copyright information
94 version output version and copyright information
95
95
96 additional help topics:
96 additional help topics:
97
97
98 config Configuration Files
98 config Configuration Files
99 dates Date Formats
99 dates Date Formats
100 patterns File Name Patterns
100 patterns File Name Patterns
101 environment Environment Variables
101 environment Environment Variables
102 revisions Specifying Single Revisions
102 revisions Specifying Single Revisions
103 multirevs Specifying Multiple Revisions
103 multirevs Specifying Multiple Revisions
104 diffs Diff Formats
104 diffs Diff Formats
105 templating Template Usage
105 templating Template Usage
106 urls URL Paths
106 urls URL Paths
107 extensions Using additional features
107 extensions Using additional features
108 hgweb Configuring hgweb
108 hgweb Configuring hgweb
109
109
110 use "hg -v help" to show aliases and global options
110 use "hg -v help" to show aliases and global options
111 add add the specified files on the next commit
111 add add the specified files on the next commit
112 addremove add all new files, delete all missing files
112 addremove add all new files, delete all missing files
113 annotate show changeset information by line for each file
113 annotate show changeset information by line for each file
114 archive create an unversioned archive of a repository revision
114 archive create an unversioned archive of a repository revision
115 backout reverse effect of earlier changeset
115 backout reverse effect of earlier changeset
116 bisect subdivision search of changesets
116 bisect subdivision search of changesets
117 branch set or show the current branch name
117 branch set or show the current branch name
118 branches list repository named branches
118 branches list repository named branches
119 bundle create a changegroup file
119 bundle create a changegroup file
120 cat output the current or given revision of files
120 cat output the current or given revision of files
121 clone make a copy of an existing repository
121 clone make a copy of an existing repository
122 commit commit the specified files or all outstanding changes
122 commit commit the specified files or all outstanding changes
123 copy mark files as copied for the next commit
123 copy mark files as copied for the next commit
124 diff diff repository (or selected files)
124 diff diff repository (or selected files)
125 export dump the header and diffs for one or more changesets
125 export dump the header and diffs for one or more changesets
126 forget forget the specified files on the next commit
126 forget forget the specified files on the next commit
127 grep search for a pattern in specified files and revisions
127 grep search for a pattern in specified files and revisions
128 heads show current repository heads or show branch heads
128 heads show current repository heads or show branch heads
129 help show help for a given topic or a help overview
129 help show help for a given topic or a help overview
130 identify identify the working copy or specified revision
130 identify identify the working copy or specified revision
131 import import an ordered set of patches
131 import import an ordered set of patches
132 incoming show new changesets found in source
132 incoming show new changesets found in source
133 init create a new repository in the given directory
133 init create a new repository in the given directory
134 locate locate files matching specific patterns
134 locate locate files matching specific patterns
135 log show revision history of entire repository or files
135 log show revision history of entire repository or files
136 manifest output the current or given revision of the project manifest
136 manifest output the current or given revision of the project manifest
137 merge merge working directory with another revision
137 merge merge working directory with another revision
138 outgoing show changesets not found in the destination
138 outgoing show changesets not found in the destination
139 parents show the parents of the working directory or revision
139 parents show the parents of the working directory or revision
140 paths show aliases for remote repositories
140 paths show aliases for remote repositories
141 pull pull changes from the specified source
141 pull pull changes from the specified source
142 push push changes to the specified destination
142 push push changes to the specified destination
143 recover roll back an interrupted transaction
143 recover roll back an interrupted transaction
144 remove remove the specified files on the next commit
144 remove remove the specified files on the next commit
145 rename rename files; equivalent of copy + remove
145 rename rename files; equivalent of copy + remove
146 resolve various operations to help finish a merge
146 resolve various operations to help finish a merge
147 revert restore individual files or directories to an earlier state
147 revert restore individual files or directories to an earlier state
148 rollback roll back the last transaction (dangerous)
148 rollback roll back the last transaction (dangerous)
149 root print the root (top) of the current working directory
149 root print the root (top) of the current working directory
150 serve start stand-alone webserver
150 serve start stand-alone webserver
151 showconfig show combined config settings from all hgrc files
151 showconfig show combined config settings from all hgrc files
152 status show changed files in the working directory
152 status show changed files in the working directory
153 summary summarize working directory state
153 summary summarize working directory state
154 tag add one or more tags for the current or given revision
154 tag add one or more tags for the current or given revision
155 tags list repository tags
155 tags list repository tags
156 tip show the tip revision
156 tip show the tip revision
157 unbundle apply one or more changegroup files
157 unbundle apply one or more changegroup files
158 update update working directory (or switch revisions)
158 update update working directory (or switch revisions)
159 verify verify the integrity of the repository
159 verify verify the integrity of the repository
160 version output version and copyright information
160 version output version and copyright information
161
161
162 additional help topics:
162 additional help topics:
163
163
164 config Configuration Files
164 config Configuration Files
165 dates Date Formats
165 dates Date Formats
166 patterns File Name Patterns
166 patterns File Name Patterns
167 environment Environment Variables
167 environment Environment Variables
168 revisions Specifying Single Revisions
168 revisions Specifying Single Revisions
169 multirevs Specifying Multiple Revisions
169 multirevs Specifying Multiple Revisions
170 diffs Diff Formats
170 diffs Diff Formats
171 templating Template Usage
171 templating Template Usage
172 urls URL Paths
172 urls URL Paths
173 extensions Using additional features
173 extensions Using additional features
174 hgweb Configuring hgweb
174 hgweb Configuring hgweb
175 %% test short command list with verbose option
175 %% test short command list with verbose option
176 Mercurial Distributed SCM (version xxx)
176 Mercurial Distributed SCM (version xxx)
177
177
178 Copyright (C) 2005-2010 Matt Mackall <mpm@selenic.com> and others
178 Copyright (C) 2005-2010 Matt Mackall <mpm@selenic.com> and others
179 This is free software; see the source for copying conditions. There is NO
179 This is free software; see the source for copying conditions. There is NO
180 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
180 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
181
181
182 basic commands:
182 basic commands:
183
183
184 add:
184 add:
185 add the specified files on the next commit
185 add the specified files on the next commit
186 annotate, blame:
186 annotate, blame:
187 show changeset information by line for each file
187 show changeset information by line for each file
188 clone:
188 clone:
189 make a copy of an existing repository
189 make a copy of an existing repository
190 commit, ci:
190 commit, ci:
191 commit the specified files or all outstanding changes
191 commit the specified files or all outstanding changes
192 diff:
192 diff:
193 diff repository (or selected files)
193 diff repository (or selected files)
194 export:
194 export:
195 dump the header and diffs for one or more changesets
195 dump the header and diffs for one or more changesets
196 forget:
196 forget:
197 forget the specified files on the next commit
197 forget the specified files on the next commit
198 init:
198 init:
199 create a new repository in the given directory
199 create a new repository in the given directory
200 log, history:
200 log, history:
201 show revision history of entire repository or files
201 show revision history of entire repository or files
202 merge:
202 merge:
203 merge working directory with another revision
203 merge working directory with another revision
204 pull:
204 pull:
205 pull changes from the specified source
205 pull changes from the specified source
206 push:
206 push:
207 push changes to the specified destination
207 push changes to the specified destination
208 remove, rm:
208 remove, rm:
209 remove the specified files on the next commit
209 remove the specified files on the next commit
210 serve:
210 serve:
211 start stand-alone webserver
211 start stand-alone webserver
212 status, st:
212 status, st:
213 show changed files in the working directory
213 show changed files in the working directory
214 summary, sum:
214 summary, sum:
215 summarize working directory state
215 summarize working directory state
216 update, up, checkout, co:
216 update, up, checkout, co:
217 update working directory (or switch revisions)
217 update working directory (or switch revisions)
218
218
219 global options:
219 global options:
220 -R --repository repository root directory or name of overlay bundle file
220 -R --repository REPO repository root directory or name of overlay bundle
221 --cwd change working directory
221 file
222 -y --noninteractive do not prompt, assume 'yes' for any required answers
222 --cwd DIR change working directory
223 -q --quiet suppress output
223 -y --noninteractive do not prompt, assume 'yes' for any required answers
224 -v --verbose enable additional output
224 -q --quiet suppress output
225 --config set/override config option (use 'section.name=value')
225 -v --verbose enable additional output
226 --debug enable debugging output
226 --config CONFIG [+] set/override config option (use 'section.name=value')
227 --debugger start debugger
227 --debug enable debugging output
228 --encoding set the charset encoding (default: ascii)
228 --debugger start debugger
229 --encodingmode set the charset encoding mode (default: strict)
229 --encoding ENCODE set the charset encoding (default: ascii)
230 --traceback always print a traceback on exception
230 --encodingmode MODE set the charset encoding mode (default: strict)
231 --time time how long the command takes
231 --traceback always print a traceback on exception
232 --profile print command execution profile
232 --time time how long the command takes
233 --version output version information and exit
233 --profile print command execution profile
234 -h --help display help and exit
234 --version output version information and exit
235 -h --help display help and exit
236
237 [+] marked option can be specified multiple times
235
238
236 use "hg help" for the full list of commands
239 use "hg help" for the full list of commands
237 hg add [OPTION]... [FILE]...
240 hg add [OPTION]... [FILE]...
238
241
239 add the specified files on the next commit
242 add the specified files on the next commit
240
243
241 Schedule files to be version controlled and added to the repository.
244 Schedule files to be version controlled and added to the repository.
242
245
243 The files will be added to the repository at the next commit. To undo an
246 The files will be added to the repository at the next commit. To undo an
244 add before that, see "hg forget".
247 add before that, see "hg forget".
245
248
246 If no names are given, add all files to the repository.
249 If no names are given, add all files to the repository.
247
250
248 use "hg -v help add" to show verbose help
251 use "hg -v help add" to show verbose help
249
252
250 options:
253 options:
251
254
252 -I --include include names matching the given patterns
255 -I --include PATTERN [+] include names matching the given patterns
253 -X --exclude exclude names matching the given patterns
256 -X --exclude PATTERN [+] exclude names matching the given patterns
254 -n --dry-run do not perform actions, just print output
257 -n --dry-run do not perform actions, just print output
258
259 [+] marked option can be specified multiple times
255
260
256 use "hg -v help add" to show global options
261 use "hg -v help add" to show global options
257 %% verbose help for add
262 %% verbose help for add
258 hg add [OPTION]... [FILE]...
263 hg add [OPTION]... [FILE]...
259
264
260 add the specified files on the next commit
265 add the specified files on the next commit
261
266
262 Schedule files to be version controlled and added to the repository.
267 Schedule files to be version controlled and added to the repository.
263
268
264 The files will be added to the repository at the next commit. To undo an
269 The files will be added to the repository at the next commit. To undo an
265 add before that, see "hg forget".
270 add before that, see "hg forget".
266
271
267 If no names are given, add all files to the repository.
272 If no names are given, add all files to the repository.
268
273
269 An example showing how new (unknown) files are added automatically by "hg
274 An example showing how new (unknown) files are added automatically by "hg
270 add":
275 add":
271
276
272 $ ls
277 $ ls
273 foo.c
278 foo.c
274 $ hg status
279 $ hg status
275 ? foo.c
280 ? foo.c
276 $ hg add
281 $ hg add
277 adding foo.c
282 adding foo.c
278 $ hg status
283 $ hg status
279 A foo.c
284 A foo.c
280
285
281 options:
286 options:
282
287
283 -I --include include names matching the given patterns
288 -I --include PATTERN [+] include names matching the given patterns
284 -X --exclude exclude names matching the given patterns
289 -X --exclude PATTERN [+] exclude names matching the given patterns
285 -n --dry-run do not perform actions, just print output
290 -n --dry-run do not perform actions, just print output
286
291
287 global options:
292 global options:
288 -R --repository repository root directory or name of overlay bundle file
293 -R --repository REPO repository root directory or name of overlay bundle
289 --cwd change working directory
294 file
290 -y --noninteractive do not prompt, assume 'yes' for any required answers
295 --cwd DIR change working directory
291 -q --quiet suppress output
296 -y --noninteractive do not prompt, assume 'yes' for any required
292 -v --verbose enable additional output
297 answers
293 --config set/override config option (use 'section.name=value')
298 -q --quiet suppress output
294 --debug enable debugging output
299 -v --verbose enable additional output
295 --debugger start debugger
300 --config CONFIG [+] set/override config option (use
296 --encoding set the charset encoding (default: ascii)
301 'section.name=value')
297 --encodingmode set the charset encoding mode (default: strict)
302 --debug enable debugging output
298 --traceback always print a traceback on exception
303 --debugger start debugger
299 --time time how long the command takes
304 --encoding ENCODE set the charset encoding (default: ascii)
300 --profile print command execution profile
305 --encodingmode MODE set the charset encoding mode (default: strict)
301 --version output version information and exit
306 --traceback always print a traceback on exception
302 -h --help display help and exit
307 --time time how long the command takes
308 --profile print command execution profile
309 --version output version information and exit
310 -h --help display help and exit
311
312 [+] marked option can be specified multiple times
303 %% test help option with version option
313 %% test help option with version option
304 Mercurial Distributed SCM (version xxx)
314 Mercurial Distributed SCM (version xxx)
305
315
306 Copyright (C) 2005-2010 Matt Mackall <mpm@selenic.com> and others
316 Copyright (C) 2005-2010 Matt Mackall <mpm@selenic.com> and others
307 This is free software; see the source for copying conditions. There is NO
317 This is free software; see the source for copying conditions. There is NO
308 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
318 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
309
319
310 hg add [OPTION]... [FILE]...
320 hg add [OPTION]... [FILE]...
311
321
312 add the specified files on the next commit
322 add the specified files on the next commit
313
323
314 Schedule files to be version controlled and added to the repository.
324 Schedule files to be version controlled and added to the repository.
315
325
316 The files will be added to the repository at the next commit. To undo an
326 The files will be added to the repository at the next commit. To undo an
317 add before that, see "hg forget".
327 add before that, see "hg forget".
318
328
319 If no names are given, add all files to the repository.
329 If no names are given, add all files to the repository.
320
330
321 use "hg -v help add" to show verbose help
331 use "hg -v help add" to show verbose help
322
332
323 options:
333 options:
324
334
325 -I --include include names matching the given patterns
335 -I --include PATTERN [+] include names matching the given patterns
326 -X --exclude exclude names matching the given patterns
336 -X --exclude PATTERN [+] exclude names matching the given patterns
327 -n --dry-run do not perform actions, just print output
337 -n --dry-run do not perform actions, just print output
338
339 [+] marked option can be specified multiple times
328
340
329 use "hg -v help add" to show global options
341 use "hg -v help add" to show global options
330 hg add: option --skjdfks not recognized
342 hg add: option --skjdfks not recognized
331 hg add [OPTION]... [FILE]...
343 hg add [OPTION]... [FILE]...
332
344
333 add the specified files on the next commit
345 add the specified files on the next commit
334
346
335 Schedule files to be version controlled and added to the repository.
347 Schedule files to be version controlled and added to the repository.
336
348
337 The files will be added to the repository at the next commit. To undo an
349 The files will be added to the repository at the next commit. To undo an
338 add before that, see "hg forget".
350 add before that, see "hg forget".
339
351
340 If no names are given, add all files to the repository.
352 If no names are given, add all files to the repository.
341
353
342 use "hg -v help add" to show verbose help
354 use "hg -v help add" to show verbose help
343
355
344 options:
356 options:
345
357
346 -I --include include names matching the given patterns
358 -I --include PATTERN [+] include names matching the given patterns
347 -X --exclude exclude names matching the given patterns
359 -X --exclude PATTERN [+] exclude names matching the given patterns
348 -n --dry-run do not perform actions, just print output
360 -n --dry-run do not perform actions, just print output
361
362 [+] marked option can be specified multiple times
349
363
350 use "hg -v help add" to show global options
364 use "hg -v help add" to show global options
351 %% test ambiguous command help
365 %% test ambiguous command help
352 list of commands:
366 list of commands:
353
367
354 add add the specified files on the next commit
368 add add the specified files on the next commit
355 addremove add all new files, delete all missing files
369 addremove add all new files, delete all missing files
356
370
357 use "hg -v help ad" to show aliases and global options
371 use "hg -v help ad" to show aliases and global options
358 %% test command without options
372 %% test command without options
359 hg verify
373 hg verify
360
374
361 verify the integrity of the repository
375 verify the integrity of the repository
362
376
363 Verify the integrity of the current repository.
377 Verify the integrity of the current repository.
364
378
365 This will perform an extensive check of the repository's integrity,
379 This will perform an extensive check of the repository's integrity,
366 validating the hashes and checksums of each entry in the changelog,
380 validating the hashes and checksums of each entry in the changelog,
367 manifest, and tracked files, as well as the integrity of their crosslinks
381 manifest, and tracked files, as well as the integrity of their crosslinks
368 and indices.
382 and indices.
369
383
370 Returns 0 on success, 1 if errors are encountered.
384 Returns 0 on success, 1 if errors are encountered.
371
385
372 use "hg -v help verify" to show global options
386 use "hg -v help verify" to show global options
373 hg diff [OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...
387 hg diff [OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...
374
388
375 diff repository (or selected files)
389 diff repository (or selected files)
376
390
377 Show differences between revisions for the specified files.
391 Show differences between revisions for the specified files.
378
392
379 Differences between files are shown using the unified diff format.
393 Differences between files are shown using the unified diff format.
380
394
381 NOTE: diff may generate unexpected results for merges, as it will default
395 NOTE: diff may generate unexpected results for merges, as it will default
382 to comparing against the working directory's first parent changeset if no
396 to comparing against the working directory's first parent changeset if no
383 revisions are specified.
397 revisions are specified.
384
398
385 When two revision arguments are given, then changes are shown between
399 When two revision arguments are given, then changes are shown between
386 those revisions. If only one revision is specified then that revision is
400 those revisions. If only one revision is specified then that revision is
387 compared to the working directory, and, when no revisions are specified,
401 compared to the working directory, and, when no revisions are specified,
388 the working directory files are compared to its parent.
402 the working directory files are compared to its parent.
389
403
390 Alternatively you can specify -c/--change with a revision to see the
404 Alternatively you can specify -c/--change with a revision to see the
391 changes in that changeset relative to its first parent.
405 changes in that changeset relative to its first parent.
392
406
393 Without the -a/--text option, diff will avoid generating diffs of files it
407 Without the -a/--text option, diff will avoid generating diffs of files it
394 detects as binary. With -a, diff will generate a diff anyway, probably
408 detects as binary. With -a, diff will generate a diff anyway, probably
395 with undesirable results.
409 with undesirable results.
396
410
397 Use the -g/--git option to generate diffs in the git extended diff format.
411 Use the -g/--git option to generate diffs in the git extended diff format.
398 For more information, read "hg help diffs".
412 For more information, read "hg help diffs".
399
413
400 Returns 0 on success.
414 Returns 0 on success.
401
415
402 options:
416 options:
403
417
404 -r --rev revision
418 -r --rev REV [+] revision
405 -c --change change made by revision
419 -c --change REV change made by revision
406 -a --text treat all files as text
420 -a --text treat all files as text
407 -g --git use git extended diff format
421 -g --git use git extended diff format
408 --nodates omit dates from diff headers
422 --nodates omit dates from diff headers
409 -p --show-function show which function each change is in
423 -p --show-function show which function each change is in
410 --reverse produce a diff that undoes the changes
424 --reverse produce a diff that undoes the changes
411 -w --ignore-all-space ignore white space when comparing lines
425 -w --ignore-all-space ignore white space when comparing lines
412 -b --ignore-space-change ignore changes in the amount of white space
426 -b --ignore-space-change ignore changes in the amount of white space
413 -B --ignore-blank-lines ignore changes whose lines are all blank
427 -B --ignore-blank-lines ignore changes whose lines are all blank
414 -U --unified number of lines of context to show
428 -U --unified NUM number of lines of context to show
415 --stat output diffstat-style summary of changes
429 --stat output diffstat-style summary of changes
416 -I --include include names matching the given patterns
430 -I --include PATTERN [+] include names matching the given patterns
417 -X --exclude exclude names matching the given patterns
431 -X --exclude PATTERN [+] exclude names matching the given patterns
432
433 [+] marked option can be specified multiple times
418
434
419 use "hg -v help diff" to show global options
435 use "hg -v help diff" to show global options
420 hg status [OPTION]... [FILE]...
436 hg status [OPTION]... [FILE]...
421
437
422 aliases: st
438 aliases: st
423
439
424 show changed files in the working directory
440 show changed files in the working directory
425
441
426 Show status of files in the repository. If names are given, only files
442 Show status of files in the repository. If names are given, only files
427 that match are shown. Files that are clean or ignored or the source of a
443 that match are shown. Files that are clean or ignored or the source of a
428 copy/move operation, are not listed unless -c/--clean, -i/--ignored,
444 copy/move operation, are not listed unless -c/--clean, -i/--ignored,
429 -C/--copies or -A/--all are given. Unless options described with "show
445 -C/--copies or -A/--all are given. Unless options described with "show
430 only ..." are given, the options -mardu are used.
446 only ..." are given, the options -mardu are used.
431
447
432 Option -q/--quiet hides untracked (unknown and ignored) files unless
448 Option -q/--quiet hides untracked (unknown and ignored) files unless
433 explicitly requested with -u/--unknown or -i/--ignored.
449 explicitly requested with -u/--unknown or -i/--ignored.
434
450
435 NOTE: status may appear to disagree with diff if permissions have changed
451 NOTE: status may appear to disagree with diff if permissions have changed
436 or a merge has occurred. The standard diff format does not report
452 or a merge has occurred. The standard diff format does not report
437 permission changes and diff only reports changes relative to one merge
453 permission changes and diff only reports changes relative to one merge
438 parent.
454 parent.
439
455
440 If one revision is given, it is used as the base revision. If two
456 If one revision is given, it is used as the base revision. If two
441 revisions are given, the differences between them are shown. The --change
457 revisions are given, the differences between them are shown. The --change
442 option can also be used as a shortcut to list the changed files of a
458 option can also be used as a shortcut to list the changed files of a
443 revision from its first parent.
459 revision from its first parent.
444
460
445 The codes used to show the status of files are:
461 The codes used to show the status of files are:
446
462
447 M = modified
463 M = modified
448 A = added
464 A = added
449 R = removed
465 R = removed
450 C = clean
466 C = clean
451 ! = missing (deleted by non-hg command, but still tracked)
467 ! = missing (deleted by non-hg command, but still tracked)
452 ? = not tracked
468 ? = not tracked
453 I = ignored
469 I = ignored
454 = origin of the previous file listed as A (added)
470 = origin of the previous file listed as A (added)
455
471
456 Returns 0 on success.
472 Returns 0 on success.
457
473
458 options:
474 options:
459
475
460 -A --all show status of all files
476 -A --all show status of all files
461 -m --modified show only modified files
477 -m --modified show only modified files
462 -a --added show only added files
478 -a --added show only added files
463 -r --removed show only removed files
479 -r --removed show only removed files
464 -d --deleted show only deleted (but tracked) files
480 -d --deleted show only deleted (but tracked) files
465 -c --clean show only files without changes
481 -c --clean show only files without changes
466 -u --unknown show only unknown (not tracked) files
482 -u --unknown show only unknown (not tracked) files
467 -i --ignored show only ignored files
483 -i --ignored show only ignored files
468 -n --no-status hide status prefix
484 -n --no-status hide status prefix
469 -C --copies show source of copied files
485 -C --copies show source of copied files
470 -0 --print0 end filenames with NUL, for use with xargs
486 -0 --print0 end filenames with NUL, for use with xargs
471 --rev show difference from revision
487 --rev REV [+] show difference from revision
472 --change list the changed files of a revision
488 --change REV list the changed files of a revision
473 -I --include include names matching the given patterns
489 -I --include PATTERN [+] include names matching the given patterns
474 -X --exclude exclude names matching the given patterns
490 -X --exclude PATTERN [+] exclude names matching the given patterns
491
492 [+] marked option can be specified multiple times
475
493
476 use "hg -v help status" to show global options
494 use "hg -v help status" to show global options
477 hg status [OPTION]... [FILE]...
495 hg status [OPTION]... [FILE]...
478
496
479 show changed files in the working directory
497 show changed files in the working directory
480 hg: unknown command 'foo'
498 hg: unknown command 'foo'
481 Mercurial Distributed SCM
499 Mercurial Distributed SCM
482
500
483 basic commands:
501 basic commands:
484
502
485 add add the specified files on the next commit
503 add add the specified files on the next commit
486 annotate show changeset information by line for each file
504 annotate show changeset information by line for each file
487 clone make a copy of an existing repository
505 clone make a copy of an existing repository
488 commit commit the specified files or all outstanding changes
506 commit commit the specified files or all outstanding changes
489 diff diff repository (or selected files)
507 diff diff repository (or selected files)
490 export dump the header and diffs for one or more changesets
508 export dump the header and diffs for one or more changesets
491 forget forget the specified files on the next commit
509 forget forget the specified files on the next commit
492 init create a new repository in the given directory
510 init create a new repository in the given directory
493 log show revision history of entire repository or files
511 log show revision history of entire repository or files
494 merge merge working directory with another revision
512 merge merge working directory with another revision
495 pull pull changes from the specified source
513 pull pull changes from the specified source
496 push push changes to the specified destination
514 push push changes to the specified destination
497 remove remove the specified files on the next commit
515 remove remove the specified files on the next commit
498 serve start stand-alone webserver
516 serve start stand-alone webserver
499 status show changed files in the working directory
517 status show changed files in the working directory
500 summary summarize working directory state
518 summary summarize working directory state
501 update update working directory (or switch revisions)
519 update update working directory (or switch revisions)
502
520
503 use "hg help" for the full list of commands or "hg -v" for details
521 use "hg help" for the full list of commands or "hg -v" for details
504 hg: unknown command 'skjdfks'
522 hg: unknown command 'skjdfks'
505 Mercurial Distributed SCM
523 Mercurial Distributed SCM
506
524
507 basic commands:
525 basic commands:
508
526
509 add add the specified files on the next commit
527 add add the specified files on the next commit
510 annotate show changeset information by line for each file
528 annotate show changeset information by line for each file
511 clone make a copy of an existing repository
529 clone make a copy of an existing repository
512 commit commit the specified files or all outstanding changes
530 commit commit the specified files or all outstanding changes
513 diff diff repository (or selected files)
531 diff diff repository (or selected files)
514 export dump the header and diffs for one or more changesets
532 export dump the header and diffs for one or more changesets
515 forget forget the specified files on the next commit
533 forget forget the specified files on the next commit
516 init create a new repository in the given directory
534 init create a new repository in the given directory
517 log show revision history of entire repository or files
535 log show revision history of entire repository or files
518 merge merge working directory with another revision
536 merge merge working directory with another revision
519 pull pull changes from the specified source
537 pull pull changes from the specified source
520 push push changes to the specified destination
538 push push changes to the specified destination
521 remove remove the specified files on the next commit
539 remove remove the specified files on the next commit
522 serve start stand-alone webserver
540 serve start stand-alone webserver
523 status show changed files in the working directory
541 status show changed files in the working directory
524 summary summarize working directory state
542 summary summarize working directory state
525 update update working directory (or switch revisions)
543 update update working directory (or switch revisions)
526
544
527 use "hg help" for the full list of commands or "hg -v" for details
545 use "hg help" for the full list of commands or "hg -v" for details
528 %% test command with no help text
546 %% test command with no help text
529 hg nohelp
547 hg nohelp
530
548
531 (no help text available)
549 (no help text available)
532
550
533 use "hg -v help nohelp" to show global options
551 use "hg -v help nohelp" to show global options
534 %% test that default list of commands omits extension commands
552 %% test that default list of commands omits extension commands
535 Mercurial Distributed SCM
553 Mercurial Distributed SCM
536
554
537 list of commands:
555 list of commands:
538
556
539 add add the specified files on the next commit
557 add add the specified files on the next commit
540 addremove add all new files, delete all missing files
558 addremove add all new files, delete all missing files
541 annotate show changeset information by line for each file
559 annotate show changeset information by line for each file
542 archive create an unversioned archive of a repository revision
560 archive create an unversioned archive of a repository revision
543 backout reverse effect of earlier changeset
561 backout reverse effect of earlier changeset
544 bisect subdivision search of changesets
562 bisect subdivision search of changesets
545 branch set or show the current branch name
563 branch set or show the current branch name
546 branches list repository named branches
564 branches list repository named branches
547 bundle create a changegroup file
565 bundle create a changegroup file
548 cat output the current or given revision of files
566 cat output the current or given revision of files
549 clone make a copy of an existing repository
567 clone make a copy of an existing repository
550 commit commit the specified files or all outstanding changes
568 commit commit the specified files or all outstanding changes
551 copy mark files as copied for the next commit
569 copy mark files as copied for the next commit
552 diff diff repository (or selected files)
570 diff diff repository (or selected files)
553 export dump the header and diffs for one or more changesets
571 export dump the header and diffs for one or more changesets
554 forget forget the specified files on the next commit
572 forget forget the specified files on the next commit
555 grep search for a pattern in specified files and revisions
573 grep search for a pattern in specified files and revisions
556 heads show current repository heads or show branch heads
574 heads show current repository heads or show branch heads
557 help show help for a given topic or a help overview
575 help show help for a given topic or a help overview
558 identify identify the working copy or specified revision
576 identify identify the working copy or specified revision
559 import import an ordered set of patches
577 import import an ordered set of patches
560 incoming show new changesets found in source
578 incoming show new changesets found in source
561 init create a new repository in the given directory
579 init create a new repository in the given directory
562 locate locate files matching specific patterns
580 locate locate files matching specific patterns
563 log show revision history of entire repository or files
581 log show revision history of entire repository or files
564 manifest output the current or given revision of the project manifest
582 manifest output the current or given revision of the project manifest
565 merge merge working directory with another revision
583 merge merge working directory with another revision
566 outgoing show changesets not found in the destination
584 outgoing show changesets not found in the destination
567 parents show the parents of the working directory or revision
585 parents show the parents of the working directory or revision
568 paths show aliases for remote repositories
586 paths show aliases for remote repositories
569 pull pull changes from the specified source
587 pull pull changes from the specified source
570 push push changes to the specified destination
588 push push changes to the specified destination
571 recover roll back an interrupted transaction
589 recover roll back an interrupted transaction
572 remove remove the specified files on the next commit
590 remove remove the specified files on the next commit
573 rename rename files; equivalent of copy + remove
591 rename rename files; equivalent of copy + remove
574 resolve various operations to help finish a merge
592 resolve various operations to help finish a merge
575 revert restore individual files or directories to an earlier state
593 revert restore individual files or directories to an earlier state
576 rollback roll back the last transaction (dangerous)
594 rollback roll back the last transaction (dangerous)
577 root print the root (top) of the current working directory
595 root print the root (top) of the current working directory
578 serve start stand-alone webserver
596 serve start stand-alone webserver
579 showconfig show combined config settings from all hgrc files
597 showconfig show combined config settings from all hgrc files
580 status show changed files in the working directory
598 status show changed files in the working directory
581 summary summarize working directory state
599 summary summarize working directory state
582 tag add one or more tags for the current or given revision
600 tag add one or more tags for the current or given revision
583 tags list repository tags
601 tags list repository tags
584 tip show the tip revision
602 tip show the tip revision
585 unbundle apply one or more changegroup files
603 unbundle apply one or more changegroup files
586 update update working directory (or switch revisions)
604 update update working directory (or switch revisions)
587 verify verify the integrity of the repository
605 verify verify the integrity of the repository
588 version output version and copyright information
606 version output version and copyright information
589
607
590 enabled extensions:
608 enabled extensions:
591
609
592 helpext (no help text available)
610 helpext (no help text available)
593
611
594 additional help topics:
612 additional help topics:
595
613
596 config Configuration Files
614 config Configuration Files
597 dates Date Formats
615 dates Date Formats
598 patterns File Name Patterns
616 patterns File Name Patterns
599 environment Environment Variables
617 environment Environment Variables
600 revisions Specifying Single Revisions
618 revisions Specifying Single Revisions
601 multirevs Specifying Multiple Revisions
619 multirevs Specifying Multiple Revisions
602 diffs Diff Formats
620 diffs Diff Formats
603 templating Template Usage
621 templating Template Usage
604 urls URL Paths
622 urls URL Paths
605 extensions Using additional features
623 extensions Using additional features
606 hgweb Configuring hgweb
624 hgweb Configuring hgweb
607
625
608 use "hg -v help" to show aliases and global options
626 use "hg -v help" to show aliases and global options
609 %% test list of commands with command with no help text
627 %% test list of commands with command with no help text
610 helpext extension - no help text available
628 helpext extension - no help text available
611
629
612 list of commands:
630 list of commands:
613
631
614 nohelp (no help text available)
632 nohelp (no help text available)
615
633
616 use "hg -v help helpext" to show aliases and global options
634 use "hg -v help helpext" to show aliases and global options
617 %% test a help topic
635 %% test a help topic
618 Specifying Single Revisions
636 Specifying Single Revisions
619
637
620 Mercurial supports several ways to specify individual revisions.
638 Mercurial supports several ways to specify individual revisions.
621
639
622 A plain integer is treated as a revision number. Negative integers are
640 A plain integer is treated as a revision number. Negative integers are
623 treated as sequential offsets from the tip, with -1 denoting the tip, -2
641 treated as sequential offsets from the tip, with -1 denoting the tip, -2
624 denoting the revision prior to the tip, and so forth.
642 denoting the revision prior to the tip, and so forth.
625
643
626 A 40-digit hexadecimal string is treated as a unique revision identifier.
644 A 40-digit hexadecimal string is treated as a unique revision identifier.
627
645
628 A hexadecimal string less than 40 characters long is treated as a unique
646 A hexadecimal string less than 40 characters long is treated as a unique
629 revision identifier and is referred to as a short-form identifier. A
647 revision identifier and is referred to as a short-form identifier. A
630 short-form identifier is only valid if it is the prefix of exactly one
648 short-form identifier is only valid if it is the prefix of exactly one
631 full-length identifier.
649 full-length identifier.
632
650
633 Any other string is treated as a tag or branch name. A tag name is a
651 Any other string is treated as a tag or branch name. A tag name is a
634 symbolic name associated with a revision identifier. A branch name denotes
652 symbolic name associated with a revision identifier. A branch name denotes
635 the tipmost revision of that branch. Tag and branch names must not contain
653 the tipmost revision of that branch. Tag and branch names must not contain
636 the ":" character.
654 the ":" character.
637
655
638 The reserved name "tip" is a special tag that always identifies the most
656 The reserved name "tip" is a special tag that always identifies the most
639 recent revision.
657 recent revision.
640
658
641 The reserved name "null" indicates the null revision. This is the revision
659 The reserved name "null" indicates the null revision. This is the revision
642 of an empty repository, and the parent of revision 0.
660 of an empty repository, and the parent of revision 0.
643
661
644 The reserved name "." indicates the working directory parent. If no
662 The reserved name "." indicates the working directory parent. If no
645 working directory is checked out, it is equivalent to null. If an
663 working directory is checked out, it is equivalent to null. If an
646 uncommitted merge is in progress, "." is the revision of the first parent.
664 uncommitted merge is in progress, "." is the revision of the first parent.
@@ -1,218 +1,220 b''
1 % help (no mq, so no qrecord)
1 % help (no mq, so no qrecord)
2 hg: unknown command 'qrecord'
2 hg: unknown command 'qrecord'
3 Mercurial Distributed SCM
3 Mercurial Distributed SCM
4
4
5 basic commands:
5 basic commands:
6
6
7 add add the specified files on the next commit
7 add add the specified files on the next commit
8 annotate show changeset information by line for each file
8 annotate show changeset information by line for each file
9 clone make a copy of an existing repository
9 clone make a copy of an existing repository
10 commit commit the specified files or all outstanding changes
10 commit commit the specified files or all outstanding changes
11 diff diff repository (or selected files)
11 diff diff repository (or selected files)
12 export dump the header and diffs for one or more changesets
12 export dump the header and diffs for one or more changesets
13 forget forget the specified files on the next commit
13 forget forget the specified files on the next commit
14 init create a new repository in the given directory
14 init create a new repository in the given directory
15 log show revision history of entire repository or files
15 log show revision history of entire repository or files
16 merge merge working directory with another revision
16 merge merge working directory with another revision
17 pull pull changes from the specified source
17 pull pull changes from the specified source
18 push push changes to the specified destination
18 push push changes to the specified destination
19 remove remove the specified files on the next commit
19 remove remove the specified files on the next commit
20 serve start stand-alone webserver
20 serve start stand-alone webserver
21 status show changed files in the working directory
21 status show changed files in the working directory
22 summary summarize working directory state
22 summary summarize working directory state
23 update update working directory (or switch revisions)
23 update update working directory (or switch revisions)
24
24
25 use "hg help" for the full list of commands or "hg -v" for details
25 use "hg help" for the full list of commands or "hg -v" for details
26 % help (mq present)
26 % help (mq present)
27 hg qrecord [OPTION]... PATCH [FILE]...
27 hg qrecord [OPTION]... PATCH [FILE]...
28
28
29 interactively record a new patch
29 interactively record a new patch
30
30
31 See "hg help qnew" & "hg help record" for more information and usage.
31 See "hg help qnew" & "hg help record" for more information and usage.
32
32
33 options:
33 options:
34
34
35 -e --edit edit commit message
35 -e --edit edit commit message
36 -g --git use git extended diff format
36 -g --git use git extended diff format
37 -U --currentuser add "From: <current user>" to patch
37 -U --currentuser add "From: <current user>" to patch
38 -u --user add "From: <given user>" to patch
38 -u --user USER add "From: <USER>" to patch
39 -D --currentdate add "Date: <current date>" to patch
39 -D --currentdate add "Date: <current date>" to patch
40 -d --date add "Date: <given date>" to patch
40 -d --date DATE add "Date: <DATE>" to patch
41 -I --include include names matching the given patterns
41 -I --include PATTERN [+] include names matching the given patterns
42 -X --exclude exclude names matching the given patterns
42 -X --exclude PATTERN [+] exclude names matching the given patterns
43 -m --message use <text> as commit message
43 -m --message TEXT use text as commit message
44 -l --logfile read commit message from <file>
44 -l --logfile FILE read commit message from file
45
46 [+] marked option can be specified multiple times
45
47
46 use "hg -v help qrecord" to show global options
48 use "hg -v help qrecord" to show global options
47 % base commit
49 % base commit
48 % changing files
50 % changing files
49 % whole diff
51 % whole diff
50 diff -r 1057167b20ef 1.txt
52 diff -r 1057167b20ef 1.txt
51 --- a/1.txt
53 --- a/1.txt
52 +++ b/1.txt
54 +++ b/1.txt
53 @@ -1,5 +1,5 @@
55 @@ -1,5 +1,5 @@
54 1
56 1
55 -2
57 -2
56 +2 2
58 +2 2
57 3
59 3
58 -4
60 -4
59 +4 4
61 +4 4
60 5
62 5
61 diff -r 1057167b20ef 2.txt
63 diff -r 1057167b20ef 2.txt
62 --- a/2.txt
64 --- a/2.txt
63 +++ b/2.txt
65 +++ b/2.txt
64 @@ -1,5 +1,5 @@
66 @@ -1,5 +1,5 @@
65 a
67 a
66 -b
68 -b
67 +b b
69 +b b
68 c
70 c
69 d
71 d
70 e
72 e
71 diff -r 1057167b20ef dir/a.txt
73 diff -r 1057167b20ef dir/a.txt
72 --- a/dir/a.txt
74 --- a/dir/a.txt
73 +++ b/dir/a.txt
75 +++ b/dir/a.txt
74 @@ -1,4 +1,4 @@
76 @@ -1,4 +1,4 @@
75 -hello world
77 -hello world
76 +hello world!
78 +hello world!
77
79
78 someone
80 someone
79 up
81 up
80 % qrecord a.patch
82 % qrecord a.patch
81 diff --git a/1.txt b/1.txt
83 diff --git a/1.txt b/1.txt
82 2 hunks, 4 lines changed
84 2 hunks, 4 lines changed
83 examine changes to '1.txt'? [Ynsfdaq?]
85 examine changes to '1.txt'? [Ynsfdaq?]
84 @@ -1,3 +1,3 @@
86 @@ -1,3 +1,3 @@
85 1
87 1
86 -2
88 -2
87 +2 2
89 +2 2
88 3
90 3
89 record change 1/6 to '1.txt'? [Ynsfdaq?]
91 record change 1/6 to '1.txt'? [Ynsfdaq?]
90 @@ -3,3 +3,3 @@
92 @@ -3,3 +3,3 @@
91 3
93 3
92 -4
94 -4
93 +4 4
95 +4 4
94 5
96 5
95 record change 2/6 to '1.txt'? [Ynsfdaq?]
97 record change 2/6 to '1.txt'? [Ynsfdaq?]
96 diff --git a/2.txt b/2.txt
98 diff --git a/2.txt b/2.txt
97 1 hunks, 2 lines changed
99 1 hunks, 2 lines changed
98 examine changes to '2.txt'? [Ynsfdaq?]
100 examine changes to '2.txt'? [Ynsfdaq?]
99 @@ -1,5 +1,5 @@
101 @@ -1,5 +1,5 @@
100 a
102 a
101 -b
103 -b
102 +b b
104 +b b
103 c
105 c
104 d
106 d
105 e
107 e
106 record change 4/6 to '2.txt'? [Ynsfdaq?]
108 record change 4/6 to '2.txt'? [Ynsfdaq?]
107 diff --git a/dir/a.txt b/dir/a.txt
109 diff --git a/dir/a.txt b/dir/a.txt
108 1 hunks, 2 lines changed
110 1 hunks, 2 lines changed
109 examine changes to 'dir/a.txt'? [Ynsfdaq?]
111 examine changes to 'dir/a.txt'? [Ynsfdaq?]
110
112
111 % after qrecord a.patch 'tip'
113 % after qrecord a.patch 'tip'
112 changeset: 1:5d1ca63427ee
114 changeset: 1:5d1ca63427ee
113 tag: a.patch
115 tag: a.patch
114 tag: qbase
116 tag: qbase
115 tag: qtip
117 tag: qtip
116 tag: tip
118 tag: tip
117 user: test
119 user: test
118 date: Thu Jan 01 00:00:00 1970 +0000
120 date: Thu Jan 01 00:00:00 1970 +0000
119 summary: aaa
121 summary: aaa
120
122
121 diff -r 1057167b20ef -r 5d1ca63427ee 1.txt
123 diff -r 1057167b20ef -r 5d1ca63427ee 1.txt
122 --- a/1.txt Thu Jan 01 00:00:00 1970 +0000
124 --- a/1.txt Thu Jan 01 00:00:00 1970 +0000
123 +++ b/1.txt Thu Jan 01 00:00:00 1970 +0000
125 +++ b/1.txt Thu Jan 01 00:00:00 1970 +0000
124 @@ -1,5 +1,5 @@
126 @@ -1,5 +1,5 @@
125 1
127 1
126 -2
128 -2
127 +2 2
129 +2 2
128 3
130 3
129 4
131 4
130 5
132 5
131 diff -r 1057167b20ef -r 5d1ca63427ee 2.txt
133 diff -r 1057167b20ef -r 5d1ca63427ee 2.txt
132 --- a/2.txt Thu Jan 01 00:00:00 1970 +0000
134 --- a/2.txt Thu Jan 01 00:00:00 1970 +0000
133 +++ b/2.txt Thu Jan 01 00:00:00 1970 +0000
135 +++ b/2.txt Thu Jan 01 00:00:00 1970 +0000
134 @@ -1,5 +1,5 @@
136 @@ -1,5 +1,5 @@
135 a
137 a
136 -b
138 -b
137 +b b
139 +b b
138 c
140 c
139 d
141 d
140 e
142 e
141
143
142
144
143 % after qrecord a.patch 'diff'
145 % after qrecord a.patch 'diff'
144 diff -r 5d1ca63427ee 1.txt
146 diff -r 5d1ca63427ee 1.txt
145 --- a/1.txt
147 --- a/1.txt
146 +++ b/1.txt
148 +++ b/1.txt
147 @@ -1,5 +1,5 @@
149 @@ -1,5 +1,5 @@
148 1
150 1
149 2 2
151 2 2
150 3
152 3
151 -4
153 -4
152 +4 4
154 +4 4
153 5
155 5
154 diff -r 5d1ca63427ee dir/a.txt
156 diff -r 5d1ca63427ee dir/a.txt
155 --- a/dir/a.txt
157 --- a/dir/a.txt
156 +++ b/dir/a.txt
158 +++ b/dir/a.txt
157 @@ -1,4 +1,4 @@
159 @@ -1,4 +1,4 @@
158 -hello world
160 -hello world
159 +hello world!
161 +hello world!
160
162
161 someone
163 someone
162 up
164 up
163 % qrecord b.patch
165 % qrecord b.patch
164 diff --git a/1.txt b/1.txt
166 diff --git a/1.txt b/1.txt
165 1 hunks, 2 lines changed
167 1 hunks, 2 lines changed
166 examine changes to '1.txt'? [Ynsfdaq?]
168 examine changes to '1.txt'? [Ynsfdaq?]
167 @@ -1,5 +1,5 @@
169 @@ -1,5 +1,5 @@
168 1
170 1
169 2 2
171 2 2
170 3
172 3
171 -4
173 -4
172 +4 4
174 +4 4
173 5
175 5
174 record change 1/3 to '1.txt'? [Ynsfdaq?]
176 record change 1/3 to '1.txt'? [Ynsfdaq?]
175 diff --git a/dir/a.txt b/dir/a.txt
177 diff --git a/dir/a.txt b/dir/a.txt
176 1 hunks, 2 lines changed
178 1 hunks, 2 lines changed
177 examine changes to 'dir/a.txt'? [Ynsfdaq?]
179 examine changes to 'dir/a.txt'? [Ynsfdaq?]
178 @@ -1,4 +1,4 @@
180 @@ -1,4 +1,4 @@
179 -hello world
181 -hello world
180 +hello world!
182 +hello world!
181
183
182 someone
184 someone
183 up
185 up
184 record change 3/3 to 'dir/a.txt'? [Ynsfdaq?]
186 record change 3/3 to 'dir/a.txt'? [Ynsfdaq?]
185
187
186 % after qrecord b.patch 'tip'
188 % after qrecord b.patch 'tip'
187 changeset: 2:b056198bf878
189 changeset: 2:b056198bf878
188 tag: b.patch
190 tag: b.patch
189 tag: qtip
191 tag: qtip
190 tag: tip
192 tag: tip
191 user: test
193 user: test
192 date: Thu Jan 01 00:00:00 1970 +0000
194 date: Thu Jan 01 00:00:00 1970 +0000
193 summary: bbb
195 summary: bbb
194
196
195 diff -r 5d1ca63427ee -r b056198bf878 1.txt
197 diff -r 5d1ca63427ee -r b056198bf878 1.txt
196 --- a/1.txt Thu Jan 01 00:00:00 1970 +0000
198 --- a/1.txt Thu Jan 01 00:00:00 1970 +0000
197 +++ b/1.txt Thu Jan 01 00:00:00 1970 +0000
199 +++ b/1.txt Thu Jan 01 00:00:00 1970 +0000
198 @@ -1,5 +1,5 @@
200 @@ -1,5 +1,5 @@
199 1
201 1
200 2 2
202 2 2
201 3
203 3
202 -4
204 -4
203 +4 4
205 +4 4
204 5
206 5
205 diff -r 5d1ca63427ee -r b056198bf878 dir/a.txt
207 diff -r 5d1ca63427ee -r b056198bf878 dir/a.txt
206 --- a/dir/a.txt Thu Jan 01 00:00:00 1970 +0000
208 --- a/dir/a.txt Thu Jan 01 00:00:00 1970 +0000
207 +++ b/dir/a.txt Thu Jan 01 00:00:00 1970 +0000
209 +++ b/dir/a.txt Thu Jan 01 00:00:00 1970 +0000
208 @@ -1,4 +1,4 @@
210 @@ -1,4 +1,4 @@
209 -hello world
211 -hello world
210 +hello world!
212 +hello world!
211
213
212 someone
214 someone
213 up
215 up
214
216
215
217
216 % after qrecord b.patch 'diff'
218 % after qrecord b.patch 'diff'
217
219
218 % --- end ---
220 % --- end ---
@@ -1,666 +1,670 b''
1 % help
1 % help
2 hg record [OPTION]... [FILE]...
2 hg record [OPTION]... [FILE]...
3
3
4 interactively select changes to commit
4 interactively select changes to commit
5
5
6 If a list of files is omitted, all changes reported by "hg status" will be
6 If a list of files is omitted, all changes reported by "hg status" will be
7 candidates for recording.
7 candidates for recording.
8
8
9 See "hg help dates" for a list of formats valid for -d/--date.
9 See "hg help dates" for a list of formats valid for -d/--date.
10
10
11 You will be prompted for whether to record changes to each modified file,
11 You will be prompted for whether to record changes to each modified file,
12 and for files with multiple changes, for each change to use. For each
12 and for files with multiple changes, for each change to use. For each
13 query, the following responses are possible:
13 query, the following responses are possible:
14
14
15 y - record this change
15 y - record this change
16 n - skip this change
16 n - skip this change
17
17
18 s - skip remaining changes to this file
18 s - skip remaining changes to this file
19 f - record remaining changes to this file
19 f - record remaining changes to this file
20
20
21 d - done, skip remaining changes and files
21 d - done, skip remaining changes and files
22 a - record all changes to all remaining files
22 a - record all changes to all remaining files
23 q - quit, recording no changes
23 q - quit, recording no changes
24
24
25 ? - display help
25 ? - display help
26
26
27 This command is not available when committing a merge.
27 This command is not available when committing a merge.
28
28
29 options:
29 options:
30
30
31 -A --addremove mark new/missing files as added/removed before committing
31 -A --addremove mark new/missing files as added/removed before
32 --close-branch mark a branch as closed, hiding it from the branch list
32 committing
33 -I --include include names matching the given patterns
33 --close-branch mark a branch as closed, hiding it from the branch
34 -X --exclude exclude names matching the given patterns
34 list
35 -m --message use <text> as commit message
35 -I --include PATTERN [+] include names matching the given patterns
36 -l --logfile read commit message from <file>
36 -X --exclude PATTERN [+] exclude names matching the given patterns
37 -d --date record datecode as commit date
37 -m --message TEXT use text as commit message
38 -u --user record the specified user as committer
38 -l --logfile FILE read commit message from file
39 -d --date DATE record datecode as commit date
40 -u --user USER record the specified user as committer
41
42 [+] marked option can be specified multiple times
39
43
40 use "hg -v help record" to show global options
44 use "hg -v help record" to show global options
41 % select no files
45 % select no files
42 diff --git a/empty-rw b/empty-rw
46 diff --git a/empty-rw b/empty-rw
43 new file mode 100644
47 new file mode 100644
44 examine changes to 'empty-rw'? [Ynsfdaq?]
48 examine changes to 'empty-rw'? [Ynsfdaq?]
45 no changes to record
49 no changes to record
46
50
47 changeset: -1:000000000000
51 changeset: -1:000000000000
48 tag: tip
52 tag: tip
49 user:
53 user:
50 date: Thu Jan 01 00:00:00 1970 +0000
54 date: Thu Jan 01 00:00:00 1970 +0000
51
55
52
56
53 % select files but no hunks
57 % select files but no hunks
54 diff --git a/empty-rw b/empty-rw
58 diff --git a/empty-rw b/empty-rw
55 new file mode 100644
59 new file mode 100644
56 examine changes to 'empty-rw'? [Ynsfdaq?]
60 examine changes to 'empty-rw'? [Ynsfdaq?]
57 abort: empty commit message
61 abort: empty commit message
58
62
59 changeset: -1:000000000000
63 changeset: -1:000000000000
60 tag: tip
64 tag: tip
61 user:
65 user:
62 date: Thu Jan 01 00:00:00 1970 +0000
66 date: Thu Jan 01 00:00:00 1970 +0000
63
67
64
68
65 % record empty file
69 % record empty file
66 diff --git a/empty-rw b/empty-rw
70 diff --git a/empty-rw b/empty-rw
67 new file mode 100644
71 new file mode 100644
68 examine changes to 'empty-rw'? [Ynsfdaq?]
72 examine changes to 'empty-rw'? [Ynsfdaq?]
69
73
70 changeset: 0:c0708cf4e46e
74 changeset: 0:c0708cf4e46e
71 tag: tip
75 tag: tip
72 user: test
76 user: test
73 date: Thu Jan 01 00:00:00 1970 +0000
77 date: Thu Jan 01 00:00:00 1970 +0000
74 summary: empty
78 summary: empty
75
79
76
80
77 % summary shows we updated to the new cset
81 % summary shows we updated to the new cset
78 parent: 0:c0708cf4e46e tip
82 parent: 0:c0708cf4e46e tip
79 empty
83 empty
80 branch: default
84 branch: default
81 commit: (clean)
85 commit: (clean)
82 update: (current)
86 update: (current)
83
87
84 % rename empty file
88 % rename empty file
85 diff --git a/empty-rw b/empty-rename
89 diff --git a/empty-rw b/empty-rename
86 rename from empty-rw
90 rename from empty-rw
87 rename to empty-rename
91 rename to empty-rename
88 examine changes to 'empty-rw' and 'empty-rename'? [Ynsfdaq?]
92 examine changes to 'empty-rw' and 'empty-rename'? [Ynsfdaq?]
89
93
90 changeset: 1:d695e8dcb197
94 changeset: 1:d695e8dcb197
91 tag: tip
95 tag: tip
92 user: test
96 user: test
93 date: Thu Jan 01 00:00:01 1970 +0000
97 date: Thu Jan 01 00:00:01 1970 +0000
94 summary: rename
98 summary: rename
95
99
96
100
97 % copy empty file
101 % copy empty file
98 diff --git a/empty-rename b/empty-copy
102 diff --git a/empty-rename b/empty-copy
99 copy from empty-rename
103 copy from empty-rename
100 copy to empty-copy
104 copy to empty-copy
101 examine changes to 'empty-rename' and 'empty-copy'? [Ynsfdaq?]
105 examine changes to 'empty-rename' and 'empty-copy'? [Ynsfdaq?]
102
106
103 changeset: 2:1d4b90bea524
107 changeset: 2:1d4b90bea524
104 tag: tip
108 tag: tip
105 user: test
109 user: test
106 date: Thu Jan 01 00:00:02 1970 +0000
110 date: Thu Jan 01 00:00:02 1970 +0000
107 summary: copy
111 summary: copy
108
112
109
113
110 % delete empty file
114 % delete empty file
111 diff --git a/empty-copy b/empty-copy
115 diff --git a/empty-copy b/empty-copy
112 deleted file mode 100644
116 deleted file mode 100644
113 examine changes to 'empty-copy'? [Ynsfdaq?]
117 examine changes to 'empty-copy'? [Ynsfdaq?]
114
118
115 changeset: 3:b39a238f01a1
119 changeset: 3:b39a238f01a1
116 tag: tip
120 tag: tip
117 user: test
121 user: test
118 date: Thu Jan 01 00:00:03 1970 +0000
122 date: Thu Jan 01 00:00:03 1970 +0000
119 summary: delete
123 summary: delete
120
124
121
125
122 % add binary file
126 % add binary file
123 1 changesets found
127 1 changesets found
124 diff --git a/tip.bundle b/tip.bundle
128 diff --git a/tip.bundle b/tip.bundle
125 new file mode 100644
129 new file mode 100644
126 this is a binary file
130 this is a binary file
127 examine changes to 'tip.bundle'? [Ynsfdaq?]
131 examine changes to 'tip.bundle'? [Ynsfdaq?]
128
132
129 changeset: 4:ad816da3711e
133 changeset: 4:ad816da3711e
130 tag: tip
134 tag: tip
131 user: test
135 user: test
132 date: Thu Jan 01 00:00:04 1970 +0000
136 date: Thu Jan 01 00:00:04 1970 +0000
133 summary: binary
137 summary: binary
134
138
135 diff -r b39a238f01a1 -r ad816da3711e tip.bundle
139 diff -r b39a238f01a1 -r ad816da3711e tip.bundle
136 Binary file tip.bundle has changed
140 Binary file tip.bundle has changed
137
141
138 % change binary file
142 % change binary file
139 1 changesets found
143 1 changesets found
140 diff --git a/tip.bundle b/tip.bundle
144 diff --git a/tip.bundle b/tip.bundle
141 this modifies a binary file (all or nothing)
145 this modifies a binary file (all or nothing)
142 examine changes to 'tip.bundle'? [Ynsfdaq?]
146 examine changes to 'tip.bundle'? [Ynsfdaq?]
143
147
144 changeset: 5:dccd6f3eb485
148 changeset: 5:dccd6f3eb485
145 tag: tip
149 tag: tip
146 user: test
150 user: test
147 date: Thu Jan 01 00:00:05 1970 +0000
151 date: Thu Jan 01 00:00:05 1970 +0000
148 summary: binary-change
152 summary: binary-change
149
153
150 diff -r ad816da3711e -r dccd6f3eb485 tip.bundle
154 diff -r ad816da3711e -r dccd6f3eb485 tip.bundle
151 Binary file tip.bundle has changed
155 Binary file tip.bundle has changed
152
156
153 % rename and change binary file
157 % rename and change binary file
154 1 changesets found
158 1 changesets found
155 diff --git a/tip.bundle b/top.bundle
159 diff --git a/tip.bundle b/top.bundle
156 rename from tip.bundle
160 rename from tip.bundle
157 rename to top.bundle
161 rename to top.bundle
158 this modifies a binary file (all or nothing)
162 this modifies a binary file (all or nothing)
159 examine changes to 'tip.bundle' and 'top.bundle'? [Ynsfdaq?]
163 examine changes to 'tip.bundle' and 'top.bundle'? [Ynsfdaq?]
160
164
161 changeset: 6:7fa44105f5b3
165 changeset: 6:7fa44105f5b3
162 tag: tip
166 tag: tip
163 user: test
167 user: test
164 date: Thu Jan 01 00:00:06 1970 +0000
168 date: Thu Jan 01 00:00:06 1970 +0000
165 summary: binary-change-rename
169 summary: binary-change-rename
166
170
167 diff -r dccd6f3eb485 -r 7fa44105f5b3 tip.bundle
171 diff -r dccd6f3eb485 -r 7fa44105f5b3 tip.bundle
168 Binary file tip.bundle has changed
172 Binary file tip.bundle has changed
169 diff -r dccd6f3eb485 -r 7fa44105f5b3 top.bundle
173 diff -r dccd6f3eb485 -r 7fa44105f5b3 top.bundle
170 Binary file top.bundle has changed
174 Binary file top.bundle has changed
171
175
172 % add plain file
176 % add plain file
173 diff --git a/plain b/plain
177 diff --git a/plain b/plain
174 new file mode 100644
178 new file mode 100644
175 examine changes to 'plain'? [Ynsfdaq?]
179 examine changes to 'plain'? [Ynsfdaq?]
176
180
177 changeset: 7:11fb457c1be4
181 changeset: 7:11fb457c1be4
178 tag: tip
182 tag: tip
179 user: test
183 user: test
180 date: Thu Jan 01 00:00:07 1970 +0000
184 date: Thu Jan 01 00:00:07 1970 +0000
181 summary: plain
185 summary: plain
182
186
183 diff -r 7fa44105f5b3 -r 11fb457c1be4 plain
187 diff -r 7fa44105f5b3 -r 11fb457c1be4 plain
184 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
188 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
185 +++ b/plain Thu Jan 01 00:00:07 1970 +0000
189 +++ b/plain Thu Jan 01 00:00:07 1970 +0000
186 @@ -0,0 +1,10 @@
190 @@ -0,0 +1,10 @@
187 +1
191 +1
188 +2
192 +2
189 +3
193 +3
190 +4
194 +4
191 +5
195 +5
192 +6
196 +6
193 +7
197 +7
194 +8
198 +8
195 +9
199 +9
196 +10
200 +10
197
201
198 % modify end of plain file
202 % modify end of plain file
199 diff --git a/plain b/plain
203 diff --git a/plain b/plain
200 1 hunks, 1 lines changed
204 1 hunks, 1 lines changed
201 examine changes to 'plain'? [Ynsfdaq?]
205 examine changes to 'plain'? [Ynsfdaq?]
202 @@ -8,3 +8,4 @@
206 @@ -8,3 +8,4 @@
203 8
207 8
204 9
208 9
205 10
209 10
206 +11
210 +11
207 record this change to 'plain'? [Ynsfdaq?]
211 record this change to 'plain'? [Ynsfdaq?]
208 % modify end of plain file, no EOL
212 % modify end of plain file, no EOL
209 diff --git a/plain b/plain
213 diff --git a/plain b/plain
210 1 hunks, 1 lines changed
214 1 hunks, 1 lines changed
211 examine changes to 'plain'? [Ynsfdaq?]
215 examine changes to 'plain'? [Ynsfdaq?]
212 @@ -9,3 +9,4 @@
216 @@ -9,3 +9,4 @@
213 9
217 9
214 10
218 10
215 11
219 11
216 +7264f99c5f5ff3261504828afa4fb4d406c3af54
220 +7264f99c5f5ff3261504828afa4fb4d406c3af54
217 \ No newline at end of file
221 \ No newline at end of file
218 record this change to 'plain'? [Ynsfdaq?]
222 record this change to 'plain'? [Ynsfdaq?]
219 % modify end of plain file, add EOL
223 % modify end of plain file, add EOL
220 diff --git a/plain b/plain
224 diff --git a/plain b/plain
221 1 hunks, 2 lines changed
225 1 hunks, 2 lines changed
222 examine changes to 'plain'? [Ynsfdaq?]
226 examine changes to 'plain'? [Ynsfdaq?]
223 @@ -9,4 +9,4 @@
227 @@ -9,4 +9,4 @@
224 9
228 9
225 10
229 10
226 11
230 11
227 -7264f99c5f5ff3261504828afa4fb4d406c3af54
231 -7264f99c5f5ff3261504828afa4fb4d406c3af54
228 \ No newline at end of file
232 \ No newline at end of file
229 +7264f99c5f5ff3261504828afa4fb4d406c3af54
233 +7264f99c5f5ff3261504828afa4fb4d406c3af54
230 record this change to 'plain'? [Ynsfdaq?]
234 record this change to 'plain'? [Ynsfdaq?]
231 % modify beginning, trim end, record both
235 % modify beginning, trim end, record both
232 diff --git a/plain b/plain
236 diff --git a/plain b/plain
233 2 hunks, 4 lines changed
237 2 hunks, 4 lines changed
234 examine changes to 'plain'? [Ynsfdaq?]
238 examine changes to 'plain'? [Ynsfdaq?]
235 @@ -1,4 +1,4 @@
239 @@ -1,4 +1,4 @@
236 -1
240 -1
237 +2
241 +2
238 2
242 2
239 3
243 3
240 4
244 4
241 record change 1/2 to 'plain'? [Ynsfdaq?]
245 record change 1/2 to 'plain'? [Ynsfdaq?]
242 @@ -8,5 +8,3 @@
246 @@ -8,5 +8,3 @@
243 8
247 8
244 9
248 9
245 10
249 10
246 -11
250 -11
247 -7264f99c5f5ff3261504828afa4fb4d406c3af54
251 -7264f99c5f5ff3261504828afa4fb4d406c3af54
248 record change 2/2 to 'plain'? [Ynsfdaq?]
252 record change 2/2 to 'plain'? [Ynsfdaq?]
249
253
250 changeset: 11:efca65c9b09e
254 changeset: 11:efca65c9b09e
251 tag: tip
255 tag: tip
252 user: test
256 user: test
253 date: Thu Jan 01 00:00:10 1970 +0000
257 date: Thu Jan 01 00:00:10 1970 +0000
254 summary: begin-and-end
258 summary: begin-and-end
255
259
256 diff -r cd07d48e8cbe -r efca65c9b09e plain
260 diff -r cd07d48e8cbe -r efca65c9b09e plain
257 --- a/plain Thu Jan 01 00:00:10 1970 +0000
261 --- a/plain Thu Jan 01 00:00:10 1970 +0000
258 +++ b/plain Thu Jan 01 00:00:10 1970 +0000
262 +++ b/plain Thu Jan 01 00:00:10 1970 +0000
259 @@ -1,4 +1,4 @@
263 @@ -1,4 +1,4 @@
260 -1
264 -1
261 +2
265 +2
262 2
266 2
263 3
267 3
264 4
268 4
265 @@ -8,5 +8,3 @@
269 @@ -8,5 +8,3 @@
266 8
270 8
267 9
271 9
268 10
272 10
269 -11
273 -11
270 -7264f99c5f5ff3261504828afa4fb4d406c3af54
274 -7264f99c5f5ff3261504828afa4fb4d406c3af54
271
275
272 % trim beginning, modify end
276 % trim beginning, modify end
273 % record end
277 % record end
274 diff --git a/plain b/plain
278 diff --git a/plain b/plain
275 2 hunks, 5 lines changed
279 2 hunks, 5 lines changed
276 examine changes to 'plain'? [Ynsfdaq?]
280 examine changes to 'plain'? [Ynsfdaq?]
277 @@ -1,9 +1,6 @@
281 @@ -1,9 +1,6 @@
278 -2
282 -2
279 -2
283 -2
280 -3
284 -3
281 4
285 4
282 5
286 5
283 6
287 6
284 7
288 7
285 8
289 8
286 9
290 9
287 record change 1/2 to 'plain'? [Ynsfdaq?]
291 record change 1/2 to 'plain'? [Ynsfdaq?]
288 @@ -4,7 +1,7 @@
292 @@ -4,7 +1,7 @@
289 4
293 4
290 5
294 5
291 6
295 6
292 7
296 7
293 8
297 8
294 9
298 9
295 -10
299 -10
296 +10.new
300 +10.new
297 record change 2/2 to 'plain'? [Ynsfdaq?]
301 record change 2/2 to 'plain'? [Ynsfdaq?]
298
302
299 changeset: 12:7d1e66983c15
303 changeset: 12:7d1e66983c15
300 tag: tip
304 tag: tip
301 user: test
305 user: test
302 date: Thu Jan 01 00:00:11 1970 +0000
306 date: Thu Jan 01 00:00:11 1970 +0000
303 summary: end-only
307 summary: end-only
304
308
305 diff -r efca65c9b09e -r 7d1e66983c15 plain
309 diff -r efca65c9b09e -r 7d1e66983c15 plain
306 --- a/plain Thu Jan 01 00:00:10 1970 +0000
310 --- a/plain Thu Jan 01 00:00:10 1970 +0000
307 +++ b/plain Thu Jan 01 00:00:11 1970 +0000
311 +++ b/plain Thu Jan 01 00:00:11 1970 +0000
308 @@ -7,4 +7,4 @@
312 @@ -7,4 +7,4 @@
309 7
313 7
310 8
314 8
311 9
315 9
312 -10
316 -10
313 +10.new
317 +10.new
314
318
315 % record beginning
319 % record beginning
316 diff --git a/plain b/plain
320 diff --git a/plain b/plain
317 1 hunks, 3 lines changed
321 1 hunks, 3 lines changed
318 examine changes to 'plain'? [Ynsfdaq?]
322 examine changes to 'plain'? [Ynsfdaq?]
319 @@ -1,6 +1,3 @@
323 @@ -1,6 +1,3 @@
320 -2
324 -2
321 -2
325 -2
322 -3
326 -3
323 4
327 4
324 5
328 5
325 6
329 6
326 record this change to 'plain'? [Ynsfdaq?]
330 record this change to 'plain'? [Ynsfdaq?]
327
331
328 changeset: 13:a09fc62a0e61
332 changeset: 13:a09fc62a0e61
329 tag: tip
333 tag: tip
330 user: test
334 user: test
331 date: Thu Jan 01 00:00:12 1970 +0000
335 date: Thu Jan 01 00:00:12 1970 +0000
332 summary: begin-only
336 summary: begin-only
333
337
334 diff -r 7d1e66983c15 -r a09fc62a0e61 plain
338 diff -r 7d1e66983c15 -r a09fc62a0e61 plain
335 --- a/plain Thu Jan 01 00:00:11 1970 +0000
339 --- a/plain Thu Jan 01 00:00:11 1970 +0000
336 +++ b/plain Thu Jan 01 00:00:12 1970 +0000
340 +++ b/plain Thu Jan 01 00:00:12 1970 +0000
337 @@ -1,6 +1,3 @@
341 @@ -1,6 +1,3 @@
338 -2
342 -2
339 -2
343 -2
340 -3
344 -3
341 4
345 4
342 5
346 5
343 6
347 6
344
348
345 % add to beginning, trim from end
349 % add to beginning, trim from end
346 % record end
350 % record end
347 diff --git a/plain b/plain
351 diff --git a/plain b/plain
348 2 hunks, 4 lines changed
352 2 hunks, 4 lines changed
349 examine changes to 'plain'? [Ynsfdaq?]
353 examine changes to 'plain'? [Ynsfdaq?]
350 @@ -1,6 +1,9 @@
354 @@ -1,6 +1,9 @@
351 +1
355 +1
352 +2
356 +2
353 +3
357 +3
354 4
358 4
355 5
359 5
356 6
360 6
357 7
361 7
358 8
362 8
359 9
363 9
360 record change 1/2 to 'plain'? [Ynsfdaq?]
364 record change 1/2 to 'plain'? [Ynsfdaq?]
361 @@ -1,7 +4,6 @@
365 @@ -1,7 +4,6 @@
362 4
366 4
363 5
367 5
364 6
368 6
365 7
369 7
366 8
370 8
367 9
371 9
368 -10.new
372 -10.new
369 record change 2/2 to 'plain'? [Ynsfdaq?]
373 record change 2/2 to 'plain'? [Ynsfdaq?]
370 % add to beginning, middle, end
374 % add to beginning, middle, end
371 % record beginning, middle
375 % record beginning, middle
372 diff --git a/plain b/plain
376 diff --git a/plain b/plain
373 3 hunks, 7 lines changed
377 3 hunks, 7 lines changed
374 examine changes to 'plain'? [Ynsfdaq?]
378 examine changes to 'plain'? [Ynsfdaq?]
375 @@ -1,2 +1,5 @@
379 @@ -1,2 +1,5 @@
376 +1
380 +1
377 +2
381 +2
378 +3
382 +3
379 4
383 4
380 5
384 5
381 record change 1/3 to 'plain'? [Ynsfdaq?]
385 record change 1/3 to 'plain'? [Ynsfdaq?]
382 @@ -1,6 +4,8 @@
386 @@ -1,6 +4,8 @@
383 4
387 4
384 5
388 5
385 +5.new
389 +5.new
386 +5.reallynew
390 +5.reallynew
387 6
391 6
388 7
392 7
389 8
393 8
390 9
394 9
391 record change 2/3 to 'plain'? [Ynsfdaq?]
395 record change 2/3 to 'plain'? [Ynsfdaq?]
392 @@ -3,4 +8,6 @@
396 @@ -3,4 +8,6 @@
393 6
397 6
394 7
398 7
395 8
399 8
396 9
400 9
397 +10
401 +10
398 +11
402 +11
399 record change 3/3 to 'plain'? [Ynsfdaq?]
403 record change 3/3 to 'plain'? [Ynsfdaq?]
400
404
401 changeset: 15:7d137997f3a6
405 changeset: 15:7d137997f3a6
402 tag: tip
406 tag: tip
403 user: test
407 user: test
404 date: Thu Jan 01 00:00:14 1970 +0000
408 date: Thu Jan 01 00:00:14 1970 +0000
405 summary: middle-only
409 summary: middle-only
406
410
407 diff -r c0b8e5fb0be6 -r 7d137997f3a6 plain
411 diff -r c0b8e5fb0be6 -r 7d137997f3a6 plain
408 --- a/plain Thu Jan 01 00:00:13 1970 +0000
412 --- a/plain Thu Jan 01 00:00:13 1970 +0000
409 +++ b/plain Thu Jan 01 00:00:14 1970 +0000
413 +++ b/plain Thu Jan 01 00:00:14 1970 +0000
410 @@ -1,5 +1,10 @@
414 @@ -1,5 +1,10 @@
411 +1
415 +1
412 +2
416 +2
413 +3
417 +3
414 4
418 4
415 5
419 5
416 +5.new
420 +5.new
417 +5.reallynew
421 +5.reallynew
418 6
422 6
419 7
423 7
420 8
424 8
421
425
422 % record end
426 % record end
423 diff --git a/plain b/plain
427 diff --git a/plain b/plain
424 1 hunks, 2 lines changed
428 1 hunks, 2 lines changed
425 examine changes to 'plain'? [Ynsfdaq?]
429 examine changes to 'plain'? [Ynsfdaq?]
426 @@ -9,3 +9,5 @@
430 @@ -9,3 +9,5 @@
427 7
431 7
428 8
432 8
429 9
433 9
430 +10
434 +10
431 +11
435 +11
432 record this change to 'plain'? [Ynsfdaq?]
436 record this change to 'plain'? [Ynsfdaq?]
433
437
434 changeset: 16:4959e3ff13eb
438 changeset: 16:4959e3ff13eb
435 tag: tip
439 tag: tip
436 user: test
440 user: test
437 date: Thu Jan 01 00:00:15 1970 +0000
441 date: Thu Jan 01 00:00:15 1970 +0000
438 summary: end-only
442 summary: end-only
439
443
440 diff -r 7d137997f3a6 -r 4959e3ff13eb plain
444 diff -r 7d137997f3a6 -r 4959e3ff13eb plain
441 --- a/plain Thu Jan 01 00:00:14 1970 +0000
445 --- a/plain Thu Jan 01 00:00:14 1970 +0000
442 +++ b/plain Thu Jan 01 00:00:15 1970 +0000
446 +++ b/plain Thu Jan 01 00:00:15 1970 +0000
443 @@ -9,3 +9,5 @@
447 @@ -9,3 +9,5 @@
444 7
448 7
445 8
449 8
446 9
450 9
447 +10
451 +10
448 +11
452 +11
449
453
450 adding subdir/a
454 adding subdir/a
451 diff --git a/subdir/a b/subdir/a
455 diff --git a/subdir/a b/subdir/a
452 1 hunks, 1 lines changed
456 1 hunks, 1 lines changed
453 examine changes to 'subdir/a'? [Ynsfdaq?]
457 examine changes to 'subdir/a'? [Ynsfdaq?]
454 @@ -1,1 +1,2 @@
458 @@ -1,1 +1,2 @@
455 a
459 a
456 +a
460 +a
457 record this change to 'subdir/a'? [Ynsfdaq?]
461 record this change to 'subdir/a'? [Ynsfdaq?]
458
462
459 changeset: 18:40698cd490b2
463 changeset: 18:40698cd490b2
460 tag: tip
464 tag: tip
461 user: test
465 user: test
462 date: Thu Jan 01 00:00:16 1970 +0000
466 date: Thu Jan 01 00:00:16 1970 +0000
463 summary: subdir-change
467 summary: subdir-change
464
468
465 diff -r 661eacdc08b9 -r 40698cd490b2 subdir/a
469 diff -r 661eacdc08b9 -r 40698cd490b2 subdir/a
466 --- a/subdir/a Thu Jan 01 00:00:16 1970 +0000
470 --- a/subdir/a Thu Jan 01 00:00:16 1970 +0000
467 +++ b/subdir/a Thu Jan 01 00:00:16 1970 +0000
471 +++ b/subdir/a Thu Jan 01 00:00:16 1970 +0000
468 @@ -1,1 +1,2 @@
472 @@ -1,1 +1,2 @@
469 a
473 a
470 +a
474 +a
471
475
472 % help, quit
476 % help, quit
473 diff --git a/subdir/f1 b/subdir/f1
477 diff --git a/subdir/f1 b/subdir/f1
474 1 hunks, 1 lines changed
478 1 hunks, 1 lines changed
475 examine changes to 'subdir/f1'? [Ynsfdaq?]
479 examine changes to 'subdir/f1'? [Ynsfdaq?]
476 y - record this change
480 y - record this change
477 n - skip this change
481 n - skip this change
478 s - skip remaining changes to this file
482 s - skip remaining changes to this file
479 f - record remaining changes to this file
483 f - record remaining changes to this file
480 d - done, skip remaining changes and files
484 d - done, skip remaining changes and files
481 a - record all changes to all remaining files
485 a - record all changes to all remaining files
482 q - quit, recording no changes
486 q - quit, recording no changes
483 ? - display help
487 ? - display help
484 examine changes to 'subdir/f1'? [Ynsfdaq?]
488 examine changes to 'subdir/f1'? [Ynsfdaq?]
485 abort: user quit
489 abort: user quit
486 % skip
490 % skip
487 diff --git a/subdir/f1 b/subdir/f1
491 diff --git a/subdir/f1 b/subdir/f1
488 1 hunks, 1 lines changed
492 1 hunks, 1 lines changed
489 examine changes to 'subdir/f1'? [Ynsfdaq?]
493 examine changes to 'subdir/f1'? [Ynsfdaq?]
490 diff --git a/subdir/f2 b/subdir/f2
494 diff --git a/subdir/f2 b/subdir/f2
491 1 hunks, 1 lines changed
495 1 hunks, 1 lines changed
492 examine changes to 'subdir/f2'? [Ynsfdaq?] abort: response expected
496 examine changes to 'subdir/f2'? [Ynsfdaq?] abort: response expected
493 % no
497 % no
494 diff --git a/subdir/f1 b/subdir/f1
498 diff --git a/subdir/f1 b/subdir/f1
495 1 hunks, 1 lines changed
499 1 hunks, 1 lines changed
496 examine changes to 'subdir/f1'? [Ynsfdaq?]
500 examine changes to 'subdir/f1'? [Ynsfdaq?]
497 diff --git a/subdir/f2 b/subdir/f2
501 diff --git a/subdir/f2 b/subdir/f2
498 1 hunks, 1 lines changed
502 1 hunks, 1 lines changed
499 examine changes to 'subdir/f2'? [Ynsfdaq?] abort: response expected
503 examine changes to 'subdir/f2'? [Ynsfdaq?] abort: response expected
500 % f, quit
504 % f, quit
501 diff --git a/subdir/f1 b/subdir/f1
505 diff --git a/subdir/f1 b/subdir/f1
502 1 hunks, 1 lines changed
506 1 hunks, 1 lines changed
503 examine changes to 'subdir/f1'? [Ynsfdaq?]
507 examine changes to 'subdir/f1'? [Ynsfdaq?]
504 diff --git a/subdir/f2 b/subdir/f2
508 diff --git a/subdir/f2 b/subdir/f2
505 1 hunks, 1 lines changed
509 1 hunks, 1 lines changed
506 examine changes to 'subdir/f2'? [Ynsfdaq?]
510 examine changes to 'subdir/f2'? [Ynsfdaq?]
507 abort: user quit
511 abort: user quit
508 % s, all
512 % s, all
509 diff --git a/subdir/f1 b/subdir/f1
513 diff --git a/subdir/f1 b/subdir/f1
510 1 hunks, 1 lines changed
514 1 hunks, 1 lines changed
511 examine changes to 'subdir/f1'? [Ynsfdaq?]
515 examine changes to 'subdir/f1'? [Ynsfdaq?]
512 diff --git a/subdir/f2 b/subdir/f2
516 diff --git a/subdir/f2 b/subdir/f2
513 1 hunks, 1 lines changed
517 1 hunks, 1 lines changed
514 examine changes to 'subdir/f2'? [Ynsfdaq?]
518 examine changes to 'subdir/f2'? [Ynsfdaq?]
515
519
516 changeset: 20:d2d8c25276a8
520 changeset: 20:d2d8c25276a8
517 tag: tip
521 tag: tip
518 user: test
522 user: test
519 date: Thu Jan 01 00:00:18 1970 +0000
523 date: Thu Jan 01 00:00:18 1970 +0000
520 summary: x
524 summary: x
521
525
522 diff -r 25eb2a7694fb -r d2d8c25276a8 subdir/f2
526 diff -r 25eb2a7694fb -r d2d8c25276a8 subdir/f2
523 --- a/subdir/f2 Thu Jan 01 00:00:17 1970 +0000
527 --- a/subdir/f2 Thu Jan 01 00:00:17 1970 +0000
524 +++ b/subdir/f2 Thu Jan 01 00:00:18 1970 +0000
528 +++ b/subdir/f2 Thu Jan 01 00:00:18 1970 +0000
525 @@ -1,1 +1,2 @@
529 @@ -1,1 +1,2 @@
526 b
530 b
527 +b
531 +b
528
532
529 % f
533 % f
530 diff --git a/subdir/f1 b/subdir/f1
534 diff --git a/subdir/f1 b/subdir/f1
531 1 hunks, 1 lines changed
535 1 hunks, 1 lines changed
532 examine changes to 'subdir/f1'? [Ynsfdaq?]
536 examine changes to 'subdir/f1'? [Ynsfdaq?]
533
537
534 changeset: 21:1013f51ce32f
538 changeset: 21:1013f51ce32f
535 tag: tip
539 tag: tip
536 user: test
540 user: test
537 date: Thu Jan 01 00:00:19 1970 +0000
541 date: Thu Jan 01 00:00:19 1970 +0000
538 summary: y
542 summary: y
539
543
540 diff -r d2d8c25276a8 -r 1013f51ce32f subdir/f1
544 diff -r d2d8c25276a8 -r 1013f51ce32f subdir/f1
541 --- a/subdir/f1 Thu Jan 01 00:00:18 1970 +0000
545 --- a/subdir/f1 Thu Jan 01 00:00:18 1970 +0000
542 +++ b/subdir/f1 Thu Jan 01 00:00:19 1970 +0000
546 +++ b/subdir/f1 Thu Jan 01 00:00:19 1970 +0000
543 @@ -1,1 +1,2 @@
547 @@ -1,1 +1,2 @@
544 a
548 a
545 +a
549 +a
546
550
547 % preserve chmod +x
551 % preserve chmod +x
548 diff --git a/subdir/f1 b/subdir/f1
552 diff --git a/subdir/f1 b/subdir/f1
549 old mode 100644
553 old mode 100644
550 new mode 100755
554 new mode 100755
551 1 hunks, 1 lines changed
555 1 hunks, 1 lines changed
552 examine changes to 'subdir/f1'? [Ynsfdaq?]
556 examine changes to 'subdir/f1'? [Ynsfdaq?]
553 @@ -1,2 +1,3 @@
557 @@ -1,2 +1,3 @@
554 a
558 a
555 a
559 a
556 +a
560 +a
557 record this change to 'subdir/f1'? [Ynsfdaq?]
561 record this change to 'subdir/f1'? [Ynsfdaq?]
558
562
559 changeset: 22:5df857735621
563 changeset: 22:5df857735621
560 tag: tip
564 tag: tip
561 user: test
565 user: test
562 date: Thu Jan 01 00:00:20 1970 +0000
566 date: Thu Jan 01 00:00:20 1970 +0000
563 summary: z
567 summary: z
564
568
565 diff --git a/subdir/f1 b/subdir/f1
569 diff --git a/subdir/f1 b/subdir/f1
566 old mode 100644
570 old mode 100644
567 new mode 100755
571 new mode 100755
568 --- a/subdir/f1
572 --- a/subdir/f1
569 +++ b/subdir/f1
573 +++ b/subdir/f1
570 @@ -1,2 +1,3 @@
574 @@ -1,2 +1,3 @@
571 a
575 a
572 a
576 a
573 +a
577 +a
574
578
575 % preserve execute permission on original
579 % preserve execute permission on original
576 diff --git a/subdir/f1 b/subdir/f1
580 diff --git a/subdir/f1 b/subdir/f1
577 1 hunks, 1 lines changed
581 1 hunks, 1 lines changed
578 examine changes to 'subdir/f1'? [Ynsfdaq?]
582 examine changes to 'subdir/f1'? [Ynsfdaq?]
579 @@ -1,3 +1,4 @@
583 @@ -1,3 +1,4 @@
580 a
584 a
581 a
585 a
582 a
586 a
583 +b
587 +b
584 record this change to 'subdir/f1'? [Ynsfdaq?]
588 record this change to 'subdir/f1'? [Ynsfdaq?]
585
589
586 changeset: 23:a4ae36a78715
590 changeset: 23:a4ae36a78715
587 tag: tip
591 tag: tip
588 user: test
592 user: test
589 date: Thu Jan 01 00:00:21 1970 +0000
593 date: Thu Jan 01 00:00:21 1970 +0000
590 summary: aa
594 summary: aa
591
595
592 diff --git a/subdir/f1 b/subdir/f1
596 diff --git a/subdir/f1 b/subdir/f1
593 --- a/subdir/f1
597 --- a/subdir/f1
594 +++ b/subdir/f1
598 +++ b/subdir/f1
595 @@ -1,3 +1,4 @@
599 @@ -1,3 +1,4 @@
596 a
600 a
597 a
601 a
598 a
602 a
599 +b
603 +b
600
604
601 % preserve chmod -x
605 % preserve chmod -x
602 diff --git a/subdir/f1 b/subdir/f1
606 diff --git a/subdir/f1 b/subdir/f1
603 old mode 100755
607 old mode 100755
604 new mode 100644
608 new mode 100644
605 1 hunks, 1 lines changed
609 1 hunks, 1 lines changed
606 examine changes to 'subdir/f1'? [Ynsfdaq?]
610 examine changes to 'subdir/f1'? [Ynsfdaq?]
607 @@ -2,3 +2,4 @@
611 @@ -2,3 +2,4 @@
608 a
612 a
609 a
613 a
610 b
614 b
611 +c
615 +c
612 record this change to 'subdir/f1'? [Ynsfdaq?]
616 record this change to 'subdir/f1'? [Ynsfdaq?]
613
617
614 changeset: 24:1460f6e47966
618 changeset: 24:1460f6e47966
615 tag: tip
619 tag: tip
616 user: test
620 user: test
617 date: Thu Jan 01 00:00:22 1970 +0000
621 date: Thu Jan 01 00:00:22 1970 +0000
618 summary: ab
622 summary: ab
619
623
620 diff --git a/subdir/f1 b/subdir/f1
624 diff --git a/subdir/f1 b/subdir/f1
621 old mode 100755
625 old mode 100755
622 new mode 100644
626 new mode 100644
623 --- a/subdir/f1
627 --- a/subdir/f1
624 +++ b/subdir/f1
628 +++ b/subdir/f1
625 @@ -2,3 +2,4 @@
629 @@ -2,3 +2,4 @@
626 a
630 a
627 a
631 a
628 b
632 b
629 +c
633 +c
630
634
631 % abort early when a merge is in progress
635 % abort early when a merge is in progress
632 1 files updated, 0 files merged, 5 files removed, 0 files unresolved
636 1 files updated, 0 files merged, 5 files removed, 0 files unresolved
633 marked working directory as branch thatbranch
637 marked working directory as branch thatbranch
634 5 files updated, 0 files merged, 2 files removed, 0 files unresolved
638 5 files updated, 0 files merged, 2 files removed, 0 files unresolved
635 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
639 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
636 (branch merge, don't forget to commit)
640 (branch merge, don't forget to commit)
637
641
638 abort: cannot partially commit a merge (use hg commit instead)
642 abort: cannot partially commit a merge (use hg commit instead)
639 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
643 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
640 % with win32ext
644 % with win32ext
641 diff --git a/subdir/f1 b/subdir/f1
645 diff --git a/subdir/f1 b/subdir/f1
642 1 hunks, 1 lines changed
646 1 hunks, 1 lines changed
643 examine changes to 'subdir/f1'? [Ynsfdaq?]
647 examine changes to 'subdir/f1'? [Ynsfdaq?]
644 @@ -3,3 +3,4 @@
648 @@ -3,3 +3,4 @@
645 a
649 a
646 b
650 b
647 c
651 c
648 +d
652 +d
649 record this change to 'subdir/f1'? [Ynsfdaq?]
653 record this change to 'subdir/f1'? [Ynsfdaq?]
650
654
651 changeset: 26:5bacc1f6e9cf
655 changeset: 26:5bacc1f6e9cf
652 tag: tip
656 tag: tip
653 parent: 24:1460f6e47966
657 parent: 24:1460f6e47966
654 user: test
658 user: test
655 date: Thu Jan 01 00:00:23 1970 +0000
659 date: Thu Jan 01 00:00:23 1970 +0000
656 summary: w1
660 summary: w1
657
661
658 diff -r 1460f6e47966 -r 5bacc1f6e9cf subdir/f1
662 diff -r 1460f6e47966 -r 5bacc1f6e9cf subdir/f1
659 --- a/subdir/f1 Thu Jan 01 00:00:22 1970 +0000
663 --- a/subdir/f1 Thu Jan 01 00:00:22 1970 +0000
660 +++ b/subdir/f1 Thu Jan 01 00:00:23 1970 +0000
664 +++ b/subdir/f1 Thu Jan 01 00:00:23 1970 +0000
661 @@ -3,3 +3,4 @@
665 @@ -3,3 +3,4 @@
662 a
666 a
663 b
667 b
664 c
668 c
665 +d
669 +d
666
670
General Comments 0
You need to be logged in to leave comments. Login now